fixed attachment remove and update operation

This commit is contained in:
pramod.mahajan 2025-11-29 13:27:07 +05:30
parent cb9d263730
commit 6fa2cc4ef0
7 changed files with 137 additions and 155 deletions

View File

@ -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);
}}
></i>
</Tooltip>

View File

@ -1,25 +1,32 @@
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 = [
const stepsConfig = useMemo(
() => [
{
name: "Party Details",
icon: "bx bx-user bx-md",
@ -38,7 +45,9 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
subtitle: "Amount, tax & due date",
component: <PurchasePaymentDetails purchaseId={purchaseId} />,
},
];
],
[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) {
if (!purchaseId || !data) return;
reset({
...data,
title: data.title,
projectId: data.project.id,
organizationId: data.organization.id,
supplierId: data.supplier.id,
attachments: data.attachments
? data?.attachments?.map((doc) => ({
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.invoiceAttachmentTypeId ?? null,
invoiceAttachmentTypeId: doc.invoiceAttachmentType?.id ?? null,
fileSize: 0,
description: "",
preSignedUrl: doc.preSignedUrl,
isActive: doc.isActive ?? true,
}))
: [],
})) || [],
});
setCompletedTabs([0, 1, 2]);
}
}, []);
}, [purchaseId, data, reset]);
const handleNext = async (e) => {
e.preventDefault();
e.stopPropagation();
const handleNext = useCallback(async () => {
const fields = getStepFields(activeTab);
const valid = await purchaseOrder.trigger(fields);
const currentStepFields = getStepFields(activeTab);
const valid = await purchaseOrder.trigger(currentStepFields);
if (!valid) return;
if (valid) {
setCompletedTabs((prev) => [...new Set([...prev, activeTab])]);
setActiveTab((prev) => Math.min(prev + 1, stepsConfig.length - 1));
}
};
}, [activeTab, purchaseOrder, stepsConfig.length]);
const handlePrev = (e) => {
e.preventDefault();
e.stopPropagation();
const handlePrev = useCallback(() => {
setActiveTab((prev) => Math.max(prev - 1, 0));
};
}, []);
const generatePatchOps = useCallback(
(formData) => {
const { dirtyFields } = formState;
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 dirtyFields = purchaseOrder.formState.dirtyFields;
const onSubmit = useCallback(
(formData) => {
if (activeTab !== 2) return;
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,
// });
const payload = generatePatchOps(formData);
updatePurchase({ purchaseId, payload });
} else {
CreateInvoice(formData);
}
};
console.log(errors)
},
[activeTab, purchaseId, generatePatchOps, updatePurchase, CreateInvoice]
);
return (
<div
id="wizard-property-listing"
className="bs-stepper horizontically mt-2 b-secondry shadow-none border-0"
>
{/* Header */}
<div className="bs-stepper horizontically mt-2 b-secondry shadow-none border-0">
{/* --- Steps Header --- */}
<div className="bs-stepper-header text-start px-0 py-1">
{stepsConfig.map((step, index) => {
const isActive = activeTab === index;
@ -158,7 +153,11 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
isCompleted ? "crossed" : ""
}`}
>
<button type="button" className="step-trigger">
<button
type="button"
className="step-trigger"
onClick={() => purchaseId && setActiveTab(index)}
>
<span className="bs-stepper-circle">
{isCompleted ? (
<i className="bx bx-check"></i>
@ -182,28 +181,18 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
})}
</div>
{/* Content */}
{/* --- Form Content --- */}
<div className="bs-stepper-content py-2 px-3">
<AppFormProvider {...purchaseOrder}>
<form
onKeyDown={(e) => {
if (e.key === "Enter" && activeTab !== 2) {
e.preventDefault();
onKeyDown={(e) =>
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);
}}
onSubmit={purchaseOrder.handleSubmit(onSubmit)}
>
{stepsConfig[activeTab].component}
{/* Buttons */}
<div className="d-flex justify-content-between mt-4">
<button
type="button"

View File

@ -74,7 +74,7 @@ const PurchaseList = ({ searchString }) => {
{col.render ? col.render(item) : item[col.key] || "NA"}
</td>
))}
<td>
<td >
<div className="dropdown z-2">
<button
type="button"

View File

@ -37,6 +37,13 @@ const PurchasePaymentDetails = ({ purchaseId = null }) => {
}, [baseAmount, taxAmount, 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;
@ -54,6 +61,8 @@ const PurchasePaymentDetails = ({ purchaseId = null }) => {
description: "",
isActive: true,
invoiceAttachmentTypeId: invoiceAttachmentType,
isNew: true, // <-- temp
tempId: crypto.randomUUID(), // temp - id
};
})
);
@ -75,36 +84,25 @@ const PurchasePaymentDetails = ({ purchaseId = null }) => {
});
};
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) => {
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 }];
}
if (purchaseId) {
const newFiles = files.map((file, i) => {
if (i !== index) return file;
return {
...file,
isActive: false,
};
return [file];
});
console.log("OLD:", files);
console.log("NEW:", newFiles);
// setValue("attachments", newFiles, { shouldValidate: true });
const new_Files = files.filter((_, i) => i !== index);
setValue("attachments", new_Files, { shouldValidate: true });
} else {
const newFiles = files.filter((_, i) => i !== index);
setValue("attachments", newFiles, { shouldValidate: true });
}
setValue("attachments", updated, {
shouldDirty: true,
shouldValidate: true,
});
};
return (

View File

@ -63,11 +63,6 @@ export const PurchaseSchema = z.object({
});
// deliveryChallanNo: z
// .string()
// .min(1, { message: "Delivery Challan No is required" }),
// deliveryDate: z.string().min(1, { message: "Delevery Date is required" }),
// shippingAddress: z.string().min(1, { message: "Delevery Date is required" }),
export const defaultPurchaseValue = {
title: "",

View File

@ -26,13 +26,13 @@ export const PurchaseColumn = [
{
key: "project",
label: "Project",
className: "text-start d-none d-sm-table-cell",
className: "text-start ",
render: (item) => <span>{item?.project?.name || "NA"}</span>,
},
{
key: "supplier",
label: "Supplier",
className: "text-start d-none d-sm-table-cell",
className: "text-start ",
render: (item) => <span>{item?.supplier?.name || "NA"}</span>,
},
{

View File

@ -49,7 +49,7 @@ const PurchasePage = () => {
/>
<div className="card px-sm-4 my-3">
<div className="row p-2">
<div className="col-12 col-md-6 text-start">
<div className="col-12 col-sm-6 text-start">
{" "}
<label className="mb-0">
<input
@ -62,7 +62,7 @@ const PurchasePage = () => {
/>
</label>
</div>
<di className="col-6 text-end">
<di className="col-sm-6 text-end">
<button
className="btn btn-sm btn-primary"
onClick={() =>