diff --git a/src/services/pmsGrid/AdvanceFilter.jsx b/src/services/pmsGrid/AdvanceFilter.jsx new file mode 100644 index 00000000..adb05fa0 --- /dev/null +++ b/src/services/pmsGrid/AdvanceFilter.jsx @@ -0,0 +1,119 @@ +import { useState, useEffect } from "react"; + +const OPERATORS = { + number: [ + { key: "eq", label: "Equals" }, + { key: "neq", label: "Not Equal" }, + { key: "gt", label: "Greater Than" }, + { key: "gte", label: "Greater or Equal" }, + { key: "lt", label: "Less Than" }, + { key: "lte", label: "Less or Equal" }, + { key: "between", label: "Between" }, + ], + text: [ + { key: "contains", label: "Contains" }, + { key: "starts", label: "Starts With" }, + { key: "ends", label: "Ends With" }, + { key: "eq", label: "Equals" }, + ], + date: [ + { key: "before", label: "Before" }, + { key: "after", label: "After" }, + { key: "between", label: "Between" }, + ], +}; + +export function AdvanceFilter({ + type = "number", + value, + onApply, + onClear, +}) { + const [operation, setOperation] = useState(""); + const [v1, setV1] = useState(""); + const [v2, setV2] = useState(""); + + useEffect(() => { + if (!value) { + setOperation(""); + setV1(""); + setV2(""); + return; + } + + setOperation(value.operation); + setV1(value.value ?? value.from ?? ""); + setV2(value.to ?? ""); + }, [value]); + + const apply = () => { + if (!operation) return; + + onApply( + operation === "between" + ? { operation, from: v1, to: v2 } + : { operation, value: v1 } + ); + }; + + return ( +
e.stopPropagation()}> +
+ + +
+ + {operation && ( +
+ + + {operation !== "between" ? ( + setV1(e.target.value)} + /> + ) : ( +
+ setV1(e.target.value)} + /> + setV2(e.target.value)} + /> +
+ )} +
+ )} + +
+ + +
+
+ ); +} diff --git a/src/services/pmsGrid/PmsGrid.jsx b/src/services/pmsGrid/PmsGrid.jsx index 435abee2..5b2a7332 100644 --- a/src/services/pmsGrid/PmsGrid.jsx +++ b/src/services/pmsGrid/PmsGrid.jsx @@ -23,7 +23,7 @@ export default function PmsGrid({ features = {}, renderExpanded, }) { - const [isFullScreen,setFullScreen] = useState(false) + const [isFullScreen, setFullScreen] = useState(false); const grid = useGridCore({ data, serverMode, @@ -59,8 +59,11 @@ export default function PmsGrid({ expanded, toggleExpand, setColState, - onAdvanceFilters, - setAdanceFilter, + // onAdvanceFilters, + // setAdanceFilter, + + advanceFilters, + setColumnAdvanceFilter, } = grid; // --- Pin / Unpin helpers --- @@ -149,7 +152,11 @@ export default function PmsGrid({ const currentRows = rows; return ( -
+
@@ -218,14 +225,22 @@ export default function PmsGrid({ } /> )} - setFullScreen(!isFullScreen)}> + setFullScreen(!isFullScreen)} + >
-
- setGroupBy(null)} advance={onAdvanceFilters} /> + setGroupBy(null)} + advance={advanceFilters} + />
{/* Table-Start*/}
pinColumn(col.key, "left")} onPinRight={() => pinColumn(col.key, "right")} onUnpin={() => unpinColumn(col.key)} - onAdvancedFilter={setAdanceFilter} + advanceFilters={advanceFilters} + setColumnAdvanceFilter={setColumnAdvanceFilter} /> )} {features.resizing && ( @@ -411,7 +427,8 @@ export default function PmsGrid({ ))} - )} {totalRows} rows + )}{" "} + {totalRows} rows
-
-
- ); -} - -// -------- HEADER OPTION --------------- const PmsHeaderOption = ({ column, pinned, onPinLeft, onPinRight, onUnpin, - onAdvancedFilter, -}) => { - const { key: columnKey, enableAdvancedFilter } = column; - const rootRef = useRef(null); + // from useGridCore + advanceFilters, + setColumnAdvanceFilter, +}) => { + const { + key: columnKey, + enableAdvancedFilter, + filterType = "number", + } = column; + + const rootRef = useRef(null); const btnRef = useRef(null); const menuRef = useRef(null); - const filterBtnRef = useRef(null); const filterMenuRef = useRef(null); - const pinBtnRef = useRef(null); const pinMenuRef = useRef(null); const [open, setOpen] = useState(false); const [openMenu, setOpenMenu] = useState(null); - // Main dropdown position const mainStyle = useDropdownPosition(btnRef, menuRef, open, 0); - - // Submenus const filterStyle = useDropdownPosition( filterBtnRef, filterMenuRef, openMenu === "filter", 1 ); - const pinStyle = useDropdownPosition( pinBtnRef, pinMenuRef, @@ -159,54 +46,47 @@ const PmsHeaderOption = ({ const toggleMenu = (name, e) => { e.stopPropagation(); - setOpenMenu((prev) => (prev === name ? null : name)); + setOpenMenu((p) => (p === name ? null : name)); }; - // ---------------------------- - // CLOSE WHEN CLICKING OUTSIDE - // ---------------------------- useEffect(() => { - const handleOutside = (e) => { - if (!rootRef.current) return; - - if (!rootRef.current.contains(e.target)) { + const close = (e) => { + if (!rootRef.current?.contains(e.target)) { setOpen(false); setOpenMenu(null); } }; - - if (open) document.addEventListener("mousedown", handleOutside); - return () => document.removeEventListener("mousedown", handleOutside); + if (open) document.addEventListener("mousedown", close); + return () => document.removeEventListener("mousedown", close); }, [open]); - + // {} + console.log(advanceFilters); + console.log(columnKey); return ( -
- {/* BUTTON */} +
- {/* MAIN MENU */} {open && (
    {/* ADVANCED FILTER */} {enableAdvancedFilter && ( -
  • +
  • )} - {/* PIN COLUMN */} -
  • + {/* PIN */} +
  • -
  • - -
  • - -
  • - -
  • - -
  • + + +
)} diff --git a/src/services/pmsGrid/useGridCore.js b/src/services/pmsGrid/useGridCore.js index 7b673f6a..4038f7cd 100644 --- a/src/services/pmsGrid/useGridCore.js +++ b/src/services/pmsGrid/useGridCore.js @@ -6,6 +6,8 @@ import { useState, useMemo, useCallback, useEffect,useLayoutEffect } from "react - rowKey - initialPageSize */ + + export function useGridCore({ data, serverMode = false, @@ -19,47 +21,57 @@ export function useGridCore({ const [search, setSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState(""); const [groupBy, setGroupBy] = useState(null); - const [serverGroupRows, setServerGroupRows] = useState(null); - const [onAdvanceFilter, setAdanceFilter] = useState(null); + + // FIX: store ADVANCED FILTERS PER COLUMN + // { amount: { columnKey, operation, value } } + const [advanceFilters, setAdvanceFilters] = useState({}); const [sortBy, setSortBy] = useState({ key: null, dir: "asc" }); const [selected, setSelected] = useState(new Set()); const [expanded, setExpanded] = useState(new Set()); + const [colState, setColState] = useState(() => - columns.map((c, i) => ({ ...c, visible: c.visible !== false, order: i })) + columns.map((c, i) => ({ + ...c, + visible: c.visible !== false, + order: i, + })) ); + const [totalRows, setTotalRows] = useState(data ? data.length : 0); const [loading, setLoading] = useState(false); const [serverRows, setServerRows] = useState([]); + /* ---------------- SEARCH (DEBOUNCE) ---------------- */ useEffect(() => { const handler = setTimeout(() => { setDebouncedSearch(search); - setPage(1); // Important — when search changes, go back to page 1 + setPage(1); }, 500); return () => clearTimeout(handler); }, [search]); - // client-side derived rows + /* ---------------- CLIENT MODE ---------------- */ const clientFiltered = useMemo(() => { if (!data) return []; - const q = (search || "").toLowerCase(); - const filtered = q - ? data.filter((r) => - Object.values(r).some((v) => - String(v ?? "") - .toLowerCase() - .includes(q) - ) + + let filtered = data; + + if (search) { + const q = search.toLowerCase(); + filtered = filtered.filter((r) => + Object.values(r).some((v) => + String(v ?? "").toLowerCase().includes(q) ) - : data; - // sort if needed + ); + } + if (sortBy.key) { const dir = sortBy.dir === "asc" ? 1 : -1; - filtered.sort((a, b) => { - const A = a[sortBy.key], - B = b[sortBy.key]; + filtered = [...filtered].sort((a, b) => { + const A = a[sortBy.key]; + const B = b[sortBy.key]; if (A == null && B == null) return 0; if (A == null) return -1 * dir; if (B == null) return 1 * dir; @@ -68,14 +80,17 @@ export function useGridCore({ return String(A).localeCompare(String(B)) * dir; }); } + setTotalRows(filtered.length); + const start = (page - 1) * pageSize; return filtered.slice(start, start + pageSize); }, [data, search, sortBy, page, pageSize]); - // server-side fetch + /* ---------------- SERVER MODE ---------------- */ const fetchServer = useCallback(async () => { - // sorting column wise + if (!serverMode || typeof fetcher !== "function") return; + const sortFilters = sortBy.key ? [ { @@ -85,22 +100,15 @@ export function useGridCore({ ] : []; - // ----------------------------- - // 3. ADVANCED FILTERS (NUMERIC FILTER POPOVER) - // The grid will pass "filter" already shaped like: - // filter = { totalAmount: { type: "gt", value: 200 } } - // ----------------------------- - let advanceFilters = []; - if (onAdvanceFilter) { - advanceFilters.push(onAdvanceFilter); - } + // convert map → array + const advanceFilterArray = Object.values(advanceFilters); + const filterPayload = JSON.stringify({ sortFilters, groupByColumn: groupBy || null, - advanceFilters, + advanceFilters: advanceFilterArray, }); - if (!serverMode || typeof fetcher !== "function") return; setLoading(true); try { const resp = await fetcher({ @@ -110,9 +118,9 @@ export function useGridCore({ search: debouncedSearch, filter: filterPayload, }); - // expected: { rows: [], total } - setServerRows(resp.rows || []); - setTotalRows(resp.total || (resp.rows ? resp.rows.length : 0)); + + setServerRows(resp?.rows || []); + setTotalRows(resp?.total ?? resp?.rows?.length ?? 0); } finally { setLoading(false); } @@ -124,14 +132,35 @@ export function useGridCore({ sortBy, debouncedSearch, groupBy, - onAdvanceFilter, + advanceFilters, ]); useEffect(() => { if (serverMode) fetchServer(); }, [serverMode, fetchServer]); - // selection + /* ---------------- ADVANCED FILTER API ---------------- */ + const setColumnAdvanceFilter = useCallback((columnKey, filter) => { + setAdvanceFilters((prev) => { + if (!filter) { + const copy = { ...prev }; + delete copy[columnKey]; + return copy; + } + + return { + ...prev, + [columnKey]: { + columnKey, + ...filter, + }, + }; + }); + + setPage(1); + }, []); + + /* ---------------- SELECTION ---------------- */ const toggleSelect = useCallback((id) => { setSelected((prev) => { const s = new Set(prev); @@ -162,6 +191,7 @@ export function useGridCore({ [rowKey] ); + /* ---------------- EXPAND ---------------- */ const toggleExpand = useCallback((id) => { setExpanded((prev) => { const s = new Set(prev); @@ -170,72 +200,81 @@ export function useGridCore({ }); }, []); + /* ---------------- SORT ---------------- */ const changeSort = useCallback((key) => { setSortBy((prev) => { - // first click = asc - if (prev.key !== key) { - return { key, dir: "asc" }; - } - - // second click = desc - if (prev.dir === "asc") { - return { key, dir: "desc" }; - } - - // third click = remove sort + if (prev.key !== key) return { key, dir: "asc" }; + if (prev.dir === "asc") return { key, dir: "desc" }; return { key: null, dir: "asc" }; }); - setPage(1); }, []); + /* ---------------- COLUMNS ---------------- */ const visibleColumns = useMemo( () => colState.filter((c) => c.visible).sort((a, b) => a.order - b.order), [colState] ); - const rows = serverMode ? serverRows : clientFiltered; - const totalPages = Math.max(1, Math.ceil((totalRows || 0) / pageSize)); - - // update columns externally (reorder/pin/resize) const updateColumn = useCallback((key, patch) => { setColState((prev) => prev.map((c) => (c.key === key ? { ...c, ...patch } : c)) ); }, []); + /* ---------------- FINAL ---------------- */ + const rows = serverMode ? serverRows : clientFiltered; + const totalPages = Math.max(1, Math.ceil(totalRows / pageSize)); + return { - // state + // paging page, setPage, pageSize, setPageSize, totalRows, totalPages, + + // loading loading, + + // search search, setSearch, + + // sorting sortBy, changeSort, + + // selection selected, + setSelected, toggleSelect, selectAllOnPage, deselectAllOnPage, - setSelected, + + // expand expanded, toggleExpand, + + // columns colState, visibleColumns, updateColumn, setColState, + + // grouping groupBy, setGroupBy, - onAdvanceFilter, - setAdanceFilter, + + // advanced filter + advanceFilters, + setColumnAdvanceFilter, + // data rows, - // mode helpers + // mode serverMode, }; } @@ -247,7 +286,6 @@ export function useGridCore({ - export function useDropdownPosition(btnRef, menuRef, isOpen, level = 0) { const [style, setStyle] = useState({}); diff --git a/src/services/pmsGrid/utils.js b/src/services/pmsGrid/utils.js index fb683008..18dbf460 100644 --- a/src/services/pmsGrid/utils.js +++ b/src/services/pmsGrid/utils.js @@ -40,4 +40,3 @@ function autoPositionInsideContainer(triggerEl, dropdownEl, containerEl) { dropdownEl.classList.remove("open-left"); } } -