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",