diff --git a/src/components/RecurringExpense/ManageRecurringExpense.jsx b/src/components/RecurringExpense/ManageRecurringExpense.jsx new file mode 100644 index 00000000..38df1105 --- /dev/null +++ b/src/components/RecurringExpense/ManageRecurringExpense.jsx @@ -0,0 +1,431 @@ +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 { 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'; + +function ManageRecurringExpense({ closeModal, requestToEdit = null }) { + + const data = {} + const { projectNames, loading: projectLoading, error, isError: isProjectError, + } = useProjectName(); + + const { data: currencyData, isLoading: currencyLoading, isError: currencyError } = useCurrencies(); + + + 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 handleClose = () => { + reset(); + closeModal(); + }; + + // const { mutate: CreatePaymentRequest, isPending: createPending } = useCreatePaymentRequest( + // () => { + // handleClose(); + // } + // ); + // 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 { + // CreatePaymentRequest(payload); + // } + console.log("Kartik", payload) + }; + + return ( +
+
+ {requestToEdit ? "Update Payment Request " : "Create Payment Request"} +
+
+ {/* 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} + )} +
+
+ + + {/* Date and Amount */} +
+
+ + + + {errors.strikeDate && ( + + {errors.strikeDate.message} + + )} +
+ +
+ + + {errors.amount && ( + {errors.amount.message} + )} +
+
+ + {/* Payee and Currency */} +
+
+ + + {errors.payee && ( + + {errors.payee.message} + + )} + +
+ + +
+ + + {errors.currencyId && ( + {errors.currencyId.message} + )} +
+ +
+ + {/* Notify To and Status Id */} +
+
+ + + {errors.notifyTo && ( + + {errors.notifyTo.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 diff --git a/src/components/RecurringExpense/RecurringExpenseFilterPanel.jsx b/src/components/RecurringExpense/RecurringExpenseFilterPanel.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/components/RecurringExpense/RecurringExpenseSchema.js b/src/components/RecurringExpense/RecurringExpenseSchema.js new file mode 100644 index 00000000..7c5e3b9e --- /dev/null +++ b/src/components/RecurringExpense/RecurringExpenseSchema.js @@ -0,0 +1,158 @@ +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" }), + + currencyId: z + .string() + .min(1, { message: "Currency is required" }), + + amount: z + .number({ + required_error: "Amount is required", + invalid_type_error: "Amount must be a number", + }) + .min(1, { message: "Amount must be greater than 0" }) + .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" }) + .refine((val) => !isNaN(Date.parse(val)), { + message: "Invalid date format", + }), + + projectId: z + .string() + .min(1, { message: "Project is required" }), + + paymentBufferDays: z + .number({ + required_error: "Buffer days is required", + invalid_type_error: "Buffer days must be a number", + }) + .min(0, { message: "Buffer days cannot be negative" }), + + numberOfIteration: z + .number({ + required_error: "Iteration is required", + invalid_type_error: "Iteration must be a number", + }) + .min(1, { message: "Iteration must be at least 1" }), + + expenseCategoryId: z + .string() + .min(1, { message: "Expense Category is required" }), + + statusId: z + .string() + .min(1, { message: "Please select a status" }), + + 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" }), + + isVariable: z.boolean().optional(), + }); +}; + +export const defaultRecurringExpense = { + title: "", + description: "", + payee: "", + notifyTo: "", + currencyId: "", + amount: 0, + strikeDate: "", // or null if your DatePicker accepts null + projectId: "", + paymentBufferDays: 0, + numberOfIteration: 1, + expenseCategoryId: "", + statusId: "", + frequency: 1, + isVariable: false, +}; + + +// 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, +// }; + + diff --git a/src/components/RecurringExpense/RecurringRexpenseList.jsx b/src/components/RecurringExpense/RecurringRexpenseList.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/pages/RecurringExpense/RecurringExpensePage.jsx b/src/pages/RecurringExpense/RecurringExpensePage.jsx new file mode 100644 index 00000000..dd7775f1 --- /dev/null +++ b/src/pages/RecurringExpense/RecurringExpensePage.jsx @@ -0,0 +1,132 @@ +import React, { createContext, useState, useEffect, useContext } from "react"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import GlobalModel from "../../components/common/GlobalModel"; +import { useFab } from "../../Context/FabContext"; +// import { defaultPaymentRequestFilter,SearchPaymentRequestSchema } from "../../components/PaymentRequest/PaymentRequestSchema"; +import ManageRecurringExpense from "../../components/RecurringExpense/ManageRecurringExpense"; + +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 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 [search, setSearch] = useState(""); + + const contextValue = { + setManageRequest, + setVieRequest + }; + + useEffect(() => { + + setShowTrigger(true); + setOffcanvasContent( + "Payment Request Filters", + // + ); + + return () => { + setShowTrigger(false); + setOffcanvasContent("", null); + }; + }, []); + + return ( + +
+ {/* Breadcrumb */} + + + {/* Top Bar */} +
+
+
+
+ setSearch(e.target.value)} + /> +
+ +
+ +
+
+
+
+ {/* */} + + {/* Add/Edit Modal */} + {ManageRequest.IsOpen && ( + + setManageRequest({ IsOpen: null, expenseId: null }) + } + > + + setManageRequest({ IsOpen: null, RequestId: null }) + } + /> + + )} + + {/* {ViewRequest.view && ( + setVieRequest({ requestId: null, view: false })} + > + + + )} */} + +
+
+ ); +}; + +export default RecurringExpensePage; diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index a36f0499..adc58771 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -56,6 +56,7 @@ import { ComingSoonPage } from "../pages/Misc/ComingSoonPage"; import CollectionPage from "../pages/collections/CollectionPage"; import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage"; import PaymentRequestPage from "../pages/PaymentRequest/PaymentRequestPage"; +import RecurringExpensePage from "../pages/RecurringExpense/RecurringExpensePage"; const router = createBrowserRouter( [ { @@ -102,6 +103,7 @@ const router = createBrowserRouter( { path: "/expenses", element: }, { path: "/payment-request", element: }, { path: "/advance-payment", element: }, + { path: "/recurring-payment", element: }, { path: "/masters", element: }, { path: "/tenants", element: }, { path: "/tenants/new-tenant", element: },