added server data

This commit is contained in:
pramod.mahajan 2025-11-21 20:00:36 +05:30
parent c615678981
commit 6d72ed4735
7 changed files with 322 additions and 177 deletions

View File

@ -6,16 +6,18 @@ import { useProjectInfra } from "../../../hooks/useProjects";
import { PmsGrid } from "../../../services/pmsGrid"; import { PmsGrid } from "../../../services/pmsGrid";
export default function BuildingTable() { export default function BuildingTable() {
const project = useSelectedProject() const project = useSelectedProject();
const {projectInfra} = useProjectInfra(project,null) const { projectInfra } = useProjectInfra(project, null);
const columns = [ const columns = [
{ key: "buildingName", title: "Building", sortable: true,}, { key: "buildingName", title: "Building", sortable: true,className: "text-start", },
{ key: "plannedWork", title: "Planned Work" }, { key: "plannedWork", title: "Planned Work" },
{ key: "completedWork", title: "Completed Work" }, { key: "completedWork", title: "Completed Work" },
{ key: "percentage", title: "Completion %" }, { key: "percentage", title: "Completion %" },
]; ];
return ( return (
<PmsGrid <PmsGrid
columns={columns} columns={columns}
@ -27,9 +29,7 @@ export default function BuildingTable() {
resizing: true, resizing: true,
selection: true, selection: true,
}} }}
renderExpanded={(building) => ( renderExpanded={(building) => <FloorTable floors={building.floors} />}
<FloorTable floors={building.floors} />
)}
/> />
); );
} }

View File

