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 d33c9795..3c4f7541 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/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 8c9fe46b..55b23cab 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -1,7 +1,6 @@ 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"; @@ -11,9 +10,9 @@ 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,