From 9ce47a9232632d6c6b236a5946b5ff9c16a02c26 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 28 Nov 2025 18:01:42 +0530 Subject: [PATCH 1/4] Adding Upload functionlaiy in ManagePurchase. --- src/components/Expenses/Filelist.jsx | 1 + src/components/purchase/ManagePurchase.jsx | 16 ++- .../purchase/PurchasePaymentDetails.jsx | 129 +++++++++++++++++- src/components/purchase/PurchaseSchema.jsx | 89 +++++++----- 4 files changed, 197 insertions(+), 38 deletions(-) diff --git a/src/components/Expenses/Filelist.jsx b/src/components/Expenses/Filelist.jsx index b5a89452..c91c044d 100644 --- a/src/components/Expenses/Filelist.jsx +++ b/src/components/Expenses/Filelist.jsx @@ -3,6 +3,7 @@ import { formatFileSize, getIconByFileType } from "../../utils/appUtils"; import Tooltip from "../common/Tooltip"; const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => { + debugger return (
{files diff --git a/src/components/purchase/ManagePurchase.jsx b/src/components/purchase/ManagePurchase.jsx index c846fd99..a011a3c7 100644 --- a/src/components/purchase/ManagePurchase.jsx +++ b/src/components/purchase/ManagePurchase.jsx @@ -16,7 +16,7 @@ import { } from "../../hooks/usePurchase"; const ManagePurchase = ({ onClose, purchaseId }) => { const { data } = usePurchase(purchaseId); - const [activeTab, setActiveTab] = useState(0); + const [activeTab, setActiveTab] = useState(2); const [completedTabs, setCompletedTabs] = useState([]); const stepsConfig = [ @@ -36,7 +36,7 @@ const ManagePurchase = ({ onClose, purchaseId }) => { name: "Payment Details", icon: "bx bx-credit-card bx-md", subtitle: "Amount, tax & due date", - component: , + component: , }, ]; @@ -60,6 +60,18 @@ const ManagePurchase = ({ onClose, purchaseId }) => { projectId: data.project.id, organizationId: data.organization.id, supplierId: data.supplier.id, + attachments: data.attachments + ? data?.attachments?.map((doc) => ({ + fileName: doc.fileName, + base64Data: null, + contentType: doc.contentType, + documentId: doc.id, + fileSize: 0, + description: "", + preSignedUrl: doc.preSignedUrl, + isActive: doc.isActive ?? true, + })) + : [] }); setCompletedTabs([0, 1, 2]); } diff --git a/src/components/purchase/PurchasePaymentDetails.jsx b/src/components/purchase/PurchasePaymentDetails.jsx index f5bb9570..a65b0cd3 100644 --- a/src/components/purchase/PurchasePaymentDetails.jsx +++ b/src/components/purchase/PurchasePaymentDetails.jsx @@ -3,8 +3,8 @@ import Label from "../common/Label"; import { useAppFormContext } from "../../hooks/appHooks/useAppForm"; import DatePicker from "../common/DatePicker"; import { useInvoiceAttachmentTypes } from "../../hooks/masterHook/useMaster"; - -const PurchasePaymentDetails = () => { +import Filelist from "../Expenses/Filelist"; +const PurchasePaymentDetails = ({purchaseId=null}) => { const { data, isLoading, error, isError } = useInvoiceAttachmentTypes(); const { register, @@ -26,6 +26,68 @@ const PurchasePaymentDetails = () => { } }, [baseAmount, taxAmount, setValue]); + const files = watch("attachments"); + 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, + }; + }) + ); + + 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 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 removeFile = (index) => { + debugger + if (purchaseId) { + const newFiles = files.map((file, i) => { + if (file.documentId !== index) return file; + return { + ...file, + isActive: false, + }; + }); + setValue("attachments", newFiles, { shouldValidate: true }); + } else { + const newFiles = files.filter((_, i) => i !== index); + setValue("attachments", newFiles, { shouldValidate: true }); + } + }; + return (
@@ -56,7 +118,7 @@ const PurchasePaymentDetails = () => { id="taxAmount" type="number" className="form-control form-control-xs" - {...register("taxAmount",{ valueAsNumber: true })} + {...register("taxAmount", { valueAsNumber: true })} /> {errors?.taxAmount && ( @@ -75,7 +137,7 @@ const PurchasePaymentDetails = () => { id="totalAmount" type="number" className="form-control form-control-xs" - {...register("totalAmount",{ valueAsNumber: true })} + {...register("totalAmount", { valueAsNumber: true })} readOnly /> @@ -93,7 +155,7 @@ const PurchasePaymentDetails = () => { id="transportCharges" type="number" className="form-control form-control-xs" - {...register("transportCharges",{ valueAsNumber: true })} + {...register("transportCharges", { valueAsNumber: true })} /> {errors?.transportCharges && ( @@ -138,6 +200,63 @@ const PurchasePaymentDetails = () => {
)}
+ +
+
+ + +
document.getElementById("attachments").click()} + > + + + Click to select or click here to browse + + (PDF, JPG, PNG, max 5MB) + + { + onFileChange(e); + e.target.value = ""; + }} + /> +
+ {errors.attachments && ( + + {errors.attachments.message} + + )} + {files.length > 0 && ( + + )} + + {Array.isArray(errors.attachments) && + errors.attachments.map((fileError, index) => ( +
+ { + (fileError?.fileSize?.message || + fileError?.contentType?.message || + fileError?.base64Data?.message, + fileError?.documentId?.message) + } +
+ ))} +
+
); }; diff --git a/src/components/purchase/PurchaseSchema.jsx b/src/components/purchase/PurchaseSchema.jsx index 2656fa58..cce47575 100644 --- a/src/components/purchase/PurchaseSchema.jsx +++ b/src/components/purchase/PurchaseSchema.jsx @@ -1,39 +1,63 @@ import { z } from "zod"; import { normalizeAllowedContentTypes } from "../../utils/appUtils"; -export const AttachmentSchema = (allowedContentType, maxSizeAllowedInMB) => { - const allowedTypes = normalizeAllowedContentTypes(allowedContentType); +const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB +const ALLOWED_TYPES = [ + "application/pdf", + "image/png", + "image/jpg", + "image/jpeg", +]; - return z.object({ - fileName: z.string().min(1, { message: "File name is required" }), - base64Data: z.string().min(1, { message: "File data is required" }), - invoiceAttachmentTypeId: z - .string() - .min(1, { message: "File data is required" }), +export const AttachmentSchema = z.object({ + documentId: z.string().optional(), + invoiceAttachmentTypeId: z.string().min(1, { message: "Attachment type is required" }), + fileName: z.string().min(1, { message: "Filename is required" }), + base64Data: z.string().min(1, { message: "File data is required" }), + contentType: z + .string() + .refine((val) => ALLOWED_TYPES.includes(val), { + message: "Only PDF, PNG, JPG, or JPEG files are allowed", + }), + fileSize: z.number().max(MAX_FILE_SIZE, { + message: "File size must be less than or equal to 5MB", + }), + description: z.string().optional().default(""), + isActive: z.boolean().default(true), +}); +// export const AttachmentSchema = (allowedContentType, maxSizeAllowedInMB) => { +// const allowedTypes = normalizeAllowedContentTypes(allowedContentType); - contentType: z - .string() - .min(1, { message: "MIME type is required" }) - .refine( - (val) => (allowedTypes.length ? allowedTypes.includes(val) : true), - { - message: `File type must be one of: ${allowedTypes.join(", ")}`, - } - ), +// return z.object({ +// fileName: z.string().min(1, { message: "File name is required" }), +// base64Data: z.string().min(1, { message: "File data is required" }), +// invoiceAttachmentTypeId: z +// .string() +// .min(1, { message: "File data is required" }), - fileSize: z - .number() - .int() - .nonnegative("fileSize must be ≥ 0") - .max( - (maxSizeAllowedInMB ?? 25) * 1024 * 1024, - `fileSize must be ≤ ${maxSizeAllowedInMB ?? 25}MB` - ), +// contentType: z +// .string() +// .min(1, { message: "MIME type is required" }) +// .refine( +// (val) => (allowedTypes.length ? allowedTypes.includes(val) : true), +// { +// message: `File type must be one of: ${allowedTypes.join(", ")}`, +// } +// ), - description: z.string().optional().default(""), - isActive: z.boolean(), - }); -}; +// fileSize: z +// .number() +// .int() +// .nonnegative("fileSize must be ≥ 0") +// .max( +// (maxSizeAllowedInMB ?? 25) * 1024 * 1024, +// `fileSize must be ≤ ${maxSizeAllowedInMB ?? 25}MB` +// ), + +// description: z.string().optional().default(""), +// isActive: z.boolean(), +// }); +// }; export const PurchaseSchema = z.object({ title: z.string().min(1, { message: "Title is required" }), @@ -65,8 +89,10 @@ export const PurchaseSchema = z.object({ paymentDueDate: z.coerce.date().nullable(), transportCharges: z.number().nullable(), description: z.string().min(1, { message: "Description is required" }), + attachments: z + .array(AttachmentSchema) + .nonempty({ message: "At least one file attachment is required" }), - // attachments: z.array(AttachmentSchema([], 25)).optional(), }); // deliveryChallanNo: z @@ -104,6 +130,7 @@ export const defaultPurchaseValue = { paymentDueDate: null, transportCharges: null, description: "", + attachments: [], // attachments: [], }; @@ -180,7 +207,7 @@ export const DeliveryChallanSchema = z.object({ invoiceAttachmentTypeId: z.string().nullable(), deliveryChallanDate: z.string().min(1, { message: "Deliver date required" }), description: z.string().min(1, { message: "Description required" }), - attachment: z.any().refine( + attachment: z.any().refine( (val) => val && typeof val === "object" && !!val.base64Data, { message: "Please upload document", From f0d21b14a27806727f6f441924e80ba46029ff58 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Fri, 28 Nov 2025 20:49:47 +0530 Subject: [PATCH 2/4] added attachement in invoice --- src/components/Expenses/Filelist.jsx | 19 +++--- src/components/InfoBlock/WarningBlock.jsx | 16 +++++ src/components/purchase/DeliveryChallane.jsx | 12 +--- src/components/purchase/ManagePurchase.jsx | 25 ++++--- .../purchase/PurchasePaymentDetails.jsx | 68 ++++++++++++++++--- src/components/purchase/PurchaseSchema.jsx | 45 ++---------- src/utils/staticContent.jsx | 4 ++ 7 files changed, 110 insertions(+), 79 deletions(-) create mode 100644 src/components/InfoBlock/WarningBlock.jsx create mode 100644 src/utils/staticContent.jsx diff --git a/src/components/Expenses/Filelist.jsx b/src/components/Expenses/Filelist.jsx index c91c044d..c9c3ce8e 100644 --- a/src/components/Expenses/Filelist.jsx +++ b/src/components/Expenses/Filelist.jsx @@ -3,7 +3,6 @@ import { formatFileSize, getIconByFileType } from "../../utils/appUtils"; import Tooltip from "../common/Tooltip"; const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => { - debugger return (
{files @@ -23,15 +22,15 @@ const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => { style={{ minWidth: "30px" }} > - +
- - {file.fileName} - - - {file.fileSize ? formatFileSize(file.fileSize) : ""} - -
+ + {file.fileName} + + + {file.fileSize ? formatFileSize(file.fileSize) : ""} + +
@@ -42,7 +41,7 @@ const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => { role="button" onClick={(e) => { e.preventDefault(); - removeFile(expenseToEdit ? file.documentId : idx); + removeFile(expenseToEdit ? file.documentId ?? idx : idx); }} > diff --git a/src/components/InfoBlock/WarningBlock.jsx b/src/components/InfoBlock/WarningBlock.jsx new file mode 100644 index 00000000..6c756593 --- /dev/null +++ b/src/components/InfoBlock/WarningBlock.jsx @@ -0,0 +1,16 @@ +import React from 'react' + +const WarningBlock = ({content}) => { + return ( +
+
+ +

+ {content} +

+
+
+ ) +} + +export default WarningBlock diff --git a/src/components/purchase/DeliveryChallane.jsx b/src/components/purchase/DeliveryChallane.jsx index 1600b77d..834f049d 100644 --- a/src/components/purchase/DeliveryChallane.jsx +++ b/src/components/purchase/DeliveryChallane.jsx @@ -19,6 +19,8 @@ import SelectField from "../common/Forms/SelectField"; import Filelist from "../Expenses/Filelist"; import SingleFileUploader from "../common/SigleFileUploader"; import { localToUtc } from "../../utils/appUtils"; +import WarningBlock from "../InfoBlock/WarningBlock"; +import { FILE_UPLOAD_INFO } from "../../utils/staticContent"; const DeliveryChallane = ({ purchaseId }) => { const [file, setFile] = useState(null); @@ -178,15 +180,7 @@ const DeliveryChallane = ({ purchaseId }) => { {!isUploaded && ( -
-
- -

- If want upload document, Please select a document type before - uploading the document. -

-
-
+ )} diff --git a/src/components/purchase/ManagePurchase.jsx b/src/components/purchase/ManagePurchase.jsx index a011a3c7..b4df00ca 100644 --- a/src/components/purchase/ManagePurchase.jsx +++ b/src/components/purchase/ManagePurchase.jsx @@ -36,7 +36,7 @@ const ManagePurchase = ({ onClose, purchaseId }) => { name: "Payment Details", icon: "bx bx-credit-card bx-md", subtitle: "Amount, tax & due date", - component: , + component: , }, ]; @@ -65,17 +65,18 @@ const ManagePurchase = ({ onClose, purchaseId }) => { fileName: doc.fileName, base64Data: null, contentType: doc.contentType, - documentId: doc.id, + documentId: doc.documentId, + invoiceAttachmentTypeId:doc.invoiceAttachmentTypeId ?? null, fileSize: 0, description: "", preSignedUrl: doc.preSignedUrl, isActive: doc.isActive ?? true, })) - : [] + : [], }); setCompletedTabs([0, 1, 2]); } - }, [purchaseId, data, reset]); + }, []); const handleNext = async (e) => { e.preventDefault(); @@ -117,7 +118,8 @@ const ManagePurchase = ({ onClose, purchaseId }) => { if (purchaseId) { const changedData = Object.keys(dirtyFields).reduce((acc, key) => { - if (dirtyFields[key]) { + debugger; + if (dirtyFields[key] && key !== "invoiceAttachmentTypeId") { acc.push({ operationType: 0, path: `/${key}`, @@ -128,15 +130,16 @@ const ManagePurchase = ({ onClose, purchaseId }) => { } return acc; }, []); - - updatePurchase({ - purchaseId, - payload: changedData, - }); + console.log(changedData) + // updatePurchase({ + // purchaseId, + // payload: changedData, + // }); } else { CreateInvoice(formData); } }; + console.log(errors) return (
{ onClick={handleNext} disabled={isPending || isUpdating} > - Next + Next ) : (
diff --git a/src/components/purchase/PurchaseSchema.jsx b/src/components/purchase/PurchaseSchema.jsx index cce47575..2fc1cead 100644 --- a/src/components/purchase/PurchaseSchema.jsx +++ b/src/components/purchase/PurchaseSchema.jsx @@ -10,10 +10,9 @@ const ALLOWED_TYPES = [ ]; export const AttachmentSchema = z.object({ - documentId: z.string().optional(), - invoiceAttachmentTypeId: z.string().min(1, { message: "Attachment type is required" }), + invoiceAttachmentTypeId: z.string().nullable(), fileName: z.string().min(1, { message: "Filename is required" }), - base64Data: z.string().min(1, { message: "File data is required" }), + base64Data: z.string().nullable(), contentType: z .string() .refine((val) => ALLOWED_TYPES.includes(val), { @@ -25,39 +24,7 @@ export const AttachmentSchema = z.object({ description: z.string().optional().default(""), isActive: z.boolean().default(true), }); -// export const AttachmentSchema = (allowedContentType, maxSizeAllowedInMB) => { -// const allowedTypes = normalizeAllowedContentTypes(allowedContentType); -// return z.object({ -// fileName: z.string().min(1, { message: "File name is required" }), -// base64Data: z.string().min(1, { message: "File data is required" }), -// invoiceAttachmentTypeId: z -// .string() -// .min(1, { message: "File data is required" }), - -// contentType: z -// .string() -// .min(1, { message: "MIME type is required" }) -// .refine( -// (val) => (allowedTypes.length ? allowedTypes.includes(val) : true), -// { -// message: `File type must be one of: ${allowedTypes.join(", ")}`, -// } -// ), - -// fileSize: z -// .number() -// .int() -// .nonnegative("fileSize must be ≥ 0") -// .max( -// (maxSizeAllowedInMB ?? 25) * 1024 * 1024, -// `fileSize must be ≤ ${maxSizeAllowedInMB ?? 25}MB` -// ), - -// description: z.string().optional().default(""), -// isActive: z.boolean(), -// }); -// }; export const PurchaseSchema = z.object({ title: z.string().min(1, { message: "Title is required" }), @@ -89,9 +56,10 @@ export const PurchaseSchema = z.object({ paymentDueDate: z.coerce.date().nullable(), transportCharges: z.number().nullable(), description: z.string().min(1, { message: "Description is required" }), + invoiceAttachmentTypeId:z.string().nullable(), attachments: z .array(AttachmentSchema) - .nonempty({ message: "At least one file attachment is required" }), + }); @@ -130,9 +98,8 @@ export const defaultPurchaseValue = { paymentDueDate: null, transportCharges: null, description: "", + invoiceAttachmentTypeId:null, attachments: [], - - // attachments: [], }; export const getStepFields = (stepIndex) => { @@ -165,7 +132,9 @@ export const getStepFields = (stepIndex) => { "totalAmount", "transportCharges", "paymentDueDate", + "invoiceAttachmentTypeId", "description", + "attachments" ], }; diff --git a/src/utils/staticContent.jsx b/src/utils/staticContent.jsx new file mode 100644 index 00000000..5e4b5783 --- /dev/null +++ b/src/utils/staticContent.jsx @@ -0,0 +1,4 @@ +export const FILE_UPLOAD_INFO = ` +If you want to upload a document, please select a document type +before uploading the document. +`; From cb9d2637308bea4103ca103701281c050593852f Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Sat, 29 Nov 2025 09:36:13 +0530 Subject: [PATCH 3/4] Adding activetab to 0 --- src/components/purchase/ManagePurchase.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/purchase/ManagePurchase.jsx b/src/components/purchase/ManagePurchase.jsx index b4df00ca..bcb8c467 100644 --- a/src/components/purchase/ManagePurchase.jsx +++ b/src/components/purchase/ManagePurchase.jsx @@ -16,7 +16,7 @@ import { } from "../../hooks/usePurchase"; const ManagePurchase = ({ onClose, purchaseId }) => { const { data } = usePurchase(purchaseId); - const [activeTab, setActiveTab] = useState(2); + const [activeTab, setActiveTab] = useState(0); const [completedTabs, setCompletedTabs] = useState([]); const stepsConfig = [ From 6fa2cc4ef0bdc0f8ed692b18c89438acd395ebf4 Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Sat, 29 Nov 2025 13:27:07 +0530 Subject: [PATCH 4/4] fixed attachment remove and update operation --- src/components/Expenses/Filelist.jsx | 2 +- src/components/purchase/ManagePurchase.jsx | 225 +++++++++--------- src/components/purchase/PurchaseList.jsx | 2 +- .../purchase/PurchasePaymentDetails.jsx | 50 ++-- src/components/purchase/PurchaseSchema.jsx | 5 - src/components/purchase/Purchasetable.jsx | 4 +- src/pages/purchase/PurchasePage.jsx | 4 +- 7 files changed, 137 insertions(+), 155 deletions(-) diff --git a/src/components/Expenses/Filelist.jsx b/src/components/Expenses/Filelist.jsx index c9c3ce8e..36a48565 100644 --- a/src/components/Expenses/Filelist.jsx +++ b/src/components/Expenses/Filelist.jsx @@ -41,7 +41,7 @@ const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => { role="button" onClick={(e) => { e.preventDefault(); - removeFile(expenseToEdit ? file.documentId ?? idx : idx); + removeFile(expenseToEdit ? file.documentId ?? file.tempId ?? idx : file.tempId ?? idx); }} > diff --git a/src/components/purchase/ManagePurchase.jsx b/src/components/purchase/ManagePurchase.jsx index bcb8c467..c28f536c 100644 --- a/src/components/purchase/ManagePurchase.jsx +++ b/src/components/purchase/ManagePurchase.jsx @@ -1,44 +1,53 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useCallback, useState } from "react"; import { AppFormProvider, useAppForm } from "../../hooks/appHooks/useAppForm"; import { zodResolver } from "@hookform/resolvers/zod"; + import { defaultPurchaseValue, PurchaseSchema, getStepFields, } from "./PurchaseSchema"; + import PurchasePartyDetails from "./PurchasePartyDetails"; import PurchaseTransportDetails from "./PurchaseTransportDetails"; import PurchasePaymentDetails from "./PurchasePaymentDetails"; + import { useCreatePurchaseInvoice, usePurchase, useUpdatePurchaseInvoice, } from "../../hooks/usePurchase"; +import { error } from "pdf-lib"; + const ManagePurchase = ({ onClose, purchaseId }) => { const { data } = usePurchase(purchaseId); + const [activeTab, setActiveTab] = useState(0); const [completedTabs, setCompletedTabs] = useState([]); - const stepsConfig = [ - { - name: "Party Details", - icon: "bx bx-user bx-md", - subtitle: "Supplier & project information", - component: , - }, - { - name: "Invoice & Transport", - icon: "bx bx-receipt bx-md", - subtitle: "Invoice, eWay bill & transport info", - component: , - }, - { - name: "Payment Details", - icon: "bx bx-credit-card bx-md", - subtitle: "Amount, tax & due date", - component: , - }, - ]; + const stepsConfig = useMemo( + () => [ + { + name: "Party Details", + icon: "bx bx-user bx-md", + subtitle: "Supplier & project information", + component: , + }, + { + name: "Invoice & Transport", + icon: "bx bx-receipt bx-md", + subtitle: "Invoice, eWay bill & transport info", + component: , + }, + { + name: "Payment Details", + icon: "bx bx-credit-card bx-md", + subtitle: "Amount, tax & due date", + component: , + }, + ], + [data, purchaseId] + ); const purchaseOrder = useAppForm({ resolver: zodResolver(PurchaseSchema), @@ -47,55 +56,63 @@ const ManagePurchase = ({ onClose, purchaseId }) => { shouldUnregister: false, }); - const { - reset, - formState: { errors }, - } = purchaseOrder; + const { reset, formState } = purchaseOrder; useEffect(() => { - if (purchaseId && data) { - reset({ - ...data, - title: data.title, - projectId: data.project.id, - organizationId: data.organization.id, - supplierId: data.supplier.id, - attachments: data.attachments - ? data?.attachments?.map((doc) => ({ - fileName: doc.fileName, - base64Data: null, - contentType: doc.contentType, - documentId: doc.documentId, - invoiceAttachmentTypeId:doc.invoiceAttachmentTypeId ?? null, - fileSize: 0, - description: "", - preSignedUrl: doc.preSignedUrl, - isActive: doc.isActive ?? true, - })) - : [], - }); - setCompletedTabs([0, 1, 2]); - } + if (!purchaseId || !data) return; + + reset({ + ...data, + projectId: data?.project?.id, + organizationId: data?.organization?.id, + supplierId: data?.supplier?.id, + invoiceAttachmentTypeId: null, + attachments: + data?.attachments?.map((doc) => ({ + fileName: doc.fileName, + base64Data: null, + contentType: doc.contentType, + documentId: doc.documentId, + invoiceAttachmentTypeId: doc.invoiceAttachmentType?.id ?? null, + fileSize: 0, + description: "", + preSignedUrl: doc.preSignedUrl, + isActive: doc.isActive ?? true, + })) || [], + }); + + setCompletedTabs([0, 1, 2]); + }, [purchaseId, data, reset]); + + const handleNext = useCallback(async () => { + const fields = getStepFields(activeTab); + const valid = await purchaseOrder.trigger(fields); + + if (!valid) return; + + setCompletedTabs((prev) => [...new Set([...prev, activeTab])]); + setActiveTab((prev) => Math.min(prev + 1, stepsConfig.length - 1)); + }, [activeTab, purchaseOrder, stepsConfig.length]); + + const handlePrev = useCallback(() => { + setActiveTab((prev) => Math.max(prev - 1, 0)); }, []); - const handleNext = async (e) => { - e.preventDefault(); - e.stopPropagation(); + const generatePatchOps = useCallback( + (formData) => { + const { dirtyFields } = formState; - const currentStepFields = getStepFields(activeTab); - const valid = await purchaseOrder.trigger(currentStepFields); - - if (valid) { - setCompletedTabs((prev) => [...new Set([...prev, activeTab])]); - setActiveTab((prev) => Math.min(prev + 1, stepsConfig.length - 1)); - } - }; - - const handlePrev = (e) => { - e.preventDefault(); - e.stopPropagation(); - setActiveTab((prev) => Math.max(prev - 1, 0)); - }; + return Object.keys(dirtyFields) + .filter((key) => key !== "invoiceAttachmentTypeId") + .map((key) => ({ + operationType: 0, + path: `/${key}`, + op: "replace", + value: formData[key], + })); + }, + [formState] + ); const { mutate: CreateInvoice, isPending } = useCreatePurchaseInvoice(() => { reset(); @@ -108,44 +125,22 @@ const ManagePurchase = ({ onClose, purchaseId }) => { onClose(); }); - const onSubmit = (formData) => { - if (activeTab !== 2) { - console.warn("Submit blocked - not on last step"); - return; - } + const onSubmit = useCallback( + (formData) => { + if (activeTab !== 2) return; - const dirtyFields = purchaseOrder.formState.dirtyFields; - - if (purchaseId) { - const changedData = Object.keys(dirtyFields).reduce((acc, key) => { - debugger; - if (dirtyFields[key] && key !== "invoiceAttachmentTypeId") { - acc.push({ - operationType: 0, - path: `/${key}`, - op: "replace", - from: null, - value: formData[key], - }); - } - return acc; - }, []); - console.log(changedData) - // updatePurchase({ - // purchaseId, - // payload: changedData, - // }); - } else { - CreateInvoice(formData); - } - }; - console.log(errors) + if (purchaseId) { + const payload = generatePatchOps(formData); + updatePurchase({ purchaseId, payload }); + } else { + CreateInvoice(formData); + } + }, + [activeTab, purchaseId, generatePatchOps, updatePurchase, CreateInvoice] + ); return ( -
- {/* Header */} +
+ {/* --- Steps Header --- */}
{stepsConfig.map((step, index) => { const isActive = activeTab === index; @@ -158,7 +153,11 @@ const ManagePurchase = ({ onClose, purchaseId }) => { isCompleted ? "crossed" : "" }`} > -
- {/* Content */} + {/* --- Form Content --- */}
{ - if (e.key === "Enter" && activeTab !== 2) { - e.preventDefault(); - } - }} - onSubmit={(e) => { - e.preventDefault(); - - if (activeTab !== 2) { - console.warn("BLOCKED SUBMIT on step:", activeTab); - return; - } - - purchaseOrder.handleSubmit(onSubmit)(e); - }} + onKeyDown={(e) => + e.key === "Enter" && activeTab !== 2 && e.preventDefault() + } + onSubmit={purchaseOrder.handleSubmit(onSubmit)} > {stepsConfig[activeTab].component} + {/* Buttons */}