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,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 { 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", name: "Party Details",
icon: "bx bx-user bx-md", icon: "bx bx-user bx-md",
@ -38,7 +45,9 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
subtitle: "Amount, tax & due date", subtitle: "Amount, tax & due date",
component: <PurchasePaymentDetails purchaseId={purchaseId} />, 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({ reset({
...data, ...data,
title: data.title, projectId: data?.project?.id,
projectId: data.project.id, organizationId: data?.organization?.id,
organizationId: data.organization.id, supplierId: data?.supplier?.id,
supplierId: data.supplier.id, invoiceAttachmentTypeId: null,
attachments: data.attachments attachments:
? data?.attachments?.map((doc) => ({ data?.attachments?.map((doc) => ({
fileName: doc.fileName, fileName: doc.fileName,
base64Data: null, base64Data: null,
contentType: doc.contentType, contentType: doc.contentType,
documentId: doc.documentId, documentId: doc.documentId,
invoiceAttachmentTypeId:doc.invoiceAttachmentTypeId ?? null, invoiceAttachmentTypeId: doc.invoiceAttachmentType?.id ?? null,
fileSize: 0, fileSize: 0,
description: "", description: "",
preSignedUrl: doc.preSignedUrl, preSignedUrl: doc.preSignedUrl,
isActive: doc.isActive ?? true, isActive: doc.isActive ?? true,
})) })) || [],
: [],
}); });
setCompletedTabs([0, 1, 2]); setCompletedTabs([0, 1, 2]);
} }, [purchaseId, data, reset]);
}, []);
const handleNext = async (e) => { const handleNext = useCallback(async () => {
e.preventDefault(); const fields = getStepFields(activeTab);
e.stopPropagation(); const valid = await purchaseOrder.trigger(fields);
const currentStepFields = getStepFields(activeTab); if (!valid) return;
const valid = await purchaseOrder.trigger(currentStepFields);
if (valid) {
setCompletedTabs((prev) => [...new Set([...prev, activeTab])]); setCompletedTabs((prev) => [...new Set([...prev, activeTab])]);
setActiveTab((prev) => Math.min(prev + 1, stepsConfig.length - 1)); setActiveTab((prev) => Math.min(prev + 1, stepsConfig.length - 1));
} }, [activeTab, purchaseOrder, stepsConfig.length]);
};
const handlePrev = (e) => { const handlePrev = useCallback(() => {
e.preventDefault();
e.stopPropagation();
setActiveTab((prev) => Math.max(prev - 1, 0)); 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(() => { 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) { if (purchaseId) {
const changedData = Object.keys(dirtyFields).reduce((acc, key) => { const payload = generatePatchOps(formData);
debugger; updatePurchase({ purchaseId, payload });
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 { } else {
CreateInvoice(formData); CreateInvoice(formData);
} }
}; },
console.log(errors) [activeTab, purchaseId, generatePatchOps, updatePurchase, CreateInvoice]
);
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 {
...file,
isActive: false,
};
}); });
console.log("OLD:", files); setValue("attachments", updated, {
console.log("NEW:", newFiles); shouldDirty: true,
shouldValidate: true,
// 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={() =>