tp-react-elements 2.0.3 → 2.0.5

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.
Files changed (41) hide show
  1. package/README.md +6 -2
  2. package/dist/_virtual/index.esm11.js +2 -2
  3. package/dist/_virtual/index.esm12.js +2 -2
  4. package/dist/_virtual/index.esm13.js +2 -2
  5. package/dist/_virtual/index.esm14.js +2 -2
  6. package/dist/_virtual/index.esm4.js +2 -2
  7. package/dist/_virtual/index.esm5.js +2 -2
  8. package/dist/_virtual/index.esm6.js +2 -2
  9. package/dist/components/DateRangePicker/DateRangePicker.esm.js +49 -72
  10. package/dist/components/Form/FormRender.esm.js +1 -1
  11. package/dist/components/FormComponents/DatePicker/DatepickerWrapperV2.esm.js +10 -15
  12. package/dist/components/FormComponents/DatePicker/DatetimepickerWrapper.esm.js +53 -61
  13. package/dist/components/FormComponents/FileUpload/FormRenderFileUpload.esm.js +54 -68
  14. package/dist/components/FormComponents/FileUpload/FormRenderMultiFileUpload.esm.js +61 -76
  15. package/dist/components/FormComponents/FileUpload/MultiFileWithPreview.esm.js +103 -132
  16. package/dist/components/FormComponents/FormNumberField/FormNumberField.d.ts +0 -38
  17. package/dist/components/FormComponents/FormNumberField/FormNumberField.esm.js +32 -97
  18. package/dist/components/FormComponents/FormNumberField/FormNumberFieldDecimal.esm.js +44 -38
  19. package/dist/components/FormComponents/FormTextField/FormTextField.esm.js +3 -3
  20. package/dist/components/FormComponents/FormTextFieldWithSelect/FormTextFieldWithSelect.esm.js +3 -2
  21. package/dist/components/FormComponents/PasswordField/PasswordField.esm.js +4 -4
  22. package/dist/components/FormComponents/RichTextEditor/RichTextEditor.esm.js +23 -22
  23. package/dist/components/FormComponents/Select/SearchableSelect.esm.js +9 -9
  24. package/dist/components/FormComponents/TimePicker/TimePicker.esm.js +19 -19
  25. package/dist/components/FormComponents/index.esm.js +2 -2
  26. package/dist/components/ModalField/ConfirmationDialog.esm.js +3 -3
  27. package/dist/components/ModalField/ModalField.esm.js +5 -12
  28. package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +17 -2
  29. package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +161 -61
  30. package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +21 -3
  31. package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +133 -92
  32. package/dist/components/SelectField/MultiSelectFieldComponent.esm.js +3 -3
  33. package/dist/components/SessionTimeOut/SessionTimeOut.esm.js +33 -34
  34. package/dist/node_modules/react-date-range/dist/components/DateInput/index.esm.js +1 -1
  35. package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
  36. package/dist/node_modules/react-date-range/dist/components/DateRangePicker/index.esm.js +1 -1
  37. package/dist/node_modules/react-date-range/dist/components/DayCell/index.esm.js +1 -1
  38. package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
  39. package/dist/node_modules/react-date-range/dist/components/InputRangeField/index.esm.js +1 -1
  40. package/dist/node_modules/react-date-range/dist/components/Month/index.esm.js +1 -1
  41. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useMemo, useState, useRef, useLayoutEffect, useCallback } from 'react';
3
- import { useMaterialReactTable, MRT_ToggleGlobalFilterButton, MRT_ShowHideColumnsButton, MRT_ToggleDensePaddingButton, MRT_ToggleFullScreenButton, MaterialReactTable } from 'material-react-table';
3
+ import * as XLSX from 'xlsx-js-style';
4
+ import { MRT_ToggleGlobalFilterButton, MRT_ShowHideColumnsButton, MRT_ToggleDensePaddingButton, MRT_ToggleFullScreenButton, useMaterialReactTable, MaterialReactTable } from 'material-react-table';
4
5
  import { useQuery, keepPreviousData } from '@tanstack/react-query';
