From c52cbb14355ad06bd8ccc5c84a7dc174407419de Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Mon, 10 Nov 2025 11:49:56 +0530 Subject: [PATCH] Adding End Date in Recurring Expense Popup and adding i icon on it. --- src/components/Layout/Sidebar.jsx | 8 +- .../PaymentRequest/ViewPaymentRequest.jsx | 27 +-- .../ManageRecurringExpense.jsx | 160 +++++++++++------- .../RecurringExpenseSchema.js | 15 +- .../RecurringExpense/ViewRecurringExpense.jsx | 2 +- src/components/common/HoverPopup.jsx | 31 ++-- 6 files changed, 149 insertions(+), 94 deletions(-) diff --git a/src/components/Layout/Sidebar.jsx b/src/components/Layout/Sidebar.jsx index fb5349c4..b41cb654 100644 --- a/src/components/Layout/Sidebar.jsx +++ b/src/components/Layout/Sidebar.jsx @@ -27,14 +27,14 @@ const Sidebar = () => { - diff --git a/src/components/PaymentRequest/ViewPaymentRequest.jsx b/src/components/PaymentRequest/ViewPaymentRequest.jsx index aa7b46b2..941fe01e 100644 --- a/src/components/PaymentRequest/ViewPaymentRequest.jsx +++ b/src/components/PaymentRequest/ViewPaymentRequest.jsx @@ -31,6 +31,7 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { EXPENSE_PROCESSED, EXPENSE_REJECTEDBY, + EXPENSE_STATUS, PROCESS_EXPENSE, REVIEW_EXPENSE, } from "../../utils/constants"; @@ -123,13 +124,22 @@ const ViewPaymentRequest = ({ requestId }) => { setImageLoaded((prev) => ({ ...prev, [id]: true })); }; + const STATUS_HEADING = { + [EXPENSE_STATUS.daft]: "Initiation", + [EXPENSE_STATUS.review_pending]: "Review & Validation", + [EXPENSE_STATUS.approve_pending]: "Approval", + [EXPENSE_STATUS.payment_pending]: " Processing & Disbursement", + }; + return (
-
Payment Request Details
+
+ Payment Request - {STATUS_HEADING[data?.expenseStatus?.id] || "Payment Request Details"} +

@@ -138,9 +148,8 @@ const ViewPaymentRequest = ({ requestId }) => {
{data?.paymentRequestUID} {data?.expenseStatus?.name} @@ -258,9 +267,8 @@ const ViewPaymentRequest = ({ requestId }) => { lastName={data?.createdBy?.lastName} /> - {`${data?.createdBy?.firstName ?? ""} ${ - data?.createdBy?.lastName ?? "" - }`.trim() || "N/A"} + {`${data?.createdBy?.firstName ?? ""} ${data?.createdBy?.lastName ?? "" + }`.trim() || "N/A"}
@@ -283,9 +291,8 @@ const ViewPaymentRequest = ({ requestId }) => { lastName={data?.paidBy?.lastName} /> - {`${data?.paidBy?.firstName ?? ""} ${ - data?.paidBy?.lastName ?? "" - }`.trim() || "N/A"} + {`${data?.paidBy?.firstName ?? ""} ${data?.paidBy?.lastName ?? "" + }`.trim() || "N/A"}
diff --git a/src/components/RecurringExpense/ManageRecurringExpense.jsx b/src/components/RecurringExpense/ManageRecurringExpense.jsx index e4b951c7..71967108 100644 --- a/src/components/RecurringExpense/ManageRecurringExpense.jsx +++ b/src/components/RecurringExpense/ManageRecurringExpense.jsx @@ -26,6 +26,7 @@ import { import InputSuggestions from "../common/InputSuggestion"; import { useEmployeesName } from "../../hooks/useEmployees"; import PmsEmployeeInputTag from "../common/PmsEmployeeInputTag"; +import HoverPopup from "../common/HoverPopup"; const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { const { @@ -111,7 +112,8 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { strikeDate: data.strikeDate?.slice(0, 10) || "", projectId: data.project.id || "", paymentBufferDays: data.paymentBufferDays || "", - numberOfIteration: data.numberOfIteration || "", + // numberOfIteration: data.numberOfIteration || "", + endDate: data.endDate?.slice(0, 10) || "", expenseCategoryId: data.expenseCategory.id || "", statusId: data.status.id || "", frequency: data.frequency || "", @@ -129,6 +131,8 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { } }, [currencyData, requestToEdit, setValue]); + const StrikeDate = watch("strikeDate") + const onSubmit = (fromdata) => { console.log(fromdata); let payload = { @@ -136,6 +140,9 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { strikeDate: fromdata.strikeDate ? new Date(fromdata.strikeDate).toISOString() : null, + endDate: fromdata.endDate + ? new Date(fromdata.endDate).toISOString() + : null, notifyTo: handleEmailGetting(fromdata.notifyTo), }; if (requestToEdit) { @@ -145,6 +152,7 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { CreateRecurringExpense(payload); } }; + return (
@@ -227,36 +235,33 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { )}
- {/*
- - - {errors.isVariable && ( - {errors.isVariable.message} - )} -
*/} -
- +
+ + + Choose whether the payment amount varies or remains fixed each cycle. +
+ Is Variable: Amount changes per cycle. +
+ Fixed: Amount stays constant. +

+ } + > + +
+
( -
+
{ checked={field.value === true} onChange={() => field.onChange(true)} /> -
@@ -281,20 +283,19 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { checked={field.value === false} onChange={() => field.onChange(false)} /> -
)} /> + {errors.isVariable && ( {errors.isVariable.message} )}
+ {/* Date and Amount */} @@ -381,12 +382,25 @@ const ManageRecurringExpense = ({ closeModal, requestToEdit = null }) => { {/* Frequency To and Status Id */}
- +
+ + + Defines how often payments or billing occur, such as monthly, quarterly, or annually. +

+ } + > + +
+
+ + {errors.frequency && ( {errors.frequency.message} )}
+
- {/* Payment Buffer Days and Number of Iteration */} + {/* Payment Buffer Days and End Date */}
+
- +
+ + + Number of extra days allowed after the due date before payment is considered late. +

+ } + > + +
+
+ + {errors.paymentBufferDays && ( {errors.paymentBufferDays.message} )}
+ +
- - + + + The final date until which the plan, subscription, or project remains valid. +

+ } + > + +
+
+ + - {errors.numberOfIteration && ( - - {errors.numberOfIteration.message} - + + {errors.endDate && ( + {errors.endDate.message} )}
+ +
diff --git a/src/components/RecurringExpense/RecurringExpenseSchema.js b/src/components/RecurringExpense/RecurringExpenseSchema.js index 2555a9ac..a2feb508 100644 --- a/src/components/RecurringExpense/RecurringExpenseSchema.js +++ b/src/components/RecurringExpense/RecurringExpenseSchema.js @@ -24,7 +24,7 @@ export const PaymentRecurringExpense = () => { strikeDate: z .string() - .min(1, { message: "Date is required" }) + .min(1, { message: "Strike Date is required" }) .refine((val) => !isNaN(Date.parse(val)), { message: "Invalid date format", }) @@ -42,12 +42,13 @@ export const PaymentRecurringExpense = () => { }) .min(0, { message: "Buffer days cannot be negative" }), - numberOfIteration: z - .number({ - required_error: "Iteration is required", - invalid_type_error: "Iteration must be a number", + endDate: z + .string() + .min(1, { message: "End Date is required" }) + .refine((val) => !isNaN(Date.parse(val)), { + message: "Invalid date format", }) - .min(1, { message: "Iteration must be at least 1" }), + .transform((val) => val.trim()), expenseCategoryId: z .string() @@ -83,7 +84,7 @@ export const defaultRecurringExpense = { strikeDate: "", projectId: "", paymentBufferDays: 0, - numberOfIteration: 1, + endDate: "", expenseCategoryId: "", statusId: "", frequency: 1, diff --git a/src/components/RecurringExpense/ViewRecurringExpense.jsx b/src/components/RecurringExpense/ViewRecurringExpense.jsx index a021d2db..1284c2ce 100644 --- a/src/components/RecurringExpense/ViewRecurringExpense.jsx +++ b/src/components/RecurringExpense/ViewRecurringExpense.jsx @@ -21,7 +21,7 @@ const ViewRecurringExpense = ({ RecurringId }) => { return (
-
Recurring Payment Details
+
Recurring Expense Details
{/*
*/} diff --git a/src/components/common/HoverPopup.jsx b/src/components/common/HoverPopup.jsx index d65f365e..295d3a0e 100644 --- a/src/components/common/HoverPopup.jsx +++ b/src/components/common/HoverPopup.jsx @@ -5,11 +5,11 @@ const HoverPopup = ({ title, content, children }) => { const triggerRef = useRef(null); const popupRef = useRef(null); - // Toggle on hover or click + // Toggle popup on hover or click const handleMouseEnter = () => setVisible(true); const handleClick = () => setVisible((prev) => !prev); - // Hide on outside click + // Hide popup on outside click useEffect(() => { const handleDocumentClick = (e) => { if ( @@ -20,13 +20,8 @@ const HoverPopup = ({ title, content, children }) => { } }; - if (visible) { - document.addEventListener("click", handleDocumentClick); - } - - return () => { - document.removeEventListener("click", handleDocumentClick); - }; + if (visible) document.addEventListener("click", handleDocumentClick); + return () => document.removeEventListener("click", handleDocumentClick); }, [visible]); return ( @@ -34,6 +29,7 @@ const HoverPopup = ({ title, content, children }) => { className="d-inline-block position-relative" ref={triggerRef} onMouseEnter={handleMouseEnter} + onMouseLeave={() => setVisible(false)} onClick={handleClick} style={{ cursor: "pointer" }} > @@ -42,19 +38,27 @@ const HoverPopup = ({ title, content, children }) => { {visible && (
- {title &&
{title}
} -
{content}
+ {title && ( +
+ {title} +
+ )} +
+ {content} +
)}
@@ -62,4 +66,3 @@ const HoverPopup = ({ title, content, children }) => { }; export default HoverPopup; -