From 7a749b14e92fbc41d61e66fe1858e44b253984b0 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Tue, 4 Nov 2025 17:03:27 +0530 Subject: [PATCH] Impleting Create API for Recurring Expense. --- .../ManageRecurringExpense.jsx | 27 +- .../RecurringRexpenseList.jsx | 342 ++++++++++++++++++ src/hooks/useExpense.js | 25 +- .../RecurringExpense/RecurringExpensePage.jsx | 197 +++++----- src/repositories/ExpsenseRepository.jsx | 6 + 5 files changed, 484 insertions(+), 113 deletions(-) diff --git a/src/components/RecurringExpense/ManageRecurringExpense.jsx b/src/components/RecurringExpense/ManageRecurringExpense.jsx index 38df1105..a9f3c913 100644 --- a/src/components/RecurringExpense/ManageRecurringExpense.jsx +++ b/src/components/RecurringExpense/ManageRecurringExpense.jsx @@ -3,12 +3,11 @@ import Label from '../common/Label'; import { useForm } from 'react-hook-form'; import { useExpenseCategory } from '../../hooks/masterHook/useMaster'; import DatePicker from '../common/DatePicker'; -// import { useCreatePaymentRequest, usePaymentRequestDetail, useUpdatePaymentRequest } from '../../hooks/useExpense'; - import { zodResolver } from '@hookform/resolvers/zod'; import { defaultRecurringExpense, PaymentRecurringExpense } from './RecurringExpenseSchema'; import { INR_CURRENCY_CODE } from '../../utils/constants'; import { useCurrencies, useProjectName } from '../../hooks/useProjects'; +import { useCreateRecurringExpense } from '../../hooks/useExpense'; function ManageRecurringExpense({ closeModal, requestToEdit = null }) { @@ -36,11 +35,11 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) { closeModal(); }; - // const { mutate: CreatePaymentRequest, isPending: createPending } = useCreatePaymentRequest( - // () => { - // handleClose(); - // } - // ); + const { mutate: CreateRecurringExpense, isPending: createPending } = useCreateRecurringExpense( + () => { + handleClose(); + } + ); // const { mutate: PaymentRequestUpdate, isPending } = useUpdatePaymentRequest(() => // handleClose() // ); @@ -76,19 +75,19 @@ function ManageRecurringExpense({ closeModal, requestToEdit = null }) { // strikeDate: localToUtc(fromdata.strikeDate), strikeDate: fromdata.strikeDate ? new Date(fromdata.strikeDate).toISOString() : null, }; - // if (requestToEdit) { - // const editPayload = { ...payload, id: data.id}; - // PaymentRequestUpdate({ id: data.id, payload: editPayload }); - // } else { - // CreatePaymentRequest(payload); - // } + if (requestToEdit) { + const editPayload = { ...payload, id: data.id}; + PaymentRequestUpdate({ id: data.id, payload: editPayload }); + } else { + CreateRecurringExpense(payload); + } console.log("Kartik", payload) }; return (
- {requestToEdit ? "Update Payment Request " : "Create Payment Request"} + {requestToEdit ? "Update Expense Recurring " : "Create Expense Recurring"}
{/* Project and Category */} diff --git a/src/components/RecurringExpense/RecurringRexpenseList.jsx b/src/components/RecurringExpense/RecurringRexpenseList.jsx index e69de29b..ef88ae00 100644 --- a/src/components/RecurringExpense/RecurringRexpenseList.jsx +++ b/src/components/RecurringExpense/RecurringRexpenseList.jsx @@ -0,0 +1,342 @@ +import React, { useState } from "react"; +import { + EXPENSE_DRAFT, + EXPENSE_REJECTEDBY, + ITEMS_PER_PAGE, +} from "../../utils/constants"; +import { + formatCurrency, + getColorNameFromHex, + useDebounce, +} from "../../utils/appUtils"; +import { usePaymentRequestList } from "../../hooks/useExpense"; +import { formatDate, formatUTCToLocalTime } from "../../utils/dateUtils"; +import Avatar from "../../components/common/Avatar"; +import { ExpenseTableSkeleton } from "../Expenses/ExpenseSkeleton"; +import ConfirmModal from "../common/ConfirmModal"; +import { useNavigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import Error from "../common/Error"; +import { useRecurringExpenseContext } from "../../pages/RecurringExpense/RecurringExpensePage"; + +const RecurringExpenseList = ({ filters, groupBy = "submittedBy", search }) => { + const { setManageRequest, setVieRequest } = useRecurringExpenseContext(); + const navigate = useNavigate(); + const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [deletingId, setDeletingId] = useState(null); + const SelfId = useSelector( + (store) => store?.globalVariables?.loginUser?.employeeInfo?.id + ); + 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 paymentRequestColumns = [ + { + key: "paymentRequestUID", + label: "Template Name", + align: "text-start mx-2", + getValue: (e) => e.paymentRequestUID || "N/A", + }, + { + key: "title", + label: "Frequency", + align: "text-start", + getValue: (e) => e.title || "N/A", + }, + { + key: "createdAt", + label: "Next Generation Date", + align: "text-start", + getValue: (e) => formatUTCToLocalTime(e?.createdAt), + }, + { + key: "createdAt", + label: "Status", + align: "text-start", + getValue: (e) => formatUTCToLocalTime(e?.createdAt), + }, + + ]; + + const [currentPage, setCurrentPage] = useState(1); + const debouncedSearch = useDebounce(search, 500); + + const { data, isLoading, isError, error, isRefetching, refetch } = + usePaymentRequestList( + ITEMS_PER_PAGE, + currentPage, + filters, + true, + debouncedSearch + ); + + const paymentRequestData = data?.data || []; + const totalPages = data?.data?.totalPages || 1; + + if (isError) { + return ; + } + const header = [ + "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?.expenseStatus?.id === EXPENSE_DRAFT || + EXPENSE_REJECTEDBY.includes(paymentRequest?.expenseStatus.id)) && + paymentRequest?.createdBy?.id === SelfId + ); + }; + const canDetetExpense = (request) => { + return ( + request?.expenseStatus?.id === EXPENSE_DRAFT && + request?.createdBy?.id === SelfId + ); + }; + + const handleDelete = (id) => { + setDeletingId(id); + DeleteExpense( + { id }, + { + onSettled: () => { + setDeletingId(null); + setIsDeleteModalOpen(false); + }, + } + ); + }; + + return ( + <> + {IsDeleteModalOpen && ( + setIsDeleteModalOpen(false)} + // loading={isPending} + paramData={deletingId} + /> + )} +
+
+ + + + {paymentRequestColumns.map((col) => ( + + ))} + + + + + + {Object.keys(grouped).length > 0 ? ( + Object.values(grouped).map(({ key, displayField, items }) => ( + + + + + {items?.map((paymentRequest) => ( + + {paymentRequestColumns.map( + (col) => + (col.isAlwaysVisible || groupBy !== col.key) && ( + + ) + )} + + + ))} + + )) + ) : ( + + + + )} + +
+ {col.label} + Action
+
+ {" "} + + {displayField} :{" "} + {" "} + + {IsGroupedByDate ? formatUTCToLocalTime(key) : key} + +
+
+ {col?.customRender + ? col?.customRender(paymentRequest) + : col?.getValue(paymentRequest)} + +
+ + setVieRequest({ + requestId: paymentRequest.id, + view: true, + }) + } + > + {canEditExpense(paymentRequest) && ( +
+ +
    +
  • + setManageRequest({ + IsOpen: true, + RequestId: paymentRequest.id, + }) + } + > + + + + Modify + + +
  • + + {canDetetExpense(paymentRequest) && ( +
  • { + setIsDeleteModalOpen(true); + setDeletingId(paymentRequest.id); + }} + > + + + + Delete + + +
  • + )} +
+
+ )} +
+
+
+