@ -15,6 +15,8 @@ import { useCollectionContext } from "../../pages/collections/CollectionPage";
import { CollectionTableSkeleton } from "./CollectionSkeleton"; import { CollectionTableSkeleton } from "./CollectionSkeleton";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { PmsGrid } from "../../services/pmsGrid";
import PmGridCollection from "./PmGridCollection";
const CollectionList = ({ fromDate, toDate, isPending, searchString }) => { const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
@ -27,17 +29,17 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const searchDebounce = useDebounce(searchString, 500); const searchDebounce = useDebounce(searchString, 500);
const { data, isLoading, isError, error } = useCollections( // const { data, isLoading, isError, error } = useCollections(
selectedProject, // selectedProject,
searchDebounce, // searchDebounce,
localToUtc(fromDate), // localToUtc(fromDate),
localToUtc(toDate), // localToUtc(toDate),
ITEMS_PER_PAGE, // ITEMS_PER_PAGE,
currentPage, // currentPage,
true, // true,
isPending // isPending
); // );
const { setProcessedPayment, setAddPayment, setViewCollection } = const { setProcessedPayment, setAddPayment, setViewCollection } =
useCollectionContext(); useCollectionContext();
@ -147,149 +149,152 @@ const CollectionList = ({ fromDate, toDate, isPending, searchString }) => {
}, },
]; ];
if (isLoading) return <CollectionTableSkeleton />; // if (isLoading) return <CollectionTableSkeleton />;
if (isError) return <p>{error.message}</p>; // if (isError) return <p>{error.message}</p>;
return ( return (
<div className="card px-sm-4 px-0"> // <div className="card px-sm-4 px-0">
<div // <div
className="card-datatable table-responsive page-min-h" // className="card-datatable table-responsive page-min-h"
id="horizontal-example" // id="horizontal-example"
> // >
<div className="dataTables_wrapper no-footer mx-3 pb-2"> // <div className="dataTables_wrapper no-footer mx-3 pb-2">
<table className="table dataTable text-nowrap"> // <table className="table dataTable text-nowrap">
<thead> // <thead>
<tr className="table_header_border"> // <tr className="table_header_border">
{collectionColumns.map((col) => ( // {collectionColumns.map((col) => (
<th key={col.key} className={col.align}> // <th key={col.key} className={col.align}>
{col.label} // {col.label}
</th> // </th>
))} // ))}
{(isAdmin || // {(isAdmin ||
canAddPayment || // canAddPayment ||
canViewCollection || // canViewCollection ||
canEditCollection || // canEditCollection ||
canCreate) && <th>Action</th>} // canCreate) && <th>Action</th>}
</tr> // </tr>
</thead> // </thead>
<tbody> // <tbody>
{Array.isArray(data?.data) && data.data.length > 0 ? ( // {Array.isArray(data?.data) && data.data.length > 0 ? (
data.data.map((row, i) => ( // data.data.map((row, i) => (
<tr key={i}> // <tr key={i}>
{collectionColumns.map((col) => ( // {collectionColumns.map((col) => (
<td key={col.key} className={col.align}> // <td key={col.key} className={col.align}>
{col.getValue(row)} // {col.getValue(row)}
</td> // </td>
))} // ))}
{(isAdmin || // {(isAdmin ||
canAddPayment || // canAddPayment ||
canViewCollection || // canViewCollection ||
canEditCollection || // canEditCollection ||
canCreate) && ( // canCreate) && (
<td // <td
className="sticky-action-column text-center" // className="sticky-action-column text-center"
style={{ padding: "12px 8px" }} // style={{ padding: "12px 8px" }}
> // >
<div className="dropdown z-2"> // <div className="dropdown z-2">
<button // <button
type="button" // type="button"
className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0" // className="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0"
data-bs-toggle="dropdown" // data-bs-toggle="dropdown"
aria-expanded="false" // aria-expanded="false"
> // >
<i // <i
className="bx bx-dots-vertical-rounded bx-sm text-muted" // className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip" // data-bs-toggle="tooltip"
data-bs-offset="0,8" // data-bs-offset="0,8"
data-bs-placement="top" // data-bs-placement="top"
data-bs-custom-class="tooltip-dark" // data-bs-custom-class="tooltip-dark"
title="More Action" // title="More Action"
></i> // ></i>
</button> // </button>
<ul className="dropdown-menu dropdown-menu-end"> // <ul className="dropdown-menu dropdown-menu-end">
{/* View */} // {/* View */}
<li> // <li>
<a // <a
className="dropdown-item cursor-pointer" // className="dropdown-item cursor-pointer"
onClick={() => setViewCollection(row.id)} // 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>
{/* Only if not completed */} // {/* Only if not completed */}
{!row?.markAsCompleted && ( // {!row?.markAsCompleted && (
<> // <>
{/* Add Payment */} // {/* Add Payment */}
{(isAdmin || canAddPayment) && ( // {(isAdmin || canAddPayment) && (
<li> // <li>
<a // <a
className="dropdown-item cursor-pointer" // className="dropdown-item cursor-pointer"
onClick={() => // onClick={() =>
setAddPayment({ // setAddPayment({
isOpen: true, // isOpen: true,
invoiceId: row.id, // invoiceId: row.id,
}) // })
} // }
> // >
<i className="bx bx-wallet me-2 text-warning"></i> // <i className="bx bx-wallet me-2 text-warning"></i>
<span>Add Payment</span> // <span>Add Payment</span>
</a> // </a>
</li> // </li>
)} // )}
{/* Mark Payment */} // {/* Mark Payment */}
{isAdmin && ( // {isAdmin && (
<li> // <li>
<a // <a
className="dropdown-item cursor-pointer" // className="dropdown-item cursor-pointer"
onClick={() => // onClick={() =>
setProcessedPayment({ // setProcessedPayment({
isOpen: true, // isOpen: true,
invoiceId: row.id, // invoiceId: row.id,
}) // })
} // }
> // >
<i className="bx bx-check-circle me-2 text-success"></i> // <i className="bx bx-check-circle me-2 text-success"></i>
<span>Mark Payment</span> // <span>Mark Payment</span>
</a> // </a>
</li> // </li>
)} // )}
</> // </>
)} // )}
</ul> // </ul>
</div> // </div>
</td> // </td>
)} // )}
</tr> // </tr>
)) // ))
) : ( // ) : (
<tr style={{ height: "200px" }}> // <tr style={{ height: "200px" }}>
<td // <td
colSpan={collectionColumns.length + 1} // colSpan={collectionColumns.length + 1}
className="text-center border-0 align-middle" // className="text-center border-0 align-middle"
> // >
No Collections Found // No Collections Found
</td> // </td>
</tr> // </tr>
)} // )}
</tbody> // </tbody>
</table> // </table>
{data?.data?.length > 0 && ( // {data?.data?.length > 0 && (
<div className="d-flex justify-content-start mt-2"> // <div className="d-flex justify-content-start mt-2">
<Pagination // <Pagination
currentPage={currentPage} // currentPage={currentPage}
totalPages={data?.totalPages} // totalPages={data?.totalPages}
onPageChange={paginate} // onPageChange={paginate}
/> // />
</div> // </div>
)} // )}
</div> // </div>
</div> // </div>
</div> // </div>
<div className="card p-2">
<PmGridCollection selectedProject={selectedProject} fromDate={localToUtc(fromDate)} toDate={localToUtc(toDate)} />
</div>
); );
}; };

