added hook source for data

This commit is contained in:
pramod.mahajan 2025-12-27 00:08:12 +05:30
parent 468797f823
commit 3ab70dc86f
3 changed files with 238 additions and 219 deletions

View File

@ -1,8 +1,9 @@
import React from "react"; import React, { useMemo,useEffect } from "react";
import { PmsGrid } from "../../services/pmsGrid"; import { PmsGrid, useGridCore } from "../../services/pmsGrid";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { formatFigure } from "../../utils/appUtils"; import { formatFigure } from "../../utils/appUtils";
import { CollectionRepository } from "../../repositories/ColllectionRepository"; import { CollectionRepository } from "../../repositories/ColllectionRepository";
import { useQuery } from "@tanstack/react-query";
const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => { const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
const columns = [ const columns = [
@ -49,23 +50,98 @@ const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
{ key: "isActive", title: "Status", enableAdvancedFilter: false }, { key: "isActive", title: "Status", enableAdvancedFilter: false },
]; ];
const fetcher = async ({ page, pageSize, search, filter }) => { // const fetcher = async ({ page, pageSize, search, filter }) => {
const response = await CollectionRepository.getCollections( // const response = await CollectionRepository.getCollections(
selectedProject, // selectedProject,
search || "", // search || "",
fromDate, // fromDate,
toDate, // toDate,
pageSize, // pageSize,
page, // page,
true, // isActive // true, // isActive
isPending, // isPending,
filter // filter
); // );
const api = response.data; // const api = response.data;
return { // return {
rows: api.data.map((item) => ({ // 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>
// ),
// })),
// total: api.totalEntities,
// };
// };
function useGridCollectionQuery({
projectId,
fromDate,
toDate,
isPending,
page,
pageSize,
search,
sortBy,
filter,
}) {
return useQuery({
queryKey: [
"collections",
projectId,
page,
pageSize,
search,
sortBy,
filter,
fromDate,
toDate,
isPending,
],
queryFn: async () => {
const res = await CollectionRepository.getCollections(
projectId,
search || "",
fromDate,
toDate,
pageSize,
page,
true,
isPending,
filter
);
const api = res.data;
return {
rows: api.data.map((item) => ({
id: item.id, id: item.id,
invoiceNumber: item.invoiceNumber, invoiceNumber: item.invoiceNumber,
title: item.title, title: item.title,
@ -97,36 +173,106 @@ const PmGridCollection = ({ selectedProject, fromDate, toDate, isPending }) => {
})), })),
total: api.totalEntities, total: api.totalEntities,
}; };
}; },
keepPreviousData: true,
});
}
const grid = useGridCore({
columns,
serverMode: true,
initialPageSize: 25,
});
const filterPayload = useMemo(() => {
return JSON.stringify({
sortFilters: grid.sortBy.key
? [
{
column: grid.sortBy.key,
sortDescending: grid.sortBy.dir === "desc",
},
]
: [],
advanceFilters: Object.values(grid.advanceFilters),
groupByColumn: grid.groupBy,
});
}, [grid.sortBy, grid.advanceFilters, grid.groupBy]);
const { data, isLoading, error } = useGridCollectionQuery({
projectId: selectedProject,
page: grid.page,
pageSize: grid.pageSize,
search: grid.debouncedSearch,
sortBy: grid.sortBy,
filter: filterPayload,
fromDate,
toDate,
isPending,
});
useEffect(() => {
if (data) {
grid.setRows(data.rows);
grid.setTotalRows(data.total);
}
}, [data]);
return ( return (
// <PmsGrid
// columns={columns}
// serverMode
// fetcher={fetcher}
// rowKey="id"
// features={{
// search: true,
// pagination: true,
// pinning: true,
// resizing: true,
// selection: false,
// reorder: true,
// columnVisibility: true,
// pageSizeSelector: true,
// grouping: true,
// groupByKey: "clientSubmitedDate",
// aggregation: true,
// IsNumbering: true,
// actions: [
// {
// label: "Edit",
// icon: "bx-edit ",
// onClick: (row) => console.log("Edit", row, col),
// },
// {
// label: "Delete",
// icon: "bx-trash text-danger",
// onClick: (row) => console.log("Delete", row),
// },
// ],
// }}
// />
<PmsGrid <PmsGrid
columns={columns} columns={columns}
serverMode grid={grid}
fetcher={fetcher} loading={isLoading}
rowKey="id"
features={{ features={{
search: true, search: true,
pagination: true, pagination: true,
pinning: true, pinning: true,
resizing: true, resizing: true,
selection: false,
reorder: true, reorder: true,
grouping: true,
columnVisibility: true, columnVisibility: true,
pageSizeSelector: true, pageSizeSelector: true,
grouping: true,
groupByKey: "clientSubmitedDate",
aggregation: true,
IsNumbering: true, IsNumbering: true,
actions: [ actions: [
{ {
label: "Edit", label: "Edit",
icon: "bx-edit ", icon: "bx-edit",
onClick: (row) => console.log("Edit", row, col), onClick: (row) => console.log("Edit", row),
}, },
{ {
label: "Delete", label: "Delete",

View File

@ -15,26 +15,16 @@ import FilterApplied from "./FilterApplied";
*/ */
export default function PmsGrid({ export default function PmsGrid({
columns = [], columns = [],
data, grid,
serverMode = false, loading = false,
fetcher, features = {},
rowKey = "id", rowKey = "id",
isDropdown = false, isDropdown = false,
features = {},
renderExpanded, renderExpanded,
}) { }) {
const [isFullScreen, setFullScreen] = useState(false); const [isFullScreen, setFullScreen] = useState(false);
const [activeCell, setActiveCell] = useState(null); const [activeCell, setActiveCell] = useState(null);
const grid = useGridCore({
data,
serverMode,
fetcher,
rowKey,
initialPageSize: features.pageSize || 25,
columns,
});
const wrapperRef = useRef(); const wrapperRef = useRef();
const { const {
rows, rows,
page, page,
@ -55,7 +45,6 @@ export default function PmsGrid({
visibleColumns, visibleColumns,
colState, colState,
updateColumn, updateColumn,
loading,
error, error,
totalRows, totalRows,
expanded, expanded,
@ -67,7 +56,6 @@ export default function PmsGrid({
advanceFilters, advanceFilters,
setColumnAdvanceFilter, setColumnAdvanceFilter,
} = grid; } = grid;
// --- Pin / Unpin helpers --- // --- Pin / Unpin helpers ---
const pinColumn = (key, side) => { const pinColumn = (key, side) => {
const col = colState.find((c) => c.key === key); const col = colState.find((c) => c.key === key);
@ -539,7 +527,12 @@ export default function PmsGrid({
col.pinned col.pinned
? "pinned-left px-3 bg-white pms-grid td pinned" ? "pinned-left px-3 bg-white pms-grid td pinned"
: "px-3" : "px-3"
} ${activeCell?.rowId == row.id && activeCell.columnKey === col.key ? "grid-cell-active" : ""} cursor-pointer`} } ${
activeCell?.rowId == row.id &&
activeCell.columnKey === col.key
? "grid-cell-active"
: ""
} cursor-pointer`}
> >
{col.render ? col.render(row) : row[col.key] ?? ""} {col.render ? col.render(row) : row[col.key] ?? ""}
</td> </td>

View File

@ -13,11 +13,9 @@ import {
- rowKey - rowKey
- initialPageSize - initialPageSize
*/ */
export function useGridCore({ export function useGridCore({
data, data = [],
serverMode = false, serverMode = false,
fetcher,
rowKey = "id", rowKey = "id",
initialPageSize = 20, initialPageSize = 20,
columns = [], columns = [],
@ -28,14 +26,15 @@ export function useGridCore({
const [debouncedSearch, setDebouncedSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState("");
const [groupBy, setGroupBy] = useState(null); const [groupBy, setGroupBy] = useState(null);
// FIX: store ADVANCED FILTERS PER COLUMN
// { amount: { columnKey, operation, value } }
const [advanceFilters, setAdvanceFilters] = useState({}); const [advanceFilters, setAdvanceFilters] = useState({});
const [sortBy, setSortBy] = useState({ key: null, dir: "asc" }); const [sortBy, setSortBy] = useState({ key: null, dir: "asc" });
const [selected, setSelected] = useState(new Set()); const [selected, setSelected] = useState(new Set());
const [expanded, setExpanded] = useState(new Set()); const [expanded, setExpanded] = useState(new Set());
const [rows, setRows] = useState([]);
const [totalRows, setTotalRows] = useState(0);
const [colState, setColState] = useState(() => const [colState, setColState] = useState(() =>
columns.map((c, i) => ({ columns.map((c, i) => ({
...c, ...c,
@ -44,24 +43,18 @@ export function useGridCore({
})) }))
); );
const [totalRows, setTotalRows] = useState(data ? data.length : 0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [serverRows, setServerRows] = useState([]);
/* ---------------- SEARCH (DEBOUNCE) ---------------- */ /* ---------------- SEARCH (DEBOUNCE) ---------------- */
useEffect(() => { useEffect(() => {
const handler = setTimeout(() => { const t = setTimeout(() => {
setDebouncedSearch(search); setDebouncedSearch(search);
setPage(1); setPage(1);
}, 500); }, 400);
return () => clearTimeout(t);
return () => clearTimeout(handler);
}, [search]); }, [search]);
/* ---------------- CLIENT MODE ---------------- */ /* ---------------- CLIENT MODE ---------------- */
const clientFiltered = useMemo(() => { const clientRows = useMemo(() => {
if (!data) return []; if (serverMode) return [];
let filtered = data; let filtered = data;
@ -69,155 +62,51 @@ export function useGridCore({
const q = search.toLowerCase(); const q = search.toLowerCase();
filtered = filtered.filter((r) => filtered = filtered.filter((r) =>
Object.values(r).some((v) => Object.values(r).some((v) =>
String(v ?? "") String(v ?? "").toLowerCase().includes(q)
.toLowerCase()
.includes(q)
) )
); );
} }
if (sortBy.key) { if (sortBy.key) {
const dir = sortBy.dir === "asc" ? 1 : -1; const dir = sortBy.dir === "asc" ? 1 : -1;
filtered = [...filtered].sort((a, b) => { filtered = [...filtered].sort((a, b) =>
const A = a[sortBy.key]; String(a[sortBy.key] ?? "").localeCompare(
const B = b[sortBy.key]; String(b[sortBy.key] ?? "")
if (A == null && B == null) return 0; ) * dir
if (A == null) return -1 * dir; );
if (B == null) return 1 * dir;
if (typeof A === "number" && typeof B === "number")
return (A - B) * dir;
return String(A).localeCompare(String(B)) * dir;
});
} }
setTotalRows(filtered.length); setTotalRows(filtered.length);
const start = (page - 1) * pageSize; const start = (page - 1) * pageSize;
return filtered.slice(start, start + pageSize); return filtered.slice(start, start + pageSize);
}, [data, search, sortBy, page, pageSize]); }, [data, search, sortBy, page, pageSize, serverMode]);
/* ---------------- SERVER MODE ---------------- */
const fetchServer = useCallback(async () => {
if (!serverMode || typeof fetcher !== "function") return;
const sortFilters = sortBy.key
? [
{
column: sortBy.key,
sortDescending: sortBy.dir === "desc",
},
]
: [];
// convert map → array
const advanceFilterArray = Object.values(advanceFilters);
const filterPayload = JSON.stringify({
sortFilters,
groupByColumn: groupBy || null,
advanceFilters: advanceFilterArray,
});
setLoading(true);
try {
const resp = await fetcher({
page,
pageSize,
sortBy,
search: debouncedSearch,
filter: filterPayload,
});
setServerRows(resp?.rows || []);
setTotalRows(resp?.total ?? resp?.rows?.length ?? 0);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [
serverMode,
fetcher,
page,
pageSize,
sortBy,
debouncedSearch,
groupBy,
advanceFilters,
]);
useEffect(() => { useEffect(() => {
if (serverMode) fetchServer(); if (!serverMode) setRows(clientRows);
}, [serverMode, fetchServer]); }, [clientRows, serverMode]);
/* ---------------- ADVANCED FILTER API ---------------- */ /* ---------------- ADVANCED FILTER ---------------- */
const setColumnAdvanceFilter = useCallback((column, filter) => { const setColumnAdvanceFilter = useCallback((column, filter) => {
setAdvanceFilters((prev) => { setAdvanceFilters((prev) => {
if (!filter) { if (!filter) {
const copy = { ...prev }; const c = { ...prev };
delete copy[column]; delete c[column];
return copy; return c;
} }
return { ...prev, [column]: { column, ...filter } };
return {
...prev,
[column]: {
column,
...filter,
},
};
}); });
setPage(1); setPage(1);
}, []); }, []);
/* ---------------- SELECTION ---------------- */
const toggleSelect = useCallback((id) => {
setSelected((prev) => {
const s = new Set(prev);
s.has(id) ? s.delete(id) : s.add(id);
return s;
});
}, []);
const selectAllOnPage = useCallback(
(rows) => {
setSelected((prev) => {
const s = new Set(prev);
rows.forEach((r) => s.add(r[rowKey]));
return s;
});
},
[rowKey]
);
const deselectAllOnPage = useCallback(
(rows) => {
setSelected((prev) => {
const s = new Set(prev);
rows.forEach((r) => s.delete(r[rowKey]));
return s;
});
},
[rowKey]
);
/* ---------------- EXPAND ---------------- */
const toggleExpand = useCallback((id) => {
setExpanded((prev) => {
const s = new Set(prev);
s.has(id) ? s.delete(id) : s.add(id);
return s;
});
}, []);
/* ---------------- SORT ---------------- */ /* ---------------- SORT ---------------- */
const changeSort = useCallback((key) => { const changeSort = useCallback((key) => {
setSortBy((prev) => { setSortBy((p) =>
if (prev.key !== key) return { key, dir: "asc" }; p.key !== key
if (prev.dir === "asc") return { key, dir: "desc" }; ? { key, dir: "asc" }
return { key: null, dir: "asc" }; : p.dir === "asc"
}); ? { key, dir: "desc" }
: { key: null, dir: "asc" }
);
setPage(1); setPage(1);
}, []); }, []);
@ -233,64 +122,55 @@ export function useGridCore({
); );
}, []); }, []);
/* ---------------- FINAL ---------------- */
const rows = serverMode ? serverRows : clientFiltered;
const totalPages = Math.max(1, Math.ceil(totalRows / pageSize));
return { return {
// paging /* paging */
page, page,
setPage, setPage,
pageSize, pageSize,
setPageSize, setPageSize,
totalRows, totalRows,
totalPages, totalPages: Math.max(1, Math.ceil(totalRows / pageSize)),
// loading & error /* search */
loading,
error,
// search
search, search,
setSearch, setSearch,
debouncedSearch,
// sorting /* sorting */
sortBy, sortBy,
changeSort, changeSort,
// selection /* grouping */
groupBy,
setGroupBy,
/* advanced filter */
advanceFilters,
setColumnAdvanceFilter,
/* selection */
selected, selected,
setSelected, setSelected,
toggleSelect,
selectAllOnPage,
deselectAllOnPage,
// expand
expanded, expanded,
toggleExpand, setExpanded,
// columns /* columns */
colState, colState,
visibleColumns, visibleColumns,
updateColumn, updateColumn,
setColState, setColState,
// grouping /* data */
groupBy,
setGroupBy,
// advanced filter
advanceFilters,
setColumnAdvanceFilter,
// data
rows, rows,
setRows,
setTotalRows,
// mode
serverMode, serverMode,
rowKey,
}; };
} }
export function useDropdownPosition(btnRef, menuRef, isOpen, level = 0) { export function useDropdownPosition(btnRef, menuRef, isOpen, level = 0) {
const [style, setStyle] = useState({}); const [style, setStyle] = useState({});