Merge branch 'upgrade_Expense' of https://git.marcoaiot.com/admin/marco.pms.web into upgrade_Expense

This commit is contained in:
pramod.mahajan 2025-11-06 13:18:00 +05:30
commit 1278e32da9
13 changed files with 322 additions and 285 deletions

View File

@ -14,8 +14,7 @@ const formSchema = z.object({
selectedRole: z.record(z.boolean()),
});
const ManageRole = ( {employeeId, onClosed} ) =>
{
const ManageRole = ({ employeeId, onClosed }) => {
const dispatch = useDispatch();
const formStateRef = useRef({});
@ -38,7 +37,7 @@ const ManageRole = ( {employeeId, onClosed} ) =>
});
const {
updateRoles,
isPending : isLoading,
isPending: isLoading,
isError,
error,
} = useUpdateEmployeeRoles({
@ -113,7 +112,7 @@ const ManageRole = ( {employeeId, onClosed} ) =>
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="text-start mb-3">
<div className="text-center mb-3">
<h5 className="lead">Select Roles :</h5>
</div>
@ -124,7 +123,8 @@ const ManageRole = ( {employeeId, onClosed} ) =>
className="d-flex flex-wrap justify-content-between pb-4"
style={{ maxHeight: "70vh", overflowY: "auto" }}
>
{roles.map((role) => (
{roles.slice()
.sort((a, b) => a.role.localeCompare(b.role)).map((role) => (
<div className="col-md-6 col-lg-4 mb-3" key={role.id}>
<div className="form-check ms-2 text-start">
<input

View File

@ -386,7 +386,7 @@ const ViewExpense = ({ ExpenseId }) => {
)}
</div>
)}
<hr className="divider my-1 border-2 divider-primary my-2" />
{/* <hr className="divider my-1 border-2 divider-primary my-2" /> */}
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
<>
@ -526,7 +526,7 @@ const ViewExpense = ({ ExpenseId }) => {
</div>
<div className="col-12 col-lg-5 col-xl-4">
<div className="d-flex align-items-center text-secondary mb-">
<div className="d-flex align-items-center text-secondary mb-4">
<i className="bx bx-time-five me-2"></i>{" "}
<p className=" m-0">TimeLine</p>
</div>

View File

@ -253,7 +253,7 @@ const PaymentRequestList = ({ filters, groupBy = "submittedBy", search }) => {
<td colSpan={8} className="text-start">
<div className="d-flex align-items-center">
{" "}
<small className="fs-6 py-1">
<small className="fs-6 py-1 ms-1">
{displayField} :{" "}
</small>{" "}
<small className="fs-6 ms-3">

View File

@ -411,7 +411,7 @@ const ViewPaymentRequest = ({ requestId }) => {
</div>
<div className="col-12 col-md-6 text-start mb-1">
<label className="form-label">Transaction Date </label>
<DatePicker
<DatePicker className="w-100"
name="paidAt"
control={control}
minDate={data?.createdAt}

View File

@ -4,6 +4,7 @@ import {
EXPENSE_REJECTEDBY,
FREQUENCY_FOR_RECURRING,
ITEMS_PER_PAGE,
PAYEE_RECURRING_EXPENSE,
} from "../../utils/constants";
import { formatCurrency, useDebounce } from "../../utils/appUtils";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
@ -16,7 +17,7 @@ import { useRecurringExpenseContext } from "../../pages/RecurringExpense/Recurri
import { useRecurringExpenseList } from "../../hooks/useExpense";
const RecurringExpenseList = ({ search, filterStatuses }) => {
const { setManageRequest, setVieRequest , setViewRecurring} = useRecurringExpenseContext();
const { setManageRequest, setVieRequest, setViewRecurring } = useRecurringExpenseContext();
const navigate = useNavigate();
const [IsDeleteModalOpen, setIsDeleteModalOpen,] = useState(false);
const [deletingId, setDeletingId] = useState(null);
@ -25,6 +26,13 @@ const RecurringExpenseList = ({ search, filterStatuses }) => {
(store) => store?.globalVariables?.loginUser?.employeeInfo?.id
);
const statusColorMap = {
"da462422-13b2-45cc-a175-910a225f6fc8": "primary", // Active
"306856fb-5655-42eb-bf8b-808bb5e84725": "success", // Completed
"3ec864d2-8bf5-42fb-ba70-5090301dd816": "danger", // De-Activated
"8bfc9346-e092-4a80-acbf-515ae1ef6868": "warning", // Paused
};
const recurringExpenseColumns = [
{
key: "expenseCategory",
@ -72,8 +80,18 @@ const RecurringExpenseList = ({ search, filterStatuses }) => {
{
key: "status",
label: "Status",
align: "text-start",
getValue: (e) => e?.status?.name || "N/A",
align: "text-center",
getValue: (e) => {
const color = statusColorMap[e?.status?.id] || "secondary";
const label = PAYEE_RECURRING_EXPENSE.find(
(s) => s.id === e?.status?.id
)?.label;
return (
<span className={`badge bg-label-${color}`}>
{label || e?.status?.name || "N/A"}
</span>
);
},
},
];
@ -261,8 +279,7 @@ const RecurringExpenseList = ({ search, filterStatuses }) => {
{[...Array(totalPages)].map((_, index) => (
<li
key={index}
className={`page-item ${
currentPage === index + 1 ? "active" : ""
className={`page-item ${currentPage === index + 1 ? "active" : ""
}`}
>
<button

View File

@ -48,19 +48,6 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
};
const collectionColumns = [
{
key: "invoiceDate",
label: "Invoice Date",
getValue: (col) => (
<span
className="text-truncate d-inline-block"
style={{ maxWidth: "200px" }}
>
{formatUTCToLocalTime(col.invoiceDate)}
</span>
),
align: "text-start",
},
{
key: "invoiceId",
label: "Invoice No",
@ -88,18 +75,33 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
align: "text-start",
},
{
key: "submittedDate",
label: "Submission Date",
key: "invoiceDate",
label: "Invoice Date",
getValue: (col) => (
<span
className="text-truncate d-inline-block"
style={{ maxWidth: "200px" }}
>
{formatUTCToLocalTime(col.createdAt)}
{formatUTCToLocalTime(col.invoiceDate)}
</span>
),
align: "text-center",
align: "text-start",
},
// {
// key: "submittedDate",
// label: "Submission Date",
// getValue: (col) => (
// <span
// className="text-truncate d-inline-block"
// style={{ maxWidth: "200px" }}
// >
// {formatUTCToLocalTime(col.createdAt)}
// </span>
// ),
// align: "text-center",
// },
{
key: "expectedSubmittedDate",
label: "Expected Payment Date",
@ -145,6 +147,19 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
),
align: "text-end",
},
{
key: "status",
label: "Status",
getValue: (col) => (
<span
className={`badge ${col.markAsCompleted ? "bg-label-primary" : "bg-label-danger"
}`}
>
{col.markAsCompleted ? "Active" : "In-active"}
</span>
),
align: "text-center",
},
];
if (isLoading) return <CollectionTableSkeleton />;

View File

@ -227,7 +227,7 @@ const ManageCollection = ({ collectionId, onClose }) => {
</div>
<div className="col-12 col-md-6 mb-2">
<Label required>Invoice Date</Label>
<DatePicker
<DatePicker className="w-100"
name="invoiceDate"
control={control}
maxDate={new Date()}
@ -240,7 +240,7 @@ const ManageCollection = ({ collectionId, onClose }) => {
</div>
<div className="col-12 col-md-6 mb-2">
<Label required>Expected Payment Date</Label>
<DatePicker
<DatePicker className="w-100"
name="exceptedPaymentDate"
control={control}
minDate={watch("invoiceDate")}
@ -253,7 +253,7 @@ const ManageCollection = ({ collectionId, onClose }) => {
</div>
<div className="col-12 col-md-6 mb-2">
<Label required>Submission Date (Client)</Label>
<DatePicker
<DatePicker className="w-100"
name="clientSubmitedDate"
control={control}
maxDate={new Date()}

View File

@ -29,47 +29,76 @@ const ViewCollection = ({ onClose }) => {
return (
<div className="container p-3">
<p className="fs-5 fw-semibold">Collection Details</p>
<div className="row text-start ">
<div className="row text-start">
{/* Row 0: Balance Amount + Expected Payment Date */}
<div className="row mb-3 px-2">
{/* Balance Amount */}
<div className="col-md-6 d-flex align-items-center">
<div className="fw-bold fs-6 text-dark me-2 ms-1">Balance Amount :</div>
<div className="fw-bold fs-6 text-dark">
{formatFigure(data?.balanceAmount, {
type: "currency",
currency: "INR",
})}
</div>
</div>
{/* Expected Payment Date */}
<div className="col-md-6 d-flex align-items-center">
<div className="fw-bold fs-6 text-dark me-2 ms-3">Expected Payment Date :</div>
<div className="fw-bold fs-6 text-dark">
{formatUTCToLocalTime(data?.exceptedPaymentDate)}
</div>
</div>
</div>
{/* Project and Status */}
<div className="col-12 mb-3 d-flex justify-content-between">
<div className="d-flex">
<label
className=" me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }}
className="me-2 mb-0 fw-semibold"
style={{ minWidth: "120px" }}
>
Project :
</label>
<div className="text-muted">{data?.project?.name}</div>
</div>
<div>
{" "}
<span
className={`badge bg-label-${
data?.isActive ? "primary" : "danger"
}`}
className={`badge bg-label-${data?.markAsCompleted ? "primary" : "danger"}`}
>
{data?.isActive ? "Active" : "Inactive"}
{data?.markAsCompleted ? "Active" : "Inactive"}
</span>
{(isAdmin || canEditCollection) &&
!data?.receivedInvoicePayments && (
!data?.receivedInvoicePayments &&
!data?.markAsCompleted && (
<span onClick={handleEdit} className="ms-2 cursor-pointer">
<i className="bx bx-edit text-primary bx-sm"></i>
</span>
)}
</div>
</div>
{/* Title */}
<div className="col-md-6">
<div className="row mb-2 text-wrap">
<div className="col-4 fw-semibold">Title :</div>
<div className="col-8 text-wrap">{data?.title}</div>
</div>
</div>
{/* Invoice Number */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Invoice Number:</div>
<div className="col-8">{data?.invoiceNumber}</div>
</div>
</div>
{/* Row 2: E-Invoice Number + Project */}
{/* E-Invoice Number */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">E-Invoice Number:</div>
@ -77,15 +106,15 @@ const ViewCollection = ({ onClose }) => {
</div>
</div>
{/* Row 3: Invoice Date + Client Submitted Date */}
{/* Invoice Date */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Invoice Date:</div>
<div className="col-8">
{formatUTCToLocalTime(data?.invoiceDate)}
</div>
<div className="col-8">{formatUTCToLocalTime(data?.invoiceDate)}</div>
</div>
</div>
{/* Client Submitted Date */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Client Submission Date:</div>
@ -94,17 +123,8 @@ const ViewCollection = ({ onClose }) => {
</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>
{/* Row 5: Basic Amount + Tax Amount */}
{/* Basic Amount */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Basic Amount :</div>
@ -116,6 +136,8 @@ const ViewCollection = ({ onClose }) => {
</div>
</div>
</div>
{/* Tax Amount */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Tax Amount :</div>
@ -127,25 +149,16 @@ const ViewCollection = ({ onClose }) => {
</div>
</div>
</div>
{/* Row 6: Balance Amount + Created At */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Balance Amount :</div>
<div className="col-8">
{formatFigure(data?.balanceAmount, {
type: "currency",
currency: "INR",
})}
</div>
</div>
</div>
{/* Created At */}
<div className="col-md-6">
<div className="row mb-4 align-items-end">
<div className="col-4 fw-semibold">Created At :</div>
<div className="col-8">{formatUTCToLocalTime(data?.createdAt)}</div>
</div>
</div>
{/* Row 7: Created By */}
{/* Created By */}
<div className="col-md-6">
<div className="row mb-4 align-items-center">
<div className="col-4 fw-semibold">Created By :</div>
@ -161,19 +174,19 @@ const ViewCollection = ({ onClose }) => {
</div>
</div>
</div>
{/* Description */}
<div className="col-12 my-1 mb-2">
<div className=" me-2 mb-0 fw-semibold">Description :</div>
<div className="me-2 mb-0 fw-semibold">Description :</div>
<div className="text-muted">{data?.description}</div>
</div>
{/* Attachments */}
<div className="col-12 text-start">
<label className=" me-2 mb-2 fw-semibold">Attachment :</label>
<label className="me-2 mb-2 fw-semibold">Attachment :</label>
<div className="d-flex flex-wrap gap-2">
{data?.attachments?.map((doc) => {
const isImage = doc.contentType?.includes("image");
return (
<div
key={doc.documentId}
@ -207,14 +220,12 @@ const ViewCollection = ({ onClose }) => {
</div>
</div>
<div className="container px-1">
{/* Tabs Navigation */}
{/* Tabs Section */}
<div className="container px-1 mt-4">
<ul className="nav nav-tabs" role="tablist">
<li className="nav-item">
<button
className={`nav-link ${
activeTab === "payments" ? "active" : ""
}`}
className={`nav-link ${activeTab === "payments" ? "active" : ""}`}
onClick={() => setActiveTab("payments")}
type="button"
>
@ -223,9 +234,7 @@ const ViewCollection = ({ onClose }) => {
</li>
<li className="nav-item">
<button
className={`nav-link ${
activeTab === "details" ? "active" : ""
}`}
className={`nav-link ${activeTab === "details" ? "active" : ""}`}
onClick={() => setActiveTab("details")}
type="button"
>
@ -235,7 +244,6 @@ const ViewCollection = ({ onClose }) => {
</li>
</ul>
{/* Tab Content */}
<div className="tab-content px-1 py-0 border-top-0">
{activeTab === "payments" && (
<div className="tab-pane fade show active">
@ -252,6 +260,7 @@ const ViewCollection = ({ onClose }) => {
</div>
</div>
</div>
);
};

View File

@ -178,7 +178,7 @@ export const DateRangePicker1 = ({
<div className={`position-relative ${className} w-max `}>
<input
type="text"
className="form-control form-control-sm ps-2 pe-3 me-2 cursor-pointer fw-medium"
className="form-control form-control-sm ps-2 pe-3 me-4 cursor-pointer"
placeholder={placeholder}
defaultValue={formattedValue}
ref={(el) => {

View File

@ -84,6 +84,7 @@ export const useMarkedPaymentReceived = (onSuccessCallBack) => {
await CollectionRepository.markPaymentReceived(payload),
onSuccess: async () => {
client.invalidateQueries({ queryKey: ["collections"] });
client.invalidateQueries({ queryKey: ["collection"] });
showToast("Payment Received marked Successfully", "success");
if (onSuccessCallBack) onSuccessCallBack();
},

View File

@ -73,13 +73,13 @@ const PaymentRequestPage = () => {
{/* Top Bar */}
<div className="card my-3 px-sm-4 px-0">
<div className="card-body py-2 px-3">
<div className="card-body py-2 px-0">
<div className="row align-items-center">
<div className="col-6">
<input
type="search"
className="form-control form-control-sm w-auto"
placeholder="Search Payment Req.."
placeholder="Search Payment Request"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>

View File

@ -56,13 +56,14 @@ const RecurringExpensePage = () => {
{/* Top Bar */}
<div className="card my-3 px-sm-4 px-0">
<div className="card-body py-2 px-1">
<div className="d-flex flex-wrap align-items-center justify-content-between">
{/* Left side: Search + Filter */}
<div className="d-flex align-items-center flex-wrap">
<div className="row align-items-center mb-0">
{/* Left Column: Search + Filter */}
<div className="col-md-8 col-sm-12 mb-2 mb-md-0">
<div className="d-flex align-items-center flex-wrap gap-0">
<input
type="search"
className="form-control form-control-sm w-auto"
placeholder="Search Recurring Expense..."
className="form-control form-control-sm w-25"
placeholder="Search Recurring Expense"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
@ -93,9 +94,10 @@ const RecurringExpensePage = () => {
</ul>
</div>
</div>
</div>
{/* Right side: Add Button */}
<div className="mt-2 mt-sm-0">
{/* Right Column: Add Button */}
<div className="col-md-4 col-sm-12 text-md-end text-start">
<button
className="btn btn-sm btn-primary"
type="button"
@ -113,6 +115,7 @@ const RecurringExpensePage = () => {
</button>
</div>
</div>
</div>
</div>
@ -150,7 +153,7 @@ const RecurringExpensePage = () => {
}
recurringId={viewRecurring.recurringId}
/>
<ViewRecurringExpense/>
<ViewRecurringExpense />
</GlobalModel>
)}

View File

@ -87,13 +87,13 @@ const CollectionPage = () => {
canEditCollection === undefined ||
canViewCollection === undefined ||
canCreate === undefined
) {
) {
return <div className="text-center py-5">Checking access...</div>;
}
}
if (!isAdmin && !canAddPayment && !canEditCollection && !canViewCollection && !canCreate) {
if (!isAdmin && !canAddPayment && !canEditCollection && !canViewCollection && !canCreate) {
return <AccessDenied data={[{ label: "Home", link: "/" }, { label: "Collection" }]} />;
}
}
return (
<CollectionContext.Provider value={contextMassager}>
<div className="container-fluid">
@ -101,15 +101,15 @@ if (!isAdmin && !canAddPayment && !canEditCollection && !canViewCollection && !c
data={[{ label: "Home", link: "/" }, { label: "Collection" }]}
/>
<div className="card my-3 py-2 px-sm-4 px-0">
<div className="row px-3">
<div className="col-12 col-md-3 mb-1">
<div className="card my-3 py-2 px-sm-4 px-2">
<div className="row align-items-center">
{/* Left side: Date Picker + Show Pending */}
<div className="col-8 col-md-6 d-flex flex-wrap align-items-center gap-3">
<FormProvider {...methods}>
<DateRangePicker1 howManyDay={180} />
</FormProvider>
</div>
<div className="col-12 col-md-3 d-flex align-items-center gap-2 ">
<div className="form-check form-switch text-start align-items-center">
<div className="form-check form-switch d-flex align-items-center mb-0">
<input
type="checkbox"
className="form-check-input"
@ -119,40 +119,32 @@ if (!isAdmin && !canAddPayment && !canEditCollection && !canViewCollection && !c
onChange={(e) => setShowPending(e.target.checked)}
/>
<label
className="form-check-label ms-0"
className="form-check-label ms-2"
htmlFor="inactiveEmployeesCheckbox"
>
Show Pending
Show Completed Collections
</label>
</div>
</div>
<div className="col-12 col-md-6 d-flex justify-content-end gap-4">
<div className=" w-md-auto">
{" "}
{/* Right side: Search + Add Button */}
<div className="col-4 col-md-6 d-flex justify-content-md-end align-items-center gap-3 mt-3 mt-md-0">
<div className="w-md-auto flex-grow-1 flex-md-grow-0">
<input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="search Collection"
placeholder="Search Collection"
className="form-control form-control-sm"
/>
</div>
{ (canCreate || isAdmin) && (
<button
className="btn btn-sm btn-primary"
type="button"
onClick={() => setCollection({ isOpen: true, invoiceId: null })}
>
<i className="bx bx-plus-circle me-2"></i>
<span className="d-none d-md-inline-block">Add New Collection</span>
</button>
)}
{(canCreate || isAdmin) && (<button className="btn btn-sm btn-primary" type="button" onClick={() => setCollection({ isOpen: true, invoiceId: null })} > <i className="bx bx-plus-circle me-2"></i> <span className="d-none d-md-inline-block">Add New Collection</span> </button>)}
</div>
</div>
</div>
<CollectionList
fromDate={fromDate}
toDate={toDate}
@ -209,7 +201,7 @@ if (!isAdmin && !canAddPayment && !canEditCollection && !canViewCollection && !c
<ConfirmModal
type="success"
header="Payment Successful Received"
message="Payment has been recored successfully."
message="Payment has been recorded successfully."
isOpen={processedPayment?.isOpen}
loading={isPending}
onSubmit={() => handleMarkedPayment(processedPayment?.invoiceId)}