tp-react-elements 2.0.4 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/index.esm11.js +2 -2
- package/dist/_virtual/index.esm12.js +2 -2
- package/dist/_virtual/index.esm13.js +2 -2
- package/dist/_virtual/index.esm14.js +2 -2
- package/dist/_virtual/index.esm4.js +2 -2
- package/dist/_virtual/index.esm6.js +2 -2
- package/dist/_virtual/index.esm7.js +2 -2
- package/dist/components/DateRangePicker/DateRangePicker.esm.js +49 -72
- package/dist/components/FormComponents/DatePicker/DatepickerWrapperV2.esm.js +10 -15
- package/dist/components/FormComponents/DatePicker/DatetimepickerWrapper.esm.js +53 -61
- package/dist/components/FormComponents/FileUpload/FormRenderFileUpload.esm.js +54 -68
- package/dist/components/FormComponents/FileUpload/FormRenderMultiFileUpload.esm.js +61 -76
- package/dist/components/FormComponents/FileUpload/MultiFileWithPreview.esm.js +103 -132
- package/dist/components/FormComponents/FormNumberField/FormNumberField.d.ts +0 -38
- package/dist/components/FormComponents/FormNumberField/FormNumberField.esm.js +32 -97
- package/dist/components/FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js +44 -38
- package/dist/components/FormComponents/FormTextField/FormTextField.esm.js +3 -3
- package/dist/components/FormComponents/FormTextFieldWithSelect/FormTextFieldWithSelect.esm.js +3 -2
- package/dist/components/FormComponents/PasswordField/PasswordField.esm.js +4 -4
- package/dist/components/FormComponents/RichTextEditor/RichTextEditor.esm.js +23 -22
- package/dist/components/FormComponents/Select/SearchableSelect.esm.js +9 -9
- package/dist/components/FormComponents/TimePicker/TimePicker.esm.js +19 -19
- package/dist/components/ModalField/ConfirmationDialog.esm.js +3 -3
- package/dist/components/ModalField/ModalField.esm.js +5 -12
- package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +1 -1
- package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +27 -24
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +36 -2
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +196 -105
- package/dist/components/SelectField/MultiSelectFieldComponent.esm.js +3 -3
- package/dist/components/SessionTimeOut/SessionTimeOut.esm.js +33 -34
- package/dist/node_modules/react-date-range/dist/components/Calendar/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateInput/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRangePicker/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DayCell/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/InputRangeField/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/Month/index.esm.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { memo, useState, useEffect } from 'react';
|
|
2
|
+
import { memo, useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { useForm } from 'react-hook-form';
|
|
4
4
|
import dayjs from 'dayjs';
|
|
5
5
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
@@ -410,21 +410,21 @@ const GlobalSearchInput = memo(({ value, onChange, chipStyle, placeholder = 'Sea
|
|
|
410
410
|
} }));
|
|
411
411
|
});
|
|
412
412
|
GlobalSearchInput.displayName = 'GlobalSearchInput';
|
|
413
|
+
// ─── PillBox — module-level so ManageFiltersButton never creates a new type ───
|
|
414
|
+
const PillBox = ({ bg, color, label }) => (jsx(Box, { component: "span", sx: {
|
|
415
|
+
mr: 1.25, flexShrink: 0,
|
|
416
|
+
fontSize: 10, fontWeight: 700,
|
|
417
|
+
px: 0.75, py: '2px',
|
|
418
|
+
borderRadius: '4px',
|
|
419
|
+
bgcolor: bg,
|
|
420
|
+
color,
|
|
421
|
+
textTransform: 'uppercase',
|
|
422
|
+
letterSpacing: 0.4,
|
|
423
|
+
lineHeight: 1.6,
|
|
424
|
+
}, children: label }));
|
|
413
425
|
const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, gridId, }) => {
|
|
414
426
|
const [anchor, setAnchor] = useState(null);
|
|
415
427
|
const atLimit = visibleIds.length >= maxVisible;
|
|
416
|
-
// Shared pill box renderer
|
|
417
|
-
const PillBox = ({ bg, color, label }) => (jsx(Box, { component: "span", sx: {
|
|
418
|
-
mr: 1.25, flexShrink: 0,
|
|
419
|
-
fontSize: 10, fontWeight: 700,
|
|
420
|
-
px: 0.75, py: '2px',
|
|
421
|
-
borderRadius: '4px',
|
|
422
|
-
bgcolor: bg,
|
|
423
|
-
color,
|
|
424
|
-
textTransform: 'uppercase',
|
|
425
|
-
letterSpacing: 0.4,
|
|
426
|
-
lineHeight: 1.6,
|
|
427
|
-
}, children: label }));
|
|
428
428
|
return (jsxs(Fragment, { children: [jsx(Tooltip, { title: "Add or remove filter chips", children: jsx(Button, { id: gridId ? `${gridId}_manage_filters_icon` : undefined, size: "small", variant: "outlined", startIcon: jsx(TuneIcon, { sx: { fontSize: 14 } }), onClick: (e) => setAnchor(e.currentTarget), sx: {
|
|
429
429
|
borderRadius: 5, textTransform: 'none', fontSize: chipStyle.fontSize,
|
|
430
430
|
borderColor: 'divider', color: 'text.secondary',
|
|
@@ -464,20 +464,24 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
|
|
|
464
464
|
const labelFontSize = filterLabelFontSize ?? (uiDensity === 'compact' ? 11 : 12);
|
|
465
465
|
// ── Single-value column filter helpers ──────────────────────────────────────
|
|
466
466
|
const getColValue = (id) => columnFilters.find((f) => f.id === id)?.value ?? '';
|
|
467
|
-
const applyColFilter = (id, value) => {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
467
|
+
const applyColFilter = useCallback((id, value) => {
|
|
468
|
+
onColumnFilterChange((prev) => {
|
|
469
|
+
const rest = prev.filter((f) => f.id !== id);
|
|
470
|
+
return value ? [...rest, { id, value }] : rest;
|
|
471
|
+
});
|
|
472
|
+
}, [onColumnFilterChange]);
|
|
471
473
|
// ── Multi-value column filter helpers ────────────────────────────────────────
|
|
472
474
|
const getColValues = (id) => {
|
|
473
475
|
const val = columnFilters.find((f) => f.id === id)?.value;
|
|
474
476
|
return Array.isArray(val) ? val : [];
|
|
475
477
|
};
|
|
476
|
-
const applyColMultiFilter = (id, values) => {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
478
|
+
const applyColMultiFilter = useCallback((id, values) => {
|
|
479
|
+
onColumnFilterChange((prev) => {
|
|
480
|
+
const rest = prev.filter((f) => f.id !== id);
|
|
481
|
+
return values.length > 0 ? [...rest, { id, value: values }] : rest;
|
|
482
|
+
});
|
|
483
|
+
}, [onColumnFilterChange]);
|
|
484
|
+
const removeFilter = useCallback((id) => {
|
|
481
485
|
const def = serverFilterDefs.find((d) => d.id === id);
|
|
482
486
|
if (def?.type === 'dateRange') {
|
|
483
487
|
onFilterValuesChange({ ...filterValues, [def.fromKey]: '', [def.toKey]: '' });
|
|
@@ -493,7 +497,6 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
|
|
|
493
497
|
applyColFilter(def.columnKey, '');
|
|
494
498
|
}
|
|
495
499
|
else {
|
|
496
|
-
// Column filter — multi-select stores string[], others store string
|
|
497
500
|
const colDef = availableColumns.find((c) => c.id === id);
|
|
498
501
|
if (colDef?.filterType === 'multi-select') {
|
|
499
502
|
applyColMultiFilter(id, []);
|
|
@@ -503,7 +506,7 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
|
|
|
503
506
|
}
|
|
504
507
|
}
|
|
505
508
|
onToggleFilter(id);
|
|
506
|
-
};
|
|
509
|
+
}, [filterValues, serverFilterDefs, availableColumns, onFilterValuesChange, onToggleFilter, applyColFilter, applyColMultiFilter]);
|
|
507
510
|
return (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0.75 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.4, mr: 0.25 }, children: [jsx(FilterListIcon, { sx: { fontSize: labelFontSize + 3, color: 'text.disabled' } }), jsx(Typography, { sx: { fontSize: labelFontSize }, color: "text.secondary", fontWeight: 500, noWrap: true, children: "Filter by:" })] }), visibleFilterIds.map((id) => {
|
|
508
511
|
const def = serverFilterDefs.find((d) => d.id === id);
|
|
509
512
|
if (def?.type === 'dateRange') {
|
|
@@ -91,10 +91,32 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
91
91
|
* Use `grid-no-grow` only when you need fixed column widths with horizontal scroll.
|
|
92
92
|
*/
|
|
93
93
|
layoutMode?: 'grid' | 'semantic' | 'grid-no-grow';
|
|
94
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Compact toolbar, filter chips, and footer.
|
|
96
|
+
* When omitted, the grid auto-selects `compact` at the `tablet`/`mobile`
|
|
97
|
+
* responsive tiers and `default` at `desktop`. Pass explicitly to opt out
|
|
98
|
+
* of that auto-switch.
|
|
99
|
+
*/
|
|
95
100
|
uiDensity?: GridUiDensity;
|
|
96
101
|
/** Override individual sizes from the `uiDensity` preset. */
|
|
97
102
|
uiDensityOptions?: GridUiDensityOptions;
|
|
103
|
+
/**
|
|
104
|
+
* Viewport-width cutoffs (px) for the three responsive tiers:
|
|
105
|
+
* `desktop` (above `tablet`), `tablet` (between `mobile` and `tablet`),
|
|
106
|
+
* and `mobile` (at/below `mobile`). Each tier gets progressively tighter
|
|
107
|
+
* defaults — density, filter-chip cap, toolbar wrap, footer layout.
|
|
108
|
+
* Default: `{ tablet: 1024, mobile: 640 }`.
|
|
109
|
+
*/
|
|
110
|
+
responsiveBreakpoints?: {
|
|
111
|
+
tablet?: number;
|
|
112
|
+
mobile?: number;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Opt out of all automatic responsive-tier adjustments (density, filter
|
|
116
|
+
* cap, toolbar wrap, footer layout) — the grid always behaves as `desktop`.
|
|
117
|
+
* Default: `false`.
|
|
118
|
+
*/
|
|
119
|
+
disableResponsive?: boolean;
|
|
98
120
|
/** Choices shown in the "Rows per page" footer dropdown. Default: [1 000, 5 000, 10 000]. */
|
|
99
121
|
pageSizeOptions?: number[];
|
|
100
122
|
/**
|
|
@@ -109,6 +131,18 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
109
131
|
* Accepts any valid CSS colour string.
|
|
110
132
|
*/
|
|
111
133
|
headerBgColor?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Background colour for the Excel export header row.
|
|
136
|
+
* Accepts a hex colour string (`#RRGGBB`).
|
|
137
|
+
* Falls back to `headerBgColor` when omitted, then to the default blue `#1565C0`.
|
|
138
|
+
*/
|
|
139
|
+
exportHeaderBgColor?: string;
|
|
140
|
+
/**
|
|
141
|
+
* Text colour for the Excel export header row.
|
|
142
|
+
* Accepts a hex colour string (`#RRGGBB`).
|
|
143
|
+
* Defaults to white (`#FFFFFF`) when omitted.
|
|
144
|
+
*/
|
|
145
|
+
exportHeaderTextColor?: string;
|
|
112
146
|
/**
|
|
113
147
|
* Minimum height of every header cell (CSS length or px number).
|
|
114
148
|
* Default: `40px` (`34px` when `uiDensity="compact"`).
|
|
@@ -198,5 +232,5 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
198
232
|
*/
|
|
199
233
|
enableExpanding?: boolean;
|
|
200
234
|
}
|
|
201
|
-
declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, gridId, tableHeight, tableMinHeight, tableMaxHeight, autoFillViewport, layoutMode, uiDensity, uiDensityOptions, pageSizeOptions, showGlobalSearchByDefault, headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
235
|
+
declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, gridId, tableHeight, tableMinHeight, tableMaxHeight, autoFillViewport, layoutMode, uiDensity, uiDensityOptions, responsiveBreakpoints, disableResponsive, pageSizeOptions, showGlobalSearchByDefault, headerBgColor, exportHeaderBgColor, exportHeaderTextColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
202
236
|
export default GenericPaginatedGrid;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useMemo, useState, useRef, useLayoutEffect, useCallback } from 'react';
|
|
3
|
-
import
|
|
4
|
-
import { useMaterialReactTable, MRT_ToggleGlobalFilterButton, MRT_ShowHideColumnsButton, MRT_ToggleDensePaddingButton, MRT_ToggleFullScreenButton, MaterialReactTable } from 'material-react-table';
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useMemo, useState, useRef, useLayoutEffect, useCallback, useEffect } from 'react';
|
|
3
|
+
import { MRT_ToggleGlobalFilterButton, MRT_ShowHideColumnsButton, MRT_ToggleDensePaddingButton, MRT_ToggleFullScreenButton, useMaterialReactTable, MaterialReactTable } from 'material-react-table';
|
|
5
4
|
import { useQuery, keepPreviousData } from '@tanstack/react-query';
|
|
6
|
-
import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, CircularProgress } from '@mui/material';
|
|
5
|
+
import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, Skeleton, CircularProgress } from '@mui/material';
|
|
7
6
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
|
8
7
|
import FileDownloadIcon from '@mui/icons-material/FileDownload';
|
|
9
8
|
import FirstPageIcon from '@mui/icons-material/FirstPage';
|
|
@@ -11,6 +10,7 @@ import LastPageIcon from '@mui/icons-material/LastPage';
|
|
|
11
10
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
12
11
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
13
12
|
import DynamicFilterBar from './DynamicFilterBar.esm.js';
|
|
13
|
+
import { getXLSX } from '../../utils/Constants/xlsx-js-style.esm.js';
|
|
14
14
|
|
|
15
15
|
const UI_DENSITY_PRESETS = {
|
|
16
16
|
default: {
|
|
@@ -194,27 +194,60 @@ const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
|
|
|
194
194
|
bgcolor: 'rgba(255,255,255,0.80)', backdropFilter: 'blur(2px)',
|
|
195
195
|
zIndex: 10, gap: 1.5,
|
|
196
196
|
}, children: [jsx(CircularProgress, { size: 50, thickness: 4 }), jsx(Typography, { variant: "body2", color: "text.secondary", fontWeight: 500, children: message })] }));
|
|
197
|
-
|
|
197
|
+
// ─── Internal: skeleton loader (initial load — no data on screen yet) ────────
|
|
198
|
+
const SkeletonOverlay = ({ columnCount, rowCount = 10, }) => (jsx(Box, { sx: {
|
|
199
|
+
position: 'absolute', inset: 0,
|
|
200
|
+
display: 'flex', flexDirection: 'column',
|
|
201
|
+
bgcolor: 'background.paper',
|
|
202
|
+
zIndex: 10, p: 1.5, gap: 1,
|
|
203
|
+
overflow: 'hidden',
|
|
204
|
+
}, children: Array.from({ length: rowCount }).map((_, rowIdx) => (jsx(Box, { sx: { display: 'flex', gap: 1.5 }, children: Array.from({ length: Math.max(columnCount, 1) }).map((_, colIdx) => (jsx(Skeleton, { variant: "rounded", height: 28, sx: { flex: 1, minWidth: 0 }, animation: "wave" }, colIdx))) }, rowIdx))) }));
|
|
205
|
+
const useResponsiveTier = (tabletBreakpoint, mobileBreakpoint, enabled) => {
|
|
206
|
+
const [tier, setTier] = useState('desktop');
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!enabled || typeof window === 'undefined') {
|
|
209
|
+
setTier('desktop');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const mqlTablet = window.matchMedia(`(max-width: ${tabletBreakpoint}px)`);
|
|
213
|
+
const mqlMobile = window.matchMedia(`(max-width: ${mobileBreakpoint}px)`);
|
|
214
|
+
const update = () => {
|
|
215
|
+
setTier(mqlMobile.matches ? 'mobile' : mqlTablet.matches ? 'tablet' : 'desktop');
|
|
216
|
+
};
|
|
217
|
+
update();
|
|
218
|
+
mqlTablet.addEventListener('change', update);
|
|
219
|
+
mqlMobile.addEventListener('change', update);
|
|
220
|
+
return () => {
|
|
221
|
+
mqlTablet.removeEventListener('change', update);
|
|
222
|
+
mqlMobile.removeEventListener('change', update);
|
|
223
|
+
};
|
|
224
|
+
}, [tabletBreakpoint, mobileBreakpoint, enabled]);
|
|
225
|
+
return enabled ? tier : 'desktop';
|
|
226
|
+
};
|
|
227
|
+
const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, density, gridId, isStacked, }) => {
|
|
198
228
|
const fs = density.footerFontSize;
|
|
199
229
|
const selectH = density.footerMinHeight - 8;
|
|
200
230
|
const startRow = totalRecords === 0 ? 0 : pageIndex * pageSize + 1;
|
|
201
231
|
const endRow = Math.min((pageIndex + 1) * pageSize, totalRecords);
|
|
202
232
|
return (jsxs(Box, { sx: {
|
|
203
|
-
display: 'flex',
|
|
233
|
+
display: 'flex',
|
|
234
|
+
flexDirection: isStacked ? 'column' : 'row',
|
|
235
|
+
alignItems: isStacked ? 'stretch' : 'center',
|
|
236
|
+
justifyContent: isStacked ? 'center' : 'flex-end',
|
|
204
237
|
gap: { xs: 1, md: 2 }, px: 1.5, py: density.footerPaddingY,
|
|
205
238
|
borderTop: '1px solid', borderColor: 'divider',
|
|
206
239
|
bgcolor: 'background.paper', flexWrap: 'wrap',
|
|
207
240
|
minHeight: density.footerMinHeight,
|
|
208
241
|
flexShrink: 0,
|
|
209
|
-
}, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.75 }, children: [jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: "Rows per page:" }), jsx(Select, { value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), inputProps: { id: `${gridId}_page_size_select` }, size: "small", variant: "outlined", sx: {
|
|
242
|
+
}, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: isStacked ? 'center' : 'flex-start', gap: 0.75 }, children: [jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: "Rows per page:" }), jsx(Select, { value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), inputProps: { id: `${gridId}_page_size_select` }, size: "small", variant: "outlined", sx: {
|
|
210
243
|
height: selectH, fontSize: fs - 1, minWidth: 72,
|
|
211
244
|
'.MuiSelect-select': { py: '2px !important' },
|
|
212
245
|
'.MuiOutlinedInput-notchedOutline': { borderColor: 'divider' },
|
|
213
|
-
}, children: pageSizeOptions.map((opt) => (jsx(MenuItem, { value: opt, sx: { fontSize: fs - 1 }, children: opt.toLocaleString() }, opt))) })] }), jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: isLoading || (isFetching && totalRecords === 0)
|
|
246
|
+
}, children: pageSizeOptions.map((opt) => (jsx(MenuItem, { value: opt, sx: { fontSize: fs - 1 }, children: opt.toLocaleString() }, opt))) })] }), jsx(Typography, { sx: { fontSize: fs, textAlign: isStacked ? 'center' : 'left' }, color: "text.secondary", noWrap: true, children: isLoading || (isFetching && totalRecords === 0)
|
|
214
247
|
? 'Loading…'
|
|
215
248
|
: totalRecords === 0
|
|
216
249
|
? 'No records'
|
|
217
|
-
: `${startRow.toLocaleString()}–${endRow.toLocaleString()} of ${totalRecords.toLocaleString()}` }), jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.25 }, children: [jsx(Tooltip, { title: "First page", children: jsx("span", { children: jsx(IconButton, { id: `${gridId}_first_page_icon`, size: "small", onClick: onFirst, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(FirstPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Previous page", children: jsx("span", { children: jsx(IconButton, { id: `${gridId}_prev_page_icon`, size: "small", onClick: onPrev, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(ChevronLeftIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Typography, { fontWeight: 600, sx: {
|
|
250
|
+
: `${startRow.toLocaleString()}–${endRow.toLocaleString()} of ${totalRecords.toLocaleString()}` }), jsxs(Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: isStacked ? 'center' : 'flex-start', gap: 0.25 }, children: [jsx(Tooltip, { title: "First page", children: jsx("span", { children: jsx(IconButton, { id: `${gridId}_first_page_icon`, size: "small", onClick: onFirst, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(FirstPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Previous page", children: jsx("span", { children: jsx(IconButton, { id: `${gridId}_prev_page_icon`, size: "small", onClick: onPrev, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(ChevronLeftIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Typography, { fontWeight: 600, sx: {
|
|
218
251
|
mx: 0.5,
|
|
219
252
|
minWidth: density.footerMinHeight - 12,
|
|
220
253
|
height: density.footerMinHeight - 12,
|
|
@@ -224,14 +257,48 @@ const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoa
|
|
|
224
257
|
}, children: pageIndex + 1 }), jsx(Tooltip, { title: "Next page", children: jsx("span", { children: jsx(IconButton, { id: `${gridId}_next_page_icon`, size: "small", onClick: onNext, disabled: !canNext, sx: { p: 0.5 }, children: jsx(ChevronRightIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Last page", children: jsx("span", { children: jsx(IconButton, { id: `${gridId}_last_page_icon`, size: "small", onClick: onLast, disabled: !canNext, sx: { p: 0.5 }, children: jsx(LastPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) })] })] }));
|
|
225
258
|
};
|
|
226
259
|
// ─── GenericPaginatedGrid ─────────────────────────────────────────────────────
|
|
227
|
-
function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', gridId, tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity
|
|
228
|
-
|
|
260
|
+
function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', gridId, tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity, uiDensityOptions, responsiveBreakpoints, disableResponsive = false, pageSizeOptions = [1000, 5000, 10000], showGlobalSearchByDefault = false, headerBgColor, exportHeaderBgColor, exportHeaderTextColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters = 4, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding = !!renderDetailPanel, }) {
|
|
261
|
+
// ── Responsive tier detection ────────────────────────────────────────────────
|
|
262
|
+
// Three tiers (desktop / tablet / mobile) drive auto density, a progressively
|
|
263
|
+
// lower filter-chip cap, toolbar wrapping, and a stacked pagination footer —
|
|
264
|
+
// unless the consumer opts out via `disableResponsive` or pins `uiDensity`
|
|
265
|
+
// explicitly (density only).
|
|
266
|
+
const tier = useResponsiveTier(responsiveBreakpoints?.tablet ?? 1024, responsiveBreakpoints?.mobile ?? 640, !disableResponsive);
|
|
267
|
+
const isNarrow = tier !== 'desktop'; // tablet or mobile — density, toolbar wrap
|
|
268
|
+
const isMobileTier = tier === 'mobile'; // mobile only — footer stacking, tightest chip cap
|
|
269
|
+
const resolvedUiDensity = uiDensity ?? (isNarrow ? 'compact' : 'default');
|
|
270
|
+
const effectiveMaxVisibleFilters = disableResponsive
|
|
271
|
+
? maxVisibleFilters
|
|
272
|
+
: tier === 'mobile'
|
|
273
|
+
? Math.min(maxVisibleFilters, 2)
|
|
274
|
+
: tier === 'tablet'
|
|
275
|
+
? Math.min(maxVisibleFilters, 3)
|
|
276
|
+
: maxVisibleFilters;
|
|
277
|
+
const densityTokens = useMemo(() => ({ ...UI_DENSITY_PRESETS[resolvedUiDensity], ...uiDensityOptions }), [resolvedUiDensity, uiDensityOptions]);
|
|
229
278
|
const resolvedGridId = gridId ?? exportFilename;
|
|
230
|
-
const resolvedHeaderMinHeight = toCssSize(headerMinHeight,
|
|
231
|
-
const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight,
|
|
232
|
-
const resolvedHeaderFontSize = toCssSize(headerFontSize ?? densityTokens.headerFontSize,
|
|
233
|
-
const resolvedBodyFontSize = toCssSize(bodyFontSize ?? densityTokens.bodyFontSize,
|
|
279
|
+
const resolvedHeaderMinHeight = toCssSize(headerMinHeight, resolvedUiDensity === 'compact' ? '34px' : '40px');
|
|
280
|
+
const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight, resolvedUiDensity === 'compact' ? '56px' : '72px');
|
|
281
|
+
const resolvedHeaderFontSize = toCssSize(headerFontSize ?? densityTokens.headerFontSize, resolvedUiDensity === 'compact' ? '11px' : '12px');
|
|
282
|
+
const resolvedBodyFontSize = toCssSize(bodyFontSize ?? densityTokens.bodyFontSize, resolvedUiDensity === 'compact' ? '11px' : '12px');
|
|
234
283
|
const tableStyleOverrides = useMemo(() => ({
|
|
284
|
+
// Static rules folded in here so the whole object comes from one useMemo
|
|
285
|
+
// and GlobalStyles receives a stable reference unless these props change.
|
|
286
|
+
'button[aria-label="Pin to right"]': { display: 'none' },
|
|
287
|
+
'.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
288
|
+
'.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
|
|
289
|
+
'.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
|
|
290
|
+
'.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
|
|
291
|
+
// Visible horizontal scrollbar — touch devices (iOS) hide scrollbars by
|
|
292
|
+
// default, leaving no affordance that a horizontally-overflowing table
|
|
293
|
+
// (common on narrow viewports) can be scrolled.
|
|
294
|
+
'.tp-gpg-root .MuiTableContainer-root::-webkit-scrollbar': {
|
|
295
|
+
height: 6,
|
|
296
|
+
width: 6,
|
|
297
|
+
},
|
|
298
|
+
'.tp-gpg-root .MuiTableContainer-root::-webkit-scrollbar-thumb': {
|
|
299
|
+
backgroundColor: 'rgba(0,0,0,0.25)',
|
|
300
|
+
borderRadius: 4,
|
|
301
|
+
},
|
|
235
302
|
...buildHeaderGlobalStyles(resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor),
|
|
236
303
|
...buildBodyGlobalStyles(resolvedBodyFontSize),
|
|
237
304
|
}), [
|
|
@@ -400,10 +467,15 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
400
467
|
const handleToggleFilter = useCallback((id) => setVisibleFilterIds((prev) => {
|
|
401
468
|
if (prev.includes(id))
|
|
402
469
|
return prev.filter((v) => v !== id); // always allow remove
|
|
403
|
-
if (prev.length >=
|
|
470
|
+
if (prev.length >= effectiveMaxVisibleFilters)
|
|
404
471
|
return prev; // cap reached — ignore add
|
|
405
472
|
return [...prev, id];
|
|
406
|
-
}), [
|
|
473
|
+
}), [effectiveMaxVisibleFilters]);
|
|
474
|
+
// Trim already-visible chips down to the new cap when the viewport narrows
|
|
475
|
+
// (e.g. resizing from desktop to mobile with 4 chips already shown).
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
setVisibleFilterIds((prev) => prev.length > effectiveMaxVisibleFilters ? prev.slice(0, effectiveMaxVisibleFilters) : prev);
|
|
478
|
+
}, [effectiveMaxVisibleFilters]);
|
|
407
479
|
const handleClearAll = useCallback(() => {
|
|
408
480
|
setFilterValues(defaultFilters);
|
|
409
481
|
setColumnFilters([]);
|
|
@@ -411,12 +483,15 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
411
483
|
setGlobalFilter('');
|
|
412
484
|
setPagination((p) => ({ ...p, pageIndex: 0 }));
|
|
413
485
|
}, [defaultFilters, defaultVisible]);
|
|
414
|
-
const handleExport = useCallback(() => {
|
|
486
|
+
const handleExport = useCallback(async () => {
|
|
487
|
+
// Loaded on demand (and cached) so the xlsx-js-style library is excluded
|
|
488
|
+
// from the initial bundle for consumers who never click Export.
|
|
489
|
+
const XLSX = await getXLSX();
|
|
415
490
|
const dataCols = columns.filter((c) => 'accessorKey' in c && c.accessorKey);
|
|
416
491
|
// xlsx-js-style requires 8-char ARGB (AA + RRGGBB). Prepend full-opacity "FF".
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
492
|
+
const toArgb = (hex, fallback = 'FF1565C0') => hex?.startsWith('#') ? 'FF' + hex.slice(1).toUpperCase().padEnd(6, '0') : fallback;
|
|
493
|
+
const headerArgb = toArgb(exportHeaderBgColor ?? headerBgColor);
|
|
494
|
+
const textArgb = toArgb(exportHeaderTextColor, 'FFFFFFFF');
|
|
420
495
|
const headerRow = dataCols.map((c) => c.header ?? '');
|
|
421
496
|
const dataRows = rows.map((row) => dataCols.map((c) => String(row[c.accessorKey] ?? '')));
|
|
422
497
|
const ws = XLSX.utils.aoa_to_sheet([headerRow, ...dataRows]);
|
|
@@ -426,7 +501,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
426
501
|
if (ws[cellRef]) {
|
|
427
502
|
ws[cellRef].s = {
|
|
428
503
|
fill: { patternType: 'solid', fgColor: { rgb: headerArgb } },
|
|
429
|
-
font: { bold: true, color: { rgb:
|
|
504
|
+
font: { bold: true, color: { rgb: textArgb }, sz: 10 },
|
|
430
505
|
alignment: { horizontal: 'center', vertical: 'center', wrapText: true },
|
|
431
506
|
border: {
|
|
432
507
|
bottom: { style: 'thin', color: { rgb: 'FFCCCCCC' } },
|
|
@@ -435,19 +510,94 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
435
510
|
};
|
|
436
511
|
}
|
|
437
512
|
});
|
|
438
|
-
// Auto-fit column widths
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
513
|
+
// Auto-fit column widths — single O(n) pass accumulates max length per column
|
|
514
|
+
// instead of one reduce() per column (was O(n × cols)).
|
|
515
|
+
const colWidths = dataCols.map((c) => (c.header ?? '').length);
|
|
516
|
+
for (const row of rows) {
|
|
517
|
+
const r = row;
|
|
518
|
+
for (let ci = 0; ci < dataCols.length; ci++) {
|
|
519
|
+
const len = String(r[dataCols[ci].accessorKey] ?? '').length;
|
|
520
|
+
if (len > colWidths[ci])
|
|
521
|
+
colWidths[ci] = len;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
ws['!cols'] = colWidths.map((w) => ({ wch: Math.max(w, 8) + 2 }));
|
|
447
525
|
const wb = XLSX.utils.book_new();
|
|
448
526
|
XLSX.utils.book_append_sheet(wb, ws, 'Data');
|
|
449
527
|
XLSX.writeFile(wb, `${exportFilename}-page${pagination.pageIndex + 1}.xlsx`);
|
|
450
|
-
}, [columns, rows, exportFilename, pagination.pageIndex, headerBgColor]);
|
|
528
|
+
}, [columns, rows, exportFilename, pagination.pageIndex, headerBgColor, exportHeaderBgColor, exportHeaderTextColor]);
|
|
529
|
+
// ── Memoized cell-prop builders ───────────────────────────────────────────
|
|
530
|
+
// These are functions passed to MRT per-cell. Without useCallback they get
|
|
531
|
+
// new references on every render, forcing MRT to rebuild every cell's sx.
|
|
532
|
+
const memoizedHeadCellProps = useCallback((params) => {
|
|
533
|
+
const { column, table } = params;
|
|
534
|
+
const colDef = column.columnDef.muiTableHeadCellProps;
|
|
535
|
+
const colResolved = typeof colDef === 'function' ? (colDef({ column, table }) ?? {}) : colDef ?? {};
|
|
536
|
+
const gridResolved = typeof gridHeadCellProps === 'function'
|
|
537
|
+
? (gridHeadCellProps({ column, table }) ?? {})
|
|
538
|
+
: gridHeadCellProps ?? {};
|
|
539
|
+
const align = (colResolved.align ?? gridResolved.align ?? 'left');
|
|
540
|
+
const { sx: gridSx, align: _ga, ...gridRest } = gridResolved;
|
|
541
|
+
const { sx: colSx, align: _ca, ...colRest } = colResolved;
|
|
542
|
+
return {
|
|
543
|
+
...gridRest,
|
|
544
|
+
...colRest,
|
|
545
|
+
align,
|
|
546
|
+
sx: [
|
|
547
|
+
buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor, headerMaxLines),
|
|
548
|
+
...(gridSx ? [gridSx] : []),
|
|
549
|
+
...(colSx ? [colSx] : []),
|
|
550
|
+
],
|
|
551
|
+
};
|
|
552
|
+
}, [
|
|
553
|
+
resolvedHeaderMinHeight,
|
|
554
|
+
resolvedHeaderMaxHeight,
|
|
555
|
+
resolvedHeaderFontSize,
|
|
556
|
+
headerBgColor,
|
|
557
|
+
headerMaxLines,
|
|
558
|
+
gridHeadCellProps,
|
|
559
|
+
]);
|
|
560
|
+
const memoizedBodyCellProps = useCallback((params) => {
|
|
561
|
+
const { cell, column, row, table } = params;
|
|
562
|
+
const colDef = column.columnDef.muiTableBodyCellProps;
|
|
563
|
+
const colResolved = typeof colDef === 'function' ? (colDef({ cell, column, row, table }) ?? {}) : colDef ?? {};
|
|
564
|
+
const gridResolved = typeof gridBodyCellProps === 'function'
|
|
565
|
+
? (gridBodyCellProps({ cell, column, row, table }) ?? {})
|
|
566
|
+
: gridBodyCellProps ?? {};
|
|
567
|
+
const { sx: gridSx, ...gridRest } = gridResolved;
|
|
568
|
+
const { sx: colSx, ...colRest } = colResolved;
|
|
569
|
+
return {
|
|
570
|
+
...gridRest,
|
|
571
|
+
...colRest,
|
|
572
|
+
sx: [
|
|
573
|
+
{
|
|
574
|
+
whiteSpace: 'nowrap',
|
|
575
|
+
fontSize: resolvedBodyFontSize,
|
|
576
|
+
lineHeight: 1.35,
|
|
577
|
+
borderRight: '1px solid rgba(224,224,224,1)',
|
|
578
|
+
},
|
|
579
|
+
...(gridSx ? [gridSx] : []),
|
|
580
|
+
...(colSx ? [colSx] : []),
|
|
581
|
+
],
|
|
582
|
+
};
|
|
583
|
+
}, [resolvedBodyFontSize, gridBodyCellProps]);
|
|
584
|
+
// ── Memoized toolbar render functions ─────────────────────────────────────
|
|
585
|
+
// Defined with useCallback so MRT receives stable references and does not
|
|
586
|
+
// re-render the toolbar on every isFetching / data update cycle.
|
|
587
|
+
const renderTopToolbarCustomActions = useCallback(() => (jsxs(Box, { sx: {
|
|
588
|
+
display: 'flex',
|
|
589
|
+
alignItems: 'center',
|
|
590
|
+
flexWrap: isNarrow ? 'wrap' : 'nowrap',
|
|
591
|
+
flex: 1, minWidth: 0, pr: 1, gap: 0.5,
|
|
592
|
+
}, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexShrink: 0, gap: 0.25 }, children: [jsx(Tooltip, { title: "Clear filters & refresh", children: jsx("span", { children: jsx(IconButton, { id: `${resolvedGridId}_refresh_icon`, size: "small", onClick: () => { handleClearAll(); refetch(); }, disabled: isFetching, sx: { p: resolvedUiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(RefreshIcon, { sx: { fontSize: resolvedUiDensity === 'compact' ? 18 : 20 } }) }) }) }), jsx(Tooltip, { title: "Export batch as Excel (.xlsx)", children: jsx("span", { children: jsx(IconButton, { id: `${resolvedGridId}_export_icon`, size: "small", onClick: handleExport, disabled: rows.length === 0 || isFetching, sx: { p: resolvedUiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(FileDownloadIcon, { sx: { fontSize: resolvedUiDensity === 'compact' ? 18 : 20 } }) }) }) })] }), jsx(Box, { sx: { flex: 1, minWidth: isNarrow ? '100%' : 0 }, children: jsx(DynamicFilterBar, { serverFilterDefs: serverFilterDefs, visibleFilterIds: visibleFilterIds, onToggleFilter: handleToggleFilter, filterValues: filterValues, onFilterValuesChange: handleFilterValuesChange, columnFilters: columnFilters, onColumnFilterChange: setColumnFilters, availableColumns: availableColumns, maxVisibleFilters: effectiveMaxVisibleFilters, uiDensity: resolvedUiDensity, filterChipFontSize: densityTokens.filterChipFontSize, filterChipMinHeight: densityTokens.filterChipMinHeight, filterLabelFontSize: densityTokens.filterLabelFontSize, gridId: resolvedGridId }) })] })), [
|
|
593
|
+
resolvedGridId, isFetching, rows.length, resolvedUiDensity, isNarrow,
|
|
594
|
+
handleClearAll, refetch, handleExport,
|
|
595
|
+
serverFilterDefs, visibleFilterIds, handleToggleFilter,
|
|
596
|
+
filterValues, handleFilterValuesChange, columnFilters, setColumnFilters,
|
|
597
|
+
availableColumns, effectiveMaxVisibleFilters, densityTokens, handleToggleFilter,
|
|
598
|
+
]);
|
|
599
|
+
const renderToolbarInternalActions = useCallback(({ table: t }) => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [jsx(MRT_ToggleGlobalFilterButton, { table: t, id: `${resolvedGridId}_global_search_icon` }), jsx(MRT_ShowHideColumnsButton, { table: t, id: `${resolvedGridId}_columns_icon` }), jsx(MRT_ToggleDensePaddingButton, { table: t, id: `${resolvedGridId}_density_icon` }), jsx(MRT_ToggleFullScreenButton, { table: t, id: `${resolvedGridId}_fullscreen_icon` })] })), [resolvedGridId]);
|
|
600
|
+
const renderBottomToolbar = useCallback(({ table: t }) => (jsx(PaginationFooter, { pageIndex: t.getState().pagination.pageIndex, pageSize: t.getState().pagination.pageSize, totalRecords: totalRecords, isFetching: isFetching, isLoading: isLoading, canPrev: t.getCanPreviousPage() && !isFetching, canNext: t.getCanNextPage() && !isFetching, onFirst: () => t.firstPage(), onPrev: () => t.previousPage(), onNext: () => t.nextPage(), onLast: () => t.lastPage(), onPageSizeChange: (s) => t.setPageSize(s), pageSizeOptions: pageSizeOptions, density: densityTokens, gridId: resolvedGridId, isStacked: isMobileTier })), [totalRecords, isFetching, isLoading, pageSizeOptions, densityTokens, resolvedGridId, isMobileTier]);
|
|
451
601
|
// ── Table instance ─────────────────────────────────────────────────────────
|
|
452
602
|
const table = useMaterialReactTable({
|
|
453
603
|
columns: processedColumns,
|
|
@@ -544,6 +694,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
544
694
|
minHeight: 0,
|
|
545
695
|
maxHeight: '100%',
|
|
546
696
|
overflow: 'auto',
|
|
697
|
+
WebkitOverflowScrolling: 'touch',
|
|
547
698
|
},
|
|
548
699
|
},
|
|
549
700
|
muiTablePaperProps: {
|
|
@@ -559,10 +710,10 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
559
710
|
muiTopToolbarProps: {
|
|
560
711
|
sx: {
|
|
561
712
|
alignItems: 'center',
|
|
562
|
-
flexWrap: 'nowrap',
|
|
713
|
+
flexWrap: isNarrow ? 'wrap' : 'nowrap',
|
|
563
714
|
minHeight: densityTokens.filterChipMinHeight + 12,
|
|
564
|
-
py:
|
|
565
|
-
'& .MuiIconButton-root': { p:
|
|
715
|
+
py: resolvedUiDensity === 'compact' ? 0.25 : 0.5,
|
|
716
|
+
'& .MuiIconButton-root': { p: resolvedUiDensity === 'compact' ? 0.35 : 0.5 },
|
|
566
717
|
'& .MuiIconButton-root svg': { fontSize: densityTokens.toolbarIconFontSize === 'small' ? 18 : 20 },
|
|
567
718
|
},
|
|
568
719
|
},
|
|
@@ -573,7 +724,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
573
724
|
size: 'small',
|
|
574
725
|
variant: 'outlined',
|
|
575
726
|
sx: {
|
|
576
|
-
minWidth:
|
|
727
|
+
minWidth: resolvedUiDensity === 'compact' ? 160 : 200,
|
|
577
728
|
'& .MuiOutlinedInput-root': {
|
|
578
729
|
height: densityTokens.filterChipMinHeight,
|
|
579
730
|
fontSize: densityTokens.filterChipFontSize,
|
|
@@ -604,69 +755,16 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
604
755
|
...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
|
|
605
756
|
},
|
|
606
757
|
},
|
|
607
|
-
muiTableHeadCellProps:
|
|
608
|
-
|
|
609
|
-
// ── Resolve column-level props ────────────────────────────────────────
|
|
610
|
-
const colDef = column.columnDef.muiTableHeadCellProps;
|
|
611
|
-
const colResolved = typeof colDef === 'function' ? (colDef({ column, table }) ?? {}) : colDef ?? {};
|
|
612
|
-
// ── Resolve grid-level props ──────────────────────────────────────────
|
|
613
|
-
const gridResolved = typeof gridHeadCellProps === 'function'
|
|
614
|
-
? (gridHeadCellProps({ column, table }) ?? {})
|
|
615
|
-
: gridHeadCellProps ?? {};
|
|
616
|
-
// Column align overrides grid align; default 'left'
|
|
617
|
-
const align = (colResolved.align ?? gridResolved.align ?? 'left');
|
|
618
|
-
const { sx: gridSx, align: _ga, ...gridRest } = gridResolved;
|
|
619
|
-
const { sx: colSx, align: _ca, ...colRest } = colResolved;
|
|
620
|
-
return {
|
|
621
|
-
...gridRest, // grid-level props (className, etc.)
|
|
622
|
-
...colRest, // column-level overrides
|
|
623
|
-
align,
|
|
624
|
-
sx: [
|
|
625
|
-
buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor, headerMaxLines),
|
|
626
|
-
...(gridSx ? [gridSx] : []),
|
|
627
|
-
...(colSx ? [colSx] : []),
|
|
628
|
-
],
|
|
629
|
-
};
|
|
630
|
-
},
|
|
631
|
-
muiTableBodyCellProps: (params) => {
|
|
632
|
-
const { cell, column, row, table } = params;
|
|
633
|
-
// ── Resolve column-level props ────────────────────────────────────────
|
|
634
|
-
const colDef = column.columnDef.muiTableBodyCellProps;
|
|
635
|
-
const colResolved = typeof colDef === 'function' ? (colDef({ cell, column, row, table }) ?? {}) : colDef ?? {};
|
|
636
|
-
// ── Resolve grid-level props ──────────────────────────────────────────
|
|
637
|
-
const gridResolved = typeof gridBodyCellProps === 'function'
|
|
638
|
-
? (gridBodyCellProps({ cell, column, row, table }) ?? {})
|
|
639
|
-
: gridBodyCellProps ?? {};
|
|
640
|
-
const { sx: gridSx, ...gridRest } = gridResolved;
|
|
641
|
-
const { sx: colSx, ...colRest } = colResolved;
|
|
642
|
-
return {
|
|
643
|
-
...gridRest, // grid-level props (title, className, etc.)
|
|
644
|
-
...colRest, // column-level overrides (align, etc.)
|
|
645
|
-
sx: [
|
|
646
|
-
// 1. base styles — always applied
|
|
647
|
-
{
|
|
648
|
-
whiteSpace: 'nowrap',
|
|
649
|
-
fontSize: resolvedBodyFontSize,
|
|
650
|
-
lineHeight: 1.35,
|
|
651
|
-
borderRight: '1px solid rgba(224,224,224,1)',
|
|
652
|
-
},
|
|
653
|
-
// 2. grid-level sx additions
|
|
654
|
-
...(gridSx ? [gridSx] : []),
|
|
655
|
-
// 3. column-level sx overrides (minWidth, maxWidth, textAlign, etc.)
|
|
656
|
-
...(colSx ? [colSx] : []),
|
|
657
|
-
],
|
|
658
|
-
};
|
|
659
|
-
},
|
|
758
|
+
muiTableHeadCellProps: memoizedHeadCellProps,
|
|
759
|
+
muiTableBodyCellProps: memoizedBodyCellProps,
|
|
660
760
|
muiToolbarAlertBannerProps: isError
|
|
661
761
|
? { color: 'error', children: 'Failed to load data. Please try again.' }
|
|
662
762
|
: undefined,
|
|
663
|
-
|
|
664
|
-
//
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
renderToolbarInternalActions: ({ table: t }) => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [jsx(MRT_ToggleGlobalFilterButton, { table: t, id: `${resolvedGridId}_global_search_icon` }), jsx(MRT_ShowHideColumnsButton, { table: t, id: `${resolvedGridId}_columns_icon` }), jsx(MRT_ToggleDensePaddingButton, { table: t, id: `${resolvedGridId}_density_icon` }), jsx(MRT_ToggleFullScreenButton, { table: t, id: `${resolvedGridId}_fullscreen_icon` })] })),
|
|
669
|
-
renderBottomToolbar: ({ table: t }) => (jsx(PaginationFooter, { pageIndex: t.getState().pagination.pageIndex, pageSize: t.getState().pagination.pageSize, totalRecords: totalRecords, isFetching: isFetching, isLoading: isLoading, canPrev: t.getCanPreviousPage() && !isFetching, canNext: t.getCanNextPage() && !isFetching, onFirst: () => t.firstPage(), onPrev: () => t.previousPage(), onNext: () => t.nextPage(), onLast: () => t.lastPage(), onPageSizeChange: (s) => t.setPageSize(s), pageSizeOptions: pageSizeOptions, density: densityTokens, gridId: resolvedGridId })),
|
|
763
|
+
// Memoized via useCallback above — stable references prevent MRT from
|
|
764
|
+
// re-rendering the toolbar on every isFetching / data update cycle.
|
|
765
|
+
renderTopToolbarCustomActions,
|
|
766
|
+
renderToolbarInternalActions,
|
|
767
|
+
renderBottomToolbar,
|
|
670
768
|
});
|
|
671
769
|
return (jsxs(Box, { ref: containerRef, id: resolvedGridId, className: "tp-gpg-root", sx: {
|
|
672
770
|
position: 'relative',
|
|
@@ -676,14 +774,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
676
774
|
display: 'flex',
|
|
677
775
|
flexDirection: 'column',
|
|
678
776
|
overflow: 'hidden',
|
|
679
|
-
}, children: [jsx(GlobalStyles, { styles: {
|
|
680
|
-
'button[aria-label="Pin to right"]': { display: 'none' },
|
|
681
|
-
'.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
682
|
-
'.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
|
|
683
|
-
'.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
|
|
684
|
-
'.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
|
|
685
|
-
...tableStyleOverrides,
|
|
686
|
-
} }), isLoading && jsx(OverlayLoader, { message: "Loading data\u2026" }), !isLoading && isFetching && jsx(OverlayLoader, { message: "Updating data\u2026" }), jsx(Box, { sx: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: jsx(MaterialReactTable, { table: table }) })] }));
|
|
777
|
+
}, children: [jsx(GlobalStyles, { styles: tableStyleOverrides }), isLoading && jsx(SkeletonOverlay, { columnCount: processedColumns.length }), !isLoading && isFetching && jsx(OverlayLoader, { message: "Updating data\u2026" }), jsx(Box, { sx: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: jsx(MaterialReactTable, { table: table }) })] }));
|
|
687
778
|
}
|
|
688
779
|
|
|
689
780
|
export { GenericPaginatedGrid as default };
|
|
@@ -6,7 +6,7 @@ import ListItemText from '@mui/material/ListItemText';
|
|
|
6
6
|
import MenuItem from '@mui/material/MenuItem';
|
|
7
7
|
import Select from '@mui/material/Select';
|
|
8
8
|
import * as React from 'react';
|
|
9
|
-
import { useState } from 'react';
|
|
9
|
+
import { useState, useCallback } from 'react';
|
|
10
10
|
|
|
11
11
|
const ITEM_HEIGHT = 36;
|
|
12
12
|
const ITEM_PADDING_TOP = 2;
|
|
@@ -52,11 +52,11 @@ function MultiSelectFieldComponent({ value, options, onChangeFn, disable = false
|
|
|
52
52
|
setSelectAll(false);
|
|
53
53
|
}
|
|
54
54
|
}, [filterOptions, fieldValue]);
|
|
55
|
-
const handleChange = (event) => {
|
|
55
|
+
const handleChange = useCallback((event) => {
|
|
56
56
|
const { target: { value }, } = event;
|
|
57
57
|
setPersonName(typeof value === 'string' ? value?.split(',') : value);
|
|
58
58
|
onChangeFn((typeof value === 'string' ? value?.split(',') : value).join(','));
|
|
59
|
-
};
|
|
59
|
+
}, [onChangeFn]);
|
|
60
60
|
const selectedOptions = options.filter((item) => fieldValue?.split(',').some((value) => value === item.value));
|
|
61
61
|
const selectedValues = selectedOptions.map((item) => item.label).join(' , ');
|
|
62
62
|
return (jsx(Box, { children: jsx(FormControl, { fullWidth: true, ref: containerRef, sx: {
|