From 5f6a00f9f2bcaa58b6291565f2a5f62bd1d24c95 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Tue, 22 Jul 2025 18:53:17 +0530 Subject: [PATCH 1/5] initiated forn edit and created with one component --- src/components/Expenses/ExpenseList.jsx | 6 +- src/components/Expenses/ExpenseSchema.js | 15 ++++ .../{CreateExpense.jsx => ManageExpense.jsx} | 41 ++++++---- src/hooks/useExpense.js | 13 ++++ src/pages/Expense/ExpensePage.jsx | 74 ++++++++++++------- src/repositories/ExpsenseRepository.jsx | 5 +- 6 files changed, 106 insertions(+), 48 deletions(-) rename src/components/Expenses/{CreateExpense.jsx => ManageExpense.jsx} (93%) diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 2ac606e5..a635ac8c 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -7,7 +7,7 @@ import Pagination from "../common/Pagination"; import { ITEMS_PER_PAGE } from "../../utils/constants"; const ExpenseList = () => { - const { setViewExpense } = useExpenseContext(); + const { setViewExpense,setExpenseModal } = useExpenseContext(); const [currentPage, setCurrentPage] = useState(1); const pageSize = 10; @@ -197,14 +197,12 @@ const ExpenseList = () => { setExpenseModal({isOpen:true,ExpEdit:expense})} > - diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js index 99b75633..a2e11ed7 100644 --- a/src/components/Expenses/ExpenseSchema.js +++ b/src/components/Expenses/ExpenseSchema.js @@ -68,6 +68,21 @@ export const ExpenseSchema = (expenseTypes) => { }); }; +export const defaultExpense = { + projectId: "", + expensesTypeId: "", + paymentModeId: "", + paidById: "", + transactionDate: "", + transactionId: "", + description: "", + location: "", + supplerName: "", + amount: "", + noOfPersons: "", + billAttachments: [], + } + export const ActionSchema = z.object({ comment : z.string().min(1,{message:"Please leave comment"}), selectedStatus: z.string().min(1, { message: "Please select a status" }), diff --git a/src/components/Expenses/CreateExpense.jsx b/src/components/Expenses/ManageExpense.jsx similarity index 93% rename from src/components/Expenses/CreateExpense.jsx rename to src/components/Expenses/ManageExpense.jsx index cddc79dd..44283453 100644 --- a/src/components/Expenses/CreateExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -1,7 +1,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; -import { ExpenseSchema } from "./ExpenseSchema"; +import { defaultExpense, ExpenseSchema } from "./ExpenseSchema"; import { formatFileSize } from "../../utils/appUtils"; import { useProjectName } from "../../hooks/useProjects"; import { useDispatch, useSelector } from "react-redux"; @@ -18,7 +18,7 @@ import { import Avatar from "../common/Avatar"; import { useCreateExpnse } from "../../hooks/useExpense"; -const CreateExpense = ({closeModal}) => { +const CreateExpense = ({closeModal,expenseToEdit,}) => { const [ExpenseType, setExpenseType] = useState(); const dispatch = useDispatch(); const { @@ -36,21 +36,30 @@ const CreateExpense = ({closeModal}) => { formState: { errors }, } = useForm({ resolver: zodResolver(schema), - defaultValues: { - projectId: "", - expensesTypeId: "", - paymentModeId: "", - paidById: "", - transactionDate: "", - transactionId: "", - description: "", - location: "", - supplerName: "", - amount: "", - noOfPersons: "", - billAttachments: [], - }, + defaultValues: defaultExpense, }); + console.log(expenseToEdit) + + useEffect(() => { + if (expenseToEdit) { + reset({ + projectId: expenseToEdit.project.id || "", + expensesTypeId: expenseToEdit.expensesType.id + || "", + paymentModeId: expenseToEdit.paymentMode.id || "", + paidById: expenseToEdit.paidBy.id || "", + transactionDate: expenseToEdit.transactionDate?.slice(0, 10) || "", + transactionId: expenseToEdit.transactionId || "", + description: expenseToEdit.description || "", + location: expenseToEdit.location || "", + supplerName: expenseToEdit.supplerName || "", + amount: expenseToEdit.amount || "", + noOfPersons: expenseToEdit.noOfPersons || "", + billAttachments: expenseToEdit.billAttachments || [], + }); + } +}, [expenseToEdit, reset]); + const selectedproject = watch("projectId"); const selectedProject = useSelector( diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js index e63f1f9d..13ea9735 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -44,6 +44,19 @@ export const useCreateExpnse = (onSuccessCallBack) => { }); }; +export const useUpdateExepse =()=>{ +const queryClient = useQueryClient(); + +return useMutation({ + mutationFn:async (id,payload)=>{ + const response = await ExpenseRepository.UpdateExpense(id,payload) + }, + onSuccess:(updatedExpense,variables)=>{ + // updation list and details + } +}) +} + export const useActionOnExpense = (onSuccessCallBack) => { const queryClient = useQueryClient(); diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index 64a1daaa..9e78eeab 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -1,7 +1,7 @@ import React, { createContext, useContext, useState } from "react"; import ExpenseList from "../../components/Expenses/ExpenseList"; -import CreateExpense from "../../components/Expenses/CreateExpense"; +import CreateExpense from "../../components/Expenses/ManageExpense"; import ViewExpense from "../../components/Expenses/ViewExpense"; import Breadcrumb from "../../components/common/Breadcrumb"; import GlobalModel from "../../components/common/GlobalModel"; @@ -10,7 +10,10 @@ export const ExpenseContext = createContext(); export const useExpenseContext = () => useContext(ExpenseContext); const ExpensePage = () => { - const [isNewExpense, setNewExpense] = useState(false); + const [expenseModal, setExpenseModal] = useState({ + isOpen: false, + ExpEdit: false, + }); const [viewExpense, setViewExpense] = useState({ expenseId: null, view: false, @@ -18,6 +21,7 @@ const ExpensePage = () => { const contextValue = { setViewExpense, + setExpenseModal, }; return ( @@ -47,7 +51,12 @@ const ExpensePage = () => { ))} diff --git a/src/utils/appUtils.js b/src/utils/appUtils.js index 3f420b31..cd59baa6 100644 --- a/src/utils/appUtils.js +++ b/src/utils/appUtils.js @@ -3,39 +3,34 @@ export const formatFileSize=(bytes)=> { else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB"; else return (bytes / (1024 * 1024)).toFixed(2) + " MB"; } -export const getExpenseIcon = (type) => { - switch (type.toLowerCase()) { - case 'vendor/supplier payments': - return 'bx-briefcase'; // Business-related - case 'transport': - return 'bx-car'; // Vehicle or logistics - case 'compliance & safety': - return 'bx-shield-quarter'; // Security/safety - case 'mobilization': - return 'bx-building-house'; // Setup / site infra - case 'procurement': - return 'bx-package'; // Box/package/supplies - case 'maintenance & utilities': - return 'bx-wrench'; // Repair/maintenance - case 'travelling': - return 'bx-plane'; // Personnel delivery - case 'employee welfare': - return 'bx-user-heart'; // Welfare / people - default: - return 'bx-folder'; // Fallback icon +export const AppColorconfig = { + colors: { + primary: '#696cff', + secondary: '#8592a3', + success: '#71dd37', + info: '#03c3ec', + warning: '#ffab00', + danger: '#ff3e1d', + dark: '#233446', + black: '#000', + white: '#fff', + cardColor: '#fff', + bodyBg: '#f5f5f9', + bodyColor: '#697a8d', + headingColor: '#566a7f', + textMuted: '#a1acb8', + borderColor: '#eceef1' } }; -export const getPaymentModeIcon = (mode) => { - switch (mode.toLowerCase()) { - case 'cash': - return 'bx-money'; // Cash/coins - case 'upi': - return 'bx-mobile-alt'; // Mobile payment - case 'cheque': - return 'bx-receipt'; // Paper receipt - case 'netbanking': - return 'bx-globe'; // Online/internet - default: - return 'bx-credit-card'; // Generic fallback +export const getColorNameFromHex = (hex) => { + const normalizedHex = hex?.replace(/'/g, '').toLowerCase(); + const colors = AppColorconfig.colors; + + for (const [name, value] of Object.entries(colors)) { + if (value.toLowerCase() === normalizedHex) { + return name; + } } + + return null; // }; From 00b3a3420e39647c891300cc22fafcdb14ca0fce Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Wed, 23 Jul 2025 11:59:02 +0530 Subject: [PATCH 3/5] renamed status of expse list - name to displayName --- src/components/Expenses/ExpenseList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 499df314..b80c901e 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -177,7 +177,7 @@ const ExpenseList = () => { className={`badge bg-label-${getColorNameFromHex(expense.status.color) || 'secondary'}`} > - {expense.status?.name || "Unknown"} + {expense.status?.displayName || "Unknown"} From 289a732600a566b0beaed1961196a1ee5c57f5de Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 24 Jul 2025 02:55:39 +0530 Subject: [PATCH 4/5] progress in expense update --- index.html | 1 + public/assets/css/skeleton.css | 32 +++ src/components/Expenses/ExpenseList.jsx | 10 +- src/components/Expenses/ExpenseSchema.js | 2 +- src/components/Expenses/ExpenseSkeleton.jsx | 218 ++++++++++++++++++++ src/components/Expenses/ManageExpense.jsx | 139 ++++++++----- src/components/Expenses/ViewExpense.jsx | 69 +++---- src/hooks/useExpense.js | 2 +- src/pages/Expense/ExpensePage.jsx | 56 +++-- 9 files changed, 414 insertions(+), 115 deletions(-) create mode 100644 public/assets/css/skeleton.css create mode 100644 src/components/Expenses/ExpenseSkeleton.jsx diff --git a/index.html b/index.html index 748027e9..a1531220 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,7 @@ + diff --git a/public/assets/css/skeleton.css b/public/assets/css/skeleton.css new file mode 100644 index 00000000..2ee909d2 --- /dev/null +++ b/public/assets/css/skeleton.css @@ -0,0 +1,32 @@ +/* skeleton.css */ +.skeleton { + background-color: #e2e8f0; /* Tailwind's gray-300 */ + border-radius: 0.25rem; /* Tailwind's rounded */ + position: relative; + overflow: hidden; +} + +.skeleton::after { + content: ''; + display: block; + position: absolute; + top: 0; left: -150px; + height: 100%; + width: 150px; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.4), + transparent + ); + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { + left: -150px; + } + 100% { + left: 100%; + } +} \ No newline at end of file diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 7a93d2d0..1538bb84 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -6,9 +6,10 @@ import { formatDate, formatUTCToLocalTime } from "../../utils/dateUtils"; import Pagination from "../common/Pagination"; import { ITEMS_PER_PAGE } from "../../utils/constants"; import { AppColorconfig, getColorNameFromHex } from "../../utils/appUtils"; +import { ExpenseTableSkeleton } from "./ExpenseSkeleton"; const ExpenseList = () => { - const { setViewExpense,setExpenseModal } = useExpenseContext(); + const { setViewExpense,setManageExpenseModal } = useExpenseContext(); const [currentPage, setCurrentPage] = useState(1); const pageSize = 10; @@ -22,8 +23,9 @@ const ExpenseList = () => { }; - const { data, isLoading, isError,isInitialLoading } = useExpenseList(10, currentPage, filter); - if (isInitialLoading) return
Loading...
; + const { data, isLoading, isError,isInitialLoading,error } = useExpenseList(10, currentPage, filter); + if (isInitialLoading) return ; + if (isError) return
{error}
; const items = data.data ?? []; const totalPages = data?.totalPages ?? 1; const hasMore = currentPage < totalPages; @@ -192,7 +194,7 @@ const ExpenseList = () => { setExpenseModal({isOpen:true,ExpEdit:expense})} + onClick={()=>setManageExpenseModal({IsOpen:true,expenseId:expense.id})} > diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js index a2e11ed7..7c4993c5 100644 --- a/src/components/Expenses/ExpenseSchema.js +++ b/src/components/Expenses/ExpenseSchema.js @@ -35,7 +35,7 @@ export const ExpenseSchema = (expenseTypes) => { .array( z.object({ 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.string().refine((val) => ALLOWED_TYPES.includes(val), { message: "Only PDF, PNG, JPG, or JPEG files are allowed", }), diff --git a/src/components/Expenses/ExpenseSkeleton.jsx b/src/components/Expenses/ExpenseSkeleton.jsx new file mode 100644 index 00000000..904c2e9d --- /dev/null +++ b/src/components/Expenses/ExpenseSkeleton.jsx @@ -0,0 +1,218 @@ +import React from "react"; + + +const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => ( +
+); + + +const ExpenseSkeleton = () => { + return ( +
+
+ +
+ + {[...Array(5)].map((_, idx) => ( +
+
+ +
+
+ +
+
+ ))} + +
+
+ +
+
+ +
+
+ +
+
+ +
+ + +
+
+ ); +}; + +export default ExpenseSkeleton; + + + + +export const ExpenseDetailsSkeleton = () => { + return ( +
+
+
+ +
+ + {[...Array(3)].map((_, i) => ( +
+ + +
+ ))} + + {[...Array(6)].map((_, i) => ( +
+ + +
+ ))} + + + +
+ + {[...Array(2)].map((_, i) => ( +
+
+
+ + +
+
+ ))} +
+ +
+ +
+ + +
+ {[...Array(2)].map((_, i) => ( + + ))} +
+
+
+
+ ); +}; +const SkeletonCell = ({ width = "100%", height = 20, className = "" }) => ( +
+); +export const ExpenseTableSkeleton = ({ rows = 5 }) => { + return ( + + + + + + + + + + + + + + + {[...Array(rows)].map((_, idx) => ( + + {/* Date Time colSpan=2 */} + + + {/* Expense Type */} + + + {/* Payment Mode */} + + + {/* Paid By (Avatar + name) */} + + + {/* Amount */} + + + {/* Status */} + + + {/* Action (icons) */} + + + ))} + +
+
Date Time
+
+
Expense Type
+
+
Payment Mode
+
Paid ByAmountStatusAction
+
+ +
+
+ + + + +
+ + +
+
+ + + + +
+ {[...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 44283453..f2b18713 100644 --- a/src/components/Expenses/ManageExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -16,9 +16,19 @@ import { useEmployeesByProject, } from "../../hooks/useEmployees"; import Avatar from "../common/Avatar"; -import { useCreateExpnse } from "../../hooks/useExpense"; +import { + useCreateExpnse, + useExpense, + useUpdateExepse, +} from "../../hooks/useExpense"; +import ExpenseSkeleton from "./ExpenseSkeleton"; -const CreateExpense = ({closeModal,expenseToEdit,}) => { +const ManageExpense = ({ closeModal, expenseToEdit = null }) => { + const { + data, + isLoading, + error: ExpenseErrorLoad, + } = useExpense(expenseToEdit); const [ExpenseType, setExpenseType] = useState(); const dispatch = useDispatch(); const { @@ -38,27 +48,6 @@ const CreateExpense = ({closeModal,expenseToEdit,}) => { resolver: zodResolver(schema), defaultValues: defaultExpense, }); - console.log(expenseToEdit) - - useEffect(() => { - if (expenseToEdit) { - reset({ - projectId: expenseToEdit.project.id || "", - expensesTypeId: expenseToEdit.expensesType.id - || "", - paymentModeId: expenseToEdit.paymentMode.id || "", - paidById: expenseToEdit.paidBy.id || "", - transactionDate: expenseToEdit.transactionDate?.slice(0, 10) || "", - transactionId: expenseToEdit.transactionId || "", - description: expenseToEdit.description || "", - location: expenseToEdit.location || "", - supplerName: expenseToEdit.supplerName || "", - amount: expenseToEdit.amount || "", - noOfPersons: expenseToEdit.noOfPersons || "", - billAttachments: expenseToEdit.billAttachments || [], - }); - } -}, [expenseToEdit, reset]); const selectedproject = watch("projectId"); @@ -83,6 +72,7 @@ const CreateExpense = ({closeModal,expenseToEdit,}) => { error: EmpError, } = useEmployeesByProject(selectedproject); + const files = watch("billAttachments"); const onFileChange = async (e) => { const newFiles = Array.from(e.target.files); @@ -132,27 +122,66 @@ const CreateExpense = ({closeModal,expenseToEdit,}) => { setValue("billAttachments", newFiles, { shouldValidate: true }); }; - const {mutate:CreateExpense,isPending} = useCreateExpnse(()=>{ + useEffect(() => { + if (expenseToEdit && data) { + reset({ + projectId: data.project.id || "", + expensesTypeId: data.expensesType.id || "", + paymentModeId: data.paymentMode.id || "", + paidById: data.paidBy.id || "", + transactionDate: data.transactionDate?.slice(0, 10) || "", + transactionId: data.transactionId || "", + description: data.description || "", + location: data.location || "", + supplerName: data.supplerName || "", + amount: data.amount || "", + noOfPersons: data.noOfPersons || "", + billAttachments: data.documents + ? data.documents.map((doc) => ({ + fileName: doc.fileName, + base64Data: null, + contentType: doc.contentType, + fileSize: 0, + description: "", + preSignedUrl: doc.preSignedUrl, + })) + : [], + }); + } + }, [data, reset, employees]); + const { mutate: UpdateExpense, isPending } = useUpdateExepse(() => handleClose() - }) + ); + const { mutate: CreateExpense, isPending: createPending } = useCreateExpnse( + () => { + handleClose(); + } + ); const onSubmit = (payload) => { - console.log("Form Data:", payload); - - CreateExpense(payload) + if (expenseToEdit) { + const editPayload = { ...payload, id: data.id }; + UpdateExpense({ id: data.id, payload: editPayload }); + } else { + CreateExpense(payload); + } }; const ExpenseTypeId = watch("expensesTypeId"); useEffect(() => { setExpenseType(ExpenseTypes?.find((type) => type.id === ExpenseTypeId)); + return () => reset(defaultExpense); }, [ExpenseTypeId]); - const handleClose =()=>{ - reset() - closeModal() - } + const handleClose = () => { + reset(); + closeModal(); + }; + if(EmpLoading || StatusLoadding || projectLoading || ExpenseLoading || isLoading) return + + return (
-
Create New Expense
+
{expenseToEdit ? "Update Expense ": "Create New Expense"}
@@ -257,16 +286,13 @@ const CreateExpense = ({closeModal,expenseToEdit,}) => { ) : ( employees?.map((emp) => ( )) )} - {errors.paidById && ( - - {errors.paidById.message} - + {errors.paidById && ( + {errors.paidById.message} )}
@@ -401,9 +427,7 @@ const CreateExpense = ({closeModal,expenseToEdit,}) => {
- + )} + {Array.isArray(errors.billAttachments) && errors.billAttachments.map((fileError, index) => (
{fileError?.fileSize?.message || - fileError?.contentType?.message} + fileError?.contentType?.message || + fileError?.base64Data?.message}
))}
@@ -470,10 +502,18 @@ const CreateExpense = ({closeModal,expenseToEdit,}) => {
{" "} - -
@@ -482,5 +522,4 @@ const CreateExpense = ({closeModal,expenseToEdit,}) => { ); }; -export default CreateExpense; - +export default ManageExpense; diff --git a/src/components/Expenses/ViewExpense.jsx b/src/components/Expenses/ViewExpense.jsx index a6fd2f45..79088512 100644 --- a/src/components/Expenses/ViewExpense.jsx +++ b/src/components/Expenses/ViewExpense.jsx @@ -5,12 +5,13 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { ActionSchema } from "./ExpenseSchema"; import { useExpenseContext } from "../../pages/Expense/ExpensePage"; +import { getColorNameFromHex } from "../../utils/appUtils"; +import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton"; const ViewExpense = ({ ExpenseId }) => { const { data, isLoading, isError, error } = useExpense(ExpenseId); const [imageLoaded, setImageLoaded] = useState({}); const { setDocumentView } = useExpenseContext(); - const { register, handleSubmit, @@ -36,16 +37,7 @@ const ViewExpense = ({ ExpenseId }) => { MakeAction(Payload); }; - if (isLoading) { - return ( -
-
Loading...
-
- ); - } + if (isLoading) return const handleImageLoad = (id) => { setImageLoaded((prev) => ({ ...prev, [id]: true })); }; @@ -59,7 +51,7 @@ const ViewExpense = ({ ExpenseId }) => {
{/* Expense Info Rows */} -
+
-
+
-
+
-
+
₹ {data.amount}
-
+
-
+
-
-
-
+
- - {ExpenseId.status.displayName} + + + {data?.status?.displayName}
-
-
-
+
{data.preApproved ? "Yes" : "No"}
-
-
+
-
{data.project.name}
+
{data?.project?.name}
-
+
- {data.createdBy.firstName} {data.createdBy.lastName} + {data?.createdBy?.firstName} {data?.createdBy?.lastName}
-
+
- {formatUTCToLocalTime(data.createdAt, true)} + {formatUTCToLocalTime(data?.createdAt, true)}
-
+
-
{data.description}
+
{data?.description}
))}
diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js index 2e9a5e56..b2e0d679 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -54,7 +54,7 @@ export const useUpdateExepse =()=>{ const queryClient = useQueryClient(); return useMutation({ - mutationFn:async (id,payload)=>{ + mutationFn:async ({id,payload})=>{ const response = await ExpenseRepository.UpdateExpense(id,payload) }, onSuccess:(updatedExpense,variables)=>{ diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index 14749708..c4bd8526 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -1,27 +1,33 @@ import React, { createContext, useContext, useState } from "react"; import ExpenseList from "../../components/Expenses/ExpenseList"; -import CreateExpense from "../../components/Expenses/ManageExpense"; import ViewExpense from "../../components/Expenses/ViewExpense"; import Breadcrumb from "../../components/common/Breadcrumb"; import GlobalModel from "../../components/common/GlobalModel"; +import PreviewDocument from "../../components/Expenses/PreviewDocument"; +import ManageExpense from "../../components/Expenses/ManageExpense"; export const ExpenseContext = createContext(); export const useExpenseContext = () => useContext(ExpenseContext); const ExpensePage = () => { - const [expenseModal, setExpenseModal] = useState({ - isOpen: false, - ExpEdit: false, + const [ManageExpenseModal, setManageExpenseModal] = useState({ + IsOpen: null, + expenseId: null, }); const [viewExpense, setViewExpense] = useState({ expenseId: null, view: false, }); + const [ViewDocument, setDocumentView] = useState({ + IsOpen: false, + Image: null, + }); const contextValue = { setViewExpense, - setExpenseModal, + setManageExpenseModal, + setDocumentView, }; return ( @@ -56,7 +62,12 @@ const ExpensePage = () => { data-bs-custom-class="tooltip" title="Add New Expense" className={`p-1 me-2 bg-primary rounded-circle `} - onClick={() => setNewExpense(true)} + onClick={() => + setManageExpenseModal({ + IsOpen: true, + expenseId: null, + }) + } > @@ -66,25 +77,22 @@ const ExpensePage = () => {
- - {expenseModal.isOpen && ( + {ManageExpenseModal.IsOpen && ( - setExpenseModal({ - isOpen: false, - ExpEdit:null + setManageExpenseModal({ + IsOpen: null, + expenseId: null, }) } > - - setExpenseModal({ - isOpen: false, - ExpEdit:null - }) + setManageExpenseModal({ IsOpen: null, expenseId: null }) } /> @@ -94,6 +102,7 @@ const ExpensePage = () => { setViewExpense({ expenseId: null, @@ -104,6 +113,17 @@ const ExpensePage = () => { )} + + {ViewDocument.IsOpen && ( + setDocumentView({ IsOpen: false, Image: null })} + > + + + )}
); From de689b0af857e55fd5f9325e1bc4cb0dd0d0efe4 Mon Sep 17 00:00:00 2001 From: pramod mahajan Date: Thu, 24 Jul 2025 12:35:12 +0530 Subject: [PATCH 5/5] completed full updation for expnse feature --- src/components/Expenses/ExpenseList.jsx | 12 +-- src/components/Expenses/ExpenseSchema.js | 2 + src/components/Expenses/ManageExpense.jsx | 114 ++++++++++++++-------- src/hooks/useExpense.js | 81 +++++++++++---- 4 files changed, 144 insertions(+), 65 deletions(-) diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 1538bb84..14430f46 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -23,10 +23,10 @@ const ExpenseList = () => { }; - const { data, isLoading, isError,isInitialLoading,error } = useExpenseList(10, currentPage, filter); - if (isInitialLoading) return ; - if (isError) return
{error}
; - const items = data.data ?? []; + const { data, isLoading, isError,isInitialLoading,error,isFetching } = useExpenseList(10, currentPage, filter); + if (isInitialLoading ) return ; + if (isError) return
{error}
; + const items = data?.data ?? []; const totalPages = data?.totalPages ?? 1; const hasMore = currentPage < totalPages; @@ -128,7 +128,7 @@ const ExpenseList = () => { - {isLoading && ( + {/* {isLoading && ( Loading... @@ -142,7 +142,7 @@ const ExpenseList = () => { No expenses found. - )} + )} */} {!isInitialLoading && items.map((expense) => ( diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js index 7c4993c5..705efe6b 100644 --- a/src/components/Expenses/ExpenseSchema.js +++ b/src/components/Expenses/ExpenseSchema.js @@ -39,10 +39,12 @@ export const ExpenseSchema = (expenseTypes) => { 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) }) ) .nonempty({ message: "At least one file attachment is required" }), diff --git a/src/components/Expenses/ManageExpense.jsx b/src/components/Expenses/ManageExpense.jsx index f2b18713..23c13250 100644 --- a/src/components/Expenses/ManageExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -19,7 +19,7 @@ import Avatar from "../common/Avatar"; import { useCreateExpnse, useExpense, - useUpdateExepse, + useUpdateExpense, } from "../../hooks/useExpense"; import ExpenseSkeleton from "./ExpenseSkeleton"; @@ -49,7 +49,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { defaultValues: defaultExpense, }); - const selectedproject = watch("projectId"); const selectedProject = useSelector( (store) => store.localVariables.projectId @@ -72,7 +71,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { error: EmpError, } = useEmployeesByProject(selectedproject); - const files = watch("billAttachments"); const onFileChange = async (e) => { const newFiles = Array.from(e.target.files); @@ -89,6 +87,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { contentType: file.type, fileSize: file.size, description: "", + isActive:true }; }) ); @@ -114,12 +113,23 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result.split(",")[1]); // base64 only, no prefix + reader.onload = () => resolve(reader.result.split(",")[1]); reader.onerror = (error) => reject(error); }); const removeFile = (index) => { - const newFiles = files.filter((_, i) => i !== index); - setValue("billAttachments", newFiles, { shouldValidate: true }); + if (expenseToEdit) { + const newFiles = files.map((file, i) => { + if (file.documentId !== index) return file; + return { + ...file, + isActive: false, + }; + }); + setValue("billAttachments", newFiles, { shouldValidate: true }); + } else { + const newFiles = files.filter((_, i) => i !== index); + setValue("billAttachments", newFiles, { shouldValidate: true }); + } }; useEffect(() => { @@ -139,17 +149,19 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { billAttachments: data.documents ? data.documents.map((doc) => ({ fileName: doc.fileName, - base64Data: null, + base64Data: null, contentType: doc.contentType, + documentId: doc.documentId, fileSize: 0, description: "", - preSignedUrl: doc.preSignedUrl, + preSignedUrl: doc.preSignedUrl, + isActive: doc.isActive || true, })) : [], }); } }, [data, reset, employees]); - const { mutate: UpdateExpense, isPending } = useUpdateExepse(() => + const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() => handleClose() ); const { mutate: CreateExpense, isPending: createPending } = useCreateExpnse( @@ -160,7 +172,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { const onSubmit = (payload) => { if (expenseToEdit) { const editPayload = { ...payload, id: data.id }; - UpdateExpense({ id: data.id, payload: editPayload }); + ExpenseUpdate({ id: data.id, payload: editPayload }); } else { CreateExpense(payload); } @@ -176,12 +188,20 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { reset(); closeModal(); }; - if(EmpLoading || StatusLoadding || projectLoading || ExpenseLoading || isLoading) return - + if ( + EmpLoading || + StatusLoadding || + projectLoading || + ExpenseLoading || + isLoading + ) + return ; return (
-
{expenseToEdit ? "Update Expense ": "Create New Expense"}
+
+ {expenseToEdit ? "Update Expense " : "Create New Expense"} +
@@ -458,43 +478,52 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { {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?.fileSize?.message || + fileError?.contentType?.message || + fileError?.base64Data?.message, + fileError?.documentId.message) + }
))}
@@ -511,6 +540,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {