added mark as completd api operation

This commit is contained in:
pramod.mahajan 2025-10-15 12:35:07 +05:30
parent 76df08e921
commit 962286a4da
10 changed files with 578 additions and 260 deletions

View File

@ -11,12 +11,19 @@ import { formatFigure, localToUtc } from "../../utils/appUtils";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import { PaymentHistorySkeleton } from "./CollectionSkeleton"; import { PaymentHistorySkeleton } from "./CollectionSkeleton";
import { usePaymentType } from "../../hooks/masterHook/useMaster";
const AddPayment = ({ onClose }) => { const AddPayment = ({ onClose }) => {
const { addPayment } = useCollectionContext(); const { addPayment } = useCollectionContext();
const { data, isLoading, isError, error } = useCollection( const { data, isLoading, isError, error } = useCollection(
addPayment?.invoiceId addPayment?.invoiceId
); );
const {
data: paymentTypes,
isLoading: isPaymentTypeLoading,
isError: isPaymentTypeError,
error: paymentError,
} = usePaymentType(true);
const methods = useForm({ const methods = useForm({
resolver: zodResolver(paymentSchema), resolver: zodResolver(paymentSchema),
defaultValues: defaultPayment, defaultValues: defaultPayment,
@ -37,7 +44,6 @@ const AddPayment = ({ onClose }) => {
paymentReceivedDate: localToUtc(formData.paymentReceivedDate), paymentReceivedDate: localToUtc(formData.paymentReceivedDate),
invoiceId: addPayment.invoiceId, invoiceId: addPayment.invoiceId,
}; };
AddPayment(payload); AddPayment(payload);
}; };
const handleClose = (formData) => { const handleClose = (formData) => {
@ -78,6 +84,38 @@ const AddPayment = ({ onClose }) => {
)} )}
</div> </div>
<div className="col-12 col-md-6 mb-2">
<Label
htmlFor="paymentAdjustmentHeadId"
className="form-label"
required
>
Payment Adjustment Head
</Label>
<select
className="form-select form-select-sm "
{...register("paymentAdjustmentHeadId")}
>
{isPaymentTypeLoading ? (
<option>Loading..</option>
) : (
<>
<option value="" >Select Payment Head</option>
{paymentTypes?.data?.sort((a, b) => a.name.localeCompare(b.name))?.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</>
)}
</select>
{errors.paymentAdjustmentHeadId && (
<small className="danger-text">
{errors.paymentAdjustmentHeadId.message}
</small>
)}
</div>
<div className="col-12 col-md-6 mb-2"> <div className="col-12 col-md-6 mb-2">
<Label htmlFor="amount" className="form-label" required> <Label htmlFor="amount" className="form-label" required>
Amount Amount
@ -114,8 +152,11 @@ const AddPayment = ({ onClose }) => {
<button <button
type="reset" type="reset"
className="btn btn-label-secondary btn-sm mt-3" className="btn btn-label-secondary btn-sm mt-3"
onClick={handleClose} onClick={()=>{
// disabled={isPending} handleClose()
onClose()
}}
disabled={isPending}
> >
Cancel Cancel
</button> </button>
@ -141,57 +182,68 @@ const AddPayment = ({ onClose }) => {
</div> </div>
<div className="row text-start mx-2"> <div className="row text-start mx-2">
{data.receivedInvoicePayments.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)).map((payment, index) => ( {data.receivedInvoicePayments
<div className="col-12 mb-2" key={payment.id}> .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
<div className=" p-2 border-start border-warning"> .map((payment, index) => (
<div className="row"> <div className="col-12 mb-2" key={payment.id}>
<div className="col-12 col-md-6 d-flex justify-content-between align-items-center "> <div className=" p-2 border-start border-warning">
<div> <div className="row">
<small className="fw-semibold me-1"> <div className="col-12 col-md-6 d-flex justify-content-between align-items-center ">
Transaction Date: <div>
</small>{" "} <small className="fw-semibold me-1">
{formatUTCToLocalTime(payment.paymentReceivedDate)} Transaction Date:
</small>{" "}
{formatUTCToLocalTime(payment.paymentReceivedDate)}
</div>
<span className="fs-semibold d-block d-md-none">
{formatFigure(payment.amount, {
type: "currency",
currency: "INR",
})}
</span>
</div>
<div className="col-12 col-md-6 mb-0 d-flex align-items-center m-0">
<small className="fw-semibold me-2">
Updated By:
</small>{" "}
<Avatar
size="xs"
firstName={payment?.createdBy?.firstName}
lastName={payment?.createdBy?.lastName}
/>{" "}
{payment?.createdBy?.firstName}{" "}
{payment.createdBy?.lastName}
</div> </div>
<span className="fs-semibold d-block d-md-none">
{formatFigure(payment.amount, {
type: "currency",
currency: "INR",
})}
</span>
</div> </div>
<div className="col-12 col-md-6 mb-0 d-flex align-items-center m-0"> <div className="row">
<small className="fw-semibold me-2">Received By:</small>{" "} <div className="col-12 col-md-6">
<Avatar <p className="mb-1">
size="xs" <small className="fw-semibold">
firstName={payment?.createdBy?.firstName} Transaction ID:
lastName={payment?.createdBy?.lastName} </small>{" "}
/>{" "} {payment.transactionId}
{payment?.createdBy?.firstName}{" "} </p>
{payment.createdBy?.lastName} </div>
</div> <div className="col-12 ">
</div> <div className="d-flex justify-content-between">
<span>{payment?.paymentAdjustmentHead?.name}</span>
<div className="row"> <span className="fs-semibold d-none d-md-block">
<div className="col-12 col-md-6"> {formatFigure(payment.amount, {
<p className="mb-1"> type: "currency",
<small className="fw-semibold">Transaction ID:</small>{" "} currency: "INR",
{payment.transactionId} })}
</p> </span>
</div> </div>
<div className="col-12 "> <p className="text-tiny m-0 mt-1">
<span className="fs-semibold d-none d-md-block"> {payment?.comment}
{formatFigure(payment.amount, { </p>
type: "currency", </div>
currency: "INR",
})}
</span>
<p className="text-tiny m-0 mt-1">{payment?.comment}</p>
</div> </div>
</div> </div>
</div> </div>
</div> ))}
))}
</div> </div>
</div> </div>
) )

View File

@ -5,6 +5,7 @@ import { formatFigure, localToUtc, useDebounce } from "../../utils/appUtils";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Pagination from "../common/Pagination"; import Pagination from "../common/Pagination";
import { useCollectionContext } from "../../pages/collections/CollectionPage"; import { useCollectionContext } from "../../pages/collections/CollectionPage";
import { CollectionTableSkeleton } from "./CollectionSkeleton";
const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@ -19,7 +20,8 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
true, true,
searchDebounce searchDebounce
); );
const {setProcessedPayment,setAddPayment,setViewCollection} = useCollectionContext() const { setProcessedPayment, setAddPayment, setViewCollection } =
useCollectionContext();
const paginate = (page) => { const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) { if (page >= 1 && page <= (data?.totalPages ?? 1)) {
@ -127,7 +129,7 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
}, },
]; ];
if (isLoading) return <p>Loading...</p>; if (isLoading) return <CollectionTableSkeleton />;
if (isError) return <p>{error.message}</p>; if (isError) return <p>{error.message}</p>;
return ( return (
@ -183,27 +185,50 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
{/* View */} {/* View */}
<li> <li>
<a className="dropdown-item cursor-pointer" onClick={()=>setViewCollection(row.id)}> <a
className="dropdown-item cursor-pointer"
onClick={() => setViewCollection(row.id)}
>
<i className="bx bx-show me-2 text-primary"></i> <i className="bx bx-show me-2 text-primary"></i>
<span>View</span> <span>View</span>
</a> </a>
</li> </li>
{/* Add Payment */} {!row?.markAsCompleted && (
<li> <>
<a className="dropdown-item cursor-pointer" onClick={()=>setAddPayment({isOpen:true,invoiceId:row.id})}> {/* Add Payment */}
<i className="bx bx-wallet me-2 text-warning"></i> <li>
<span>Add Payment</span> <a
</a> className="dropdown-item cursor-pointer"
</li> onClick={() =>
setAddPayment({
isOpen: true,
invoiceId: row.id,
})
}
>
<i className="bx bx-wallet me-2 text-warning"></i>
<span>Add Payment</span>
</a>
</li>
{/* Mark Payment */} {/* Mark Payment */}
<li> <li>
<a className="dropdown-item cursor-pointer" onClick={()=>setProcessedPayment({isOpen:true,invoiceId:row})}> <a
<i className="bx bx-check-circle me-2 text-success"></i> className="dropdown-item cursor-pointer"
<span>Mark Payment</span> onClick={() =>
</a> setProcessedPayment({
</li> isOpen: true,
invoiceId: row.id,
})
}
>
<i className="bx bx-check-circle me-2 text-success"></i>
<span>Mark Payment</span>
</a>
</li>
</>
)}
</ul> </ul>
</div> </div>
</td> </td>

View File

@ -30,7 +30,12 @@ export const PaymentHistorySkeleton = ({ count = 2 }) => {
{/* Received By (Avatar + Name) */} {/* Received By (Avatar + Name) */}
<div className="col-12 col-md-6 d-flex align-items-center gap-2"> <div className="col-12 col-md-6 d-flex align-items-center gap-2">
<SkeletonLine width="30px" height={30} className="rounded-circle" /> {/* Avatar */} <SkeletonLine
width="30px"
height={30}
className="rounded-circle"
/>{" "}
{/* Avatar */}
<SkeletonLine width="120px" height={16} /> {/* Name */} <SkeletonLine width="120px" height={16} /> {/* Name */}
</div> </div>
</div> </div>
@ -41,3 +46,166 @@ export const PaymentHistorySkeleton = ({ count = 2 }) => {
); );
}; };
export const CollectionDetailsSkeleton = () => {
return (
<div className="container p-3">
{/* Title */}
<SkeletonLine height={24} width="200px" className="mx-auto" />
{/* Header Row */}
<div className="row mb-3 px-1">
<div className="col-10">
<SkeletonLine height={20} />
</div>
<div className="col-2 d-flex justify-content-end">
<SkeletonLine height={20} width="60px" />
</div>
</div>
{/* Project */}
<div className="row mb-3">
<div className="col-md-6">
<SkeletonLine width="60%" />
</div>
</div>
{/* Invoice & E-Invoice */}
<div className="row mb-3">
<div className="col-md-6">
<SkeletonLine />
</div>
<div className="col-md-6">
<SkeletonLine />
</div>
</div>
{/* Invoice Date & Client Submitted */}
<div className="row mb-3">
<div className="col-md-6">
<SkeletonLine />
</div>
<div className="col-md-6">
<SkeletonLine />
</div>
</div>
{/* Expected Payment & Mark as Completed */}
<div className="row mb-3">
<div className="col-md-6">
<SkeletonLine />
</div>
<div className="col-md-6">
<SkeletonLine width="40%" />
</div>
</div>
{/* Basic & Tax Amount */}
<div className="row mb-3">
<div className="col-md-6">
<SkeletonLine />
</div>
<div className="col-md-6">
<SkeletonLine />
</div>
</div>
{/* Balance & Created At */}
<div className="row mb-3">
<div className="col-md-6">
<SkeletonLine />
</div>
<div className="col-md-6">
<SkeletonLine />
</div>
</div>
{/* Created By */}
<div className="row mb-3">
<div className="col-md-6 d-flex align-items-center">
<SkeletonLine
width="40px"
height={40}
className="me-2 rounded-circle"
/>
<SkeletonLine width="100px" />
</div>
</div>
{/* Description */}
<div className="row mb-3">
<div className="col-12">
<SkeletonLine height={50} />
</div>
</div>
{/* Attachments */}
<div className="row mb-3">
<div className="col-12 d-flex gap-2 flex-wrap">
{[...Array(3)].map((_, idx) => (
<SkeletonLine key={idx} height={60} width="80px" />
))}
</div>
</div>
{/* Tabs */}
<div className="row mb-2">
<div className="col-12 d-flex gap-2">
<SkeletonLine height={35} width="120px" />
<SkeletonLine height={35} width="150px" />
</div>
</div>
{/* Tab Content (Comments / Payments) */}
<SkeletonLine height={200} />
</div>
);
};
export const CollectionTableSkeleton = () => {
const columnCount = 8;
return (
<div className="card ">
<div
className="card-datatable table-responsive page-min-h"
id="horizontal-example"
>
<div className="dataTables_wrapper no-footer mx-5 pb-2">
<table className="table dataTable text-nowrap">
<thead>
<tr >
{[...Array(columnCount - 1)].map((_, i) => (
<th key={i}>
<SkeletonLine height={15} width="80px" />
</th>
))}
<th className="d-flex justify-content-center">
<SkeletonLine height={16} width="40px" />
</th>
</tr>
</thead>
<tbody>
{[...Array(8)].map((_, rowIdx) => (
<tr key={rowIdx}>
{[...Array(columnCount - 1)].map((_, colIdx) => (
<td key={colIdx}>
<SkeletonLine height={33} />
</td>
))}
<td className="d-flex justify-content-center">
<SkeletonLine height={16} width="20px" />
</td>
</tr>
))}
</tbody>
</table>
{/* Pagination Skeleton */}
<div className="d-flex justify-content-end mt-2">
<SkeletonLine height={30} width="200px" />
</div>
</div>
</div>
</div>
);
};

View File

@ -9,7 +9,8 @@ import moment from "moment";
const Comment = ({ invoice }) => { const Comment = ({ invoice }) => {
const { const {
register,reset, register,
reset,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
@ -17,45 +18,16 @@ const Comment = ({ invoice }) => {
defaultValues: { comment: "" }, defaultValues: { comment: "" },
}); });
const { mutate: AddComment, isPending } = useAddComment(() => {reset()}); const { mutate: AddComment, isPending } = useAddComment(() => {
reset();
});
const onSubmit = (formData) => { const onSubmit = (formData) => {
const payload = { ...formData, invoiceId: invoice?.id }; const payload = { ...formData, invoiceId: invoice?.id };
debugger;
AddComment(payload); AddComment(payload);
}; };
return ( return (
<div className="row"> <div className="row pt-1">
{invoice?.comments?.length > 0 ? (
invoice.comments.map((comment, index) => (
<div
className="border-start border-primary ps-3 py-2 mb-3"
key={comment.id || index}
>
<div className="d-flex justify-content-between align-items-center mb-1">
<div className="d-flex align-items-center">
<Avatar
size="xs"
firstName={comment?.createdBy?.firstName}
lastName={comment?.createdBy?.lastName}
/>
<span className="ms-1 fw-semibold">
{comment?.createdBy?.firstName} {comment?.createdBy?.lastName}
</span>
</div>
<small className="text-secondary">
{moment.utc(comment?.createdAt).local().fromNow()}
</small>
</div>
<p className="mb-1">{comment?.comment}</p>
</div>
))
) : (
<p className="text-muted">No comments yet.</p>
)}
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="col-12"> <div className="col-12">
<textarea <textarea
@ -77,6 +49,35 @@ const Comment = ({ invoice }) => {
</button> </button>
</div> </div>
</form> </form>
{invoice?.comments?.length > 0 ? (
invoice.comments.map((comment, index) => (
<div
className="border-start border-primary ps-1 py-2 mb-3"
key={comment.id || index}
>
<div className="d-flex justify-content-between align-items-center mb-1">
<div className="d-flex align-items-center">
<Avatar
size="xs"
firstName={comment?.createdBy?.firstName}
lastName={comment?.createdBy?.lastName}
/>
<span className="ms-1 fw-semibold">
{comment?.createdBy?.firstName} {comment?.createdBy?.lastName}
</span>
</div>
<small className="text-secondary">
{moment.utc(comment?.createdAt).local().fromNow()}
</small>
</div>
<div className="ms-9"> <p className="mb-1">{comment?.comment}</p></div>
</div>
))
) : (
<p className="text-muted">No comments yet.</p>
)}
</div> </div>
); );
}; };

View File

@ -8,13 +8,14 @@ const PaymentHistoryTable = ({data}) => {
<div> <div>
{data?.receivedInvoicePayments?.length > 0 && ( {data?.receivedInvoicePayments?.length > 0 && (
<div className="mt-4"> <div className="pt-1 data-tabe table-responsive">
<table className="table table-bordered mt-2"> <table className="table table-bordered table-responsive mt-2">
<thead className="table-light"> <thead className="table-light">
<tr> <tr>
<th className="text-center">Sr.No</th> <th className="text-center">Sr.No</th>
<th className="text-center">Transaction ID</th> <th className="text-center">Transaction ID</th>
<th className="text-center"> Received Date</th> <th className="text-center"> Received Date</th>
<th className="text-center"> Payment Adjustment-Head</th>
<th className="text-center">Amount</th> <th className="text-center">Amount</th>
<th className="text-center">Updated By</th> <th className="text-center">Updated By</th>
</tr> </tr>
@ -25,6 +26,7 @@ const PaymentHistoryTable = ({data}) => {
<td className="text-center">{index + 1}</td> <td className="text-center">{index + 1}</td>
<td ><span className="mx-2">{payment.transactionId}</span></td> <td ><span className="mx-2">{payment.transactionId}</span></td>
<td className="text-center">{formatUTCToLocalTime(payment.paymentReceivedDate)}</td> <td className="text-center">{formatUTCToLocalTime(payment.paymentReceivedDate)}</td>
<td className="text-start">{payment?.paymentAdjustmentHead?.name ?? "--"}</td>
<td className="text-end"> <td className="text-end">
<span className="px-1">{formatFigure(payment.amount, { <span className="px-1">{formatFigure(payment.amount, {
type: "currency", type: "currency",
@ -32,12 +34,8 @@ const PaymentHistoryTable = ({data}) => {
})}</span> })}</span>
</td> </td>
<td> <td>
<div className="d-flex align-items-center mx-2"> <div className="d-flex align-items-center mx-2 py-1">
<Avatar
size="xs"
firstName={payment.createdBy?.firstName}
lastName={payment.createdBy?.lastName}
/>
{payment.createdBy?.firstName}{" "} {payment.createdBy?.firstName}{" "}
{payment.createdBy?.lastName} {payment.createdBy?.lastName}
</div> </div>

View File

@ -6,9 +6,11 @@ import { formatFigure, getIconByFileType } from "../../utils/appUtils";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import PaymentHistoryTable from "./PaymentHistoryTable"; import PaymentHistoryTable from "./PaymentHistoryTable";
import Comment from "./Comment"; import Comment from "./Comment";
import { CollectionDetailsSkeleton } from "./CollectionSkeleton";
const ViewCollection = ({ onClose }) => { const ViewCollection = ({ onClose }) => {
const { viewCollection, setCollection , setDocumentView} = useCollectionContext(); const { viewCollection, setCollection, setDocumentView } =
useCollectionContext();
const { data, isLoading, isError, error } = useCollection(viewCollection); const { data, isLoading, isError, error } = useCollection(viewCollection);
const handleEdit = () => { const handleEdit = () => {
@ -16,18 +18,25 @@ const ViewCollection = ({ onClose }) => {
onClose(); onClose();
}; };
if (isLoading) return <div>isLoading...</div>; if (isLoading) return <CollectionDetailsSkeleton />;
if (isError) return <div>{error.message}</div>; if (isError) return <div>{error.message}</div>;
return ( return (
<div className="container p-3"> <div className="container p-3">
<p className="fs-5 fw-semibold">Collection Details</p> <p className="fs-5 fw-semibold">Collection Details</p>
<div className="text-start "> <div className="row text-start ">
<div className="row mb-3 px-1"> <div className="col-12 mb-3 d-flex justify-content-between">
<div className="col-10"> <div className="d-flex">
<p className="mb-1 fs-6">{data?.title}</p> <label
className=" me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }}
>
Project :
</label>
<div className="text-muted">{data?.project?.name}</div>
</div> </div>
<div className="col-2"> <div>
{" "}
<span <span
className={`badge bg-label-${ className={`badge bg-label-${
data?.isActive ? "primary" : "danger" data?.isActive ? "primary" : "danger"
@ -35,101 +44,127 @@ const ViewCollection = ({ onClose }) => {
> >
{data?.isActive ? "Active" : "Inactive"} {data?.isActive ? "Active" : "Inactive"}
</span> </span>
{!data?.receivedInvoicePayments && (<span onClick={handleEdit} className="ms-2 cursor-pointer"> {!data?.receivedInvoicePayments && (
<i className="bx bx-edit text-primary bx-sm"></i> <span onClick={handleEdit} className="ms-2 cursor-pointer">
</span>)} <i className="bx bx-edit text-primary bx-sm"></i>
</span>
)}
</div> </div>
</div> </div>
<div className="row mb-3"> <div className="col-md-6">
<div className="col-md-6 d-flex "> <div className="row mb-2 text-wrap">
<p className="m-0 fw-semibold me-1">Project:</p>{" "} <div className="col-4 fw-semibold">Title :</div>
{data?.project?.name} <div className="col-8 text-wrap">{data?.title}</div>
</div> </div>
</div> </div>
<div className="row mb-3"> <div className="col-md-6">
<div className="col-md-6 mb-3 d-flex"> <div className="row mb-4 align-items-end">
<p className="m-0 fw-semibold me-1">Invoice Number:</p>{" "} <div className="col-4 fw-semibold">Invoice Number:</div>
{data?.invoiceNumber} <div className="col-8">{data?.invoiceNumber}</div>
</div> </div>
<div className="col-md-6 d-flex"> </div>
<p className="m-0 fw-semibold me-1">E-Invoice Number:</p>{" "} {/* Row 2: E-Invoice Number + Project */}
{data?.eInvoiceNumber} <div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">E-Invoice Number:</div>
<div className="col-8">{data?.eInvoiceNumber}</div>
</div> </div>
</div> </div>
<div className="row mb-3"> {/* Row 3: Invoice Date + Client Submitted Date */}
<div className="col-md-6 mb-3 d-flex"> <div className="col-md-6">
<p className="m-0 fw-semibold me-1">Invoice Date:</p>{" "} <div className="row mb-4 align-items-end">
{formatUTCToLocalTime(data?.invoiceDate)} <div className="col-4 fw-semibold">Invoice Date:</div>
<div className="col-8">
{formatUTCToLocalTime(data?.invoiceDate)}
</div>
</div> </div>
<div className="col-md-6 d-flex"> </div>
<p className="m-0 fw-semibold me-1">Client Submitted Date:</p>{" "} <div className="col-md-6">
{formatUTCToLocalTime(data?.clientSubmitedDate)} <div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Client Submitted Date:</div>
<div className="col-8">
{formatUTCToLocalTime(data?.clientSubmitedDate)}
</div>
</div>
</div>
{/* Row 4: Expected Payment Date + Mark as Completed */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Expected Payment Date:</div>
<div className="col-8">
{formatUTCToLocalTime(data?.exceptedPaymentDate)}
</div>
</div> </div>
</div> </div>
<div className="row mb-3"> {/* Row 5: Basic Amount + Tax Amount */}
<div className="col-md-6 mb-3 d-flex"> <div className="col-md-6">
<p className="m-0 fw-semibold me-1">Expected Payment Date:</p>{" "} <div className="row mb-4 align-items-end">
{formatUTCToLocalTime(data?.exceptedPaymentDate)} <div className="col-4 fw-semibold">Basic Amount :</div>
</div> <div className="col-8">
<div className="col-md-6 d-flex"> {formatFigure(data?.basicAmount, {
<p className="m-0 fw-semibold me-1">Mark as Completed:</p>{" "} type: "currency",
{data?.markAsCompleted ? "Yes" : "No"} currency: "INR",
})}
</div>
</div> </div>
</div> </div>
<div className="col-md-6">
<div className="row mb-3"> <div className="row mb-4 align-items-end">
<div className="col-md-6 mb-3 d-flex"> <div className="col-4 fw-semibold">Tax Amount :</div>
<p className="m-0 fw-semibold me-1">Basic Amount:</p>{" "} <div className="col-8">
{formatFigure(data?.basicAmount, { {formatFigure(data?.taxAmount, {
type: "currency", type: "currency",
currency: "INR", currency: "INR",
})} })}
</div> </div>
<div className="col-md-6 d-flex">
<p className="m-0 fw-semibold me-1">Tax Amount:</p>{" "}
{formatFigure(data?.taxAmount, {
type: "currency",
currency: "INR",
})}
</div> </div>
</div> </div>
{/* Row 6: Balance Amount + Created At */}
<div className="row mb-3"> <div className="col-md-6">
<div className="col-md-6 mb-3 d-flex"> <div className="row mb-4 align-items-end">
<p className="m-0 fw-semibold me-1">Balance Amount:</p>{" "} <div className="col-4 fw-semibold">Balance Amount :</div>
{formatFigure(data?.balanceAmount, { <div className="col-8">
type: "currency", {formatFigure(data?.balanceAmount, {
currency: "INR", type: "currency",
})} currency: "INR",
</div> })}
<div className="col-md-6 d-flex"> </div>
<p className="m-0 fw-semibold me-1">Created At:</p>{" "}
{formatUTCToLocalTime(data?.createdAt)}
</div> </div>
</div> </div>
<div className="col-md-6">
<div className="row mb-3"> <div className="row mb-4 align-items-end">
<div className="col-md-6 mb-3 d-flex align-items-center"> <div className="col-4 fw-semibold">Created At :</div>
<p className="m-0 fw-semibold">Created By:</p>{" "} <div className="col-8">{formatUTCToLocalTime(data?.createdAt)}</div>
<div className="d-flex align-items-center"> </div>
</div>
{/* Row 7: Created By */}
<div className="col-md-6">
<div className="row mb-4 align-items-center">
<div className="col-4 fw-semibold">Created By :</div>
<div className="col-8 d-flex align-items-center">
<Avatar <Avatar
size="xs" size="xs"
firstName={data.createdBy?.firstName} firstName={data.createdBy?.firstName}
lastName={data.createdBy?.lastName} lastName={data.createdBy?.lastName}
/> />
{data?.createdBy?.firstName} {data?.createdBy?.lastName} <span className="ms-1 text-muted">
</div>{" "} {data?.createdBy?.firstName} {data?.createdBy?.lastName}
</div> </span>
</div>
<div className="col-12">
<p className="m-0 fw-semibold">Description : </p>
{data?.description}
</div> </div>
</div> </div>
{/* Description */}
<div className="col-12 my-1 mb-2">
<div className=" me-2 mb-0 fw-semibold">
Description :
</div>
<div className="text-muted">{data?.description}</div>
</div>
<div className="col-12 text-start"> <div className="col-12 text-start">
<label className="form-label me-2 mb-2 fw-semibold"> <label className=" me-2 mb-2 fw-semibold">
Attachment : Attachment :
</label> </label>
@ -166,27 +201,15 @@ const ViewCollection = ({ onClose }) => {
</small> </small>
</div> </div>
); );
})} }) ?? "No Attachment"}
</div> </div>
</div> </div>
<div className="container px-1 "> <div className="container px-1 ">
{/* Tabs Navigation */} {/* Tabs Navigation */}
<ul className="nav nav-tabs" role="tablist"> <ul className="nav nav-tabs" role="tablist">
<li className="nav-item"> <li className="nav-item active">
<button
className="nav-link active"
id="details-tab"
data-bs-toggle="tab"
data-bs-target="#details"
type="button"
role="tab"
>
<i className="bx bx-message-dots bx-sm me-2"></i> Comments (
{data?.comments?.length ?? "0"})
</button>
</li>
<li className="nav-item">
<button <button
className="nav-link" className="nav-link"
id="payments-tab" id="payments-tab"
@ -198,19 +221,32 @@ const ViewCollection = ({ onClose }) => {
<i className="bx bx-history bx-sm me-1"></i> Payments History <i className="bx bx-history bx-sm me-1"></i> Payments History
</button> </button>
</li> </li>
<li className="nav-item">
<button
className="nav-link "
id="details-tab"
data-bs-toggle="tab"
data-bs-target="#details"
type="button"
role="tab"
>
<i className="bx bx-message-dots bx-sm me-2"></i> Comments (
{data?.comments?.length ?? "0"})
</button>
</li>
</ul> </ul>
<div className="tab-content py-1 border-top-0 "> <div className="tab-content px-1 py-0 border-top-0 ">
<div <div className="tab-pane fade " id="details" role="tabpanel">
className="tab-pane fade show active"
id="details"
role="tabpanel"
>
<Comment invoice={data} /> <Comment invoice={data} />
</div> </div>
{/* Payments History Tab Content */} {/* Payments History Tab Content */}
<div className="tab-pane fade" id="payments" role="tabpanel"> <div
className="tab-pane fade show active"
id="payments"
role="tabpanel"
>
<div className="row text-start"> <div className="row text-start">
<PaymentHistoryTable data={data} /> <PaymentHistoryTable data={data} />
</div> </div>

View File

@ -77,7 +77,8 @@ export const paymentSchema = z.object({
paymentReceivedDate: z.string().min(1, { message: "Date is required" }), paymentReceivedDate: z.string().min(1, { message: "Date is required" }),
transactionId: z.string().min(1, "Transaction ID is required"), transactionId: z.string().min(1, "Transaction ID is required"),
amount: z.number().min(1, "Amount must be greater than zero"), amount: z.number().min(1, "Amount must be greater than zero"),
comment:z.string().min(1,{message:"Comment required"}) comment:z.string().min(1,{message:"Comment required"}),
paymentAdjustmentHeadId:z.string().min(1,{message:"Payment Type required"})
}); });
// Default Value // Default Value
@ -85,7 +86,8 @@ export const defaultPayment = {
paymentReceivedDate: null, paymentReceivedDate: null,
transactionId: "", transactionId: "",
amount: 0, amount: 0,
comment:"" comment:"",
paymentAdjustmentHeadId:""
}; };

View File

@ -10,6 +10,14 @@ import {
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
export const usePaymentType = (isActive) => {
return useQuery({
queryKey: ["paymentType",isActive],
queryFn: async () => await MasterRespository.getPaymentType(isActive),
});
};
export const useServices = () => { export const useServices = () => {
return useQuery({ return useQuery({
queryKey: ["services"], queryKey: ["services"],
@ -30,7 +38,6 @@ export const useActivitiesByGroups = (groupId) => {
queryFn: async () => await MasterRespository.getActivitesByGroup(groupId), queryFn: async () => await MasterRespository.getActivitesByGroup(groupId),
enabled: !!groupId, enabled: !!groupId,
}); });
}; };
export const useGlobalServices = () => { export const useGlobalServices = () => {
@ -448,8 +455,6 @@ export const useUpdateApplicationRole = (onSuccessCallback) => {
}); });
}; };
//-----Create work Category------------------------------- //-----Create work Category-------------------------------
export const useCreateWorkCategory = (onSuccessCallback) => { export const useCreateWorkCategory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -703,13 +708,11 @@ export const useCreateService = (onSuccessCallback) => {
return useMutation({ return useMutation({
mutationFn: async (payload) => { mutationFn: async (payload) => {
const resp = await MasterRespository.createService(payload); const resp = await MasterRespository.createService(payload);
return resp.data; return resp.data;
}, },
onSuccess: (data) => { onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] }); queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] });
showToast(data?.message || "Service added successfully", "success"); showToast(data?.message || "Service added successfully", "success");
@ -717,8 +720,12 @@ export const useCreateService = (onSuccessCallback) => {
if (onSuccessCallback) onSuccessCallback(data?.data); if (onSuccessCallback) onSuccessCallback(data?.data);
}, },
onError: (error) => { onError: (error) => {
showToast(
showToast( error?.response?.data?.message || error?.message || "Something went wrong", "error"); error?.response?.data?.message ||
error?.message ||
"Something went wrong",
"error"
);
}, },
}); });
}; };
@ -741,7 +748,12 @@ export const useUpdateService = (onSuccessCallback) => {
if (onSuccessCallback) onSuccessCallback(data); if (onSuccessCallback) onSuccessCallback(data);
}, },
onError: (error) => { onError: (error) => {
showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); showToast(
error?.response?.data?.message ||
error?.message ||
"Something went wrong",
"error"
);
}, },
}); });
}; };
@ -759,15 +771,22 @@ export const useCreateActivityGroup = (onSuccessCallback) => {
queryKey: ["groups"], queryKey: ["groups"],
}); });
showToast( data?.message || showToast(
data?.response?.data?.message || "Activity Group created successfully.", data?.message ||
data?.response?.data?.message ||
"Activity Group created successfully.",
"success" "success"
); );
if (onSuccessCallback) onSuccessCallback(data); if (onSuccessCallback) onSuccessCallback(data);
}, },
onError: (error) => { onError: (error) => {
showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); showToast(
error?.response?.data?.message ||
error?.message ||
"Something went wrong",
"error"
);
}, },
}); });
}; };
@ -775,8 +794,8 @@ export const useUpdateActivityGroup = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({id,payload}) => { mutationFn: async ({ id, payload }) => {
const response = await MasterRespository.updateActivityGrop(id,payload); const response = await MasterRespository.updateActivityGrop(id, payload);
return response; return response;
}, },
onSuccess: (data, variables) => { onSuccess: (data, variables) => {
@ -786,7 +805,8 @@ export const useUpdateActivityGroup = (onSuccessCallback) => {
showToast( showToast(
data?.message || data?.message ||
data?.response?.data?.message|| "Activity Group Updated successfully.", data?.response?.data?.message ||
"Activity Group Updated successfully.",
"success" "success"
); );
@ -812,7 +832,12 @@ export const useCreateActivity = (onSuccessCallback) => {
if (onSuccessCallback) onSuccessCallback(data); if (onSuccessCallback) onSuccessCallback(data);
}, },
onError: (error) => { onError: (error) => {
showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); showToast(
error?.response?.data?.message ||
error?.message ||
"Something went wrong",
"error"
);
}, },
}); });
}; };
@ -834,7 +859,12 @@ export const useUpdateActivity = (onSuccessCallback) => {
if (onSuccessCallback) onSuccessCallback(data); if (onSuccessCallback) onSuccessCallback(data);
}, },
onError: (error) => { onError: (error) => {
showToast(error?.response?.data?.message || error?.message || "Something went wrong", "error"); showToast(
error?.response?.data?.message ||
error?.message ||
"Something went wrong",
"error"
);
}, },
}); });
}; };
@ -1001,14 +1031,12 @@ export const useDeleteMasterItem = () => {
}); });
}; };
export const useDeleteServiceGroup = () => {
export const useDeleteServiceGroup =()=>{ const queryClient = useQueryClient();
const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (id)=>await MasterRespository.deleteActivityGroup(id), mutationFn: async (id) => await MasterRespository.deleteActivityGroup(id),
onSuccess: ({_,variable}) => { onSuccess: ({ _, variable }) => {
queryClient.invalidateQueries({ queryKey: ["groups"] }); queryClient.invalidateQueries({ queryKey: ["groups"] });
showToast(`Group deleted successfully.`, "success"); showToast(`Group deleted successfully.`, "success");
@ -1022,15 +1050,13 @@ export const useDeleteServiceGroup =()=>{
showToast(message, "error"); showToast(message, "error");
}, },
}); });
} };
export const useDeleteActivity =()=>{ export const useDeleteActivity = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (id)=>await MasterRespository.deleteActivity(id), mutationFn: async (id) => await MasterRespository.deleteActivity(id),
onSuccess: ({_,variable}) => { onSuccess: ({ _, variable }) => {
queryClient.invalidateQueries({ queryKey: ["activties"] }); queryClient.invalidateQueries({ queryKey: ["activties"] });
showToast(`Acivity deleted successfully.`, "success"); showToast(`Acivity deleted successfully.`, "success");
@ -1044,4 +1070,4 @@ export const useDeleteActivity =()=>{
showToast(message, "error"); showToast(message, "error");
}, },
}); });
} };

View File

@ -26,15 +26,15 @@ export const useCollectionContext = () => {
return context; return context;
}; };
const CollectionPage = () => { const CollectionPage = () => {
const [viewCollection,setViewCollection] = useState(null) const [viewCollection, setViewCollection] = useState(null);
const [makeCollection, setCollection] = useState({ const [makeCollection, setCollection] = useState({
isOpen: false, isOpen: false,
invoiceId: null, invoiceId: null,
}); });
const [ViewDocument, setDocumentView] = useState({ const [ViewDocument, setDocumentView] = useState({
IsOpen: false, IsOpen: false,
Image: null, Image: null,
}); });
const [processedPayment, setProcessedPayment] = useState(null); const [processedPayment, setProcessedPayment] = useState(null);
const [addPayment, setAddPayment] = useState({ const [addPayment, setAddPayment] = useState({
isOpen: false, isOpen: false,
@ -59,12 +59,14 @@ const CollectionPage = () => {
addPayment, addPayment,
setViewCollection, setViewCollection,
viewCollection, viewCollection,
setDocumentView setDocumentView,
}; };
const { mutate: MarkedReceived, isPending } = useMarkedPaymentReceived(() => { const { mutate: MarkedReceived, isPending } = useMarkedPaymentReceived(() => {
setProcessedPayment(null); setProcessedPayment(null);
}); });
const handleMarkedPayment = () => {}; const handleMarkedPayment = (payload) => {
MarkedReceived(payload);
};
return ( return (
<CollectionContext.Provider value={contextMassager}> <CollectionContext.Provider value={contextMassager}>
<div className="container-fluid"> <div className="container-fluid">
@ -99,7 +101,7 @@ const CollectionPage = () => {
</div> </div>
<div className="col-12 col-md-6 d-flex justify-content-end gap-4"> <div className="col-12 col-md-6 d-flex justify-content-end gap-4">
<div className=""> <div className=" w-md-auto">
{" "} {" "}
<input <input
type="search" type="search"
@ -156,12 +158,16 @@ const CollectionPage = () => {
)} )}
{viewCollection && ( {viewCollection && (
<GlobalModel size="lg" isOpen={viewCollection} closeModal={()=>setViewCollection(null)}> <GlobalModel
<ViewCollection onClose={()=>setViewCollection(null)}/> size="lg"
isOpen={viewCollection}
closeModal={() => setViewCollection(null)}
>
<ViewCollection onClose={() => setViewCollection(null)} />
</GlobalModel> </GlobalModel>
)} )}
{ViewDocument.IsOpen && ( {ViewDocument.IsOpen && (
<GlobalModel <GlobalModel
isOpen isOpen
size="md" size="md"
@ -169,7 +175,8 @@ const CollectionPage = () => {
closeModal={() => setDocumentView({ IsOpen: false, Image: null })} closeModal={() => setDocumentView({ IsOpen: false, Image: null })}
> >
<PreviewDocument imageUrl={ViewDocument.Image} /> <PreviewDocument imageUrl={ViewDocument.Image} />
</GlobalModel>)} </GlobalModel>
)}
<ConfirmModal <ConfirmModal
type="success" type="success"
@ -177,7 +184,7 @@ const CollectionPage = () => {
message="Your payment has been processed successfully. Do you want to continue?" message="Your payment has been processed successfully. Do you want to continue?"
isOpen={processedPayment?.isOpen} isOpen={processedPayment?.isOpen}
loading={isPending} loading={isPending}
onSubmit={handleMarkedPayment} onSubmit={() => handleMarkedPayment(processedPayment?.invoiceId)}
onClose={() => setProcessedPayment(null)} onClose={() => setProcessedPayment(null)}
/> />
</div> </div>

View File

@ -124,10 +124,13 @@ export const MasterRespository = {
api.put(`/api/Master/activity-group/edit/${serviceId}`, data), api.put(`/api/Master/activity-group/edit/${serviceId}`, data),
getActivitesByGroup: (activityGroupId) => getActivitesByGroup: (activityGroupId) =>
api.get(`api/master/activities?activityGroupId=${activityGroupId}`), api.get(`api/master/activities?activityGroupId=${activityGroupId}`),
deleteActivityGroup:(id)=>api.delete(`/api/Master/activity-group/delete/${id}`), deleteActivityGroup: (id) =>
api.delete(`/api/Master/activity-group/delete/${id}`),
deleteActivity: (id) => api.delete(`/api/Master/activity/delete/${id}`),
deleteActivity:(id)=>api.delete(`/api/Master/activity/delete/${id}`),
getOrganizationType: () => api.get("/api/Master/organization-type/list"), getOrganizationType: () => api.get("/api/Master/organization-type/list"),
getPaymentType: (isActive) =>
api.get(`/api/Master/payment-adjustment-head/list?isActive=${isActive}`),
}; };