diff --git a/src/components/Dashboard/ExpenseByProject.jsx b/src/components/Dashboard/ExpenseByProject.jsx index 819824ca..06eaa6b7 100644 --- a/src/components/Dashboard/ExpenseByProject.jsx +++ b/src/components/Dashboard/ExpenseByProject.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import Chart from "react-apexcharts"; -import { useExpenseType } from "../../hooks/masterHook/useMaster"; +import { useExpenseCategory } from "../../hooks/masterHook/useMaster"; import { useSelector } from "react-redux"; import { useExpenseDataByProject } from "../../hooks/useDashboard_Data"; import { formatCurrency } from "../../utils/appUtils"; @@ -13,7 +13,7 @@ const ExpenseByProject = () => { const [viewMode, setViewMode] = useState("Category"); const [chartData, setChartData] = useState({ categories: [], data: [] }); - const { ExpenseTypes, loading: typeLoading } = useExpenseType(); + const { ExpenseTypes, loading: typeLoading } = useExpenseCategory(); const { data: expenseApiData, isLoading } = useExpenseDataByProject( projectId, diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 03b33bb9..b09f6f1c 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -92,8 +92,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { key = item?.paymentMode?.name || "Unknown Mode"; displayField = "Payment Mode"; break; - case "expensesType": - key = item?.expensesType?.name || "Unknown Type"; + case "expenseCategory": + key = item?.expenseCategory?.name || "Unknown Type"; displayField = "Expense Category"; break; case "createdAt": @@ -184,7 +184,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { ), }, ]; - const headers = ["Expense Type","Payment Mode","Submitted By","Submitted","Amount","Status","Action"] + const headers = ["Expense Category","Payment Mode","Submitted By","Submitted","Amount","Status","Action"] if (isInitialLoading && !data) return ; if (isError) return
{error?.message}
; diff --git a/src/components/Expenses/ExpenseSkeleton.jsx b/src/components/Expenses/ExpenseSkeleton.jsx index 6566bbd3..6fd56fb3 100644 --- a/src/components/Expenses/ExpenseSkeleton.jsx +++ b/src/components/Expenses/ExpenseSkeleton.jsx @@ -143,7 +143,11 @@ const SkeletonCell = ({ /> ); -export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3, headers }) => { +export const ExpenseTableSkeleton = ({ + groups = 3, + rowsPerGroup = 3, + headers, +}) => { return (
- {headers.map((header)=>( - + {headers.map((header) => ( + ))} - @@ -199,7 +202,7 @@ export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3, headers }) - {/* Submitted */} + {/* Submitted */} diff --git a/src/components/Expenses/ManageExpense.jsx b/src/components/Expenses/ManageExpense.jsx index f6608ccc..52095a8c 100644 --- a/src/components/Expenses/ManageExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -7,8 +7,8 @@ import { useProjectName } from "../../hooks/useProjects"; import { useDispatch, useSelector } from "react-redux"; import { changeMaster } from "../../slices/localVariablesSlice"; import useMaster, { + useExpenseCategory, useExpenseStatus, - useExpenseType, usePaymentMode, } from "../../hooks/masterHook/useMaster"; import { @@ -42,7 +42,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { ExpenseTypes, loading: ExpenseLoading, error: ExpenseError, - } = useExpenseType(); + } = useExpenseCategory; const schema = ExpenseSchema(ExpenseTypes); const { register, diff --git a/src/components/PaymentRequest/ManagePaymentRequest.jsx b/src/components/PaymentRequest/ManagePaymentRequest.jsx index 31754535..a390b0f4 100644 --- a/src/components/PaymentRequest/ManagePaymentRequest.jsx +++ b/src/components/PaymentRequest/ManagePaymentRequest.jsx @@ -2,16 +2,17 @@ import React, { useEffect } from 'react' import { useCurrencies, useProjectName } from '../../hooks/useProjects'; import Label from '../common/Label'; import { useForm } from 'react-hook-form'; -import { useExpenseType } from '../../hooks/masterHook/useMaster'; +import { useExpenseCategory } from '../../hooks/masterHook/useMaster'; import DatePicker from '../common/DatePicker'; -import { useCreatePaymentRequest, useUpdatePaymentRequest } from '../../hooks/useExpense'; -import { defaultPaymentRequest, PaymentRequestSchema } from './PaymentRequestSchema'; +import { useCreatePaymentRequest, usePaymentRequestDetail, useUpdatePaymentRequest } from '../../hooks/useExpense'; + import { zodResolver } from '@hookform/resolvers/zod'; import { formatFileSize, localToUtc } from '../../utils/appUtils'; +import { defaultPaymentRequest, PaymentRequestSchema } from './PaymentRequestSchema'; -function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { - - const { data } = {} +function ManagePaymentRequest({ closeModal, requestToEdit = null }) { + const {data,isLoading,isError,error:requestError}= usePaymentRequestDetail(requestToEdit) +// const data = {} const { projectNames, loading: projectLoading, error, isError: isProjectError, } = useProjectName(); @@ -23,7 +24,8 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { ExpenseTypes, loading: ExpenseLoading, error: ExpenseError, - } = useExpenseType(); + } = useExpenseCategory(); + const schema = PaymentRequestSchema(ExpenseTypes); const { register, control, watch, handleSubmit, setValue, reset, formState: { errors }, } = useForm({ resolver: zodResolver(schema), @@ -78,7 +80,7 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { reader.onerror = (error) => reject(error); }); const removeFile = (index) => { - if (expenseToEdit) { + if (requestToEdit) { const newFiles = files.map((file, i) => { if (file.documentId !== index) return file; return { @@ -108,16 +110,16 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { ); useEffect(() => { - if (expenseToEdit && data) { + if (requestToEdit && data) { reset({ title: data.title || "", description: data.description || "", payee: data.payee || "", - currencyId: data.currencyId.id || "", + currencyId: data.currency.id || "", amount: data.amount || "", dueDate: data.dueDate?.slice(0, 10) || "", projectId: data.project.id || "", - expenseCategoryId: data.expenseCategoryId.id || "", + expenseCategoryId: data.expenseCategory.id || "", isAdvancePayment: data.isAdvancePayment || false, billAttachments: data.documents ? data.documents.map((doc) => ({ @@ -141,7 +143,7 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { ...fromdata, dueDate: localToUtc(fromdata.dueDate), }; - if (expenseToEdit) { + if (requestToEdit) { const editPayload = { ...payload, id: data.id }; PaymentRequestUpdate({ id: data.id, payload: editPayload }); } else { @@ -153,7 +155,7 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { return (
- {expenseToEdit ? "Update Payment Request " : "Create Payment Request"} + {requestToEdit ? "Update Payment Request " : "Create Payment Request"}
@@ -395,7 +397,7 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) {
{files .filter((file) => { - if (expenseToEdit) { + if (requestToEdit) { return file.isActive; } return true; @@ -420,7 +422,7 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { className="bx bx-trash bx-sm cursor-pointer text-danger" onClick={(e) => { e.preventDefault(); - removeFile(expenseToEdit ? file.documentId : idx); + removeFile(requestToEdit ? file.documentId : idx); }} > @@ -458,7 +460,7 @@ function ManagePaymentRequest({ closeModal, expenseToEdit = null }) { > {createPending ? "Please Wait..." - : expenseToEdit + : requestToEdit ? "Update" : "Submit"} diff --git a/src/components/PaymentRequest/PaymentRequestList.jsx b/src/components/PaymentRequest/PaymentRequestList.jsx index 62e7b132..6356736d 100644 --- a/src/components/PaymentRequest/PaymentRequestList.jsx +++ b/src/components/PaymentRequest/PaymentRequestList.jsx @@ -1,20 +1,139 @@ import React, { useState } from "react"; -import { ITEMS_PER_PAGE } from "../../utils/constants"; -import { useDebounce } from "../../utils/appUtils"; -import { formatDate } from "../../utils/dateUtils"; +import { EXPENSE_DRAFT, ITEMS_PER_PAGE } from "../../utils/constants"; +import { + formatCurrency, + getColorNameFromHex, + useDebounce, +} from "../../utils/appUtils"; import { usePaymentRequestList } from "../../hooks/useExpense"; -import ExpenseSkeleton, { - ExpenseTableSkeleton, -} from "../Expenses/ExpenseSkeleton"; +import { formatDate, formatUTCToLocalTime } from "../../utils/dateUtils"; +import Avatar from "../../components/common/Avatar"; +import { usePaymentRequestContext } from "../../pages/PaymentRequest/PaymentRequestPage"; +import { ExpenseTableSkeleton } from "../Expenses/ExpenseSkeleton"; + +const PaymentRequestList = ({ groupBy = "submittedBy", search }) => { + const { setManageRequest } = usePaymentRequestContext(); + const groupByField = (items, field) => { + return items.reduce((acc, item) => { + let key; + let displayField; + + switch (field) { + case "transactionDate": + key = item?.transactionDate?.split("T")[0]; + displayField = "Transaction Date"; + break; + case "status": + key = item?.status?.displayName || "Unknown"; + displayField = "Status"; + break; + case "submittedBy": + key = `${item?.createdBy?.firstName ?? ""} ${ + item.createdBy?.lastName ?? "" + }`.trim(); + displayField = "Submitted By"; + break; + case "project": + key = item?.project?.name || "Unknown Project"; + displayField = "Project"; + break; + case "paymentMode": + key = item?.paymentMode?.name || "Unknown Mode"; + displayField = "Payment Mode"; + break; + case "expensesType": + key = item?.expensesType?.name || "Unknown Type"; + displayField = "Expense Category"; + break; + case "createdAt": + key = item?.createdAt?.split("T")[0] || "Unknown Date"; + displayField = "Created Date"; + break; + default: + key = "Others"; + displayField = "Others"; + } + + const groupKey = `${field}_${key}`; // unique key for object property + if (!acc[groupKey]) { + acc[groupKey] = { key, displayField, items: [] }; + } + + acc[groupKey].items.push(item); + return acc; + }, {}); + }; -const PaymentRequestList = ({ setManagePaymentRequestModal, search }) => { const paymentRequestColumns = [ - { key: "paymentRequestUID", label: "Request ID", align: "text-start mx-2" }, - { key: "title", label: "Request Title", align: "text-start" }, - { key: "payee", label: "Payee", align: "text-start" }, - { key: "createdAt", label: "Submitted On", align: "text-start" }, - { key: "amount", label: "Amount", align: "text-start" }, - { key: "expenseStatus", label: "Status", align: "text-start" }, + { + key: "paymentRequestUID", + label: "Request ID", + align: "text-start mx-2", + getValue: (e) => e.paymentRequestUID || "N/A", + }, + { + key: "title", + label: "Request Title", + align: "text-start", + getValue: (e) => e.title || "N/A", + }, + // { key: "payee", label: "Payee", align: "text-start" }, + { + key: "SubmittedBy", + label: "Submitted By", + align: "text-start", + getValue: (e) => + `${e.createdBy?.firstName ?? ""} ${ + e.createdBy?.lastName ?? "" + }`.trim() || "N/A", + customRender: (e) => ( +
navigate(`/employee/${e.createdBy?.id}`)} + > + + + {`${e.createdBy?.firstName ?? ""} ${ + e.createdBy?.lastName ?? "" + }`.trim() || "N/A"} + +
+ ), + }, + { + key: "createdAt", + label: "Submitted On", + align: "text-start", + getValue: (e) => formatUTCToLocalTime(e?.createdAt), + }, + { + key: "amount", + label: " Amount", + align: "text-start", + getValue: (e) => ( + <>{formatCurrency(e?.amount, e.currency.currencyCode)} + ), + align: "text-end px-3", + }, + { + key: "expenseStatus", + label: "Status", + align: "text-center", + getValue: (e) => ( + + {e?.expenseStatus?.name || "Unknown"} + + ), + }, ]; const [currentPage, setCurrentPage] = useState(1); @@ -40,16 +159,37 @@ const PaymentRequestList = ({ setManagePaymentRequestModal, search }) => { ); } const header = [ - "Expense ID", - "Expense Category", - "Payment Mode", - "Sumitted By", - "Submitted", + "Request ID", + "Request Title", + "Submitted By", + "Submitted On", "Amount", "Status", "Action", ]; if (isLoading) return ; + + const grouped = groupBy + ? groupByField(data?.data ?? [], groupBy) + : { All: data?.data ?? [] }; + const IsGroupedByDate = [ + { key: "transactionDate", displayField: "Transaction Date" }, + { key: "createdAt", displayField: "created Date" }, + ]?.includes(groupBy); + + const canEditExpense = (paymentRequest) => { + return ( + (paymentRequest?.status?.id === EXPENSE_DRAFT || + EXPENSE_REJECTEDBY.includes(paymentRequest?.status?.id)) && + paymentRequest?.createdBy?.id === SelfId + ); + }; + const canDetetExpense = (expense) => { + return ( + expense?.status?.id === EXPENSE_DRAFT && expense?.createdBy?.id === SelfId + ); + }; + return (
@@ -66,70 +206,105 @@ const PaymentRequestList = ({ setManagePaymentRequestModal, search }) => {
- {isLoading || isFetching ? ( - - - - ) : paymentRequestData.length > 0 ? ( - paymentRequestData.map((row) => ( - - - - - - - - + + + {items?.map((paymentRequest) => ( + + {paymentRequestColumns.map( + (col) => + (col.isAlwaysVisible || groupBy !== col.key) && ( + ) - } - > + )} + - +
+ +
    +
  • + setManageRequest({ + IsOpen: true, + RequestId: paymentRequest.id, + }) + } + > + + + Modify + +
  • + +
  • { + setIsDeleteModalOpen(true); + setDeletingId(paymentRequest.id); + }} + > + + + Delete + +
  • +
+
+ + + + ))} + )) ) : ( - )} diff --git a/src/components/common/Tooltip.jsx b/src/components/common/Tooltip.jsx new file mode 100644 index 00000000..527e839d --- /dev/null +++ b/src/components/common/Tooltip.jsx @@ -0,0 +1,24 @@ +import { useEffect } from "react"; + +const Tooltip = ({ text, placement = "top", children }) => { + useEffect(() => { + + const el = document.querySelector(`[data-tooltip-id="${text}"]`); + if (el) { + new window.bootstrap.Tooltip(el); + } + }, [text]); + + return ( + + {children} + + ); +}; +export default Tooltip; \ No newline at end of file diff --git a/src/components/master/ManageExpenseType.jsx b/src/components/master/ManageExpenseCategory.jsx similarity index 68% rename from src/components/master/ManageExpenseType.jsx rename to src/components/master/ManageExpenseCategory.jsx index a98cedf3..d200c0dd 100644 --- a/src/components/master/ManageExpenseType.jsx +++ b/src/components/master/ManageExpenseCategory.jsx @@ -3,18 +3,25 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { - useCreateExpenseType, - useUpdateExpenseType, + useCreateExpenseCategory, + useUpdateExpenseCategory, } from "../../hooks/masterHook/useMaster"; import Label from "../common/Label"; +import Tooltip from "../common/Tooltip"; + + + + + const ExpnseSchema = z.object({ name: z.string().min(1, { message: "Name is required" }), noOfPersonsRequired: z.boolean().default(false), + isAttachmentRequried: z.boolean().default(false), description: z.string().min(1, { message: "Description is required" }), }); -const ManageExpenseType = ({ data = null, onClose }) => { +const ManageExpenseCategory = ({ data = null, onClose }) => { const { register, handleSubmit, @@ -22,12 +29,12 @@ const ManageExpenseType = ({ data = null, onClose }) => { formState: { errors }, } = useForm({ resolver: zodResolver(ExpnseSchema), - defaultValues: { name: "", noOfPersonsRequired: false, description: "" }, + defaultValues: { name: "", noOfPersonsRequired: false,isAttachmentRequried:false, description: "" }, }); - const { mutate: UpdateExpenseType, isPending:isPendingUpdate } = useUpdateExpenseType( + const { mutate: UpdateExpenseType, isPending:isPendingUpdate } = useUpdateExpenseCategory( () => onClose?.() ); - const { mutate: CreateExpenseType, isPending } = useCreateExpenseType(() => + const { mutate: CreateExpenseType, isPending } = useCreateExpenseCategory(() => onClose?.() ); @@ -47,6 +54,7 @@ const ManageExpenseType = ({ data = null, onClose }) => { reset({ name: data.name ?? "", noOfPersonsRequired: data.noOfPersonsRequired ?? false, + isAttachmentRequried:data.isAttachmentRequried ?? false, description: data.description ?? "", }); } @@ -76,14 +84,37 @@ const ManageExpenseType = ({ data = null, onClose }) => {

{errors.description.message}

)} -
- {" "} +
+
+ + + + + +
+ return ( + +
+ {/* Breadcrumb */} + + + {/* Top Bar */} +
+
+
+
+ setSearch(e.target.value)} + /> +
+ +
+ +
-
- - {/* Add/Edit Modal */} - {ManagePaymentRequestModal.IsOpen && ( - - setManagePaymentRequestModal({ IsOpen: null, expenseId: null }) - } - > - - setManagePaymentRequestModal({ IsOpen: null, expenseId: null }) - } - /> - - )} - - {/* Payment Request List */} -
+ search={search} + /> + + {/* Add/Edit Modal */} + {ManageRequest.IsOpen && ( + + setManageRequest({ IsOpen: null, expenseId: null }) + } + > + + setManageRequest({ IsOpen: null, RequestId: null }) + } + /> + + )} +
-
+ ); }; diff --git a/src/repositories/ExpsenseRepository.jsx b/src/repositories/ExpsenseRepository.jsx index aebb9cd8..f69aa4cf 100644 --- a/src/repositories/ExpsenseRepository.jsx +++ b/src/repositories/ExpsenseRepository.jsx @@ -23,6 +23,8 @@ const ExpenseRepository = { }, 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}`), + //#endregion diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index efd8764b..792cb376 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -49,8 +49,8 @@ export const MasterRespository = { "Work Category": (id) => api.delete(`api/master/work-category/${id}`), "Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`), "Contact Tag": (id) => api.delete(`/api/master/contact-tag/${id}`), - "Expense Type": (id, isActive) => - api.delete(`/api/Master/expenses-type/delete/${id}`, (isActive = false)), + "Expense Category": (id, isActive) => + api.delete(`/api/Master/expenses-category/delete/${id}`, (isActive = false)), "Payment Mode": (id, isActive) => api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)), "Expense Status": (id, isActive) => @@ -82,10 +82,10 @@ export const MasterRespository = { getAuditStatus: () => api.get("/api/Master/work-status"), - getExpenseType: () => api.get("/api/Master/expenses-types"), - createExpenseType: (data) => api.post("/api/Master/expenses-type", data), - updateExpenseType: (id, data) => - api.put(`/api/Master/expenses-type/edit/${id}`, data), + getExpenseCategory: () => api.get("/api/Master/expenses-categories"), + createExpenseCategory: (data) => api.post("/api/Master/expenses-category", data), + updateExpenseCategory: (id, data) => + api.put(`/api/Master/expenses-category/edit/${id}`, data), getPaymentMode: () => api.get("/api/Master/payment-modes"), createPaymentMode: (data) => api.post(`/api/Master/payment-mode`, data),
-
{header}
-
+
{header}
+
- Loading Payment Requests... -
{row.paymentRequestUID}{row.title}{row.payee}{formatDate(row.createdAt)} - {row.currency?.symbol} - {row.amount} - - - {row.expenseStatus?.displayName || "Unknown"} - - -
- - console.log( - "View clicked for:", - row.paymentRequestUID + {Object.keys(grouped).length > 0 ? ( + Object.values(grouped).map(({ key, displayField, items }) => ( + +
+
+ {" "} + + {displayField} :{" "} + {" "} + + {IsGroupedByDate ? formatUTCToLocalTime(key) : key} + +
+
+ {col?.customRender + ? col?.customRender(paymentRequest) + : col?.getValue(paymentRequest)} + +
+ + setViewExpense({ + expenseId: paymentRequest.id, + view: true, + }) + } + > - - setManagePaymentRequestModal({ - IsOpen: true, - expenseId: row.id, // Pass ID for editing - }) - } - > -
-
- No Payment Requests Found + +
+

No Request Found

+