diff --git a/src/components/collections/AddPayment.jsx b/src/components/collections/AddPayment.jsx new file mode 100644 index 00000000..71b87edf --- /dev/null +++ b/src/components/collections/AddPayment.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const AddPayment = () => { + return ( +
+ +
+ ) +} + +export default AddPayment diff --git a/src/components/collections/CollectionList.jsx b/src/components/collections/CollectionList.jsx index 6151b723..72ef34b1 100644 --- a/src/components/collections/CollectionList.jsx +++ b/src/components/collections/CollectionList.jsx @@ -4,6 +4,7 @@ import { ITEMS_PER_PAGE } from "../../utils/constants"; import { formatFigure, localToUtc, useDebounce } from "../../utils/appUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils"; import Pagination from "../common/Pagination"; +import { useCollectionContext } from "../../pages/collections/CollectionPage"; const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { const [currentPage, setCurrentPage] = useState(1); @@ -18,6 +19,7 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { true, searchDebounce ); + const {setProcessedPayment} = useCollectionContext() const paginate = (page) => { if (page >= 1 && page <= (data?.totalPages ?? 1)) { @@ -99,7 +101,10 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { className="text-truncate d-inline-block" style={{ maxWidth: "200px" }} > - {formatFigure(col?.basicAmount, { type: "currency", currency: "INR" }) ?? 0} + {formatFigure(col?.basicAmount, { + type: "currency", + currency: "INR", + }) ?? 0} ), align: "text-end", @@ -112,7 +117,10 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { className="text-truncate d-inline-block py-3" style={{ maxWidth: "200px" }} > - {formatFigure(col?.balanceAmount, { type: "currency", currency: "INR" }) ?? 0} + {formatFigure(col?.balanceAmount, { + type: "currency", + currency: "INR", + }) ?? 0} ), align: "text-end", @@ -155,10 +163,48 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { className="sticky-action-column text-center" style={{ padding: "12px 8px" }} > -
- - - +
+ + +
diff --git a/src/components/collections/NewCollection.jsx b/src/components/collections/NewCollection.jsx index e1d10226..5e3480cb 100644 --- a/src/components/collections/NewCollection.jsx +++ b/src/components/collections/NewCollection.jsx @@ -11,10 +11,9 @@ import DatePicker from "../common/DatePicker"; import { useCreateCollection } from "../../hooks/useCollections"; import { formatFileSize, localToUtc } from "../../utils/appUtils"; -const NewCollection = ({ collectionId }) => { - const { onClose, onOpen, isOpen } = useModal("newCollection"); +const NewCollection = ({ collectionId, onClose }) => { + const { projectNames, projectLoading } = useProjectName(); - console.log(projectNames); const methods = useForm({ resolver: zodResolver(newCollection), defaultValues: defaultCollection, @@ -108,12 +107,13 @@ const NewCollection = ({ collectionId }) => { onClose(); }; - const bodyContent = ( -
+ return ( +
+
New Collection
-
+
{
+ ); - return ( - - ); + + + }; export default NewCollection; diff --git a/src/components/collections/collectionSchema.jsx b/src/components/collections/collectionSchema.jsx index 06a432ca..36510552 100644 --- a/src/components/collections/collectionSchema.jsx +++ b/src/components/collections/collectionSchema.jsx @@ -15,8 +15,15 @@ export const newCollection = z.object({ description: z.string().trim().optional(), clientSubmitedDate: z.string().min(1, { message: "Date is required" }), exceptedPaymentDate: z.string().min(1, { message: "Date is required" }), - invoiceNumber: z.string().trim().min(1, { message: "Invoice is required" }).max(17,{message:"Invalid Number"}), - eInvoiceNumber: z.string().trim().min(1, { message: "E-Invoice No is required" }), + invoiceNumber: z + .string() + .trim() + .min(1, { message: "Invoice is required" }) + .max(17, { message: "Invalid Number" }), + eInvoiceNumber: z + .string() + .trim() + .min(1, { message: "E-Invoice No is required" }), taxAmount: z.coerce .number({ invalid_type_error: "Amount is required and must be a number", @@ -25,7 +32,7 @@ export const newCollection = z.object({ .refine((val) => /^\d+(\.\d{1,2})?$/.test(val.toString()), { message: "Amount must have at most 2 decimal places", }), - basicAmount: z.coerce + basicAmount: z.coerce .number({ invalid_type_error: "Amount is required and must be a number", }) @@ -61,7 +68,22 @@ export const defaultCollection = { invoiceDate: null, exceptedPaymentDate: null, taxAmount: "", - basicAmount:"", + basicAmount: "", description: "", billAttachments: [], }; + +export const paymentSchema = z.object({ + invoiceId: z.string().min(1, { message: "Invoice Id required" }), + paymentReceivedDate: z.string().datetime(), + transactionId: z.string().min(1, "Transaction ID is required"), + amount: z.number().min(1, "Amount must be greater than zero"), +}); + +// Default Value +export const defaultPayment = { + invoiceId: "", + paymentReceivedDate: null, + transactionId: "", + amount: 0, +}; diff --git a/src/components/common/ConfirmModal.jsx b/src/components/common/ConfirmModal.jsx index 037c6a59..dd0158eb 100644 --- a/src/components/common/ConfirmModal.jsx +++ b/src/components/common/ConfirmModal.jsx @@ -13,19 +13,18 @@ const ConfirmModal = ({ if (!isOpen) return null; const TypeofIcon = () => { - if (type === "delete") { - return ( - - ); + switch (type) { + case "delete": + return ; + case "success": + return ; + case "warning": + return ; + default: + return null; } - return null; }; - const modalSize = type === "delete" ? "sm" : "md"; - return (
-
+
{header && {header}}
-
{TypeofIcon()}
-
+
+ {TypeofIcon()} +
+
{message}
+
diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index 3a93853d..737a4a19 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -88,7 +88,6 @@ export default DateRangePicker; - export const DateRangePicker1 = ({ startField = "startDate", endField = "endDate", @@ -98,6 +97,7 @@ export const DateRangePicker1 = ({ resetSignal, defaultRange = true, maxDate = null, + howManyDay = 6, ...rest }) => { const inputRef = useRef(null); @@ -107,12 +107,13 @@ export const DateRangePicker1 = ({ field: { ref }, } = useController({ name: startField, control }); + // Apply default range const applyDefaultDates = () => { const today = new Date(); - const past = new Date(); - past.setDate(today.getDate() - 6); + const past = new Date(today.getTime()); + past.setDate(today.getDate() - howManyDay); - const format = (d) => flatpickr.formatDate(d, "d-m-Y"); + const format = (d) => window.flatpickr.formatDate(d, "d-m-Y"); const formattedStart = format(past); const formattedEnd = format(today); @@ -127,15 +128,19 @@ export const DateRangePicker1 = ({ useEffect(() => { if (!inputRef.current || inputRef.current._flatpickr) return; - const instance = flatpickr(inputRef.current, { + if (defaultRange && !getValues(startField) && !getValues(endField)) { + applyDefaultDates(); + } + + const instance = window.flatpickr(inputRef.current, { mode: "range", dateFormat: "d-m-Y", allowInput: allowText, - maxDate , + maxDate, onChange: (selectedDates) => { if (selectedDates.length === 2) { const [start, end] = selectedDates; - const format = (d) => flatpickr.formatDate(d, "d-m-Y"); + const format = (d) => window.flatpickr.formatDate(d, "d-m-Y"); setValue(startField, format(start), { shouldValidate: true }); setValue(endField, format(end), { shouldValidate: true }); } else { @@ -148,12 +153,10 @@ export const DateRangePicker1 = ({ const currentStart = getValues(startField); const currentEnd = getValues(endField); - if (defaultRange && !currentStart && !currentEnd) { - applyDefaultDates(); - } else if (currentStart && currentEnd) { + if (currentStart && currentEnd) { instance.setDate([ - flatpickr.parseDate(currentStart, "d-m-Y"), - flatpickr.parseDate(currentEnd, "d-m-Y"), + window.flatpickr.parseDate(currentStart, "d-m-Y"), + window.flatpickr.parseDate(currentEnd, "d-m-Y"), ]); } @@ -161,20 +164,19 @@ export const DateRangePicker1 = ({ }, []); useEffect(() => { - if (resetSignal !== undefined) { - if (defaultRange) { - applyDefaultDates(); - } else { - setValue(startField, "", { shouldValidate: true }); - setValue(endField, "", { shouldValidate: true }); + if (resetSignal !== undefined) { + if (defaultRange) { + applyDefaultDates(); + } else { + setValue(startField, "", { shouldValidate: true }); + setValue(endField, "", { shouldValidate: true }); - if (inputRef.current?._flatpickr) { - inputRef.current._flatpickr.clear(); + if (inputRef.current?._flatpickr) { + inputRef.current._flatpickr.clear(); + } } } - } -}, [resetSignal, defaultRange, setValue, startField, endField]); - + }, [resetSignal, defaultRange, setValue, startField, endField]); const start = getValues(startField); const end = getValues(endField); @@ -186,7 +188,7 @@ export const DateRangePicker1 = ({ type="text" className="form-control form-control-sm" placeholder={placeholder} - defaultValue={formattedValue} + value={formattedValue} ref={(el) => { inputRef.current = el; ref(el); diff --git a/src/hooks/useCollections.js b/src/hooks/useCollections.js index d2403f3b..7c1b3776 100644 --- a/src/hooks/useCollections.js +++ b/src/hooks/useCollections.js @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { CollectionRepository } from "../repositories/ColllectionRepository"; import showToast from "../services/toastService"; @@ -42,16 +42,36 @@ export const useCollections = ( }; export const useCreateCollection = (onSuccessCallBack) => { + const clinent = useQueryClient(); return useMutation({ mutationFn: async (payload) => await CollectionRepository.createNewCollection(payload), onSuccess: (_, variables) => { showToast("New Collection created Successfully", "success"); + clinent.invalidateQueries({ queryKey: ["collections"] }); if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { showToast( - error.message || error.response.data.message || "Something Went wrong" + error.response.data.message || error.message || "Something Went wrong" + ); + }, + }); +}; + +export const useMarkedPaymentReceived = (onSuccessCallBack) => { + const client = useQueryClient(); + return useMutation({ + mutationFn: async (payload) => + await CollectionRepository.markPaymentReceived(payload), + onSuccess: async () => { + client.invalidateQueries({ queryKey: ["collections"] }); + showToast("Payment Received marked Successfully", "success"); + if (onSuccessCallBack) onSuccessCallBack(); + }, + onError: (error) => { + showToast( + error.response.data.message || error.message || "Something Went wrong" ); }, }); diff --git a/src/pages/collections/CollectionPage.jsx b/src/pages/collections/CollectionPage.jsx index 9512ff99..79ff9780 100644 --- a/src/pages/collections/CollectionPage.jsx +++ b/src/pages/collections/CollectionPage.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { createContext, useContext, useState } from "react"; import moment from "moment"; import Breadcrumb from "../../components/common/Breadcrumb"; import CollectionList from "../../components/collections/CollectionList"; @@ -7,84 +7,135 @@ import { FormProvider, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { DateRangePicker1 } from "../../components/common/DateRangePicker"; import { isPending } from "@reduxjs/toolkit"; +import ConfirmModal from "../../components/common/ConfirmModal"; +import showToast from "../../services/toastService"; +import { useMarkedPaymentReceived } from "../../hooks/useCollections"; +import NewCollection from "../../components/collections/NewCollection"; +import GlobalModel from "../../components/common/GlobalModel"; +const CollectionContext = createContext(); +export const useCollectionContext = () => { + const context = useContext(CollectionContext); + if (!context) { + window.location = "/dashboard"; + showToast("Out of Context Happend inside Collection Context", "warning"); + } + return context; +}; const CollectionPage = () => { const { onOpen } = useModal("newCollection"); + const [makeCollection, setCollection] = useState({ + isOpen: false, + invoiceId: null, + }); + const [processedPayment, setProcessedPayment] = useState(null); const [showPending, setShowPending] = useState(false); const [searchText, setSearchText] = useState(""); const methods = useForm({ defaultValues: { - fromDate: moment().subtract(6, "days").format("DD-MM-YYYY"), + fromDate: moment().subtract(180, "days").format("DD-MM-YYYY"), toDate: moment().format("DD-MM-YYYY"), }, }); const { watch } = methods; const [fromDate, toDate] = watch(["fromDate", "toDate"]); const handleToggleActive = (e) => setShowPending(e.target.checked); + + const contextMassager = { + setProcessedPayment, + }; + const { mutate: MarkedReceived, isPending } = useMarkedPaymentReceived(() => { + setProcessedPayment(null); + }); + const handleMarkedPayment = () => {}; return ( -
- + +
+ -
-
-
- - - -
-
-
- setShowPending(e.target.checked)} - /> -
-
- -
+ + + {makeCollection.isOpen && ( + setCollection({ isOpen: false, invoiceId: null })} + > + setCollection({ isOpen: false, invoiceId: null })} + /> + + )} + + setProcessedPayment(null)} + /> +
+ ); }; diff --git a/src/repositories/ColllectionRepository.jsx b/src/repositories/ColllectionRepository.jsx index f0f885ef..7f51da60 100644 --- a/src/repositories/ColllectionRepository.jsx +++ b/src/repositories/ColllectionRepository.jsx @@ -16,5 +16,9 @@ export const CollectionRepository = { } return api.get(url); }, + + makeReceivePayment:(data)=> api.post(`/api/Collection/invoice/payment/received`), + markPaymentReceived:(invoiceId)=>api.put(`/api/Collection/invoice/marked/completed/${invoiceId}`) + };