diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index fb82d067..d7a0a1b4 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -12,7 +12,6 @@ import { } from "../../utils/constants"; import { formatCurrency, - formatFigure, getColorNameFromHex, useDebounce, } from "../../utils/appUtils"; @@ -167,7 +166,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { { key: "amount", label: "Amount", - getValue: (e) => <>{formatFigure(e?.amount,{type:"currency",currency : e?.currency?.currencyCode ?? "INR"} )}, + getValue: (e) => <>{formatCurrency(e?.amount)}, isAlwaysVisible: true, align: "text-end", }, @@ -296,7 +295,8 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { ${col.key === "submitted" ? "justify-content-center":""} `}>{col.customRender ? col.customRender(expense) - : col.getValue(expense)} + : col.getValue(expense)} + ) )} @@ -311,7 +311,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => { }) } > - { + {canDetetExpense(expense) && canEditExpense(expense) && (
- -
*/} - -
- - -
- - - +
+ + + {errors.expenseCategoryId && ( + + {errors.expenseCategoryId.message} + + )} +
- ) + + {/* Title and Is Variable */} +
+
+ + + {errors.title && ( + {errors.title.message} + )} +
+ +
+ + + ( +
+
+ field.onChange(true)} + /> + +
+ +
+ field.onChange(false)} + /> + +
+
+ )} + /> + {errors.isVariable && ( + {errors.isVariable.message} + )} +
+
+ + {/* Date and Amount */} +
+
+ + + {errors.strikeDate && ( + {errors.strikeDate.message} + )} +
+ +
+ + + {errors.amount && ( + {errors.amount.message} + )} +
+
+ + {/* Payee and Currency */} +
+
+ + + setValue("payee", val, { shouldValidate: true }) + } + error={errors.payee?.message} + placeholder="Select or enter payee" + /> +
+ +
+ + + {errors.currencyId && ( + {errors.currencyId.message} + )} +
+
+ + {/* Frequency To and Status Id */} +
+
+ + + {errors.frequency && ( + {errors.frequency.message} + )} +
+ +
+ + + {errors.statusId && ( + {errors.statusId.message} + )} +
+
+ + {/* Payment Buffer Days and Number of Iteration */} +
+
+ + + {errors.paymentBufferDays && ( + + {errors.paymentBufferDays.message} + + )} +
+
+ + + {errors.numberOfIteration && ( + + {errors.numberOfIteration.message} + + )} +
+
+ +
+
+ + + +
+
+ + {/* Description */} +
+
+ + + {errors.description && ( + + {errors.description.message} + + )} +
+
+ +
+ + +
+ + + ); } -export default ManageRecurringExpense +export default ManageRecurringExpense; diff --git a/src/components/RecurringExpense/RecurringExpenseList.jsx b/src/components/RecurringExpense/RecurringExpenseList.jsx new file mode 100644 index 00000000..c5a6a4f4 --- /dev/null +++ b/src/components/RecurringExpense/RecurringExpenseList.jsx @@ -0,0 +1,283 @@ +import React, { useState } from "react"; +import { + EXPENSE_DRAFT, + EXPENSE_REJECTEDBY, + FREQUENCY_FOR_RECURRING, + ITEMS_PER_PAGE, +} from "../../utils/constants"; +import { + formatCurrency, + useDebounce, +} from "../../utils/appUtils"; +import { formatUTCToLocalTime } from "../../utils/dateUtils"; +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"; +import { useRecurringExpenseList } from "../../hooks/useExpense"; + +const RecurringExpenseList = ({ search, filterStatuses }) => { + 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 recurringExpenseColumns = [ + { + key: "expenseCategory", + label: "Category", + align: "text-start", + getValue: (e) => e?.expenseCategory?.name || "N/A", + }, + { + key: "title", + label: "Title", + align: "text-start", + getValue: (e) => e?.title || "N/A", + }, + { + key: "payee", + label: "Payee", + align: "text-start", + getValue: (e) => e?.payee || "N/A", + }, + { + key: "frequency", + label: "Frequency", + align: "text-start", + getValue: (e) => + e?.frequency !== undefined && e?.frequency !== null + ? FREQUENCY_FOR_RECURRING[e.frequency] || "N/A" + : "N/A", + }, + { + key: "amount", + label: "Amount", + align: "text-end", + getValue: (e) => + e?.amount + ? `${e?.currency?.symbol || ""}${e.amount.toLocaleString()}` + : "N/A", + }, + { + key: "createdAt", + label: "Next Generation Date", + align: "text-center", + getValue: (e) => + e?.createdAt ? formatUTCToLocalTime(e.createdAt) : "N/A", + }, + { + key: "status", + label: "Status", + align: "text-start", + getValue: (e) => e?.status?.name || "N/A", + }, + + ]; + + + const [currentPage, setCurrentPage] = useState(1); + const debouncedSearch = useDebounce(search, 500); + + const { data, isLoading, isError, error, isRefetching, refetch } = + useRecurringExpenseList( + ITEMS_PER_PAGE, + currentPage, + {}, + true, + debouncedSearch + ); + + const recurringExpenseData = data?.data || []; + const totalPages = data?.totalPages || 1; + + if (isError) { + return ; + } + + const header = [ + "Category", + "Title", + "Amount", + "Payee", + "Frequency", + "Next Generation", + "Status", + "Action", + ]; + + if (isLoading) return ; + + const canEditExpense = (recurringExpense) => { + // return ( + // (recurringExpense?.expenseStatus?.id === EXPENSE_DRAFT || + // EXPENSE_REJECTEDBY.includes(recurringExpense?.expenseStatus.id)) && + // recurringExpense?.createdBy?.id === SelfId + // ); + }; + + const canDeleteExpense = (request) => { + return ( + request?.expenseStatus?.id === EXPENSE_DRAFT && + request?.createdBy?.id === SelfId + ); + }; + + const filteredData = recurringExpenseData.filter((item) => + filterStatuses.includes(item?.status?.id) + ); + + const handleDelete = (id) => { + setDeletingId(id); + DeleteExpense( + { id }, + { + onSettled: () => { + setDeletingId(null); + setIsDeleteModalOpen(false); + }, + } + ); + }; + + return ( + <> + {IsDeleteModalOpen && ( + setIsDeleteModalOpen(false)} + paramData={deletingId} + /> + )} + +
+
+ + + + {recurringExpenseColumns.map((col) => ( + + ))} + + + + + + {filteredData.length > 0 ? ( + filteredData.map((recurringExpense) => ( + + {recurringExpenseColumns.map((col) => ( + + ))} + + + )) + ) : ( + + + + )} + +
+ {col.label} + Action
+ {col?.customRender + ? col?.customRender(recurringExpense) + : col?.getValue(recurringExpense)} + +
+ + // setVieRequest({ + // requestId: recurringExpense.id, + // view: true, + // }) + // } + > + {/* Uncomment for edit/delete actions */} + +
+ +
    +
  • + setManageRequest({ + IsOpen: true, + RecurringId: recurringExpense.id, + }) + } + > + + + Modify + +
  • + +
  • { + setIsDeleteModalOpen(true); + setDeletingId(recurringExpense.id); + }} + > + + + Delete + +
  • + +
+
+
+
+

No Recurring Expense Found

+
+
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ +
+ )} +
+ + ); +}; + +export default RecurringExpenseList; diff --git a/src/components/RecurringExpense/RecurringExpenseSchema.js b/src/components/RecurringExpense/RecurringExpenseSchema.js index 7c5e3b9e..da62b341 100644 --- a/src/components/RecurringExpense/RecurringExpenseSchema.js +++ b/src/components/RecurringExpense/RecurringExpenseSchema.js @@ -1,65 +1,16 @@ import { boolean, z } from "zod"; import { INR_CURRENCY_CODE } from "../../utils/constants"; -// export const PaymentRecurringExpense = (expenseTypes) => { -// return z -// .object({ -// title: z.string().min(1, { message: "Project is required" }), - -// description: z.string().min(1, { message: "Description is required" }), - -// payee: z.string().min(1, { message: "Supplier name is required" }), - -// notifyTo: z.string().min(1, { message: "Notification is required" }), - -// currencyId: z -// .string() -// .min(1, { message: "Currency is required" }), - -// amount: z.coerce -// .number({ -// invalid_type_error: "Amount is required and must be a number", -// }) -// .min(1, "Amount must be Enter") -// .refine((val) => /^\d+(\.\d{1,2})?$/.test(val.toString()), { -// message: "Amount must have at most 2 decimal places", -// }), - -// strikeDate: z.string().min(1, { message: "Date is required" }), - -// projectId: z.string().min(1, { message: "Project is required" }), - -// paymentBufferDays: z.string().min(1, { message: "Buffer days is required" }), - -// numberOfIteration: z.string().min(1, { message: "Iteration is required" }), - -// expenseCategoryId: z -// .string() -// .min(1, { message: "Expense Category is required" }), - -// statusId: z.string().min(1, { message: "Please select a status" }), - -// frequency: z.string().min(1, { message: "Frequency is required" }), - -// isVariable: z.boolean().optional(), - -// }) -// }; - - export const PaymentRecurringExpense = (expenseTypes) => { return z.object({ - title: z.string().min(1, { message: "Project is required" }), - - description: z.string().min(1, { message: "Description is required" }), - - payee: z.string().min(1, { message: "Supplier name is required" }), - - notifyTo: z.string().min(1, { message: "Notification is required" }), - + title: z.string().min(1, { message: "Title is required" }).transform((val) => val.trim()), + description: z.string().min(1, { message: "Description is required" }).transform((val) => val.trim()), + payee: z.string().min(1, { message: "Payee name is required" }).transform((val) => val.trim()), + notifyTo: z.string().min(1, { message: "Notification e-mail is required" }).transform((val) => val.trim()), currencyId: z .string() - .min(1, { message: "Currency is required" }), + .min(1, { message: "Currency is required" }) + .transform((val) => val.trim()), amount: z .number({ @@ -76,11 +27,13 @@ export const PaymentRecurringExpense = (expenseTypes) => { .min(1, { message: "Date is required" }) .refine((val) => !isNaN(Date.parse(val)), { message: "Invalid date format", - }), + }) + .transform((val) => val.trim()), projectId: z .string() - .min(1, { message: "Project is required" }), + .min(1, { message: "Project is required" }) + .transform((val) => val.trim()), paymentBufferDays: z .number({ @@ -98,23 +51,28 @@ export const PaymentRecurringExpense = (expenseTypes) => { expenseCategoryId: z .string() - .min(1, { message: "Expense Category is required" }), + .min(1, { message: "Expense Category is required" }) + .transform((val) => val.trim()), statusId: z .string() - .min(1, { message: "Please select a status" }), + .min(1, { message: "Please select a status" }) + .transform((val) => val.trim()), frequency: z .number({ required_error: "Frequency is required", invalid_type_error: "Frequency must be a number", }) - .min(1, { message: "Frequency must be greater than 0" }), + .refine((val) => [0, 1, 2, 3, 4, 5].includes(val), { + message: "Invalid frequency selected", + }), isVariable: z.boolean().optional(), }); }; + export const defaultRecurringExpense = { title: "", description: "", @@ -122,37 +80,33 @@ export const defaultRecurringExpense = { notifyTo: "", currencyId: "", amount: 0, - strikeDate: "", // or null if your DatePicker accepts null + strikeDate: "", projectId: "", paymentBufferDays: 0, numberOfIteration: 1, expenseCategoryId: "", statusId: "", frequency: 1, - isVariable: false, + isVariable: true, }; -// export const SearchPaymentRequestSchema = z.object({ -// projectIds: z.array(z.string()).optional(), -// statusIds: z.array(z.string()).optional(), -// createdByIds: z.array(z.string()).optional(), -// currencyIds: z.array(z.string()).optional(), -// expenseCategoryIds: z.array(z.string()).optional(), -// payees: z.array(z.string()).optional(), -// startDate: z.string().optional(), -// endDate: z.string().optional(), -// }); - -// export const defaultPaymentRequestFilter = { -// projectIds: [], -// statusIds: [], -// createdByIds: [], -// currencyIds: [], -// expenseCategoryIds: [], -// payees: [], -// startDate: null, -// endDate: null, -// }; +export const SearchRecurringExpenseSchema = z.object({ + title: z.array(z.string()).optional(), + description: z.array(z.string()).optional(), + payee: z.array(z.string()).optional(), + notifyTo: z.array(z.string()).optional(), + currencyId: z.array(z.string()).optional(), + amount: z.array(z.string()).optional(), + strikeDate: z.string().optional(), + projectId: z.string().optional(), + paymentBufferDays: z.string().optional(), + numberOfIteration: z.string().optional(), + expenseCategoryId: z.string().optional(), + statusId: z.string().optional(), + frequency: z.string().optional(), + isVariable: z.string().optional(), +}); + diff --git a/src/components/common/EmployeeSearchInput.jsx b/src/components/common/EmployeeSearchInput.jsx index 60bddd54..c4c68db2 100644 --- a/src/components/common/EmployeeSearchInput.jsx +++ b/src/components/common/EmployeeSearchInput.jsx @@ -3,9 +3,6 @@ import { useEmployeesName } from "../../hooks/useEmployees"; import { useDebounce } from "../../utils/appUtils"; import { useController } from "react-hook-form"; import Avatar from "./Avatar"; - - - const EmployeeSearchInput = ({ control, name, diff --git a/src/components/common/MultiEmployeeSearchInput.jsx b/src/components/common/MultiEmployeeSearchInput.jsx new file mode 100644 index 00000000..23a3a5a0 --- /dev/null +++ b/src/components/common/MultiEmployeeSearchInput.jsx @@ -0,0 +1,182 @@ +import { useState, useEffect, useRef } from "react"; +import { useEmployeesName } from "../../hooks/useEmployees"; +import { useDebounce } from "../../utils/appUtils"; +import { useController } from "react-hook-form"; +import Avatar from "../common/Avatar"; + +const MultiEmployeeSearchInput = ({ + control, + name, + projectId, + placeholder, + forAll, +}) => { + const { + field: { onChange, value, ref }, + fieldState: { error }, + } = useController({ name, control }); + + const [search, setSearch] = useState(""); + const [showDropdown, setShowDropdown] = useState(false); + const [selectedEmployees, setSelectedEmployees] = useState([]); + const dropdownRef = useRef(null); + + const debouncedSearch = useDebounce(search, 500); + + const { data: employees, isLoading } = useEmployeesName( + projectId, + debouncedSearch, + forAll + ); + + useEffect(() => { + if (value && employees?.data) { + // Ensure value is a string (sometimes it may come as array/object) + const stringValue = + typeof value === "string" + ? value + : Array.isArray(value) + ? value.join(",") + : ""; + + const emails = stringValue.split(",").filter(Boolean); + const foundEmps = employees.data.filter((emp) => + emails.includes(emp.email) + ); + + setSelectedEmployees(foundEmps); + + if (forAll && foundEmps.length > 0) { + setSearch(""); // clear search field + } + } + }, [value, employees?.data, forAll]); + + const handleSelect = (employee) => { + if (!selectedEmployees.find((emp) => emp.email === employee.email)) { + const newSelected = [...selectedEmployees, employee]; + setSelectedEmployees(newSelected); + // Store emails instead of IDs + onChange( + newSelected + .map((e) => e.email) + .filter(Boolean) + .join(",") + ); + setSearch(""); + setShowDropdown(false); + } + }; + + const handleRemove = (email) => { + const newSelected = selectedEmployees.filter((e) => e.email !== email); + setSelectedEmployees(newSelected); + onChange( + newSelected + .map((e) => e.email) + .filter(Boolean) + .join(",") + ); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setShowDropdown(false); + } + }; + const handleEsc = (event) => { + if (event.key === "Escape") setShowDropdown(false); + }; + + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("keydown", handleEsc); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("keydown", handleEsc); + }; + }, []); + + return ( +
+
+ {selectedEmployees.map((emp) => ( +
+ + {emp.firstName} {emp.lastName} + handleRemove(emp.email)} + > + × + +
+ ))} +
+ + { + setSearch(e.target.value); + setShowDropdown(true); + }} + onFocus={() => setShowDropdown(true)} + /> + + {showDropdown && (employees?.data?.length > 0 || isLoading) && ( +
    + {isLoading ? ( +
  • + Searching... +
  • + ) : ( + employees?.data + ?.filter( + (emp) => !selectedEmployees.find((e) => e.email === emp.email) + ) + .map((emp) => ( +
  • handleSelect(emp)} + > +
    + + {`${emp.firstName} ${emp.lastName}`} +
    +
  • + )) + )} +
+ )} + + {error && {error.message}} +
+ ); +}; + +export default MultiEmployeeSearchInput; diff --git a/src/components/common/TimeLine.jsx b/src/components/common/TimeLine.jsx index 10ed9ebd..43d32873 100644 --- a/src/components/common/TimeLine.jsx +++ b/src/components/common/TimeLine.jsx @@ -1,8 +1,5 @@ import React from "react"; import Avatar from "./Avatar"; -import Tooltip from "./Tooltip"; -import { formatUTCToLocalTime } from "../../utils/dateUtils"; -import moment from "moment"; const Timeline = ({ items = [], transparent = true }) => { return ( @@ -11,99 +8,82 @@ const Timeline = ({ items = [], transparent = true }) => { transparent ? "timeline-transparent text-start" : "" }`} > - {items && - items?.map((item) => ( -
  • ( +
  • + - + > -
    -
    -
    {item.title}
    - - - {moment.utc(item.timeAgo).local().fromNow()} - - +
    +
    +
    {item.title}
    + {item.timeAgo} +
    + + {item.description &&

    {item.description}

    } + + {item.attachments && item.attachments.length > 0 && ( +
    + {item.attachments.map((att, i) => ( +
    + {att.icon && ( + file + )} + {att.name} +
    + ))}
    + )} - {item.description &&

    {item.description}

    } - - {item.attachments && item.attachments.length > 0 && ( -
    - {item.attachments.map((att, i) => ( -
    - {att.icon && ( + {item.users && item.users.length > 0 && ( +
    +
      + {item.users.map((user, i) => ( +
    • + {user.avatar ? ( file + ) : ( + )} - {att.name} -
    +
  • ))} - - )} + - {item.users && item.users.length > 0 && ( -
    -
      - {item.users.map((user, i) => ( -
    • - {user.avatar ? ( - {user.name} - ) : ( - - )} -
    • - ))} -
    - - {item.users?.length === 1 && ( -
    -

    {`${item.users[0].firstName} ${item.users[0].lastName}`}

    - {item.users[0].role} -
    - )} -
    - )} - - - ))} - - {!items || - (items.length == 0 && ( -
  • - Not action yet. -
  • - ))} + {item.users?.length === 1 && ( +
    +

    {`${item.users[0].firstName} ${item.users[0].lastName}`}

    + {item.users[0].role} +
    + )} + + )} + + + ))} ); }; diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index 991719e9..1e4b72bd 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -10,6 +10,20 @@ import { } from "@tanstack/react-query"; import showToast from "../../services/toastService"; + + + + + +export const useRecurringStatus = ()=>{ + return useQuery({ + queryKey:["RecurringStatus"], + queryFn:async()=>{ + const resp = await MasterRespository.getRecurringStatus(); + return resp.data + } + }) +} export const usePaymentAjustmentHead = (isActive) => { return useQuery({ queryKey: ["paymentType", isActive], diff --git a/src/hooks/useExpense.js b/src/hooks/useExpense.js index 00e99fda..68cac17f 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -5,8 +5,7 @@ import { queryClient } from "../layouts/AuthLayout"; import { useSelector } from "react-redux"; import moment from "moment"; - - +// -------------------Query------------------------------------------------------ export const usePayee =()=>{ return useQuery({ queryKey:["payee"], @@ -16,10 +15,6 @@ export const usePayee =()=>{ } }) } - - -// -------------------Query------------------------------------------------------ - const cleanFilter = (filter) => { const cleaned = { ...filter }; @@ -467,3 +462,52 @@ export const useCreateRecurringExpense = (onSuccessCallBack) => { }); }; +export const useUpdateRecurringExpense = (onSuccessCallBack) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ id, payload }) => { + const response = await ExpenseRepository.UpdateRecurringExpense(id, payload); + return response.data; + }, + onSuccess: (updatedExpense, variables) => { + queryClient.removeQueries({ queryKey: ["recurringExpense", variables.id] }); + queryClient.invalidateQueries({ queryKey: ["recurringExpenseList"] }); + showToast("Recurring Expense updated Successfully", "success"); + + if (onSuccessCallBack) onSuccessCallBack(); + }, + onError: (error) => { + showToast("Something went wrong.Please try again later.", "error"); + }, + }); +}; + +export const useRecurringExpenseList = ( + pageSize, + pageNumber, + filter, + isActive, + searchString = "", +) => { + return useQuery({ + queryKey: ["recurringExpenseList",pageSize,pageNumber,filter,isActive,searchString], + queryFn: async()=>{ + debugger + const resp = await ExpenseRepository.GetRecurringExpenseList(pageSize,pageNumber,filter,isActive,searchString); + return resp.data; + }, + keepPreviousData: true, + }); +}; + +export const useRecurringExpenseDetail =(RequestId)=>{ + return useQuery({ + queryKey:['recurringExpense',RequestId], + queryFn:async()=>{ + RequestId + const resp = await ExpenseRepository.GetRecurringExpense(RequestId); + return resp.data; + }, + enabled:!!RequestId + }) +} diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index a50644e8..f5c3ea5d 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -1,10 +1,4 @@ -import React, { - createContext, - useContext, - useState, - useEffect, - useRef, -} from "react"; +import React, { createContext, useContext, useState, useEffect, useRef } from "react"; import { useForm, useFormContext } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useSelector } from "react-redux"; @@ -25,10 +19,7 @@ import { VIEW_SELF_EXPENSE, } from "../../utils/constants"; -import { - defaultFilter, - SearchSchema, -} from "../../components/Expenses/ExpenseSchema"; +import { defaultFilter, SearchSchema } from "../../components/Expenses/ExpenseSchema"; import PreviewDocument from "../../components/Expenses/PreviewDocument"; // Context @@ -111,7 +102,7 @@ const ExpensePage = () => { setManageExpenseModal, setDocumentView, filterData, - removeFilterChip, + removeFilterChip }; return ( @@ -137,6 +128,7 @@ const ExpensePage = () => {
    + {IsCreatedAble && (
    + + { {viewExpense.view && ( setViewExpense({ expenseId: null, view: false })} > diff --git a/src/pages/PaymentRequest/PaymentRequestPage.jsx b/src/pages/PaymentRequest/PaymentRequestPage.jsx index e2f3cdf1..f4c8bb55 100644 --- a/src/pages/PaymentRequest/PaymentRequestPage.jsx +++ b/src/pages/PaymentRequest/PaymentRequestPage.jsx @@ -15,7 +15,7 @@ export const PaymentRequestContext = createContext(); export const usePaymentRequestContext = () => { const context = useContext(PaymentRequestContext); if (!context) { - throw new Error("usePaymentRequestContext must be used within an RequestPaymentProvider"); + throw new Error("usePaymentRequestContext must be used within an ExpenseProvider"); } return context; }; diff --git a/src/pages/RecurringExpense/RecurringExpensePage.jsx b/src/pages/RecurringExpense/RecurringExpensePage.jsx index 60da6be8..0fd5ceb2 100644 --- a/src/pages/RecurringExpense/RecurringExpensePage.jsx +++ b/src/pages/RecurringExpense/RecurringExpensePage.jsx @@ -4,7 +4,9 @@ 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"; +import RecurringExpenseList from "../../components/RecurringExpense/RecurringExpenseList"; +import { PAYEE_RECURRING_EXPENSE } from "../../utils/constants"; +import { SearchRecurringExpenseSchema } from "../../components/RecurringExpense/RecurringExpenseSchema"; export const RecurringExpenseContext = createContext(); export const useRecurringExpenseContext = () => { @@ -20,8 +22,10 @@ const RecurringExpensePage = () => { RequestId: null, }); const [ViewRequest, setVieRequest] = useState({ view: false, requestId: null }) - const { setOffcanvasContent, setShowTrigger } = useFab(); - // const [filters, setFilters] = useState(defaultPaymentRequestFilter); + + const [selectedStatuses, setSelectedStatuses] = useState( + PAYEE_RECURRING_EXPENSE.map((s) => s.id) + ); const [search, setSearch] = useState(""); @@ -30,19 +34,13 @@ const RecurringExpensePage = () => { setVieRequest }; - useEffect(() => { - - setShowTrigger(true); - setOffcanvasContent( - "Payment Request Filters", - // + const handleStatusChange = (id) => { + setSelectedStatuses((prev) => + prev.includes(id) + ? prev.filter((s) => s !== id) + : [...prev, id] ); - - return () => { - setShowTrigger(false); - setOffcanvasContent("", null); - }; - }, []); + }; return ( @@ -57,19 +55,47 @@ const RecurringExpensePage = () => { {/* Top Bar */}
    -
    -
    -
    +
    +
    + {/* Left side: Search + Filter */} +
    setSearch(e.target.value)} + onChange={(e) => setSearch(e.target.value)} /> + +
    + +
      + {PAYEE_RECURRING_EXPENSE.map(({ id, label }) => ( +
    • +
      + handleStatusChange(id)} + /> + +
      +
    • + ))} +
    +
    -
    + {/* Right side: Add Button */} +
    - {/* */} - + + {/* Add/Edit Modal */} {ManageRequest.IsOpen && ( diff --git a/src/repositories/ExpsenseRepository.jsx b/src/repositories/ExpsenseRepository.jsx index 4a5cedc9..3770f6cc 100644 --- a/src/repositories/ExpsenseRepository.jsx +++ b/src/repositories/ExpsenseRepository.jsx @@ -1,7 +1,6 @@ import { api } from "../utils/axiosClient"; const ExpenseRepository = { - GetPayee: () => api.get("/api/Expense/payment-request/payee"), //#region Expense GetExpenseList: (pageSize, pageNumber, filter, searchString) => { const payloadJsonString = JSON.stringify(filter); @@ -40,20 +39,33 @@ const ExpenseRepository = { GetPaymentRequestFilter: () => api.get("/api/Expense/payment-request/filter"), ActionOnPaymentRequest: (data) => api.post("/api/Expense/payment-request/action", data), - DeletePaymentRequest:()=>api.get("delete here come"), - CreatePaymentRequestExpense:(data)=>api.post('/api/Expense/payment-request/expense/create',data), + DeletePaymentRequest: () => api.get("delete here come"), + CreatePaymentRequestExpense: (data) => + api.post("/api/Expense/payment-request/expense/create", data), + GetPayee:()=>api.get('/api/Expense/payment-request/payee'), //#endregion //#region Recurring Expense - - CreateRecurringExpense: (data) => api.post("/api/Expense/recurring-payment/create", data), - - //#endregion - - //#region Advance Payment - GetTranctionList: (employeeId)=>api.get(`/api/Expense/get/transactions/${employeeId}`), + GetRecurringExpenseList:(pageSize, pageNumber, filter, searchString) => { + const payloadJsonString = JSON.stringify(filter); + return api.get( + `/api/expense/get/recurring-payment/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}` + ); + }, + CreateRecurringExpense: (data) => + api.post("/api/Expense/recurring-payment/create", data), + UpdateRecurringExpense: (id, data) => + api.put(`/api/Expense/recurring-payment/edit/${id}`, data), + GetRecurringExpense: (id) => + api.get(`/api/Expense/get/recurring-payment/details/${id}`), //#endregion + //#region Advance Payment + GetTranctionList: (employeeId) => + api.get(`/api/Expense/get/transactions/${employeeId}`), + //#endregion + + }; export default ExpenseRepository; diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index 792cb376..24324030 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -141,4 +141,7 @@ export const MasterRespository = { api.post(`/api/Master/payment-adjustment-head`, data), updatePaymentAjustmentHead: (id, data) => api.put(`/api/Master/payment-adjustment-head/edit/${id}`, data), + + + getRecurringStatus:()=>api.get(`/api/Master/recurring-status/list`) }; diff --git a/src/utils/appUtils.js b/src/utils/appUtils.js index 62ac15d4..b3a884fd 100644 --- a/src/utils/appUtils.js +++ b/src/utils/appUtils.js @@ -108,7 +108,7 @@ export function localToUtc(dateString) { export const formatCurrency = (amount, currency = "INR", locale = "en-US") => { return new Intl.NumberFormat(locale, { style: "currency", - notation: "compact", // standard or compact + notation: "compact", compactDisplay: "short", currency: currency, minimumFractionDigits: 0, diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index fe8f4e5b..433c906a 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -160,10 +160,10 @@ export const PROJECT_STATUS = [ export const DEFAULT_CURRENCY = "78e96e4a-7ce0-4164-ae3a-c833ad45ec2c"; export const EXPENSE_STATUS = { - daft:"297e0d8f-f668-41b5-bfea-e03b354251c8", - review_pending:"6537018f-f4e9-4cb3-a210-6c3b2da999d7", - payment_pending:"f18c5cfd-7815-4341-8da2-2c2d65778e27", - approve_pending:"4068007f-c92f-4f37-a907-bc15fe57d4d8", + daft: "297e0d8f-f668-41b5-bfea-e03b354251c8", + review_pending: "6537018f-f4e9-4cb3-a210-6c3b2da999d7", + payment_pending: "f18c5cfd-7815-4341-8da2-2c2d65778e27", + approve_pending: "4068007f-c92f-4f37-a907-bc15fe57d4d8", } @@ -178,4 +178,32 @@ export const ALLOW_PROJECTSTATUS_ID = [ export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; +export const FREQUENCY_FOR_RECURRING = { + 0: "Monthly", + 1: "Quarterly", + 2: "Half-Yearly", + 3: "Yearly", + 4: "Daily", + 5: "Weekly" +}; + +export const PAYEE_RECURRING_EXPENSE = [ + { + id: "da462422-13b2-45cc-a175-910a225f6fc8", + label: "Active", + }, + { + id: "306856fb-5655-42eb-bf8b-808bb5e84725", + label: "Completed", + }, + { + id: "3ec864d2-8bf5-42fb-ba70-5090301dd816", + label: "De-Activited", + }, + { + id: "8bfc9346-e092-4a80-acbf-515ae1ef6868", + label: "Paused", + }, +]; +