5
6
  import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, CircularProgress } from '@mui/material';
6
7
  import RefreshIcon from '@mui/icons-material/Refresh';
@@ -52,6 +53,8 @@ const buildBodyGlobalStyles = (bodyFont) => ({
52
53
  '.tp-gpg-root .MuiTableBody-root .MuiTableCell-root': {
53
54
  fontSize: `${bodyFont} !important`,
54
55
  lineHeight: 1.35,
56
+ borderRight: '1px solid rgba(224,224,224,1)',
57
+ userSelect: 'text',
55
58
  },
56
59
  });
57
60
  const buildHeaderGlobalStyles = (headerMin, headerMax, headerFont, headerBgColor) => {
@@ -191,7 +194,7 @@ const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
191
194
  bgcolor: 'rgba(255,255,255,0.80)', backdropFilter: 'blur(2px)',
192
195
  zIndex: 10, gap: 1.5,
193
196
  }, children: [jsx(CircularProgress, { size: 50, thickness: 4 }), jsx(Typography, { variant: "body2", color: "text.secondary", fontWeight: 500, children: message })] }));
194
- const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, density, }) => {
197
+ const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, density, gridId, }) => {
195
198
  const fs = density.footerFontSize;
196
199
  const selectH = density.footerMinHeight - 8;
197
200
  const startRow = totalRecords === 0 ? 0 : pageIndex * pageSize + 1;
@@ -203,7 +206,7 @@ const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoa
203
206
  bgcolor: 'background.paper', flexWrap: 'wrap',
204
207
  minHeight: density.footerMinHeight,
205
208
  flexShrink: 0,
206
- }, 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)), size: "small", variant: "outlined", sx: {
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: {
207
210
  height: selectH, fontSize: fs - 1, minWidth: 72,
208
211
  '.MuiSelect-select': { py: '2px !important' },
209
212
  '.MuiOutlinedInput-notchedOutline': { borderColor: 'divider' },
@@ -211,23 +214,31 @@ const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoa
211
214
  ? 'Loading…'
212
215
  : totalRecords === 0
213
216
  ? 'No records'
214
- : `${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, { 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, { size: "small", onClick: onPrev, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(ChevronLeftIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Typography, { fontWeight: 600, sx: {
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: {
215
218
  mx: 0.5,
216
219
  minWidth: density.footerMinHeight - 12,
217
220
  height: density.footerMinHeight - 12,
218
221
  display: 'flex', alignItems: 'center', justifyContent: 'center',
219
222
  borderRadius: 1, border: '1px solid', borderColor: 'primary.main',
220
223
  color: 'primary.main', bgcolor: 'rgba(25,118,210,0.07)', fontSize: fs - 1,
221
- }, children: pageIndex + 1 }), jsx(Tooltip, { title: "Next page", children: jsx("span", { children: jsx(IconButton, { 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, { size: "small", onClick: onLast, disabled: !canNext, sx: { p: 0.5 }, children: jsx(LastPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) })] })] }));
224
+ }, 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 } }) }) }) })] })] }));
222
225
  };
223
226
  // ─── GenericPaginatedGrid ─────────────────────────────────────────────────────
224
- function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity = 'default', uiDensityOptions, pageSizeOptions = [1000, 5000, 10000], showGlobalSearchByDefault = true, headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters = 4, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding = !!renderDetailPanel, }) {
227
+ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', gridId, tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity = 'default', uiDensityOptions, pageSizeOptions = [1000, 5000, 10000], showGlobalSearchByDefault = false, headerBgColor, exportHeaderBgColor, exportHeaderTextColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters = 4, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding = !!renderDetailPanel, }) {
225
228
  const densityTokens = useMemo(() => ({ ...UI_DENSITY_PRESETS[uiDensity], ...uiDensityOptions }), [uiDensity, uiDensityOptions]);
229
+ const resolvedGridId = gridId ?? exportFilename;
226
230
  const resolvedHeaderMinHeight = toCssSize(headerMinHeight, uiDensity === 'compact' ? '34px' : '40px');
227
231
  const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight, uiDensity === 'compact' ? '56px' : '72px');
228
232
  const resolvedHeaderFontSize = toCssSize(headerFontSize ?? densityTokens.headerFontSize, uiDensity === 'compact' ? '11px' : '12px');
229
233
  const resolvedBodyFontSize = toCssSize(bodyFontSize ?? densityTokens.bodyFontSize, uiDensity === 'compact' ? '11px' : '12px');
230
234
  const tableStyleOverrides = useMemo(() => ({
235
+ // Static rules folded in here so the whole object comes from one useMemo
236
+ // and GlobalStyles receives a stable reference unless these props change.
237
+ 'button[aria-label="Pin to right"]': { display: 'none' },
238
+ '.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
239
+ '.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
240
+ '.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
241
+ '.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
231
242
  ...buildHeaderGlobalStyles(resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor),
232
243
  ...buildBodyGlobalStyles(resolvedBodyFontSize),
233
244
  }), [
@@ -408,22 +419,112 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
408
419
  setPagination((p) => ({ ...p, pageIndex: 0 }));
409
420
  }, [defaultFilters, defaultVisible]);
410
421
  const handleExport = useCallback(() => {
411
- const headers = columns.map((c) => c.header).join(',');
412
- const csvRows = rows.map((row) => columns
413
- .map((c) => {
414
- const key = c.accessorKey ?? '';
415
- const val = row[key] ?? '';
416
- return `"${String(val).replace(/"/g, '""')}"`;
417
- })
418
- .join(','));
419
- const blob = new Blob([[headers, ...csvRows].join('\n')], { type: 'text/csv' });
420
- const url = URL.createObjectURL(blob);
421
- const a = document.createElement('a');
422
- a.href = url;
423
- a.download = `${exportFilename}-page${pagination.pageIndex + 1}.csv`;
424
- a.click();
425
- URL.revokeObjectURL(url);
426
- }, [columns, rows, exportFilename, pagination.pageIndex]);
422
+ const dataCols = columns.filter((c) => 'accessorKey' in c && c.accessorKey);
423
+ // xlsx-js-style requires 8-char ARGB (AA + RRGGBB). Prepend full-opacity "FF".
424
+ const toArgb = (hex, fallback = 'FF1565C0') => hex?.startsWith('#') ? 'FF' + hex.slice(1).toUpperCase().padEnd(6, '0') : fallback;
425
+ const headerArgb = toArgb(exportHeaderBgColor ?? headerBgColor);
426
+ const textArgb = toArgb(exportHeaderTextColor, 'FFFFFFFF');
427
+ const headerRow = dataCols.map((c) => c.header ?? '');
428
+ const dataRows = rows.map((row) => dataCols.map((c) => String(row[c.accessorKey] ?? '')));
429
+ const ws = XLSX.utils.aoa_to_sheet([headerRow, ...dataRows]);
430
+ // Style header row
431
+ dataCols.forEach((_, colIdx) => {
432
+ const cellRef = XLSX.utils.encode_cell({ r: 0, c: colIdx });
433
+ if (ws[cellRef]) {
434
+ ws[cellRef].s = {
435
+ fill: { patternType: 'solid', fgColor: { rgb: headerArgb } },
436
+ font: { bold: true, color: { rgb: textArgb }, sz: 10 },
437
+ alignment: { horizontal: 'center', vertical: 'center', wrapText: true },
438
+ border: {
439
+ bottom: { style: 'thin', color: { rgb: 'FFCCCCCC' } },
440
+ right: { style: 'thin', color: { rgb: 'FFCCCCCC' } },
441
+ },
442
+ };
443
+ }
444
+ });
445
+ // Auto-fit column widths — single O(n) pass accumulates max length per column
446
+ // instead of one reduce() per column (was O(n × cols)).
447
+ const colWidths = dataCols.map((c) => (c.header ?? '').length);
448
+ for (const row of rows) {
449
+ const r = row;
450
+ for (let ci = 0; ci < dataCols.length; ci++) {
451
+ const len = String(r[dataCols[ci].accessorKey] ?? '').length;
452
+ if (len > colWidths[ci])
453
+ colWidths[ci] = len;
454
+ }
455
+ }
456
+ ws['!cols'] = colWidths.map((w) => ({ wch: Math.max(w, 8) + 2 }));
457
+ const wb = XLSX.utils.book_new();
458
+ XLSX.utils.book_append_sheet(wb, ws, 'Data');
459
+ XLSX.writeFile(wb, `${exportFilename}-page${pagination.pageIndex + 1}.xlsx`);
460
+ }, [columns, rows, exportFilename, pagination.pageIndex, headerBgColor, exportHeaderBgColor, exportHeaderTextColor]);
461
+ // ── Memoized cell-prop builders ───────────────────────────────────────────
462
+ // These are functions passed to MRT per-cell. Without useCallback they get
463
+ // new references on every render, forcing MRT to rebuild every cell's sx.
464
+ const memoizedHeadCellProps = useCallback((params) => {
465
+ const { column, table } = params;
466
+ const colDef = column.columnDef.muiTableHeadCellProps;
467
+ const colResolved = typeof colDef === 'function' ? (colDef({ column, table }) ?? {}) : colDef ?? {};
468
+ const gridResolved = typeof gridHeadCellProps === 'function'
469
+ ? (gridHeadCellProps({ column, table }) ?? {})
470
+ : gridHeadCellProps ?? {};
471
+ const align = (colResolved.align ?? gridResolved.align ?? 'left');
472
+ const { sx: gridSx, align: _ga, ...gridRest } = gridResolved;
473
+ const { sx: colSx, align: _ca, ...colRest } = colResolved;
474
+ return {
475
+ ...gridRest,
476
+ ...colRest,
477
+ align,
478
+ sx: [
479
+ buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor, headerMaxLines),
480
+ ...(gridSx ? [gridSx] : []),
481
+ ...(colSx ? [colSx] : []),
482
+ ],
483
+ };
484
+ }, [
485
+ resolvedHeaderMinHeight,
486
+ resolvedHeaderMaxHeight,
487
+ resolvedHeaderFontSize,
488
+ headerBgColor,
489
+ headerMaxLines,
490
+ gridHeadCellProps,
491
+ ]);
492
+ const memoizedBodyCellProps = useCallback((params) => {
493
+ const { cell, column, row, table } = params;
494
+ const colDef = column.columnDef.muiTableBodyCellProps;
495
+ const colResolved = typeof colDef === 'function' ? (colDef({ cell, column, row, table }) ?? {}) : colDef ?? {};
496
+ const gridResolved = typeof gridBodyCellProps === 'function'
497
+ ? (gridBodyCellProps({ cell, column, row, table }) ?? {})
498
+ : gridBodyCellProps ?? {};
499
+ const { sx: gridSx, ...gridRest } = gridResolved;
500
+ const { sx: colSx, ...colRest } = colResolved;
501
+ return {
502
+ ...gridRest,
503
+ ...colRest,
504
+ sx: [
505
+ {
506
+ whiteSpace: 'nowrap',
507
+ fontSize: resolvedBodyFontSize,
508
+ lineHeight: 1.35,
509
+ borderRight: '1px solid rgba(224,224,224,1)',
510
+ },
511
+ ...(gridSx ? [gridSx] : []),
512
+ ...(colSx ? [colSx] : []),
513
+ ],
514
+ };
515
+ }, [resolvedBodyFontSize, gridBodyCellProps]);
516
+ // ── Memoized toolbar render functions ─────────────────────────────────────
517
+ // Defined with useCallback so MRT receives stable references and does not
518
+ // re-render the toolbar on every isFetching / data update cycle.
519
+ const renderTopToolbarCustomActions = useCallback(() => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0, pr: 1, gap: 0.5 }, 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: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(RefreshIcon, { sx: { fontSize: uiDensity === '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: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(FileDownloadIcon, { sx: { fontSize: uiDensity === 'compact' ? 18 : 20 } }) }) }) })] }), jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: jsx(DynamicFilterBar, { serverFilterDefs: serverFilterDefs, visibleFilterIds: visibleFilterIds, onToggleFilter: handleToggleFilter, filterValues: filterValues, onFilterValuesChange: handleFilterValuesChange, columnFilters: columnFilters, onColumnFilterChange: setColumnFilters, availableColumns: availableColumns, maxVisibleFilters: maxVisibleFilters, uiDensity: uiDensity, filterChipFontSize: densityTokens.filterChipFontSize, filterChipMinHeight: densityTokens.filterChipMinHeight, filterLabelFontSize: densityTokens.filterLabelFontSize, gridId: resolvedGridId }) })] })), [
520
+ resolvedGridId, isFetching, rows.length, uiDensity,
521
+ handleClearAll, refetch, handleExport,
522
+ serverFilterDefs, visibleFilterIds, handleToggleFilter,
523
+ filterValues, handleFilterValuesChange, columnFilters, setColumnFilters,
524
+ availableColumns, maxVisibleFilters, densityTokens, handleToggleFilter,
525
+ ]);
526
+ 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]);
527
+ 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 })), [totalRecords, isFetching, isLoading, pageSizeOptions, densityTokens, resolvedGridId]);
427
528
  // ── Table instance ─────────────────────────────────────────────────────────
