import React, { useRef, useState } from "react"; import { useGridCore } from "./useGridCore"; import { exportToCSV } from "./utils"; import "./pms-grid.css"; import PmsHeaderOption from "./PmsHeaderOption"; import FilterApplied from "./FilterApplied"; /* Props: - columns: [{ key, title, width, pinned: 'left'|'right'|null, sortable, render, aggregate }] - data OR serverMode + fetcher - rowKey - features: { selection, search, export, pagination, pinning, resizing, reorder, grouping, aggregation, expand } - renderExpanded */ export default function PmsGrid({ columns = [], grid, loading = false, features = {}, rowKey = "id", isDropdown = false, renderExpanded, }) { const [isFullScreen, setFullScreen] = useState(false); const [activeCell, setActiveCell] = useState(null); const wrapperRef = useRef(); const { rows, page, totalPages, pageSize, setPage, setPageSize, setGroupBy, groupBy, search, setSearch, selected, toggleSelect, selectAllOnPage, deselectAllOnPage, changeSort, sortBy, visibleColumns, colState, updateColumn, error, totalRows, expanded, toggleExpand, setColState, // onAdvanceFilters, // setAdanceFilter, advanceFilters, setColumnAdvanceFilter, } = grid; // --- Pin / Unpin helpers --- const pinColumn = (key, side) => { const col = colState.find((c) => c.key === key); if (!col) return; // if already pinned to that side → unpin const newPinned = col.pinned === side || side === "none" ? null : side; updateColumn(key, { pinned: newPinned }); }; const unpinColumn = (key) => { updateColumn(key, { pinned: null }); }; // resizing via mouse down on handle const onResizeMouseDown = (e, key) => { e.preventDefault(); const startX = e.clientX; const col = colState.find((c) => c.key === key); const startWidth = col.width || 120; const onMove = (ev) => { const diff = ev.clientX - startX; const newWidth = Math.max(60, startWidth + diff); updateColumn(key, { width: newWidth }); }; const onUp = () => { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); }; document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); }; // reorder columns (drag/drop) const dragState = useRef({ from: null }); const onDragStart = (e, key) => { dragState.current.from = key; e.dataTransfer.effectAllowed = "move"; }; const onDrop = (e, toKey) => { e.preventDefault(); const fromKey = dragState.current.from; if (!fromKey || fromKey === toKey) return; const from = colState.find((c) => c.key === fromKey); const to = colState.find((c) => c.key === toKey); if (!from || !to) return; // swap orders setColState((prev) => { const next = prev.map((p) => ({ ...p })); const f = next.find((p) => p.key === fromKey); const t = next.find((p) => p.key === toKey); const tmp = f.order; f.order = t.order; t.order = tmp; return next.sort((a, b) => a.order - b.order); }); }; // group & aggregate (simple client-side grouping by column key) // const groupBy = features.groupByKey || null; const groupedRows = React.useMemo(() => { if (!groupBy) return null; const map = {}; rows.forEach((r) => { const g = String(r[groupBy] ?? "Unknown"); if (!map[g]) map[g] = []; map[g].push(r); }); const groups = Object.keys(map).map((k) => ({ key: k, items: map[k] })); // apply aggregation const aggregates = groups.map((g) => { const ag = {}; visibleColumns.forEach((col) => { if (col.aggregate) { ag[col.key] = col.aggregate(g.items.map((it) => it[col.key])); } }); return { ...g, aggregates: ag }; }); return aggregates; }, [rows, groupBy, visibleColumns]); const currentRows = rows; return (
{features.search && ( setSearch(e.target.value)} /> )}
{features.export && ( )} {features.grouping && (
{visibleColumns .filter((c) => c.groupable) .map((c) => (
  • { setGroupBy(c.key); // groupByKey = c.key; }} > {c.title}
  • ))}
    )}
    {features.columnVisibility && ( updateColumn(k, { visible: !colState.find((c) => c.key === k).visible, }) } /> )} setFullScreen(!isFullScreen)} >
    setGroupBy(null)} advance={advanceFilters} />
    {/* Table-Start*/}
    {features.IsNumbering && } {features.expand && ( )} {features.selection && ( )} {visibleColumns.map((col) => { const style = { minWidth: col.width || 120, width: col.width || undefined, }; if (col.pinned) style.position = "sticky"; if (col.pinned === "left") style.left = `${getLeftOffset(colState, col.key)}px`; if (col.pinned === "right") style.right = `${getRightOffset(colState, col.key)}px`; return ( ); })} {features.actions && ( )} {loading || totalRows === 0 ? Array.from({ length: 1 }).map((_, index) => ( )) : !loading && groupBy && groupedRows && groupedRows.length > 0 ? groupedRows.map((g, indG) => ( {g.items.map((row, indG) => renderRow(row, indG))} )) : currentRows.map((row, ind) => renderRow(row, ind))}
    # 0 && currentRows.every((r) => selected.has(r[rowKey])) } onChange={(e) => e.target.checked ? selectAllOnPage(currentRows) : deselectAllOnPage(currentRows) } /> onDragStart(e, col.key)} onDragOver={(e) => e.preventDefault()} onDrop={(e) => onDrop(e, col.key)} className={`pms-col-header vs-th ${ col.pinned ? `pinned pinned-${col.pinned}` : "" }`} style={style} >
    col.sortable && changeSort(col.key)} style={{ cursor: col.sortable ? "pointer" : "default" }} > {col.title} {sortBy.key === col.key && ( )} {col.pinned === col.key && }
    {features.pinning && ( pinColumn(col.key, "left")} onPinRight={() => pinColumn(col.key, "right")} onUnpin={() => unpinColumn(col.key)} advanceFilters={advanceFilters} setColumnAdvanceFilter={setColumnAdvanceFilter} /> )} {features.resizing && ( onResizeMouseDown(e, col.key)} > )}
    Action
    {loading ? (

    Loading...

    ) : ( Image )}
    {g.key} {features.aggregation && Object.keys(g.aggregates).length > 0 && ( {Object.entries(g.aggregates) .map(([k, v]) => `${k}: ${v}`) .join(" | ")} )}
    {/* Table-End */} {features.pagination && (
    {features.pageSizeSelector && ( )}{" "} {totalRows} rows
    {page}/{totalPages}
    )}
    ); // render a single row (function hoisted so it can reference visibleColumns) function renderRow(row, ind) { const isSelected = selected.has(row[rowKey]); const isExpanded = expanded.has(row[rowKey]); return ( {features.IsNumbering && ( {ind + 1} )} {/* Expand toggle next to selection */} {features.expand && ( )} {/* Selection checkbox (always left) */} {features.selection && ( toggleSelect(row[rowKey])} /> )} {/* Data columns */} {visibleColumns.map((col) => { const style = { minWidth: col.width || 120, width: col.width || undefined, }; if (col.pinned) style.position = "sticky"; if (col.pinned === "left") style.left = `${getLeftOffset(colState, col.key)}px`; if (col.pinned === "right") style.right = `${getRightOffset(colState, col.key)}px`; return ( { e.stopPropagation(); setActiveCell({ rowId: row[rowKey], columnKey: col.key, }); col.onCellClick && col.onCellClick(row, col); }} className={`${col.className ?? ""} ${ col.pinned ? "pinned-left px-3 bg-white pms-grid td pinned" : "px-3" } ${ activeCell?.rowId == row.id && activeCell.columnKey === col.key ? "grid-cell-active" : "" } cursor-pointer`} > {col.render ? col.render(row) : row[col.key] ?? ""} ); })} {/* Actions column (always right) */} {features.actions && ( {isDropdown ? (
    ) : (
    {Array.isArray(features.actions) ? features.actions.map((act, i) => ( )) : typeof features.actions === "function" ? features.actions(row, toggleExpand) : null}
    )} )} {/* 5. Expanded row content (full width) */} {isExpanded && renderExpanded && ( {renderExpanded(row)} )}
    ); } } // small helpers to compute sticky offsets function getLeftOffset(colState, key) { let offset = 0; for (const c of colState .filter((c) => c.visible) .sort((a, b) => a.order - b.order)) { if (c.key === key) return offset; if (c.pinned === "left") offset += c.width || 120; } return offset; } function getRightOffset(colState, key) { let offset = 0; const rightCols = colState .filter((c) => c.visible && c.pinned === "right") .sort((a, b) => b.order - a.order); for (const c of rightCols) { if (c.key === key) return offset; offset += c.width || 120; } return offset; } /* ColumnVisibilityPanel component (inline) */ function ColumnVisibilityPanel({ columns, onToggle }) { return (
    {columns.map((c) => (
    onToggle(c.key)} />
    ))}
    ); } // For Full screen - class card card-action card-fullscreen