added attachement in invoice
This commit is contained in:
parent
9ce47a9232
commit
f0d21b14a2
@ -3,7 +3,6 @@ import { formatFileSize, getIconByFileType } from "../../utils/appUtils";
|
|||||||
import Tooltip from "../common/Tooltip";
|
import Tooltip from "../common/Tooltip";
|
||||||
|
|
||||||
const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => {
|
const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => {
|
||||||
debugger
|
|
||||||
return (
|
return (
|
||||||
<div className="d-flex flex-wrap gap-2 my-1">
|
<div className="d-flex flex-wrap gap-2 my-1">
|
||||||
{files
|
{files
|
||||||
@ -23,15 +22,15 @@ const Filelist = ({ files, removeFile, expenseToEdit, sm = 6, md = 4 }) => {
|
|||||||
style={{ minWidth: "30px" }}
|
style={{ minWidth: "30px" }}
|
||||||
></i>
|
></i>
|
||||||
|
|
||||||
<Tooltip text={file.fileName} >
|
<Tooltip text={file.fileName}>
|
||||||
<div className="d-flex flex-column text-truncate">
|
<div className="d-flex flex-column text-truncate">
|
||||||
<span className="fw-semibold small text-truncate">
|
<span className="fw-semibold small text-truncate">
|
||||||
{file.fileName}
|
{file.fileName}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-body-secondary small">
|
<span className="text-body-secondary small">
|
||||||
{file.fileSize ? formatFileSize(file.fileSize) : ""}
|
{file.fileSize ? formatFileSize(file.fileSize) : ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -42,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);
|
removeFile(expenseToEdit ? file.documentId ?? idx : idx);
|
||||||
}}
|
}}
|
||||||
></i>
|
></i>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
16
src/components/InfoBlock/WarningBlock.jsx
Normal file
16
src/components/InfoBlock/WarningBlock.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const WarningBlock = ({content}) => {
|
||||||
|
return (
|
||||||
|
<div className="col-12 my-1">
|
||||||
|
<div className="d-flex align-items-center gap-2 p-3 rounded bg-warning bg-opacity-10 border border-warning-subtle text-start align-item-start">
|
||||||
|
<i className="bx bx-info-circle text-warning fs-5"></i>
|
||||||
|
<p className="mb-0 small">
|
||||||
|
{content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WarningBlock
|
||||||
@ -19,6 +19,8 @@ import SelectField from "../common/Forms/SelectField";
|
|||||||
import Filelist from "../Expenses/Filelist";
|
import Filelist from "../Expenses/Filelist";
|
||||||
import SingleFileUploader from "../common/SigleFileUploader";
|
import SingleFileUploader from "../common/SigleFileUploader";
|
||||||
import { localToUtc } from "../../utils/appUtils";
|
import { localToUtc } from "../../utils/appUtils";
|
||||||
|
import WarningBlock from "../InfoBlock/WarningBlock";
|
||||||
|
import { FILE_UPLOAD_INFO } from "../../utils/staticContent";
|
||||||
|
|
||||||
const DeliveryChallane = ({ purchaseId }) => {
|
const DeliveryChallane = ({ purchaseId }) => {
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
@ -178,15 +180,7 @@ const DeliveryChallane = ({ purchaseId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{!isUploaded && (
|
{!isUploaded && (
|
||||||
<div className="col-12 my-1">
|
<WarningBlock content={FILE_UPLOAD_INFO} />
|
||||||
<div className="d-flex align-items-center gap-2 p-3 rounded bg-warning bg-opacity-10 border border-warning-subtle text-start align-item-start">
|
|
||||||
<i className="bx bx-info-circle text-warning fs-5"></i>
|
|
||||||
<p className="mb-0 small">
|
|
||||||
If want upload document, Please select a document type before
|
|
||||||
uploading the document.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
|
|||||||
name: "Payment Details",
|
name: "Payment Details",
|
||||||
icon: "bx bx-credit-card bx-md",
|
icon: "bx bx-credit-card bx-md",
|
||||||
subtitle: "Amount, tax & due date",
|
subtitle: "Amount, tax & due date",
|
||||||
component: <PurchasePaymentDetails purchaseId={purchaseId}/>,
|
component: <PurchasePaymentDetails purchaseId={purchaseId} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -65,17 +65,18 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
|
|||||||
fileName: doc.fileName,
|
fileName: doc.fileName,
|
||||||
base64Data: null,
|
base64Data: null,
|
||||||
contentType: doc.contentType,
|
contentType: doc.contentType,
|
||||||
documentId: doc.id,
|
documentId: doc.documentId,
|
||||||
|
invoiceAttachmentTypeId:doc.invoiceAttachmentTypeId ?? 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 = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -117,7 +118,8 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
|
|||||||
|
|
||||||
if (purchaseId) {
|
if (purchaseId) {
|
||||||
const changedData = Object.keys(dirtyFields).reduce((acc, key) => {
|
const changedData = Object.keys(dirtyFields).reduce((acc, key) => {
|
||||||
if (dirtyFields[key]) {
|
debugger;
|
||||||
|
if (dirtyFields[key] && key !== "invoiceAttachmentTypeId") {
|
||||||
acc.push({
|
acc.push({
|
||||||
operationType: 0,
|
operationType: 0,
|
||||||
path: `/${key}`,
|
path: `/${key}`,
|
||||||
@ -128,15 +130,16 @@ const ManagePurchase = ({ onClose, purchaseId }) => {
|
|||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
console.log(changedData)
|
||||||
updatePurchase({
|
// updatePurchase({
|
||||||
purchaseId,
|
// purchaseId,
|
||||||
payload: changedData,
|
// payload: changedData,
|
||||||
});
|
// });
|
||||||
} else {
|
} else {
|
||||||
CreateInvoice(formData);
|
CreateInvoice(formData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
console.log(errors)
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="wizard-property-listing"
|
id="wizard-property-listing"
|
||||||
|
|||||||
@ -1,11 +1,21 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import Label from "../common/Label";
|
import Label from "../common/Label";
|
||||||
import { useAppFormContext } from "../../hooks/appHooks/useAppForm";
|
import {
|
||||||
|
AppFormController,
|
||||||
|
useAppFormContext,
|
||||||
|
} from "../../hooks/appHooks/useAppForm";
|
||||||
import DatePicker from "../common/DatePicker";
|
import DatePicker from "../common/DatePicker";
|
||||||
import { useInvoiceAttachmentTypes } from "../../hooks/masterHook/useMaster";
|
import { useInvoiceAttachmentTypes } from "../../hooks/masterHook/useMaster";
|
||||||
import Filelist from "../Expenses/Filelist";
|
import Filelist from "../Expenses/Filelist";
|
||||||
const PurchasePaymentDetails = ({purchaseId=null}) => {
|
import SelectField from "../common/Forms/SelectField";
|
||||||
|
import { useWatch } from "react-hook-form";
|
||||||
|
import WarningBlock from "../InfoBlock/WarningBlock";
|
||||||
|
import { FILE_UPLOAD_INFO } from "../../utils/staticContent";
|
||||||
|
|
||||||
|
const PurchasePaymentDetails = ({ purchaseId = null }) => {
|
||||||
const { data, isLoading, error, isError } = useInvoiceAttachmentTypes();
|
const { data, isLoading, error, isError } = useInvoiceAttachmentTypes();
|
||||||
|
const { data: InvoiceDocTypes, isLoading: invoiceDocLoading } =
|
||||||
|
useInvoiceAttachmentTypes();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
watch,
|
watch,
|
||||||
@ -25,7 +35,7 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
setValue("totalAmount", (base + tax).toFixed(2));
|
setValue("totalAmount", (base + tax).toFixed(2));
|
||||||
}
|
}
|
||||||
}, [baseAmount, taxAmount, setValue]);
|
}, [baseAmount, taxAmount, setValue]);
|
||||||
|
const invoiceAttachmentType = watch("invoiceAttachmentTypeId");
|
||||||
const files = watch("attachments");
|
const files = watch("attachments");
|
||||||
const onFileChange = async (e) => {
|
const onFileChange = async (e) => {
|
||||||
const newFiles = Array.from(e.target.files);
|
const newFiles = Array.from(e.target.files);
|
||||||
@ -43,6 +53,7 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
fileSize: file.size,
|
fileSize: file.size,
|
||||||
description: "",
|
description: "",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
invoiceAttachmentTypeId: invoiceAttachmentType,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -72,16 +83,24 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
reader.onerror = (error) => reject(error);
|
reader.onerror = (error) => reject(error);
|
||||||
});
|
});
|
||||||
const removeFile = (index) => {
|
const removeFile = (index) => {
|
||||||
debugger
|
|
||||||
|
|
||||||
if (purchaseId) {
|
if (purchaseId) {
|
||||||
const newFiles = files.map((file, i) => {
|
const newFiles = files.map((file, i) => {
|
||||||
if (file.documentId !== index) return file;
|
if (i !== index) return file;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...file,
|
...file,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setValue("attachments", newFiles, { 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 {
|
} else {
|
||||||
const newFiles = files.filter((_, i) => i !== index);
|
const newFiles = files.filter((_, i) => i !== index);
|
||||||
setValue("attachments", newFiles, { shouldValidate: true });
|
setValue("attachments", newFiles, { shouldValidate: true });
|
||||||
@ -89,7 +108,7 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row g-3 text-start">
|
<div className="row g-1 text-start">
|
||||||
<div className="col-12 col-md-4">
|
<div className="col-12 col-md-4">
|
||||||
<Label htmlFor="baseAmount" required>
|
<Label htmlFor="baseAmount" required>
|
||||||
Base Amount
|
Base Amount
|
||||||
@ -200,6 +219,31 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<AppFormController
|
||||||
|
name="invoiceAttachmentTypeId"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<SelectField
|
||||||
|
label="Select Document Type"
|
||||||
|
options={InvoiceDocTypes ?? []}
|
||||||
|
placeholder="Choose Type"
|
||||||
|
labelKeyKey="name"
|
||||||
|
valueKeyKey="id"
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
isLoading={invoiceDocLoading}
|
||||||
|
className="m-0"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errors?.invoiceAttachmentTypeId && (
|
||||||
|
<small className="danger-text">
|
||||||
|
{errors.invoiceAttachmentTypeId.message}
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="text-start">
|
<div className="text-start">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
@ -223,6 +267,7 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
id="attachments"
|
id="attachments"
|
||||||
accept=".pdf,.jpg,.jpeg,.png"
|
accept=".pdf,.jpg,.jpeg,.png"
|
||||||
multiple
|
multiple
|
||||||
|
disabled={!invoiceAttachmentType}
|
||||||
style={{ display: "none" }}
|
style={{ display: "none" }}
|
||||||
{...register("attachments")}
|
{...register("attachments")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@ -232,9 +277,7 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.attachments && (
|
{errors.attachments && (
|
||||||
<small className="danger-text">
|
<small className="danger-text">{errors.attachments.message}</small>
|
||||||
{errors.attachments.message}
|
|
||||||
</small>
|
|
||||||
)}
|
)}
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<Filelist
|
<Filelist
|
||||||
@ -251,10 +294,13 @@ const PurchasePaymentDetails = ({purchaseId=null}) => {
|
|||||||
(fileError?.fileSize?.message ||
|
(fileError?.fileSize?.message ||
|
||||||
fileError?.contentType?.message ||
|
fileError?.contentType?.message ||
|
||||||
fileError?.base64Data?.message,
|
fileError?.base64Data?.message,
|
||||||
fileError?.documentId?.message)
|
fileError?.documentId?.message)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{!invoiceAttachmentType && (
|
||||||
|
<WarningBlock content={FILE_UPLOAD_INFO} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,10 +10,9 @@ const ALLOWED_TYPES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const AttachmentSchema = z.object({
|
export const AttachmentSchema = z.object({
|
||||||
documentId: z.string().optional(),
|
invoiceAttachmentTypeId: z.string().nullable(),
|
||||||
invoiceAttachmentTypeId: z.string().min(1, { message: "Attachment type is required" }),
|
|
||||||
fileName: z.string().min(1, { message: "Filename is required" }),
|
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
|
contentType: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ALLOWED_TYPES.includes(val), {
|
.refine((val) => ALLOWED_TYPES.includes(val), {
|
||||||
@ -25,39 +24,7 @@ export const AttachmentSchema = z.object({
|
|||||||
description: z.string().optional().default(""),
|
description: z.string().optional().default(""),
|
||||||
isActive: z.boolean().default(true),
|
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({
|
export const PurchaseSchema = z.object({
|
||||||
title: z.string().min(1, { message: "Title is required" }),
|
title: z.string().min(1, { message: "Title is required" }),
|
||||||
@ -89,9 +56,10 @@ export const PurchaseSchema = z.object({
|
|||||||
paymentDueDate: z.coerce.date().nullable(),
|
paymentDueDate: z.coerce.date().nullable(),
|
||||||
transportCharges: z.number().nullable(),
|
transportCharges: z.number().nullable(),
|
||||||
description: z.string().min(1, { message: "Description is required" }),
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
|
invoiceAttachmentTypeId:z.string().nullable(),
|
||||||
attachments: z
|
attachments: z
|
||||||
.array(AttachmentSchema)
|
.array(AttachmentSchema)
|
||||||
.nonempty({ message: "At least one file attachment is required" }),
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,9 +98,8 @@ export const defaultPurchaseValue = {
|
|||||||
paymentDueDate: null,
|
paymentDueDate: null,
|
||||||
transportCharges: null,
|
transportCharges: null,
|
||||||
description: "",
|
description: "",
|
||||||
|
invoiceAttachmentTypeId:null,
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
|
||||||
// attachments: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStepFields = (stepIndex) => {
|
export const getStepFields = (stepIndex) => {
|
||||||
@ -165,7 +132,9 @@ export const getStepFields = (stepIndex) => {
|
|||||||
"totalAmount",
|
"totalAmount",
|
||||||
"transportCharges",
|
"transportCharges",
|
||||||
"paymentDueDate",
|
"paymentDueDate",
|
||||||
|
"invoiceAttachmentTypeId",
|
||||||
"description",
|
"description",
|
||||||
|
"attachments"
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
4
src/utils/staticContent.jsx
Normal file
4
src/utils/staticContent.jsx
Normal file
@ -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.
|
||||||
|
`;
|
||||||
Loading…
x
Reference in New Issue
Block a user