428
529
  const table = useMaterialReactTable({
429
530
  columns: processedColumns,
@@ -580,71 +681,18 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
580
681
  ...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
581
682
  },
582
683
  },
583
- muiTableHeadCellProps: (params) => {
584
- const { column, table } = params;
585
- // ── Resolve column-level props ────────────────────────────────────────
586
- const colDef = column.columnDef.muiTableHeadCellProps;
587
- const colResolved = typeof colDef === 'function' ? (colDef({ column, table }) ?? {}) : colDef ?? {};
588
- // ── Resolve grid-level props ──────────────────────────────────────────
589
- const gridResolved = typeof gridHeadCellProps === 'function'
590
- ? (gridHeadCellProps({ column, table }) ?? {})
591
- : gridHeadCellProps ?? {};
592
- // Column align overrides grid align; default 'left'
593
- const align = (colResolved.align ?? gridResolved.align ?? 'left');
594
- const { sx: gridSx, align: _ga, ...gridRest } = gridResolved;
595
- const { sx: colSx, align: _ca, ...colRest } = colResolved;
596
- return {
597
- ...gridRest, // grid-level props (className, etc.)
598
- ...colRest, // column-level overrides
599
- align,
600
- sx: [
601
- buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor, headerMaxLines),
602
- ...(gridSx ? [gridSx] : []),
603
- ...(colSx ? [colSx] : []),
604
- ],
605
- };
606
- },
607
- muiTableBodyCellProps: (params) => {
608
- const { cell, column, row, table } = params;
609
- // ── Resolve column-level props ────────────────────────────────────────
610
- const colDef = column.columnDef.muiTableBodyCellProps;
611
- const colResolved = typeof colDef === 'function' ? (colDef({ cell, column, row, table }) ?? {}) : colDef ?? {};
612
- // ── Resolve grid-level props ──────────────────────────────────────────
613
- const gridResolved = typeof gridBodyCellProps === 'function'
614
- ? (gridBodyCellProps({ cell, column, row, table }) ?? {})
615
- : gridBodyCellProps ?? {};
616
- const { sx: gridSx, ...gridRest } = gridResolved;
617
- const { sx: colSx, ...colRest } = colResolved;
618
- return {
619
- ...gridRest, // grid-level props (title, className, etc.)
620
- ...colRest, // column-level overrides (align, etc.)
621
- sx: [
622
- // 1. base styles — always applied
623
- {
624
- whiteSpace: 'nowrap',
625
- fontSize: resolvedBodyFontSize,
626
- lineHeight: 1.35,
627
- borderRight: '1px solid rgba(224,224,224,1)',
628
- },
629
- // 2. grid-level sx additions
630
- ...(gridSx ? [gridSx] : []),
631
- // 3. column-level sx overrides (minWidth, maxWidth, textAlign, etc.)
632
- ...(colSx ? [colSx] : []),
633
- ],
634
- };
635
- },
684
+ muiTableHeadCellProps: memoizedHeadCellProps,
685
+ muiTableBodyCellProps: memoizedBodyCellProps,
636
686
  muiToolbarAlertBannerProps: isError
637
687
  ? { color: 'error', children: 'Failed to load data. Please try again.' }
638
688
  : undefined,
639
- renderTopToolbarCustomActions: () => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0, pr: 1, gap: 0.5 }, 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, { size: "small", onClick: () => { handleClearAll(); refetch(); }, disabled: isFetching, sx: { p: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(RefreshIcon, { sx: { fontSize: uiDensity === 'compact' ? 18 : 20 } }) }) }) }), jsx(Tooltip, { title: "Export batch as CSV", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: handleExport, disabled: rows.length === 0 || isFetching, sx: { p: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(FileDownloadIcon, { sx: { fontSize: uiDensity === 'compact' ? 18 : 20 } }) }) }) })] }), jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: jsx(DynamicFilterBar, { serverFilterDefs: serverFilterDefs, visibleFilterIds: visibleFilterIds, onToggleFilter: handleToggleFilter, filterValues: filterValues, onFilterValuesChange: handleFilterValuesChange, columnFilters: columnFilters, onColumnFilterChange: setColumnFilters, availableColumns: availableColumns, maxVisibleFilters: maxVisibleFilters, uiDensity: uiDensity, filterChipFontSize: densityTokens.filterChipFontSize, filterChipMinHeight: densityTokens.filterChipMinHeight, filterLabelFontSize: densityTokens.filterLabelFontSize }) })] })),
640
- // Single global search control: MRT's built-in search icon/input in the
641
- // top-right toolbar actions. The inline search box next to "Manage
642
- // Filters" is intentionally not rendered (see renderTopToolbarCustomActions)
643
- // to avoid showing two search controls.
644
- renderToolbarInternalActions: ({ table: t }) => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [jsx(MRT_ToggleGlobalFilterButton, { table: t }), jsx(MRT_ShowHideColumnsButton, { table: t }), jsx(MRT_ToggleDensePaddingButton, { table: t }), jsx(MRT_ToggleFullScreenButton, { table: t })] })),
645
- 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 })),
689
+ // Memoized via useCallback above — stable references prevent MRT from
690
+ // re-rendering the toolbar on every isFetching / data update cycle.
691
+ renderTopToolbarCustomActions,
692
+ renderToolbarInternalActions,
693
+ renderBottomToolbar,
646
694
  });