View File

@ -0,0 +1,95 @@
import React from "react";
import { PmsGrid } from "../../services/pmsGrid";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { formatFigure } from "../../utils/appUtils";
import { CollectionRepository } from "../../repositories/ColllectionRepository";
const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
const columns = [
{ key: "invoiceNumber", title: "Invoice Number", className: "text-start" },
{ key: "title", title: "Title", sortable: true, className: "text-start" },
{
key: "clientSubmitedDate",
title: "Submission Date",
className: "text-start",
},
{
key: "exceptedPaymentDate",
title: "Expected Payment Date",
className: "text-start",
},
{ key: "totalAmount", title: "Total Amount" },
{ key: "balanceAmount", title: "Balance" },
{ key: "isActive", title: "Status" },
];
// --- SERVER SIDE FETCHER (correct) ---
const fetcher = async ({ page, pageSize, search }) => {
const response = await CollectionRepository.getCollections(
selectedProject,
search || "",
fromDate,
toDate,
pageSize,
page,
true, // isActive
isPending
);
const api = response.data;
return {
rows: api.data.map((item) => ({
id: item.id,
invoiceNumber: item.invoiceNumber,
title: item.title,
clientSubmitedDate: formatUTCToLocalTime(item.clientSubmitedDate),
exceptedPaymentDate: formatUTCToLocalTime(item.exceptedPaymentDate),
totalAmount: formatFigure(item.basicAmount + item.taxAmount, {
type: "currency",
currency: "INR",
}),
balanceAmount: formatFigure(item.balanceAmount, {
type: "currency",
currency: "INR",
}),
isActive: item.isActive ? (
<span className="badge bg-label-primary">
<span className="badge badge-dot bg-primary me-1"></span>
Active
</span>
) : (
<span className="badge bg-label-danger">
<span className="badge badge-dot bg-danger me-1"></span>
In-Active
</span>
),
})),
// MUST use totalRows only
total: api.totalEntities,
};
};
return (
<PmsGrid
columns={columns}
serverMode
fetcher={fetcher}
rowKey="id"
features={{
search: true,
pagination: true,
pinning: true,
resizing: true,
selection: false,
}}
/>
);
};
export default PmGridCollection;

View File

