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

View File

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

View File

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

View File

@ -170,7 +170,40 @@ export default function PmsGrid({
Export CSV
</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 className="col-4 ">

View File

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