No Request Found

+
+
+
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ +
+ )} +
+ + ); +}; + +export default RecurringExpenseList; diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js index af8fb1b0..4f38de56 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -384,4 +384,27 @@ export const useExpenseTransactions = (employeeId)=>{ enabled:!!employeeId }) } -//#endregion \ No newline at end of file +//#endregion + +// ---------------------------Put Post Recurring Expense--------------------------------------- + +export const useCreateRecurringExpense = (onSuccessCallBack) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (payload) => { + return await ExpenseRepository.CreateRecurringExpense(payload); + }, + + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: ["recurringExpense"] }); + showToast("Recurring Expense Created Successfully", "success"); + if (onSuccessCallBack) onSuccessCallBack(); + }, + onError: (error) => { + showToast( + error.message || "Something went wrong please try again !", + "error" + ); + }, + }); +}; \ No newline at end of file diff --git a/src/pages/RecurringExpense/RecurringExpensePage.jsx b/src/pages/RecurringExpense/RecurringExpensePage.jsx index dd7775f1..60da6be8 100644 --- a/src/pages/RecurringExpense/RecurringExpensePage.jsx +++ b/src/pages/RecurringExpense/RecurringExpensePage.jsx @@ -4,116 +4,117 @@ import GlobalModel from "../../components/common/GlobalModel"; import { useFab } from "../../Context/FabContext"; // import { defaultPaymentRequestFilter,SearchPaymentRequestSchema } from "../../components/PaymentRequest/PaymentRequestSchema"; import ManageRecurringExpense from "../../components/RecurringExpense/ManageRecurringExpense"; +import RecurringExpenseList from "../../components/RecurringExpense/RecurringRexpenseList"; export const RecurringExpenseContext = createContext(); export const useRecurringExpenseContext = () => { - const context = useContext(RecurringExpenseContext); - if (!context) { - throw new Error("useRecurringExpenseContext must be used within an ExpenseProvider"); - } - return context; + const context = useContext(RecurringExpenseContext); + if (!context) { + throw new Error("useRecurringExpenseContext must be used within an ExpenseProvider"); + } + return context; }; const RecurringExpensePage = () => { - const [ManageRequest, setManageRequest] = useState({ - IsOpen: null, - RequestId: null, - }); - const [ViewRequest,setVieRequest] = useState({view:false,requestId:null}) - const { setOffcanvasContent, setShowTrigger } = useFab(); -// const [filters, setFilters] = useState(defaultPaymentRequestFilter); + const [ManageRequest, setManageRequest] = useState({ + IsOpen: null, + RequestId: null, + }); + const [ViewRequest, setVieRequest] = useState({ view: false, requestId: null }) + const { setOffcanvasContent, setShowTrigger } = useFab(); + // const [filters, setFilters] = useState(defaultPaymentRequestFilter); - const [search, setSearch] = useState(""); + const [search, setSearch] = useState(""); - const contextValue = { - setManageRequest, - setVieRequest - }; - - useEffect(() => { - - setShowTrigger(true); - setOffcanvasContent( - "Payment Request Filters", - // - ); - - return () => { - setShowTrigger(false); - setOffcanvasContent("", null); + const contextValue = { + setManageRequest, + setVieRequest }; - }, []); - return ( - -
- {/* Breadcrumb */} - + useEffect(() => { - {/* Top Bar */} -
-
-
-
- setSearch(e.target.value)} + setShowTrigger(true); + setOffcanvasContent( + "Payment Request Filters", + // + ); + + return () => { + setShowTrigger(false); + setOffcanvasContent("", null); + }; + }, []); + + return ( + +
+ {/* Breadcrumb */} + -
-
- -
-
-
-
- {/* +
+
+
+ setSearch(e.target.value)} + /> +
+ +
+ +
+
+
+
+ {/* */} + - {/* Add/Edit Modal */} - {ManageRequest.IsOpen && ( - - setManageRequest({ IsOpen: null, expenseId: null }) - } - > - - setManageRequest({ IsOpen: null, RequestId: null }) - } - /> - - )} - - {/* {ViewRequest.view && ( + {/* Add/Edit Modal */} + {ManageRequest.IsOpen && ( + + setManageRequest({ IsOpen: null, expenseId: null }) + } + > + + setManageRequest({ IsOpen: null, RequestId: null }) + } + /> + + )} + + {/* {ViewRequest.view && ( { )} */} - -
-
- ); + +
+ + ); }; export default RecurringExpensePage; diff --git a/src/repositories/ExpsenseRepository.jsx b/src/repositories/ExpsenseRepository.jsx index a57c89e6..2bcc5596 100644 --- a/src/repositories/ExpsenseRepository.jsx +++ b/src/repositories/ExpsenseRepository.jsx @@ -28,6 +28,12 @@ const ExpenseRepository = { ActionOnPaymentRequest: (data) => api.post('/api/Expense/payment-request/action', data), //#endregion + //#region Recurring Expense + + CreateRecurringExpense: (data) => api.post("/api/Expense/recurring-payment/create", data), + + //#endregion + //#region Advance Payment GetTranctionList: () => api.get(`/get/transactions/${employeeId}`)