647
- return (jsxs(Box, { ref: containerRef, className: "tp-gpg-root", sx: {
695
+ return (jsxs(Box, { ref: containerRef, id: resolvedGridId, className: "tp-gpg-root", sx: {
648
696
  position: 'relative',
649
697
  height: resolvedHeight,
650
698
  minHeight: tableMinHeight,
@@ -652,14 +700,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
652
700
  display: 'flex',
653
701
  flexDirection: 'column',
654
702
  overflow: 'hidden',
655
- }, children: [jsx(GlobalStyles, { styles: {
656
- 'button[aria-label="Pin to right"]': { display: 'none' },
657
- '.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
658
- '.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
659
- '.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
660
- '.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
661
- ...tableStyleOverrides,
662
- } }), 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 }) })] }));
703
+ }, children: [jsx(GlobalStyles, { styles: tableStyleOverrides }), 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 }) })] }));
663
704
  }
664
705
 
665
706
  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: {
@@ -1,62 +1,61 @@
1
- import { jsx, Fragment } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useState, useCallback, useRef, useEffect } from 'react';
3
3
  import { useIdleTimer } from 'react-idle-timer';
4
4
  import ConfirmationDialog from '../ModalField/ConfirmationDialog.esm.js';
5
5
 
6
6
  const secondsToMMSS = (totalSeconds) => {
7
7
  const minutes = Math.floor(totalSeconds / 60);
8
8
  const seconds = Math.floor(totalSeconds % 60);
9
- const formattedMinutes = String(minutes).padStart(2, '0');
10
- const formattedSeconds = String(seconds).padStart(2, '0');
11
- return `${formattedMinutes}:${formattedSeconds}`;
9
+ return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
12
10
  };
