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" role="button"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
removeFile(expenseToEdit ? file.documentId ?? idx : idx); removeFile(expenseToEdit ? file.documentId ?? file.tempId ?? idx : file.tempId ?? idx);
}} }}
></i> ></i>
</Tooltip> </Tooltip>

View File

@ -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 { AppFormProvider, useAppForm } from "../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { import {
defaultPurchaseValue, defaultPurchaseValue,
PurchaseSchema, PurchaseSchema,
getStepFields, getStepFields,
} from "./PurchaseSchema"; } from "./PurchaseSchema";
import PurchasePartyDetails from "./PurchasePartyDetails"; import PurchasePartyDetails from "./PurchasePartyDetails";
import PurchaseTransportDetails from "./PurchaseTransportDetails"; import PurchaseTransportDetails from "./PurchaseTransportDetails";
import PurchasePaymentDetails from "./PurchasePaymentDetails"; import PurchasePaymentDetails from "./PurchasePaymentDetails";
import { import {
useCreatePurchaseInvoice, useCreatePurchaseInvoice,
usePurchase, usePurchase,
useUpdatePurchaseInvoice, useUpdatePurchaseInvoice,
} from "../../hooks/usePurchase"; } from "../../hooks/usePurchase";
import { error } from "pdf-lib";
const ManagePurchase = ({ onClose, purchaseId }) => { const ManagePurchase = ({ onClose, purchaseId }) => {
const { data } = usePurchase(purchaseId); const { data } = usePurchase(purchaseId);
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [completedTabs, setCompletedTabs] = useState([]); const [completedTabs, setCompletedTabs] = useState([]);
const stepsConfig = [ const stepsConfig = useMemo(
{ () => [
name: "Party Details", {
icon: "bx bx-user bx-md", name: "Party Details",
subtitle: "Supplier & project information", icon: "bx bx-user bx-md",
component: <PurchasePartyDetails purchase={data} />, subtitle: "Supplier & project information",
}, component: <PurchasePartyDetails purchase={data} />,
{ },
name: "Invoice & Transport", {
icon: "bx bx-receipt bx-md", name: "Invoice & Transport",
subtitle: "Invoice, eWay bill & transport info", icon: "bx bx-receipt bx-md",
component: <PurchaseTransportDetails />, subtitle: "Invoice, eWay bill & transport info",
}, component: <PurchaseTransportDetails />,
{ },
name: "Payment Details", {
icon: "bx bx-credit-card bx-md", name: "Payment Details",
subtitle: "Amount, tax & due date", icon: "bx bx-credit-card bx-md",
component: <PurchasePaymentDetails purchaseId={purchaseId} />, subtitle: "Amount, tax & due date",
}, component: <PurchasePaymentDetails purchaseId={purchaseId} />,
]; },
],
[data, purchaseId]
);
const purchaseOrder = useAppForm({ const purchaseOrder = useAppForm({
resolver: zodResolver(PurchaseSchema), resolver: zodResolver(PurchaseSchema),
@ -47,55 +56,63 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
shouldUnregister: false, shouldUnregister: false,
}); });
const { const { reset, formState } = purchaseOrder;
reset,
formState: { errors },
} = purchaseOrder;
useEffect(() => { useEffect(() => {
if (purchaseId && data) { if (!purchaseId || !data) return;
reset({
...data, reset({
title: data.title, ...data,
projectId: data.project.id, projectId: data?.project?.id,
organizationId: data.organization.id, organizationId: data?.organization?.id,
supplierId: data.supplier.id, supplierId: data?.supplier?.id,
attachments: data.attachments invoiceAttachmentTypeId: null,
? data?.attachments?.map((doc) => ({ attachments:
fileName: doc.fileName, data?.attachments?.map((doc) => ({
base64Data: null, fileName: doc.fileName,
contentType: doc.contentType, base64Data: null,
documentId: doc.documentId, contentType: doc.contentType,
invoiceAttachmentTypeId:doc.invoiceAttachmentTypeId ?? null, documentId: doc.documentId,
fileSize: 0, invoiceAttachmentTypeId: doc.invoiceAttachmentType?.id ?? null,
description: "", fileSize: 0,
preSignedUrl: doc.preSignedUrl, description: "",
isActive: doc.isActive ?? true, preSignedUrl: doc.preSignedUrl,
})) isActive: doc.isActive ?? true,
: [], })) || [],
}); });
setCompletedTabs([0, 1, 2]);
} 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) => { const generatePatchOps = useCallback(
e.preventDefault(); (formData) => {
e.stopPropagation(); const { dirtyFields } = formState;
const currentStepFields = getStepFields(activeTab); return Object.keys(dirtyFields)
const valid = await purchaseOrder.trigger(currentStepFields); .filter((key) => key !== "invoiceAttachmentTypeId")
.map((key) => ({
if (valid) { operationType: 0,
setCompletedTabs((prev) => [...new Set([...prev, activeTab])]); path: `/${key}`,
setActiveTab((prev) => Math.min(prev + 1, stepsConfig.length - 1)); op: "replace",
} value: formData[key],
}; }));
},
const handlePrev = (e) => { [formState]
e.preventDefault(); );
e.stopPropagation();
setActiveTab((prev) => Math.max(prev - 1, 0));
};
const { mutate: CreateInvoice, isPending } = useCreatePurchaseInvoice(() => { const { mutate: CreateInvoice, isPending } = useCreatePurchaseInvoice(() => {
reset(); reset();
@ -108,44 +125,22 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
onClose(); onClose();
}); });
const onSubmit = (formData) => { const onSubmit = useCallback(
if (activeTab !== 2) { (formData) => {
console.warn("Submit blocked - not on last step"); if (activeTab !== 2) return;
return;
}
const dirtyFields = purchaseOrder.formState.dirtyFields; if (purchaseId) {
const payload = generatePatchOps(formData);
if (purchaseId) { updatePurchase({ purchaseId, payload });
const changedData = Object.keys(dirtyFields).reduce((acc, key) => { } else {
debugger; CreateInvoice(formData);
if (dirtyFields[key] && key !== "invoiceAttachmentTypeId") { }
acc.push({ },
operationType: 0, [activeTab, purchaseId, generatePatchOps, updatePurchase, CreateInvoice]
path: `/${key}`, );
op: "replace",
from: null,
value: formData[key],
});
}
return acc;
}, []);
console.log(changedData)
// updatePurchase({
// purchaseId,
// payload: changedData,
// });
} else {
CreateInvoice(formData);
}
};
console.log(errors)
return ( return (
<div <div className="bs-stepper horizontically mt-2 b-secondry shadow-none border-0">
id="wizard-property-listing" {/* --- Steps Header --- */}
className="bs-stepper horizontically mt-2 b-secondry shadow-none border-0"
>
{/* Header */}
<div className="bs-stepper-header text-start px-0 py-1"> <div className="bs-stepper-header text-start px-0 py-1">
{stepsConfig.map((step, index) => { {stepsConfig.map((step, index) => {
const isActive = activeTab === index; const isActive = activeTab === index;
@ -158,7 +153,11 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
isCompleted ? "crossed" : "" isCompleted ? "crossed" : ""
}`} }`}
> >
<button type="button" className="step-trigger"> <button
type="button"
className="step-trigger"
onClick={() => purchaseId && setActiveTab(index)}
>
<span className="bs-stepper-circle"> <span className="bs-stepper-circle">
{isCompleted ? ( {isCompleted ? (
<i className="bx bx-check"></i> <i className="bx bx-check"></i>
@ -182,28 +181,18 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
})} })}
</div> </div>
{/* Content */} {/* --- Form Content --- */}
<div className="bs-stepper-content py-2 px-3"> <div className="bs-stepper-content py-2 px-3">
<AppFormProvider {...purchaseOrder}> <AppFormProvider {...purchaseOrder}>
<form <form
onKeyDown={(e) => { onKeyDown={(e) =>
if (e.key === "Enter" && activeTab !== 2) { e.key === "Enter" && activeTab !== 2 && e.preventDefault()
e.preventDefault(); }
} onSubmit={purchaseOrder.handleSubmit(onSubmit)}
}}
onSubmit={(e) => {
e.preventDefault();
if (activeTab !== 2) {
console.warn("BLOCKED SUBMIT on step:", activeTab);
return;
}
purchaseOrder.handleSubmit(onSubmit)(e);
}}
> >
{stepsConfig[activeTab].component} {stepsConfig[activeTab].component}
{/* Buttons */}
<div className="d-flex justify-content-between mt-4"> <div className="d-flex justify-content-between mt-4">
<button <button
type="button" type="button"

View File

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

View File

@ -37,6 +37,13 @@ const PurchasePaymentDetails = ({ purchaseId = null }) => {
}, [baseAmount, taxAmount, setValue]); }, [baseAmount, taxAmount, setValue]);
const invoiceAttachmentType = watch("invoiceAttachmentTypeId"); const invoiceAttachmentType = watch("invoiceAttachmentTypeId");
const files = watch("attachments"); 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 onFileChange = async (e) => {
const newFiles = Array.from(e.target.files); const newFiles = Array.from(e.target.files);
if (newFiles.length === 0) return; if (newFiles.length === 0) return;
@ -54,6 +61,8 @@ const PurchasePaymentDetails = ({ purchaseId = null }) => {
description: "", description: "",
isActive: true, isActive: true,
invoiceAttachmentTypeId: invoiceAttachmentType, 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 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) { return [file];
const newFiles = files.map((file, i) => { });
if (i !== index) return file;
return { setValue("attachments", updated, {
...file, shouldDirty: true,
isActive: false, shouldValidate: true,
}; });
});
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 });
}
}; };
return ( 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 = { export const defaultPurchaseValue = {
title: "", title: "",

View File

@ -26,13 +26,13 @@ export const PurchaseColumn = [
{ {
key: "project", key: "project",
label: "Project", label: "Project",
className: "text-start d-none d-sm-table-cell", className: "text-start ",
render: (item) => <span>{item?.project?.name || "NA"}</span>, render: (item) => <span>{item?.project?.name || "NA"}</span>,
}, },
{ {
key: "supplier", key: "supplier",
label: "Supplier", label: "Supplier",
className: "text-start d-none d-sm-table-cell", className: "text-start ",
render: (item) => <span>{item?.supplier?.name || "NA"}</span>, 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="card px-sm-4 my-3">
<div className="row p-2"> <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"> <label className="mb-0">
<input <input
@ -62,7 +62,7 @@ const PurchasePage = () => {
/> />
</label> </label>
</div> </div>
<di className="col-6 text-end"> <di className="col-sm-6 text-end">
<button <button
className="btn btn-sm btn-primary" className="btn btn-sm btn-primary"
onClick={() => onClick={() =>