diff --git a/src/components/PaymentRequest/ViewPaymentRequest.jsx b/src/components/PaymentRequest/ViewPaymentRequest.jsx index b6a9e2ad..27447d0a 100644 --- a/src/components/PaymentRequest/ViewPaymentRequest.jsx +++ b/src/components/PaymentRequest/ViewPaymentRequest.jsx @@ -6,6 +6,7 @@ import { } from "../../hooks/useExpense"; import { formatCurrency, + formatFigure, getColorNameFromHex, getIconByFileType, localToUtc, @@ -536,7 +537,7 @@ const ViewPaymentRequest = ({ requestId }) => { {" "}

TimeLine

- + diff --git a/src/components/RecurringExpense/ManageRecurringExpense.jsx b/src/components/RecurringExpense/ManageRecurringExpense.jsx index 9ba9146a..a5e3bdaf 100644 --- a/src/components/RecurringExpense/ManageRecurringExpense.jsx +++ b/src/components/RecurringExpense/ManageRecurringExpense.jsx @@ -1,484 +1,490 @@ -import React, { useEffect, useState } from 'react' -import Label from '../common/Label'; -import { useForm } from 'react-hook-form'; -import { useExpenseCategory } from '../../hooks/masterHook/useMaster'; -import DatePicker from '../common/DatePicker'; -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, usePayee, useRecurringExpenseDetail, useUpdateRecurringExpense } from '../../hooks/useExpense'; -import InputSuggestions from '../common/InputSuggestion'; -import MultiEmployeeSearchInput from '../common/MultiEmployeeSearchInput'; +import React, { useEffect, useState } from "react"; +import Label from "../common/Label"; +import { Controller, useForm } from "react-hook-form"; +import { + useExpenseCategory, + useRecurringStatus, +} from "../../hooks/masterHook/useMaster"; +import DatePicker from "../common/DatePicker"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + defaultRecurringExpense, + PaymentRecurringExpense, +} from "./RecurringExpenseSchema"; +import { + FREQUENCY_FOR_RECURRING, + INR_CURRENCY_CODE, +} from "../../utils/constants"; +import { useCurrencies, useProjectName } from "../../hooks/useProjects"; +import { + useCreateRecurringExpense, + usePayee, + useRecurringExpenseDetail, + useUpdateRecurringExpense, +} from "../../hooks/useExpense"; +import InputSuggestions from "../common/InputSuggestion"; +import MultiEmployeeSearchInput from "../common/MultiEmployeeSearchInput"; function ManageRecurringExpense({ closeModal, requestToEdit = null }) { - const { + const { data, isLoading, isError, error: requestError, } = useRecurringExpenseDetail(requestToEdit); - //APIs - const { projectNames, loading: projectLoading, error, isError: isProjectError, } = useProjectName(); - const { data: currencyData, isLoading: currencyLoading, isError: currencyError } = useCurrencies(); - const { data: statusData, isLoading: statusLoading, isError: statusError } = useRecurringStatus(); - const { data: Payees, isLoading: isPayeeLoaing, isError: isPayeeError, error: payeeError } = usePayee() - const { ExpenseCategories, loading: ExpenseLoading, error: ExpenseError } = useExpenseCategory(); + //APIs + const { + projectNames, + loading: projectLoading, + error, + isError: isProjectError, + } = useProjectName(); + const { + data: currencyData, + isLoading: currencyLoading, + isError: currencyError, + } = useCurrencies(); + const { + data: statusData, + isLoading: statusLoading, + isError: statusError, + } = useRecurringStatus(); + const { + data: Payees, + isLoading: isPayeeLoaing, + isError: isPayeeError, + error: payeeError, + } = usePayee(); + const { + ExpenseCategories, + loading: ExpenseLoading, + error: ExpenseError, + } = useExpenseCategory(); - const schema = PaymentRecurringExpense(); - const { register, control, watch, handleSubmit, setValue, reset, formState: { errors }, } = useForm({ - resolver: zodResolver(schema), - defaultValues: defaultRecurringExpense, + const schema = PaymentRecurringExpense(); + const { + register, + control, + watch, + handleSubmit, + setValue, + reset, + formState: { errors }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: defaultRecurringExpense, + }); + const handleClose = () => { + reset(); + closeModal(); + }; + + const { mutate: CreateRecurringExpense, isPending: createPending } = + useCreateRecurringExpense(() => { + handleClose(); }); - const handleClose = () => { - reset(); - closeModal(); + // const { mutate: PaymentRequestUpdate, isPending } = useUpdatePaymentRequest(() => + // handleClose() + // ); + + useEffect(() => { + if (requestToEdit && data) { + reset({ + title: data.title || "", + description: data.description || "", + payee: data.payee || "", + notifyTo: data.notifyTo || "", + currencyId: data.currency.id || "", + amount: data.amount || "", + strikeDate: data.strikeDate?.slice(0, 10) || "", + projectId: data.project.id || "", + paymentBufferDays: data.paymentBufferDays || "", + numberOfIteration: data.numberOfIteration || "", + expenseCategoryId: data.expenseCategory.id || "", + statusId: data.statusId || "", + frequency: data.frequency || "", + isVariable: data.isVariable || false, + }); + } + }, [data, reset]); + + // console.log("Veer",data) + + const onSubmit = (fromdata) => { + let payload = { + ...fromdata, + // 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 { + CreateRecurringExpense(payload); + } + console.log("Kartik", payload); + }; - const { mutate: CreateRecurringExpense, isPending: createPending } = useCreateRecurringExpense( - () => { - handleClose(); - } - ); - // const { mutate: PaymentRequestUpdate, isPending } = useUpdatePaymentRequest(() => - // handleClose() - // ); + return ( +
+
+ {requestToEdit + ? "Update Expense Recurring " + : "Create Expense Recurring"} +
+
+ {/* Project and Category */} +
+
+ + + {errors.projectId && ( + {errors.projectId.message} + )} +
- useEffect(() => { - if (requestToEdit && data) { - reset({ - title: data.title || "", - description: data.description || "", - payee: data.payee || "", - notifyTo: data.notifyTo || "", - currencyId: data.currency.id || "", - amount: data.amount || "", - strikeDate: data.strikeDate?.slice(0, 10) || "", - projectId: data.project.id || "", - paymentBufferDays: data.paymentBufferDays || "", - numberOfIteration: data.numberOfIteration || "", - expenseCategoryId: data.expenseCategory.id || "", - statusId: data.statusId || "", - frequency: data.frequency || "", - isVariable: data.isVariable || false, - - }); - } - }, [data, reset]); - - // console.log("Veer",data) - - const onSubmit = (fromdata) => { - let payload = { - ...fromdata, - // 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 { - CreateRecurringExpense(payload); - } - console.log("Kartik", payload) - }; - - return ( -
-
- {requestToEdit ? "Update Expense Recurring " : "Create Expense Recurring"} -
- - {/* Project and Category */} -
-
- - - {errors.projectId && ( - {errors.projectId.message} - )} -
- -
- - - {errors.expenseCategoryId && ( - - {errors.expenseCategoryId.message} - - )} -
-
- - {/* Title and Is Variable */} -
-
- - - {errors.title && ( - - {errors.title.message} - - )} -
- - {/*
- - - {errors.isVariable && ( - {errors.isVariable.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} - )} -
-
- - {/* Notify */} - {/*
-
- - - {errors.notifyTo && ( - - {errors.notifyTo.message} - - )} -
-
*/} - -
-
- - - -
-
- - {/* Description */} -
-
- - - {errors.description && ( - - {errors.description.message} - - )} -
-
- -
- - -
- +
+ + + {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/RecurringExpenseSchema.js b/src/components/RecurringExpense/RecurringExpenseSchema.js index b7c312a2..da62b341 100644 --- a/src/components/RecurringExpense/RecurringExpenseSchema.js +++ b/src/components/RecurringExpense/RecurringExpenseSchema.js @@ -80,7 +80,7 @@ export const defaultRecurringExpense = { notifyTo: "", currencyId: "", amount: 0, - strikeDate: "", // or null if your DatePicker accepts null + strikeDate: "", projectId: "", paymentBufferDays: 0, numberOfIteration: 1, diff --git a/src/components/common/MultiEmployeeSearchInput.jsx b/src/components/common/MultiEmployeeSearchInput.jsx index bb42e2b2..23a3a5a0 100644 --- a/src/components/common/MultiEmployeeSearchInput.jsx +++ b/src/components/common/MultiEmployeeSearchInput.jsx @@ -5,177 +5,178 @@ import { useController } from "react-hook-form"; import Avatar from "../common/Avatar"; const MultiEmployeeSearchInput = ({ - control, - name, - projectId, - placeholder, - forAll, + control, + name, + projectId, + placeholder, + forAll, }) => { - const { - field: { onChange, value, ref }, - fieldState: { error }, - } = useController({ name, control }); + 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 [search, setSearch] = useState(""); + const [showDropdown, setShowDropdown] = useState(false); + const [selectedEmployees, setSelectedEmployees] = useState([]); + const dropdownRef = useRef(null); - const debouncedSearch = useDebounce(search, 500); + const debouncedSearch = useDebounce(search, 500); - const { data: employees, isLoading } = useEmployeesName( - projectId, - debouncedSearch, - forAll - ); + const { data: employees, isLoading } = useEmployeesName( + projectId, + debouncedSearch, + forAll + ); -useEffect(() => { + 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(",") - : ""; + // 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) - ); + const emails = stringValue.split(",").filter(Boolean); + const foundEmps = employees.data.filter((emp) => + emails.includes(emp.email) + ); - setSelectedEmployees(foundEmps); + setSelectedEmployees(foundEmps); - if (forAll && foundEmps.length > 0) { - setSearch(""); // clear search field - } + if (forAll && foundEmps.length > 0) { + setSearch(""); // clear search field + } } -}, [value, employees?.data, forAll]); + }, [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 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}} -
+ 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/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 d7cf8ac4..68cac17f 100644 --- a/src/hooks/useExpense.js +++ b/src/hooks/useExpense.js @@ -6,7 +6,15 @@ import { useSelector } from "react-redux"; import moment from "moment"; // -------------------Query------------------------------------------------------ - +export const usePayee =()=>{ + return useQuery({ + queryKey:["payee"], + queryFn:async()=>{ + const resp = await ExpenseRepository.GetPayee(); + return resp.data; + } + }) +} const cleanFilter = (filter) => { const cleaned = { ...filter }; @@ -484,6 +492,7 @@ export const useRecurringExpenseList = ( 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; }, diff --git a/src/repositories/ExpsenseRepository.jsx b/src/repositories/ExpsenseRepository.jsx index 432edf07..3770f6cc 100644 --- a/src/repositories/ExpsenseRepository.jsx +++ b/src/repositories/ExpsenseRepository.jsx @@ -1,25 +1,34 @@ import { api } from "../utils/axiosClient"; - const ExpenseRepository = { //#region Expense GetExpenseList: (pageSize, pageNumber, filter, searchString) => { const payloadJsonString = JSON.stringify(filter); - return api.get(`/api/expense/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`); + return api.get( + `/api/expense/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}` + ); }, GetExpenseDetails: (id) => api.get(`/api/Expense/details/${id}`), CreateExpense: (data) => api.post("/api/Expense/create", data), UpdateExpense: (id, data) => api.put(`/api/Expense/edit/${id}`, data), DeleteExpense: (id) => api.delete(`/api/Expense/delete/${id}`), - ActionOnExpense: (data) => api.post('/api/expense/action', data), - GetExpenseFilter: () => api.get('/api/Expense/filter'), + ActionOnExpense: (data) => api.post("/api/expense/action", data), + GetExpenseFilter: () => api.get("/api/Expense/filter"), //#endregion //#region Payment Request - GetPaymentRequestList: (pageSize, pageNumber, filter, isActive, searchString) => { + GetPaymentRequestList: ( + pageSize, + pageNumber, + filter, + isActive, + searchString + ) => { const payloadJsonString = JSON.stringify(filter); - return api.get(`/api/Expense/get/payment-requests/list?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`); + return api.get( + `/api/Expense/get/payment-requests/list?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}` + ); }, CreatePaymentRequest: (data) => api.post("/api/expense/payment-request/create", data), @@ -30,21 +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), - 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}`), + 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`) };