From 0dc68eb20d8e8d45fec108a693537b5e4bf880bd Mon Sep 17 00:00:00 2001 From: "pramod.mahajan" Date: Tue, 4 Nov 2025 17:15:42 +0530 Subject: [PATCH] fixed file uploading and payee --- src/components/Expenses/ExpenseList.jsx | 2 +- src/components/Expenses/ExpenseSchema.js | 2 +- src/components/Expenses/Filelist.jsx | 2 +- src/components/Expenses/ManageExpense.jsx | 7 +- .../PaymentRequest/ManagePaymentRequest.jsx | 990 +++++++++--------- .../PaymentRequest/PaymentRequestSchema.js | 95 +- .../PaymentRequest/ViewPaymentRequest.jsx | 8 +- src/components/common/TimeLine.jsx | 6 +- src/hooks/useExpense.js | 13 + .../PaymentRequest/PaymentRequestPage.jsx | 22 +- src/repositories/ExpsenseRepository.jsx | 41 +- 11 files changed, 604 insertions(+), 584 deletions(-) diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 3b58316e..d4ad8eb4 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -307,7 +307,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { }) } > - {canDetetExpense(expense) && + { canEditExpense(expense) && (
- -
- - +
+ + + {errors.expenseCategoryId && ( + + {errors.expenseCategoryId.message} + + )} +
- ) + + {/* Title and Advance Payment */} +
+
+ + + {errors.title && ( + {errors.title.message} + )} +
+ +
+ + + {errors.isAdvancePayment && ( + + {errors.isAdvancePayment.message} + + )} +
+
+ + {/* Date and Amount */} +
+
+ + + + {errors.dueDate && ( + {errors.dueDate.message} + )} +
+ +
+ + + {errors.amount && ( + {errors.amount.message} + )} +
+
+ + {/* Payee and Currency */} +
+
+ + + setValue("payee", val, { shouldValidate: true }) + } + error={errors.payee?.message} + /> + + {errors.payee && ( + {errors.payee.message} + )} + + {/* Checkbox below input */} +
+ + +
+
+ +
+ + + {errors.currencyId && ( + {errors.currencyId.message} + )} +
+
+ + {/* Description */} +
+
+ + + {errors.description && ( + + {errors.description.message} + + )} +
+
+ + {/* Upload Document */} +
+
+ + +
document.getElementById("billAttachments").click()} + > + + + Click to select or click here to browse + + (PDF, JPG, PNG, max 5MB) + + { + onFileChange(e); + e.target.value = ""; + }} + /> +
+ {errors.billAttachments && ( + + {errors.billAttachments.message} + + )} + {files.length > 0 && ( + + )} + + {Array.isArray(errors.billAttachments) && + errors.billAttachments.map((fileError, index) => ( +
+ { + (fileError?.fileSize?.message || + fileError?.contentType?.message || + fileError?.base64Data?.message, + fileError?.documentId?.message) + } +
+ ))} +
+
+
+ + +
+ + + ); } -export default ManagePaymentRequest +export default ManagePaymentRequest; diff --git a/src/components/PaymentRequest/PaymentRequestSchema.js b/src/components/PaymentRequest/PaymentRequestSchema.js index fb813b63..006c207f 100644 --- a/src/components/PaymentRequest/PaymentRequestSchema.js +++ b/src/components/PaymentRequest/PaymentRequestSchema.js @@ -7,56 +7,48 @@ const ALLOWED_TYPES = [ "image/jpg", "image/jpeg", ]; -export const PaymentRequestSchema = (expenseTypes,isItself) => { - return z - .object({ - title: z.string().min(1, { message: "Project is required" }), - projectId: z.string().min(1, { message: "Project is required" }), - expenseCategoryId: z - .string() - .min(1, { message: "Expense Category is required" }), - currencyId: z - .string() - .min(1, { message: "Currency is required" }), - dueDate: z.string().min(1, { message: "Date is required" }), - description: z.string().min(1, { message: "Description is required" }), - payee: z.string().min(1, { message: "Supplier name is required" }), - isAdvancePayment: z.boolean().optional(), - amount: z.coerce - .number({ - invalid_type_error: "Amount is required and must be a number", +export const PaymentRequestSchema = (expenseTypes, isItself) => { + return z.object({ + title: z.string().min(1, { message: "Project is required" }), + projectId: z.string().min(1, { message: "Project is required" }), + expenseCategoryId: z + .string() + .min(1, { message: "Expense Category is required" }), + currencyId: z.string().min(1, { message: "Currency is required" }), + dueDate: z.string().min(1, { message: "Date is required" }), + description: z.string().min(1, { message: "Description is required" }), + payee: z.string().min(1, { message: "Supplier name is required" }), + isAdvancePayment: z.boolean().optional(), + amount: z.coerce + .number({ + invalid_type_error: "Amount is required and must be a number", + }) + .min(1, "Amount must be Enter") + .refine((val) => /^\d+(\.\d{1,2})?$/.test(val.toString()), { + message: "Amount must have at most 2 decimal places", + }), + billAttachments: z + .array( + z.object({ + fileName: z.string().min(1, { message: "Filename is required" }), + base64Data: z.string().nullable(), + contentType: z.string().refine((val) => ALLOWED_TYPES.includes(val), { + message: "Only PDF, PNG, JPG, or JPEG files are allowed", + }), + documentId: z.string().optional(), + fileSize: z.number().max(MAX_FILE_SIZE, { + message: "File size must be less than or equal to 5MB", + }), + description: z.string().optional(), + isActive: z.boolean().default(true), }) - .min(1, "Amount must be Enter") - .refine((val) => /^\d+(\.\d{1,2})?$/.test(val.toString()), { - message: "Amount must have at most 2 decimal places", - }), - billAttachments: z - .array( - z.object({ - fileName: z.string().min(1, { message: "Filename is required" }), - base64Data: z.string().nullable(), - contentType: z - .string() - .refine((val) => ALLOWED_TYPES.includes(val), { - message: "Only PDF, PNG, JPG, or JPEG files are allowed", - }), - documentId: z.string().optional(), - fileSize: z.number().max(MAX_FILE_SIZE, { - message: "File size must be less than or equal to 5MB", - }), - description: z.string().optional(), - isActive: z.boolean().default(true), - }) - ).refine((data)=>{ - if(isItself){ - payee.z.string().optional(); - } - }), - }) - }; + ) + .optional(), + }); +}; export const defaultPaymentRequest = { - title:"", + title: "", description: "", payee: "", currencyId: "", @@ -64,11 +56,10 @@ export const defaultPaymentRequest = { dueDate: "", projectId: "", expenseCategoryId: "", - isAdvancePayment:boolean, + isAdvancePayment: boolean, billAttachments: [], }; - export const SearchPaymentRequestSchema = z.object({ projectIds: z.array(z.string()).optional(), statusIds: z.array(z.string()).optional(), @@ -91,7 +82,6 @@ export const defaultPaymentRequestFilter = { endDate: null, }; - export const PaymentRequestActionScheam = ( isTransaction = false, transactionDate @@ -103,14 +93,13 @@ export const PaymentRequestActionScheam = ( paymentRequestId: z.string().nullable().optional(), paidAt: z.string().nullable().optional(), paidById: z.string().nullable().optional(), - }) .superRefine((data, ctx) => { if (isTransaction) { if (!data.paymentRequestId?.trim()) { ctx.addIssue({ code: z.ZodIssueCode.custom, - path: ["reimburseTransactionId"], + path: ["paymentRequestId"], message: "Reimburse Transaction ID is required", }); } @@ -148,4 +137,4 @@ export const defaultActionValues = { paidTransactionId: null, paidAt: null, paidById: null, -}; \ No newline at end of file +}; diff --git a/src/components/PaymentRequest/ViewPaymentRequest.jsx b/src/components/PaymentRequest/ViewPaymentRequest.jsx index 7be1b5e3..a5ef0dd6 100644 --- a/src/components/PaymentRequest/ViewPaymentRequest.jsx +++ b/src/components/PaymentRequest/ViewPaymentRequest.jsx @@ -111,7 +111,7 @@ const ViewPaymentRequest = ({ requestId }) => { }; return ( -
+
Payment Request Details

@@ -325,14 +325,14 @@ const ViewPaymentRequest = ({ requestId }) => {
{data?.description}
-
+
- {data?.documents?.length > 0 ? ( - data?.documents?.map((doc) => { + {data?.attachments?.length > 0 ? ( + data?.attachments?.map((doc) => { const isImage = doc?.contentType?.includes("image"); return ( diff --git a/src/components/common/TimeLine.jsx b/src/components/common/TimeLine.jsx index 43d32873..96ea6628 100644 --- a/src/components/common/TimeLine.jsx +++ b/src/components/common/TimeLine.jsx @@ -54,7 +54,7 @@ const Timeline = ({ items = [], transparent = true }) => {
    {item.users.map((user, i) => ( -
  • +
  • {user.avatar ? ( { height="32" /> ) : ( - @@ -75,7 +75,7 @@ const Timeline = ({ items = [], transparent = true }) => { {item.users?.length === 1 && (
    -

    {`${item.users[0].firstName} ${item.users[0].lastName}`}

    +

    {`${item.users[0].firstName} ${item.users[0].lastName}`}

    {item.users[0].role}
    )} diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js index af8fb1b0..43866090 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -5,6 +5,19 @@ import { queryClient } from "../layouts/AuthLayout"; import { useSelector } from "react-redux"; import moment from "moment"; + + +export const usePayee =()=>{ + return useQuery({ + queryKey:["payee"], + queryFn:async()=>{ + const resp = await ExpenseRepository.GetPayee(); + return resp.data; + } + }) +} + + // -------------------Query------------------------------------------------------ const cleanFilter = (filter) => { diff --git a/src/pages/PaymentRequest/PaymentRequestPage.jsx b/src/pages/PaymentRequest/PaymentRequestPage.jsx index be53bd31..7bcf78f9 100644 --- a/src/pages/PaymentRequest/PaymentRequestPage.jsx +++ b/src/pages/PaymentRequest/PaymentRequestPage.jsx @@ -8,6 +8,7 @@ import PaymentRequestList from "../../components/PaymentRequest/PaymentRequestLi import PaymentRequestFilterPanel from "../../components/PaymentRequest/PaymentRequestFilterPanel"; import { defaultPaymentRequestFilter,SearchPaymentRequestSchema } from "../../components/PaymentRequest/PaymentRequestSchema"; import ViewPaymentRequest from "../../components/PaymentRequest/ViewPaymentRequest"; +import PreviewDocument from "../../components/Expenses/PreviewDocument"; export const PaymentRequestContext = createContext(); export const usePaymentRequestContext = () => { @@ -25,12 +26,16 @@ const PaymentRequestPage = () => { const [ViewRequest,setVieRequest] = useState({view:false,requestId:null}) const { setOffcanvasContent, setShowTrigger } = useFab(); const [filters, setFilters] = useState(defaultPaymentRequestFilter); - + const [ViewDocument, setDocumentView] = useState({ + IsOpen: false, + Image: null, + }); const [search, setSearch] = useState(""); const contextValue = { setManageRequest, - setVieRequest + setVieRequest, + setDocumentView }; useEffect(() => { @@ -120,13 +125,24 @@ const PaymentRequestPage = () => { {ViewRequest.view && ( setVieRequest({ requestId: null, view: false })} > )} + + {ViewDocument.IsOpen && ( + setDocumentView({ IsOpen: false, Image: null })} + > + + + )}
diff --git a/src/repositories/ExpsenseRepository.jsx b/src/repositories/ExpsenseRepository.jsx index a57c89e6..f8920e4d 100644 --- a/src/repositories/ExpsenseRepository.jsx +++ b/src/repositories/ExpsenseRepository.jsx @@ -1,37 +1,50 @@ import { api } from "../utils/axiosClient"; - const ExpenseRepository = { + GetPayee: () => api.get("/api/Expense/payment-request/payee"), //#region Expense GetExpenseList: (pageSize, pageNumber, filter, searchString) => { const payloadJsonString = JSON.stringify(filter); - return api.get(`/api/expense/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`); + return api.get( + `/api/expense/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}` + ); }, GetExpenseDetails: (id) => api.get(`/api/Expense/details/${id}`), CreateExpense: (data) => api.post("/api/Expense/create", data), UpdateExpense: (id, data) => api.put(`/api/Expense/edit/${id}`, data), DeleteExpense: (id) => api.delete(`/api/Expense/delete/${id}`), - ActionOnExpense: (data) => api.post('/api/expense/action', data), - GetExpenseFilter: () => api.get('/api/Expense/filter'), + ActionOnExpense: (data) => api.post("/api/expense/action", data), + GetExpenseFilter: () => api.get("/api/Expense/filter"), //#endregion //#region Payment Request - GetPaymentRequestList: (pageSize, pageNumber, filter, isActive, searchString) => { + GetPaymentRequestList: ( + pageSize, + pageNumber, + filter, + isActive, + searchString + ) => { const payloadJsonString = JSON.stringify(filter); - return api.get(`/api/Expense/get/payment-requests/list?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`); + return api.get( + `/api/Expense/get/payment-requests/list?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}` + ); }, - CreatePaymentRequest: (data) => api.post("/api/expense/payment-request/create", data), - UpdatePaymentRequest: (id, data) => api.put(`/api/Expense/payment-request/edit/${id}`, data), - GetPaymentRequest: (id) => api.get(`/api/Expense/get/payment-request/details/${id}`), - GetPaymentRequestFilter: () => api.get('/api/Expense/payment-request/filter'), - ActionOnPaymentRequest: (data) => api.post('/api/Expense/payment-request/action', data), + CreatePaymentRequest: (data) => + api.post("/api/expense/payment-request/create", data), + UpdatePaymentRequest: (id, data) => + api.put(`/api/Expense/payment-request/edit/${id}`, data), + GetPaymentRequest: (id) => + api.get(`/api/Expense/get/payment-request/details/${id}`), + GetPaymentRequestFilter: () => api.get("/api/Expense/payment-request/filter"), + ActionOnPaymentRequest: (data) => + api.post("/api/Expense/payment-request/action", data), //#endregion - //#region Advance Payment - GetTranctionList: () => api.get(`/get/transactions/${employeeId}`) + GetTranctionList: () => api.get(`/get/transactions/${employeeId}`), //#endregion -} +}; export default ExpenseRepository;