marco.pms.web/src/components/purchase/PurchasePaymentDetails.jsx

306 lines
8.9 KiB
JavaScript

import React, { useEffect } from "react";
import Label from "../common/Label";
import {
AppFormController,
useAppFormContext,
} from "../../hooks/appHooks/useAppForm";
import DatePicker from "../common/DatePicker";
import { useInvoiceAttachmentTypes } from "../../hooks/masterHook/useMaster";
import Filelist from "../Expenses/Filelist";
import SelectField from "../common/Forms/SelectField";
import { useWatch } from "react-hook-form";
import WarningBlock from "../InfoBlock/WarningBlock";
import { FILE_UPLOAD_INFO } from "../../utils/staticContent";
const PurchasePaymentDetails = ({ purchaseId = null }) => {
const { data, isLoading, error, isError } = useInvoiceAttachmentTypes();
const { data: InvoiceDocTypes, isLoading: invoiceDocLoading } =
useInvoiceAttachmentTypes();
const {
register,
watch,
setValue,
control,
formState: { errors },
} = useAppFormContext();
const baseAmount = watch("baseAmount");
const taxAmount = watch("taxAmount");
const trCharge = watch("transportCharges");
useEffect(() => {
const base = parseFloat(baseAmount) || 0;
const tax = parseFloat(taxAmount) || 0;
const transportCharges = parseFloat(trCharge) || 0;
if (base || tax || transportCharges) {
setValue("totalAmount", (base + tax + transportCharges).toFixed(2));
}
}, [baseAmount, taxAmount, trCharge, setValue]);
const invoiceAttachmentType = watch("invoiceAttachmentTypeId");
const files = watch("attachments");
const toBase64 = (file) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result.split(",")[1]);
reader.onerror = (error) => reject(error);
});
const onFileChange = async (e) => {
const newFiles = Array.from(e.target.files);
if (newFiles.length === 0) return;
const existingFiles = watch("attachments") || [];
const parsedFiles = await Promise.all(
newFiles.map(async (file) => {
const base64Data = await toBase64(file);
return {
fileName: file.name,
base64Data,
contentType: file.type,
fileSize: file.size,
description: "",
isActive: true,
invoiceAttachmentTypeId: invoiceAttachmentType,
isNew: true, // <-- temp
tempId: crypto.randomUUID(), // temp - id
};
})
);
const combinedFiles = [
...existingFiles,
...parsedFiles.filter(
(newFile) =>
!existingFiles.some(
(f) =>
f.fileName === newFile.fileName && f.fileSize === newFile.fileSize
)
),
];
setValue("attachments", combinedFiles, {
shouldDirty: true,
shouldValidate: true,
});
};
const removeFile = (index) => {
const updated = files.flatMap((file, i) => {
// NEW FILE → remove completely
if (file.isNew && file.tempId === index) {
return []; // remove from array
}
// EXISTING FILE → mark inactive
if (!file.isNew && file.documentId === index) {
return [{ ...file, isActive: false }];
}
return [file];
});
setValue("attachments", updated, {
shouldDirty: true,
shouldValidate: true,
});
};
const hasNumber = !!watch("proformaInvoiceNumber");
return (
<div className="row g-1 text-start">
<div className="col-12 col-md-4">
<Label htmlFor="baseAmount" required={!hasNumber}>
Base Amount
</Label>
<input
id="baseAmount"
type="number"
className="form-control form-control-xs"
{...register("baseAmount", { valueAsNumber: true })}
/>
{errors?.baseAmount && (
<div className="small danger-text mt-1">
{errors.baseAmount.message}
</div>
)}
</div>
<div className="col-12 col-md-4">
<Label htmlFor="taxAmount" required={!hasNumber}>
Tax Amount
</Label>
<input
id="taxAmount"
type="number"
className="form-control form-control-xs"
{...register("taxAmount", { valueAsNumber: true })}
/>
{errors?.taxAmount && (
<div className="small danger-text mt-1">
{errors.taxAmount.message}
</div>
)}
</div>
<div className="col-12 col-md-4">
<Label htmlFor="transportCharges">Transport Charges</Label>
<input
id="transportCharges"
type="number"
className="form-control form-control-xs"
{...register("transportCharges", { valueAsNumber: true })}
/>
{errors?.transportCharges && (
<div className="small danger-text mt-1">
{errors.transportCharges.message}
</div>
)}
</div>
<div className="col-12 col-md-6">
<Label htmlFor="totalAmount" required={!hasNumber}>
Total Amount
</Label>
<input
id="totalAmount"
type="number"
className="form-control form-control-xs"
{...register("totalAmount", { valueAsNumber: true })}
/>
{errors?.totalAmount && (
<div className="small danger-text mt-1">
{errors.totalAmount.message}
</div>
)}
</div>
<div className="col-12 col-md-6">
<Label htmlFor="paymentDueDate">Payment Due Date</Label>
<DatePicker
name={"paymentDueDate"}
control={control}
className="w-full"
size="xs"
/>
{errors?.paymentDueDate && (
<div className="small danger-text mt-1">
{errors.paymentDueDate.message}
</div>
)}
</div>
<div className="col-12 mb-2">
<Label htmlFor="description">Description</Label>
<textarea
id="description"
rows="3"
className="form-control form-control-xs"
{...register("description")}
/>
{errors?.description && (
<div className="small danger-text mt-1">
{errors.description.message}
</div>
)}
</div>
<div className="col-12">
<AppFormController
name="invoiceAttachmentTypeId"
control={control}
render={({ field }) => (
<SelectField
label="Select Document Type"
options={InvoiceDocTypes ?? []}
placeholder="Choose Type"
labelKeyKey="name"
valueKeyKey="id"
value={field.value}
onChange={field.onChange}
isLoading={invoiceDocLoading}
className="m-0"
/>
)}
/>
{errors?.invoiceAttachmentTypeId && (
<small className="danger-text">
{errors.invoiceAttachmentTypeId.message}
</small>
)}
</div>
<div className="text-start">
<div className="col-md-12">
<Label className="form-label" required>
Upload Bill{" "}
</Label>
<div
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
style={{ cursor: "pointer" }}
onClick={() => document.getElementById("attachments").click()}
>
<i className="bx bx-cloud-upload d-block bx-lg"> </i>
<span className="text-muted d-block">
Click to select or click here to browse
</span>
<small className="text-muted">(PDF, JPG, PNG, max 5MB)</small>
<input
type="file"
id="attachments"
accept=".pdf,.jpg,.jpeg,.png"
multiple
disabled={!invoiceAttachmentType}
style={{ display: "none" }}
{...register("attachments")}
onChange={(e) => {
onFileChange(e);
e.target.value = "";
}}
/>
</div>
{errors.attachments && (
<small className="danger-text">{errors.attachments.message}</small>
)}
{files.length > 0 && (
<Filelist
files={files}
removeFile={removeFile}
expenseToEdit={purchaseId}
/>
)}
{Array.isArray(errors.attachments) &&
errors.attachments.map((fileError, index) => (
<div key={index} className="danger-text small mt-1">
{
(fileError?.fileSize?.message ||
fileError?.contentType?.message ||
fileError?.base64Data?.message,
fileError?.documentId?.message)
}
</div>
))}
{!invoiceAttachmentType && (
<WarningBlock content={FILE_UPLOAD_INFO} />
)}
</div>
</div>
</div>
);
};
export default PurchasePaymentDetails;