13
11
  const promptBeforeValue = 120000;
14
12
  const SessionTimeOut = ({ sessionTime, handleSubmitSession, onSessionExpire, handleSessionCancel, SubmitButtonName = 'Submit', }) => {
15
13
  const [openConfirmModal, setOpenConfirmModal] = useState(false);
16
- const expiryDetails = sessionTime;
17
- const providedTimestamp = new Date(expiryDetails).getTime();
14
+ const providedTimestamp = new Date(sessionTime).getTime();
18
15
  const currentTimestamp = new Date().getTime();
19
- const remainingTime = providedTimestamp - currentTimestamp;
20
- const timeout = remainingTime;
16
+ const timeout = providedTimestamp - currentTimestamp;
21
17
  const promptBeforeIdle = Math.min(promptBeforeValue, timeout - 1000);
22
18
  const [, setRemainingTimeInSeconds] = useState(Math.ceil(timeout / 1000));
23
19
  const [formattedRemainingTime, setFormattedRemainingTime] = useState(secondsToMMSS(Math.ceil(timeout / 1000)));
24
- const onIdle = () => {
25
- if (sessionTime) {
20
+ const onIdle = useCallback(() => {
21
+ if (sessionTime)
26
22
  onSessionExpire();
27
- }
28
- };
29
- const handleSubmit = () => {
30
- handleSubmitSession();
31
- setOpenConfirmModal(false);
32
- };
33
- const handleCancel = () => {
34
- handleSessionCancel();
35
- setOpenConfirmModal(false);
36
- };
37
- const onPrompt = () => {
23
+ }, [sessionTime, onSessionExpire]);
24
+ const onPrompt = useCallback(() => {
38
25
  setOpenConfirmModal(true);
39
- };
26
+ }, []);
40
27
  const { getRemainingTime } = useIdleTimer({
41
28
  onIdle,
42
29
  onPrompt,
43
30
  promptBeforeIdle,
44
31
  timeout,
45
- throttle: 1000, // Adjust throttle to reduce delays
32
+ throttle: 1000,
46
33
  });
34
+ // Keep a ref to getRemainingTime so the interval effect never needs it
35
+ // as a reactive dependency — react-idle-timer guarantees it is stable,
36
+ // but ESLint cannot verify that, which previously caused a spurious
37
+ // re-subscription on every render.
38
+ const getRemainingTimeRef = useRef(getRemainingTime);
39
+ getRemainingTimeRef.current = getRemainingTime;
47
40
  useEffect(() => {
48
41
  const interval = setInterval(() => {
49
- const remainingSeconds = Math.ceil(getRemainingTime() / 1000);
50
- setRemainingTimeInSeconds(promptBeforeIdle !== promptBeforeValue ? remainingSeconds : 0);
51
- const formattedTime = secondsToMMSS(remainingSeconds);
52
- setFormattedRemainingTime(promptBeforeIdle !== promptBeforeValue ? formattedTime : '00:00');
53
- }, 1000); // Adjust interval to 1000 ms
54
- return () => {
55
- clearInterval(interval);
56
- };
57
- }, [promptBeforeIdle, getRemainingTime]);
42
+ const remainingSeconds = Math.ceil(getRemainingTimeRef.current() / 1000);
43
+ const show = promptBeforeIdle !== promptBeforeValue;
44
+ setRemainingTimeInSeconds(show ? remainingSeconds : 0);
45
+ setFormattedRemainingTime(show ? secondsToMMSS(remainingSeconds) : '00:00');
46
+ }, 1000);
47
+ return () => clearInterval(interval);
48
+ }, [promptBeforeIdle]); // getRemainingTime intentionally omitted — stable ref used instead
49
+ const handleSubmit = useCallback(() => {
50
+ handleSubmitSession();
51
+ setOpenConfirmModal(false);
52
+ }, [handleSubmitSession]);
53
+ const handleCancel = useCallback(() => {
54
+ handleSessionCancel();
55
+ setOpenConfirmModal(false);
56
+ }, [handleSessionCancel]);
58
57
  const sessionMessage = `Your session will expire in ${formattedRemainingTime} seconds. Do you want to extend the session?`;
