From ee6c8069d0658810b0e4db6afdb53f80209db70c Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 24 Jul 2025 19:49:38 +0530 Subject: [PATCH 1/2] group wise row of expenses list --- src/components/Expenses/ExpenseList.jsx | 436 +++++++++++--------- src/components/Expenses/ExpenseSkeleton.jsx | 112 ++--- src/components/Expenses/ManageExpense.jsx | 8 +- src/components/Expenses/ViewExpense.jsx | 134 +++--- src/pages/Expense/ExpensePage.jsx | 41 +- 5 files changed, 410 insertions(+), 321 deletions(-) diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 15c238d4..2755f79f 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -16,8 +16,7 @@ const ExpenseList = () => { const { setViewExpense, setManageExpenseModal } = useExpenseContext(); const [currentPage, setCurrentPage] = useState(1); const pageSize = 10; - const {profile} = useProfile() -console.log(profile) + const { profile } = useProfile(); const filter = { projectIds: [], statusIds: [], @@ -37,8 +36,8 @@ console.log(profile) { id }, { onSettled: () => { - setDeletingId(null); - setIsDeleteModalOpen(false) + setDeletingId(null); + setIsDeleteModalOpen(false); }, } ); @@ -55,9 +54,42 @@ console.log(profile) setCurrentPage(page); } }; + const STATUS_ORDER = [ + "Draft", + "Review Pending", + "Approval Pending", + "Process Pending", + "Processed", + "Paid", + "Rejected", + ]; + + const groupExpensesByDateAndStatus = (expenses) => { + const grouped = {}; + + expenses.forEach((expense) => { + const dateKey = expense.transactionDate.split("T")[0]; + if (!grouped[dateKey]) grouped[dateKey] = []; + grouped[dateKey].push(expense); + }); + + const sortedDates = Object.keys(grouped).sort( + (a, b) => new Date(b) - new Date(a) + ); + + return sortedDates.map((date) => ({ + date, + expenses: grouped[date].sort((a, b) => { + return ( + STATUS_ORDER.indexOf(a.status.name) - + STATUS_ORDER.indexOf(b.status.name) + ); + }), + })); + }; + return ( <> - {IsDeleteModalOpen && (
- setIsDeleteModalOpen(false)} loading={isPending} - paramData={deletingId} + paramData={deletingId} />
)} -
-
-
- +
+
-
- - + + {/* - - - - - - - - - - {/* {isLoading && ( - - + */} + + + + + + - )} + + + {!isInitialLoading && + groupExpensesByDateAndStatus(items).map( + ({ date, expenses }) => ( + <> + + + - {!isInitialLoading && items.length === 0 && ( - - - - )} */} - - {!isInitialLoading && - items.map((expense) => ( - - - - - - - - + + + - - ))} - -
+
Date Time
-
-
Expense Type
-
-
Payment Mode
-
-
Paid By
-
- Amount - - Status - - Action -
- Loading... - +
Expense Type
+
+
Payment Mode
+
+
Paid By
+
+ Amount + + Status + + Action +
+ {formatUTCToLocalTime(date)} +
- No expenses found. -
-
- - {formatUTCToLocalTime(expense.transactionDate)} - -
-
- {expense.expensesType?.name || "N/A"} - - {expense.paymentMode?.name || "N/A"} - -
- - - {`${expense.paidBy?.firstName ?? ""} ${ - expense.paidBy?.lastName ?? "" - }`.trim() || "N/A"} - -
-
- - {expense?.amount} - - - {expense.status?.displayName || "Unknown"} - - -
- - setViewExpense({ - expenseId: expense.id, - view: true, - }) - } - > - - - {(expense.status.name === 'Draft' || expense.status.name === 'Rejected') && (expense.createdBy.id === profile.employeeInfo.id ) &&( - - setManageExpenseModal({ - IsOpen: true, - expenseId: expense.id, - }) - } - > - - - )} - {expense.status.name == "Draft" && ( - { - setIsDeleteModalOpen(true) - setDeletingId(expense.id) - }} - > - {/* {deletingId === expense.id ? ( -
- - Loading... + {expenses.map((expense) => ( +
+ {expense.expensesType?.name || "N/A"} + + {expense.paymentMode?.name || "N/A"} + +
+ + + {`${expense.paidBy?.firstName ?? ""} ${ + expense.paidBy?.lastName ?? "" + }`.trim() || "N/A"}
- ) : ( */} - - {/* )} */} - - )} - -
+ + + + {expense?.amount} + + + + {expense.status?.displayName || "Unknown"} + + + +
+ + + setViewExpense({ + expenseId: expense.id, + view: true, + }) + } + > + + + + {(expense.status.name === "Draft" || + expense.status.name === "Rejected") && + expense.createdBy.id === + profile?.employeeInfo.id ? ( + + setManageExpenseModal({ + IsOpen: true, + expenseId: expense.id, + }) + } + > + ) : ( + + )} + + + + {expense.status.name === "Draft" ? ( + { + setIsDeleteModalOpen(true); + setDeletingId(expense.id); + }} + > + ) : ( + + )} + +
+ + + ))} + + ) + )} + + +
+ {!isInitialLoading && items.length > 0 && ( + + )}
- {!isInitialLoading && items.length > 0 && ( - - )}
- ); }; diff --git a/src/components/Expenses/ExpenseSkeleton.jsx b/src/components/Expenses/ExpenseSkeleton.jsx index 904c2e9d..59ae1792 100644 --- a/src/components/Expenses/ExpenseSkeleton.jsx +++ b/src/components/Expenses/ExpenseSkeleton.jsx @@ -123,17 +123,19 @@ export const ExpenseDetailsSkeleton = () => { ); }; -const SkeletonCell = ({ width = "100%", height = 20, className = "" }) => ( +const SkeletonCell = ({ width = "100%", height = 20, className = "", style = {} }) => (
); -export const ExpenseTableSkeleton = ({ rows = 5 }) => { + +export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 6 }) => { return ( { - {[...Array(rows)].map((_, idx) => ( - - {/* Date Time colSpan=2 */} - + {[...Array(groups)].map((_, groupIdx) => ( + + {/* Fake Date Group Header Row */} + + + - {/* Expense Type */} - + {/* Rows under this group */} + {[...Array(rowsPerGroup)].map((__, rowIdx) => ( + + {/* Date Time colSpan=2 */} + - {/* Payment Mode */} - + {/* Expense Type */} + - {/* Paid By (Avatar + name) */} - + {/* Payment Mode */} + - {/* Amount */} - + {/* Paid By (Avatar + name) */} + - {/* Status */} - + {/* Amount */} + - {/* Action (icons) */} - - + {/* Status */} + + + {/* Action */} + + + ))} + ))}
-
- -
-
+ +
- -
+
+ +
+
- - + + -
- - -
-
+ + - - +
+ + +
+
- - + + -
- {[...Array(3)].map((__, i) => ( - - ))} -
-
+ + +
+ {[...Array(3)].map((__, i) => ( + + ))} +
+
); -}; \ No newline at end of file +}; diff --git a/src/components/Expenses/ManageExpense.jsx b/src/components/Expenses/ManageExpense.jsx index c9f63386..f33fb2f7 100644 --- a/src/components/Expenses/ManageExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -133,7 +133,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { }; useEffect(() => { - if (expenseToEdit && data) { + if (expenseToEdit && data && employees) { reset({ projectId: data.project.id || "", expensesTypeId: data.expensesType.id || "", @@ -170,6 +170,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { } ); const onSubmit = (payload) => { + debugger if (expenseToEdit) { const editPayload = { ...payload, id: data.id }; ExpenseUpdate({ id: data.id, payload: editPayload }); @@ -194,7 +195,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { isLoading ) return ; - return (
@@ -520,7 +520,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { (fileError?.fileSize?.message || fileError?.contentType?.message || fileError?.base64Data?.message, - fileError?.documentId.message) + fileError?.documentId?.message) }
))} @@ -532,7 +532,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { diff --git a/src/components/Expenses/ViewExpense.jsx b/src/components/Expenses/ViewExpense.jsx index 28142cb4..a9f7ae0f 100644 --- a/src/components/Expenses/ViewExpense.jsx +++ b/src/components/Expenses/ViewExpense.jsx @@ -26,7 +26,7 @@ const ViewExpense = ({ ExpenseId }) => { }, }); - const { mutate: MakeAction } = useActionOnExpense(()=>reset()); + const { mutate: MakeAction } = useActionOnExpense(() => reset()); const onSubmit = (formData) => { const Payload = { @@ -38,7 +38,7 @@ const ViewExpense = ({ ExpenseId }) => { MakeAction(Payload); }; - if (isLoading) return + if (isLoading) return ; const handleImageLoad = (id) => { setImageLoaded((prev) => ({ ...prev, [id]: true })); }; @@ -52,7 +52,7 @@ const ViewExpense = ({ ExpenseId }) => {
{/* Expense Info Rows */} -
+
-
+
-
- - - - {data?.status?.displayName} - -
+
+ -
- -
{data.preApproved ? "Yes" : "No"}
-
+ + {data?.status?.displayName} + +
+ +
+ +
{data.preApproved ? "Yes" : "No"}
+
@@ -153,7 +157,7 @@ const ViewExpense = ({ ExpenseId }) => {
-
+
@@ -163,52 +167,61 @@ const ViewExpense = ({ ExpenseId }) => { - {data?.documents && data?.documents?.map((doc) => ( -
+ {data?.documents && + data?.documents?.map((doc) => (
- {doc.contentType === "application/pdf" ? ( -
- ) : ( - <> - {!imageLoaded[doc.id] && ( -
- Loading... -
- )} - {doc.fileName} handleImageLoad(doc.id)} - onClick={() => - setDocumentView({ IsOpen: true, Image: doc.preSignedUrl }) - } - /> - - )} -
+
+ {doc.contentType === "application/pdf" ? ( +
+ +
+ ) : ( + <> + {!imageLoaded[doc.id] && ( +
+ Loading... +
+ )} + {doc.fileName} handleImageLoad(doc.id)} + onClick={() => + setDocumentView({ + IsOpen: true, + Image: doc.preSignedUrl, + }) + } + /> + + )} +
-
- {doc.fileName} -
- +
+ {doc.fileName} +
+ +
-
- ))} + ))}

@@ -237,9 +250,8 @@ const ViewExpense = ({ ExpenseId }) => { handleSubmit(onSubmit)(); }} className="btn btn-primary btn-sm cursor-pointer mx-2 border-0" - > - {status.displayName || status.name} + {status.displayName || status.name} ))}
diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index c4bd8526..b405f1c3 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -6,6 +6,8 @@ import Breadcrumb from "../../components/common/Breadcrumb"; import GlobalModel from "../../components/common/GlobalModel"; import PreviewDocument from "../../components/Expenses/PreviewDocument"; import ManageExpense from "../../components/Expenses/ManageExpense"; +import { useProjectName } from "../../hooks/useProjects"; +import { useExpenseStatus } from "../../hooks/masterHook/useMaster"; export const ExpenseContext = createContext(); export const useExpenseContext = () => useContext(ExpenseContext); @@ -30,6 +32,8 @@ const ExpensePage = () => { setDocumentView, }; + const { projectNames } = useProjectName(); + const {} = useExpenseStatus(); return (
@@ -51,7 +55,42 @@ const ExpensePage = () => { aria-controls="DataTables_Table_0" /> - +
+ + +
+
+
+ + +
+ +
+ + +
+
+
+