@ -61,6 +61,7 @@ import RecurringExpensePage from "../pages/RecurringExpense/RecurringExpensePage
import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage"; import AdvancePaymentPage from "../pages/AdvancePayment/AdvancePaymentPage";
import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail"; import ServiceProjectDetail from "../pages/ServiceProject/ServiceProjectDetail";
import ManageJob from "../components/ServiceProject/ManageJob"; import ManageJob from "../components/ServiceProject/ManageJob";
import DemoBOQGrid from "../services/pmsGrid/BasicTable";
const router = createBrowserRouter( const router = createBrowserRouter(
[ [
{ {

View File

@ -738,7 +738,7 @@ export default function DemoBOQGrid() {
reorder: true, reorder: true,
columnVisibility: true, columnVisibility: true,
pageSizeSelector: true, pageSizeSelector: true,
// groupByKey: "status", groupByKey: "status",
aggregation: true, aggregation: true,
expand: true, expand: true,
maxHeight: "70vh", maxHeight: "70vh",

View File

@ -170,7 +170,40 @@ export default function PmsGrid({
Export CSV Export CSV
</button> </button>
)} )}
{features.grouping && (
<div className="dropdown">
<button
className="btn btn-sm btn-outline-secondary"
data-bs-toggle="dropdown"
>
Group By
</button>
<div className="dropdown-menu p-2">
{visibleColumns
.filter((c) => c.groupable)
.map((c) => (
<div
key={c.key}
className="dropdown-item"
onClick={() => grid.setGroupBy(c.key)}
style={{ cursor: "pointer" }}
>
{c.title}
</div>
))}
{grid.groupBy && (
<div
className="dropdown-item text-danger"
onClick={() => grid.setGroupBy(null)}
>
Clear Grouping
</div>
)}
</div>
</div>
)}
</div> </div>
</div> </div>
<div className="col-4 "> <div className="col-4 ">

View File

@ -1,4 +1,3 @@
import { useState, useMemo, useCallback, useEffect } from "react"; import { useState, useMemo, useCallback, useEffect } from "react";
/* /*
@ -7,7 +6,14 @@ import { useState, useMemo, useCallback, useEffect } from "react";
- rowKey - rowKey
- initialPageSize - initialPageSize
*/ */
export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id", initialPageSize = 25, columns = [] }) { export function useGridCore({
data,
serverMode = false,
fetcher,
rowKey = "id",
initialPageSize = 5,
columns = [],
}) {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(initialPageSize); const [pageSize, setPageSize] = useState(initialPageSize);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@ -26,9 +32,11 @@ export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id",
if (!data) return []; if (!data) return [];
const q = (search || "").toLowerCase(); const q = (search || "").toLowerCase();
const filtered = q const filtered = q
? data.filter(r => ? data.filter((r) =>
Object.values(r).some(v => Object.values(r).some((v) =>
String(v ?? "").toLowerCase().includes(q) String(v ?? "")
.toLowerCase()
.includes(q)
) )
) )
: data; : data;
@ -41,7 +49,8 @@ export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id",
if (A == null && B == null) return 0; if (A == null && B == null) return 0;
if (A == null) return -1 * dir; if (A == null) return -1 * dir;
if (B == null) return 1 * dir; if (B == null) return 1 * dir;
if (typeof A === "number" && typeof B === "number") return (A - B) * dir; if (typeof A === "number" && typeof B === "number")
return (A - B) * dir;
return String(A).localeCompare(String(B)) * dir; return String(A).localeCompare(String(B)) * dir;
}); });
} }
@ -69,8 +78,8 @@ export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id",
}, [serverMode, fetchServer]); }, [serverMode, fetchServer]);
// selection // selection
const toggleSelect = useCallback(id => { const toggleSelect = useCallback((id) => {
setSelected(prev => { setSelected((prev) => {
const s = new Set(prev); const s = new Set(prev);
s.has(id) ? s.delete(id) : s.add(id); s.has(id) ? s.delete(id) : s.add(id);
return s; return s;
@ -78,10 +87,10 @@ export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id",
}, []); }, []);
const selectAllOnPage = useCallback( const selectAllOnPage = useCallback(
rows => { (rows) => {
setSelected(prev => { setSelected((prev) => {
const s = new Set(prev); const s = new Set(prev);
rows.forEach(r => s.add(r[rowKey])); rows.forEach((r) => s.add(r[rowKey]));
return s; return s;
}); });
}, },
@ -89,26 +98,26 @@ export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id",
); );
const deselectAllOnPage = useCallback( const deselectAllOnPage = useCallback(
rows => { (rows) => {
setSelected(prev => { setSelected((prev) => {
const s = new Set(prev); const s = new Set(prev);
rows.forEach(r => s.delete(r[rowKey])); rows.forEach((r) => s.delete(r[rowKey]));
return s; return s;
}); });
}, },
[rowKey] [rowKey]
); );
const toggleExpand = useCallback(id => { const toggleExpand = useCallback((id) => {
setExpanded(prev => { setExpanded((prev) => {
const s = new Set(prev); const s = new Set(prev);
s.has(id) ? s.delete(id) : s.add(id); s.has(id) ? s.delete(id) : s.add(id);
return s; return s;
}); });
}, []); }, []);
const changeSort = useCallback(key => { const changeSort = useCallback((key) => {
setSortBy(prev => setSortBy((prev) =>
prev.key === key prev.key === key
? { key, dir: prev.dir === "asc" ? "desc" : "asc" } ? { key, dir: prev.dir === "asc" ? "desc" : "asc" }
: { key, dir: "asc" } : { key, dir: "asc" }
@ -117,7 +126,7 @@ export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id",
}, []); }, []);
const visibleColumns = useMemo( const visibleColumns = useMemo(
() => colState.filter(c => c.visible).sort((a, b) => a.order - b.order), () => colState.filter((c) => c.visible).sort((a, b) => a.order - b.order),
[colState] [colState]
); );
@ -126,7 +135,9 @@ export function useGridCore({ data, serverMode = false, fetcher, rowKey = "id",
// update columns externally (reorder/pin/resize) // update columns externally (reorder/pin/resize)
const updateColumn = useCallback((key, patch) => { const updateColumn = useCallback((key, patch) => {
setColState(prev => prev.map(c => (c.key === key ? { ...c, ...patch } : c))); setColState((prev) =>
prev.map((c) => (c.key === key ? { ...c, ...patch } : c))
);
}, []); }, []);
return { return {