59
- return (jsx(Fragment, { children: jsx(ConfirmationDialog, { openConfirmDialog: openConfirmModal, handleCancel: handleCancel, onClickSubmit: handleSubmit, text: sessionMessage, Submit: SubmitButtonName }) }));
58
+ return (jsx(ConfirmationDialog, { openConfirmDialog: openConfirmModal, handleCancel: handleCancel, onClickSubmit: handleSubmit, text: sessionMessage, Submit: SubmitButtonName }));
60
59
  };
61
60
 
62
61
  export { SessionTimeOut as default };
@@ -1,4 +1,4 @@
1
- import { __exports as DateInput } from '../../../../../_virtual/index.esm14.js';
1
+ import { __exports as DateInput } from '../../../../../_virtual/index.esm13.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireClassnames } from '../../../../classnames/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DateRange } from '../../../../../_virtual/index.esm4.js';
1
+ import { __exports as DateRange } from '../../../../../_virtual/index.esm6.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireCalendar } from '../Calendar/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DateRangePicker } from '../../../../../_virtual/index.esm5.js';
1
+ import { __exports as DateRangePicker } from '../../../../../_virtual/index.esm4.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireDateRange } from '../DateRange/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DayCell } from '../../../../../_virtual/index.esm11.js';
1
+ import { __exports as DayCell } from '../../../../../_virtual/index.esm12.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireClassnames } from '../../../../classnames/index.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as DefinedRange } from '../../../../../_virtual/index.esm6.js';
1
+ import { __exports as DefinedRange } from '../../../../../_virtual/index.esm5.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireStyles } from '../../styles.esm.js';
@@ -1,4 +1,4 @@
1
- import { __exports as InputRangeField } from '../../../../../_virtual/index.esm12.js';
1
+ import { __exports as InputRangeField } from '../../../../../_virtual/index.esm11.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
 
@@ -1,4 +1,4 @@
1
- import { __exports as Month } from '../../../../../_virtual/index.esm13.js';
1
+ import { __exports as Month } from '../../../../../_virtual/index.esm14.js';
2
2
  import React__default from 'react';
3
3
  import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
4
4
  import { __require as requireDayCell } from '../DayCell/index.esm.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tp-react-elements",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "React form components library built with React Hook Form and Yup",