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,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';
@@ -17,6 +17,8 @@ import FilterListIcon from '@mui/icons-material/FilterList';
17
17
  import CheckIcon from '@mui/icons-material/Check';
18
18
  import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
19
19
  import SearchIcon from '@mui/icons-material/Search';
20
+ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
21
+ import ChevronRightIcon from '@mui/icons-material/ChevronRight';
20
22
 
21
23
  // Enable strict-format parsing so dayjs can handle the DD/MM/YYYY strings
22
24
  // that DatepickerWrapperV2 passes to onChangeFn
@@ -30,6 +32,7 @@ const TYPE_PILL = {
30
32
  dropdown: { label: 'Select', color: '#6a1b9a', bg: '#f3e5f5' },
31
33
  searchableDropdown: { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
32
34
  multiSelect: { label: 'Multi', color: '#0277bd', bg: '#e1f5fe' },
35
+ monthPicker: { label: 'Month', color: '#6a1b9a', bg: '#f3e5f5' },
33
36
  };
34
37
  // Column filter type pills (used in COLUMN FILTERS section of Manage Filters)
35
38
  const COLUMN_TYPE_PILL = {
@@ -78,7 +81,7 @@ const fmtDate = (iso) => {
78
81
  const yearSuffix = y !== thisYear ? ` '${y.slice(2)}` : '';
79
82
  return `${Number(d)} ${months[Number(m) - 1]}${yearSuffix}`;
80
83
  };
81
- const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove, chipStyle }) => {
84
+ const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove, chipStyle, baseId }) => {
82
85
  // Inject react-date-range CSS via JS (not CSS import) to prevent it from being
83
86
  // bundled into the library's extracted CSS file and leaking into consumer apps.
84
87
  useRdrStyles();
@@ -120,14 +123,14 @@ const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove, chipStyle }
120
123
  setAnchor(null);
121
124
  }
122
125
  };
123
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipLabel : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: openPicker, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipLabel }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, overflow: 'hidden' } }, children: [jsx(distExports.DateRange, { editableDateInputs: false, onChange: handleRangeChange, moveRangeOnFirstSelection: false, ranges: selRange, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#1976d2'] }), active && (jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end', px: 2, pb: 1.5 }, children: jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply('', ''); setAnchor(null); }, children: "Clear" }) }))] })] }));
126
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipLabel : '', placement: "top", arrow: true, children: jsx(Button, { id: baseId ? `${baseId}_chip` : undefined, variant: "outlined", size: "small", onClick: openPicker, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { id: baseId ? `${baseId}_remove_icon` : undefined, sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipLabel }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, overflow: 'hidden' } }, children: [jsx(distExports.DateRange, { editableDateInputs: false, onChange: handleRangeChange, moveRangeOnFirstSelection: false, ranges: selRange, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#1976d2'] }), active && (jsx(Box, { sx: { display: 'flex', justifyContent: 'flex-end', px: 2, pb: 1.5 }, children: jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply('', ''); setAnchor(null); }, children: "Clear" }) }))] })] }));
124
127
  };
125
- const TextFilterChip = ({ label, value, onApply, onRemove, placeholder, chipStyle }) => {
128
+ const TextFilterChip = ({ label, value, onApply, onRemove, placeholder, chipStyle, baseId }) => {
126
129
  const [anchor, setAnchor] = useState(null);
127
130
  const [draft, setDraft] = useState('');
128
131
  const active = Boolean(value);
129
132
  const chipText = active ? `${label}: ${value}` : label;
130
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: (e) => { setDraft(value); setAnchor(e.currentTarget); }, endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, minWidth: 240 } }, children: jsxs(Box, { sx: { p: 2 }, children: [jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", display: "block", mb: 1.5, children: label }), jsx(TextField, { value: draft, onChange: (e) => setDraft(e.target.value), placeholder: placeholder, size: "small", fullWidth: true, autoFocus: true, onKeyDown: (e) => {
133
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { id: baseId ? `${baseId}_chip` : undefined, variant: "outlined", size: "small", onClick: (e) => { setDraft(value); setAnchor(e.currentTarget); }, endIcon: jsx(CloseIcon, { id: baseId ? `${baseId}_remove_icon` : undefined, sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, minWidth: 240 } }, children: jsxs(Box, { sx: { p: 2 }, children: [jsx(Typography, { variant: "caption", fontWeight: 600, color: "text.secondary", display: "block", mb: 1.5, children: label }), jsx(TextField, { value: draft, onChange: (e) => setDraft(e.target.value), placeholder: placeholder, size: "small", fullWidth: true, autoFocus: true, onKeyDown: (e) => {
131
134
  if (e.key === 'Enter') {
132
135
  onApply(draft);
133
136
  setAnchor(null);
@@ -136,7 +139,7 @@ const TextFilterChip = ({ label, value, onApply, onRemove, placeholder, chipStyl
136
139
  setAnchor(null);
137
140
  } }), jsxs(Stack, { direction: "row", spacing: 1, justifyContent: "flex-end", mt: 1.5, children: [active && (jsx(Button, { size: "small", color: "inherit", onClick: () => { onApply(''); setAnchor(null); }, children: "Clear" })), jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: () => { onApply(draft); setAnchor(null); }, children: "Apply" })] })] }) })] }));
138
141
  };
139
- const DatePickerChip = ({ label, value, onApply, onRemove, chipStyle }) => {
142
+ const DatePickerChip = ({ label, value, onApply, onRemove, chipStyle, baseId }) => {
140
143
  const [open, setOpen] = useState(false);
141
144
  const [anchorEl, setAnchorEl] = useState(null);
142
145
  const active = Boolean(value);
@@ -160,7 +163,7 @@ const DatePickerChip = ({ label, value, onApply, onRemove, chipStyle }) => {
160
163
  // LocalizationProvider must wrap DatePicker — scoped here so it only
161
164
  // loads when the chip is actually used (DatePickerChip is tree-shaken
162
165
  // if the 'date' filter type is never included in serverFilterDefs).
163
- jsxs(LocalizationProvider, { dateAdapter: AdapterDayjs, children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: handleChipClick, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(DatePicker, { value: dayjsValue, open: open, onClose: () => setOpen(false), onChange: handleChange, slotProps: {
166
+ jsxs(LocalizationProvider, { dateAdapter: AdapterDayjs, children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { id: baseId ? `${baseId}_chip` : undefined, variant: "outlined", size: "small", onClick: handleChipClick, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { id: baseId ? `${baseId}_remove_icon` : undefined, sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(DatePicker, { value: dayjsValue, open: open, onClose: () => setOpen(false), onChange: handleChange, slotProps: {
164
167
  textField: { sx: { display: 'none' }, size: 'small' },
165
168
  popper: {
166
169
  anchorEl,
@@ -168,7 +171,7 @@ const DatePickerChip = ({ label, value, onApply, onRemove, chipStyle }) => {
168
171
  },
169
172
  } })] }));
170
173
  };
171
- const DropdownFilterChip = ({ label, value, options, onApply, onRemove, chipStyle }) => {
174
+ const DropdownFilterChip = ({ label, value, options, onApply, onRemove, chipStyle, baseId }) => {
172
175
  const [anchor, setAnchor] = useState(null);
173
176
  const active = Boolean(value);
174
177
  const selectedLabel = options.find((o) => o.value === value)?.label ?? '';
@@ -199,7 +202,7 @@ const DropdownFilterChip = ({ label, value, options, onApply, onRemove, chipStyl
199
202
  ],
200
203
  };
201
204
  const chipText = active ? `${label}: ${selectedLabel}` : label;
202
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: (e) => setAnchor(e.currentTarget), startIcon: jsx(ArrowDropDownIcon, { sx: { fontSize: '15px !important', mr: -0.5 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, p: 1.5, minWidth: 240 } }, children: jsx(SingleSelectNonAutoComplete, { props: {
205
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { id: baseId ? `${baseId}_chip` : undefined, variant: "outlined", size: "small", onClick: (e) => setAnchor(e.currentTarget), startIcon: jsx(ArrowDropDownIcon, { sx: { fontSize: '15px !important', mr: -0.5 } }), endIcon: jsx(CloseIcon, { id: baseId ? `${baseId}_remove_icon` : undefined, sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, p: 1.5, minWidth: 240 } }, children: jsx(SingleSelectNonAutoComplete, { props: {
203
206
  item,
204
207
  register,
205
208
  control,
@@ -210,7 +213,7 @@ const DropdownFilterChip = ({ label, value, options, onApply, onRemove, chipStyl
210
213
  watch,
211
214
  }, variant: "outlined" }) })] }));
212
215
  };
213
- const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeholder = 'Search…', chipStyle, }) => {
216
+ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeholder = 'Search…', chipStyle, baseId, }) => {
214
217
  const [anchor, setAnchor] = useState(null);
215
218
  const [search, setSearch] = useState('');
216
219
  const active = Boolean(value);
@@ -228,7 +231,7 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
228
231
  ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))
229
232
  : options;
230
233
  const chipText = active ? `${label}: ${selectedLabel}` : label;
231
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: open, startIcon: jsx(SearchIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: {
234
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { id: baseId ? `${baseId}_chip` : undefined, variant: "outlined", size: "small", onClick: open, startIcon: jsx(SearchIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { id: baseId ? `${baseId}_remove_icon` : undefined, sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: {
232
235
  sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 280, display: 'flex', flexDirection: 'column' },
233
236
  }, children: [jsx(Box, { sx: { px: 1.5, pt: 1.5, pb: 0.75 }, children: jsx(TextField, { size: "small", fullWidth: true, autoFocus: true, placeholder: placeholder, value: search, onChange: (e) => setSearch(e.target.value), onKeyDown: (e) => { if (e.key === 'Escape')
234
237
  setAnchor(null); }, InputProps: {
@@ -246,15 +249,24 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
246
249
  } }), isSelected && (jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } }))] }, opt.value));
247
250
  }))] })] })] }));
248
251
  };
249
- const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemove, chipStyle, }) => {
252
+ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemove, chipStyle, baseId, }) => {
250
253
  const [anchor, setAnchor] = useState(null);
254
+ // Draft is only committed on Apply — prevents an API call per checkbox tick.
255
+ const [draft, setDraft] = useState([]);
251
256
  const active = selectedValues.length > 0;
252
- const toggle = (value) => {
253
- const next = selectedValues.includes(value)
254
- ? selectedValues.filter((v) => v !== value)
255
- : [...selectedValues, value];
256
- onApply(next);
257
+ const openPopover = (e) => {
258
+ setDraft(selectedValues); // always start from the committed state
259
+ setAnchor(e.currentTarget);
260
+ };
261
+ const toggleDraft = (value) => setDraft((prev) => prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]);
262
+ const allSelected = draft.length === options.length && options.length > 0;
263
+ const someSelected = draft.length > 0 && !allSelected;
264
+ const toggleAll = () => setDraft(allSelected ? [] : options.map((o) => o.value));
265
+ const handleApply = () => {
266
+ onApply(draft);
267
+ setAnchor(null);
257
268
  };
269
+ // Chip label reflects committed values, not the open draft
258
270
  const chipText = (() => {
259
271
  if (!active)
260
272
  return label;
@@ -268,18 +280,100 @@ const MultiSelectFilterChip = ({ label, selectedValues, options, onApply, onRemo
268
280
  }
269
281
  return `${label}: ${selectedValues.length} selected`;
270
282
  })();
271
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { variant: "outlined", size: "small", onClick: (e) => setAnchor(e.currentTarget), startIcon: jsx(FilterListIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 260 } }, children: [active && (jsxs(Box, { sx: {
283
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { id: baseId ? `${baseId}_chip` : undefined, variant: "outlined", size: "small", onClick: openPopover, startIcon: jsx(FilterListIcon, { sx: { fontSize: '14px !important', mr: -0.25 } }), endIcon: jsx(CloseIcon, { id: baseId ? `${baseId}_remove_icon` : undefined, sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 260 } }, children: [jsxs(Box, { sx: {
272
284
  px: 2, py: 0.75,
273
285
  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
274
286
  borderBottom: '1px solid', borderColor: 'divider',
275
- }, children: [jsxs(Typography, { sx: { fontSize: 11, color: 'primary.main', fontWeight: 600 }, children: [selectedValues.length, " selected"] }), jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => onApply([]), children: "Clear all" })] })), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 300, overflowY: 'auto', py: 0.5 }, children: options.map((opt) => {
276
- const checked = selectedValues.includes(opt.value);
277
- return (jsxs(ListItemButton, { onClick: () => toggle(opt.value), sx: { px: 1, py: 0.25 }, children: [jsx(Checkbox, { checked: checked, size: "small", disableRipple: true, sx: { p: 0.5, mr: 0.5, '& .MuiSvgIcon-root': { fontSize: 16 } } }), jsx(ListItemText, { primary: opt.label, primaryTypographyProps: {
287
+ }, children: [jsx(Typography, { sx: { fontSize: 11, color: draft.length > 0 ? 'primary.main' : 'text.secondary', fontWeight: draft.length > 0 ? 600 : 400 }, children: draft.length > 0 ? `${draft.length} of ${options.length} selected` : 'Select options' }), draft.length > 0 && (jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => setDraft([]), children: "Clear all" }))] }), jsxs(ListItemButton, { onClick: toggleAll, sx: { px: 1, py: 0.25 }, children: [jsx(Checkbox, { checked: allSelected, indeterminate: someSelected, size: "small", disableRipple: true, sx: { p: 0.5, mr: 0.5, '& .MuiSvgIcon-root': { fontSize: 16 } } }), jsx(ListItemText, { primary: "All", primaryTypographyProps: {
288
+ fontSize: 13,
289
+ fontStyle: 'italic',
290
+ color: allSelected || someSelected ? 'primary.main' : 'text.secondary',
291
+ fontWeight: allSelected ? 600 : 400,
292
+ } })] }), jsx(Divider, {}), jsx(List, { dense: true, disablePadding: true, sx: { maxHeight: 240, overflowY: 'auto', py: 0.5 }, children: options.map((opt) => {
293
+ const checked = draft.includes(opt.value);
294
+ return (jsxs(ListItemButton, { onClick: () => toggleDraft(opt.value), sx: { px: 1, py: 0.25 }, children: [jsx(Checkbox, { checked: checked, size: "small", disableRipple: true, sx: { p: 0.5, mr: 0.5, '& .MuiSvgIcon-root': { fontSize: 16 } } }), jsx(ListItemText, { primary: opt.label, primaryTypographyProps: {
278
295
  fontSize: 13,
279
296
  color: checked ? 'primary.main' : 'text.primary',
280
297
  fontWeight: checked ? 600 : 400,
281
298
  } })] }, opt.value));
282
- }) })] })] }));
299
+ }) }), jsx(Box, { sx: {
300
+ px: 1.5, py: 1,
301
+ display: 'flex', justifyContent: 'flex-end',
302
+ borderTop: '1px solid', borderColor: 'divider',
303
+ }, children: jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: handleApply, children: "Apply" }) })] })] }));
304
+ };
305
+ // ─── MonthPickerChip ─────────────────────────────────────────────────────────
306
+ //
307
+ // Multi-month selection chip. Users navigate by year and tick individual months.
308
+ // Draft changes are buffered locally; `onApply` is called once when Apply is
309
+ // clicked (single API call per filter action).
310
+ //
311
+ // Value format: comma-separated "YYYY-MM" strings, e.g. "2025-01,2025-03".
312
+ // ─────────────────────────────────────────────────────────────────────────────
313
+ const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
314
+ const MonthPickerChip = ({ label, value, onApply, onRemove, chipStyle, baseId }) => {
315
+ const parseYMs = (v) => v ? v.split(',').filter(Boolean) : [];
316
+ const [anchor, setAnchor] = useState(null);
317
+ const [viewYear, setViewYear] = useState(() => new Date().getFullYear());
318
+ const [draft, setDraft] = useState(() => parseYMs(value));
319
+ const committed = parseYMs(value);
320
+ const active = committed.length > 0;
321
+ // Sync view year to earliest selected year when opening
322
+ const openPopover = (e) => {
323
+ const yms = parseYMs(value);
324
+ setDraft(yms);
325
+ if (yms.length > 0) {
326
+ const years = yms.map((ym) => Number(ym.split('-')[0]));
327
+ setViewYear(Math.min(...years));
328
+ }
329
+ setAnchor(e.currentTarget);
330
+ };
331
+ const toggleMonth = (ym) => setDraft((prev) => prev.includes(ym) ? prev.filter((v) => v !== ym) : [...prev, ym]);
332
+ const handleApply = () => {
333
+ // Sort selected months chronologically before storing
334
+ onApply([...draft].sort().join(','));
335
+ setAnchor(null);
336
+ };
337
+ const chipText = (() => {
338
+ if (!active)
339
+ return label;
340
+ if (committed.length === 1) {
341
+ const [y, m] = committed[0].split('-');
342
+ return `${MONTH_LABELS[Number(m) - 1]} ${y}`;
343
+ }
344
+ if (committed.length <= 3) {
345
+ const parts = committed.map((ym) => {
346
+ const [y, m] = ym.split('-');
347
+ return `${MONTH_LABELS[Number(m) - 1]} '${y.slice(2)}`;
348
+ });
349
+ return `${label}: ${parts.join(', ')}`;
350
+ }
351
+ return `${label}: ${committed.length} months`;
352
+ })();
353
+ return (jsxs(Fragment, { children: [jsx(Tooltip, { title: active ? chipText : '', placement: "top", arrow: true, children: jsx(Button, { id: baseId ? `${baseId}_chip` : undefined, variant: "outlined", size: "small", onClick: openPopover, startIcon: jsx(CalendarTodayIcon, { sx: { fontSize: 13 } }), endIcon: jsx(CloseIcon, { id: baseId ? `${baseId}_remove_icon` : undefined, sx: { fontSize: '12px !important' }, onClick: (e) => { e.stopPropagation(); onRemove(); } }), sx: chipSx(active, chipStyle), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: { sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 280 } }, children: [jsxs(Box, { sx: {
354
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
355
+ px: 1.5, py: 0.75,
356
+ borderBottom: '1px solid', borderColor: 'divider',
357
+ }, children: [jsx(IconButton, { id: baseId ? `${baseId}_prev_year_icon` : undefined, size: "small", onClick: () => setViewYear((y) => y - 1), children: jsx(ChevronLeftIcon, { sx: { fontSize: 18 } }) }), jsx(Typography, { fontWeight: 600, sx: { fontSize: 14, userSelect: 'none' }, children: viewYear }), jsx(IconButton, { id: baseId ? `${baseId}_next_year_icon` : undefined, size: "small", onClick: () => setViewYear((y) => y + 1), children: jsx(ChevronRightIcon, { sx: { fontSize: 18 } }) })] }), jsx(Box, { sx: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 0.75, p: 1.5 }, children: MONTH_LABELS.map((mon, idx) => {
358
+ const ym = `${viewYear}-${String(idx + 1).padStart(2, '0')}`;
359
+ const isSelected = draft.includes(ym);
360
+ return (jsx(Button, { id: baseId ? `${baseId}_month_${ym}` : undefined, variant: isSelected ? 'contained' : 'outlined', size: "small", disableElevation: true, onClick: () => toggleMonth(ym), sx: {
361
+ fontSize: 12,
362
+ textTransform: 'none',
363
+ borderRadius: 2,
364
+ py: 0.75,
365
+ borderColor: isSelected ? 'primary.main' : 'divider',
366
+ fontWeight: isSelected ? 700 : 400,
367
+ }, children: mon }, ym));
368
+ }) }), draft.length > 0 && (jsxs(Box, { sx: {
369
+ px: 2, py: 0.75,
370
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
371
+ borderTop: '1px solid', borderColor: 'divider',
372
+ }, children: [jsxs(Typography, { sx: { fontSize: 11, color: 'primary.main', fontWeight: 600 }, children: [draft.length, " month", draft.length > 1 ? 's' : '', " selected"] }), jsx(Button, { size: "small", color: "inherit", sx: { fontSize: 11, py: 0, textTransform: 'none', minWidth: 'auto' }, onClick: () => setDraft([]), children: "Clear" })] })), jsx(Box, { sx: {
373
+ px: 1.5, py: 1,
374
+ display: 'flex', justifyContent: 'flex-end',
375
+ borderTop: '1px solid', borderColor: 'divider',
376
+ }, children: jsx(Button, { size: "small", variant: "contained", disableElevation: true, onClick: handleApply, children: "Apply" }) })] })] }));
283
377
  };
284
378
  const GlobalSearchInput = memo(({ value, onChange, chipStyle, placeholder = 'Search…' }) => {
285
379
  const [draft, setDraft] = useState(value);
@@ -316,22 +410,22 @@ const GlobalSearchInput = memo(({ value, onChange, chipStyle, placeholder = 'Sea
316
410
  } }));
317
411
  });
318
412
  GlobalSearchInput.displayName = 'GlobalSearchInput';
319
- const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, }) => {
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 }));
425
+ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, gridId, }) => {
320
426
  const [anchor, setAnchor] = useState(null);
321
427
  const atLimit = visibleIds.length >= maxVisible;
322
- // Shared pill box renderer
323
- const PillBox = ({ bg, color, label }) => (jsx(Box, { component: "span", sx: {
324
- mr: 1.25, flexShrink: 0,
325
- fontSize: 10, fontWeight: 700,
326
- px: 0.75, py: '2px',
327
- borderRadius: '4px',
328
- bgcolor: bg,
329
- color,
330
- textTransform: 'uppercase',
331
- letterSpacing: 0.4,
332
- lineHeight: 1.6,
333
- }, children: label }));
334
- return (jsxs(Fragment, { children: [jsx(Tooltip, { title: "Add or remove filter chips", children: jsx(Button, { size: "small", variant: "outlined", startIcon: jsx(TuneIcon, { sx: { fontSize: 14 } }), onClick: (e) => setAnchor(e.currentTarget), sx: {
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: {
335
429
  borderRadius: 5, textTransform: 'none', fontSize: chipStyle.fontSize,
336
430
  borderColor: 'divider', color: 'text.secondary',
337
431
  px: chipStyle.px ?? 1.5, minHeight: chipStyle.minHeight,
@@ -362,7 +456,7 @@ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, o
362
456
  } }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }) }) }, col.id));
363
457
  }) })] }))] })] })] }));
364
458
  };
365
- const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, uiDensity = 'default', filterChipFontSize, filterChipMinHeight, filterLabelFontSize, globalFilter, onGlobalFilterChange, globalSearchPlaceholder, }) => {
459
+ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, uiDensity = 'default', filterChipFontSize, filterChipMinHeight, filterLabelFontSize, globalFilter, onGlobalFilterChange, globalSearchPlaceholder, gridId, }) => {
366
460
  const chipStyle = {
367
461
  fontSize: filterChipFontSize ?? (uiDensity === 'compact' ? 11 : 12),
368
462
  minHeight: filterChipMinHeight ?? (uiDensity === 'compact' ? 26 : 30),
@@ -370,20 +464,24 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
370
464
  const labelFontSize = filterLabelFontSize ?? (uiDensity === 'compact' ? 11 : 12);
371
465
  // ── Single-value column filter helpers ──────────────────────────────────────
372
466
  const getColValue = (id) => columnFilters.find((f) => f.id === id)?.value ?? '';
373
- const applyColFilter = (id, value) => {
374
- const rest = columnFilters.filter((f) => f.id !== id);
375
- onColumnFilterChange(value ? [...rest, { id, value }] : rest);
376
- };
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]);
377
473
  // ── Multi-value column filter helpers ────────────────────────────────────────
378
474
  const getColValues = (id) => {
379
475
  const val = columnFilters.find((f) => f.id === id)?.value;
380
476
  return Array.isArray(val) ? val : [];
381
477
  };
382
- const applyColMultiFilter = (id, values) => {
383
- const rest = columnFilters.filter((f) => f.id !== id);
384
- onColumnFilterChange(values.length > 0 ? [...rest, { id, value: values }] : rest);
385
- };
386
- const removeFilter = (id) => {
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) => {
387
485
  const def = serverFilterDefs.find((d) => d.id === id);
388
486
  if (def?.type === 'dateRange') {
389
487
  onFilterValuesChange({ ...filterValues, [def.fromKey]: '', [def.toKey]: '' });
@@ -391,14 +489,14 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
391
489
  else if (def?.type === 'text' ||
392
490
  def?.type === 'dropdown' ||
393
491
  def?.type === 'searchableDropdown' ||
394
- def?.type === 'multiSelect') {
492
+ def?.type === 'multiSelect' ||
493
+ def?.type === 'monthPicker') {
395
494
  onFilterValuesChange({ ...filterValues, [def.filterKey]: '' });
396
495
  }
397
496
  else if (def?.type === 'date') {
398
497
  applyColFilter(def.columnKey, '');
399
498
  }
400
499
  else {
401
- // Column filter — multi-select stores string[], others store string
402
500
  const colDef = availableColumns.find((c) => c.id === id);
403
501
  if (colDef?.filterType === 'multi-select') {
404
502
  applyColMultiFilter(id, []);
@@ -408,50 +506,52 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
408
506
  }
409
507
  }
410
508
  onToggleFilter(id);
411
- };
509
+ }, [filterValues, serverFilterDefs, availableColumns, onFilterValuesChange, onToggleFilter, applyColFilter, applyColMultiFilter]);
412
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) => {
413
511
  const def = serverFilterDefs.find((d) => d.id === id);
414
512
  if (def?.type === 'dateRange') {
415
- return (jsx(DateRangeChip, { label: def.label, fromDate: filterValues[def.fromKey] ?? '', toDate: filterValues[def.toKey] ?? '', onApply: (from, to) => onFilterValuesChange({ ...filterValues, [def.fromKey]: from, [def.toKey]: to }), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
513
+ return (jsx(DateRangeChip, { label: def.label, fromDate: filterValues[def.fromKey] ?? '', toDate: filterValues[def.toKey] ?? '', onApply: (from, to) => onFilterValuesChange({ ...filterValues, [def.fromKey]: from, [def.toKey]: to }), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
416
514
  }
417
515
  if (def?.type === 'text') {
418
- return (jsx(TextFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder, chipStyle: chipStyle }, id));
516
+ return (jsx(TextFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder, chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
419
517
  }
420
518
  if (def?.type === 'dropdown') {
421
- return (jsx(DropdownFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
519
+ return (jsx(DropdownFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
422
520
  }
423
521
  if (def?.type === 'searchableDropdown') {
424
- return (jsx(SearchableSelectChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder ?? 'Search…', chipStyle: chipStyle }, id));
522
+ return (jsx(SearchableSelectChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder ?? 'Search…', chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
425
523
  }
426
524
  if (def?.type === 'multiSelect') {
427
- // filterValues[key] stores comma-separated values: "Pending,Shipped"
428
525
  const selected = (filterValues[def.filterKey] ?? '')
429
526
  .split(',')
430
527
  .filter(Boolean);
431
528
  return (jsx(MultiSelectFilterChip, { label: def.label, selectedValues: selected, options: def.options, onApply: (vals) => onFilterValuesChange({
432
529
  ...filterValues,
433
530
  [def.filterKey]: vals.join(','),
434
- }), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
531
+ }), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
532
+ }
533
+ if (def?.type === 'monthPicker') {
534
+ return (jsx(MonthPickerChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
435
535
  }
436
536
  if (def?.type === 'date') {
437
- return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
537
+ return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
438
538
  }
439
539
  // Column filter chip — render the variant that matches the column's filterType
440
540
  const col = availableColumns.find((c) => c.id === id);
441
541
  if (!col)
442
542
  return null;
443
543
  if (col.filterType === 'date') {
444
- return (jsx(DatePickerChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
544
+ return (jsx(DatePickerChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
445
545
  }
446
546
  if (col.filterType === 'select' || col.filterType === 'searchable-select') {
447
- return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
547
+ return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
448
548
  }
449
549
  if (col.filterType === 'multi-select') {
450
- return (jsx(MultiSelectFilterChip, { label: col.label, selectedValues: getColValues(id), options: col.filterOptions ?? [], onApply: (vals) => applyColMultiFilter(id, vals), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
550
+ return (jsx(MultiSelectFilterChip, { label: col.label, selectedValues: getColValues(id), options: col.filterOptions ?? [], onApply: (vals) => applyColMultiFilter(id, vals), onRemove: () => removeFilter(id), chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
451
551
  }
452
552
  // Default: free-text input
453
- return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}`, chipStyle: chipStyle }, id));
454
- }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle }), onGlobalFilterChange && (jsx(GlobalSearchInput, { value: globalFilter ?? '', onChange: onGlobalFilterChange, chipStyle: chipStyle, placeholder: globalSearchPlaceholder }))] }));
553
+ return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}`, chipStyle: chipStyle, baseId: gridId ? `${gridId}_${id}` : undefined }, id));
554
+ }), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle, gridId: gridId }), onGlobalFilterChange && (jsx(GlobalSearchInput, { value: globalFilter ?? '', onChange: onGlobalFilterChange, chipStyle: chipStyle, placeholder: globalSearchPlaceholder }))] }));
455
555
  };
456
556
  var DynamicFilterBar$1 = memo(DynamicFilterBar);
457
557
 
@@ -55,8 +55,14 @@ export interface GenericPaginatedGridProps<TData extends object> {
55
55
  defaultVisibleFilterIds?: string[];
56
56
  /** Number of rows fetched per API call. Default: 10 000. */
57
57
  batchSize?: number;
58
- /** CSV file name prefix. Default: "export". */
58
+ /** Excel (.xlsx) file name prefix (e.g. "invoice" → "invoice-page1.xlsx"). Default: "export". */
59
59
  exportFilename?: string;
60
+ /**
61
+ * ID prefix applied to every interactive element in this grid instance.
62
+ * Useful for automated testing and deep-linking. Defaults to `exportFilename`.
63
+ * Pattern: `{gridId}_refresh_icon`, `{gridId}_export_icon`, `{gridId}_status_chip`, etc.
64
+ */
65
+ gridId?: string;
60
66
  /**
61
67
  * Height of the entire grid component.
62
68
  * When omitted (default) the grid auto-sizes to fill the remaining viewport
@@ -95,7 +101,7 @@ export interface GenericPaginatedGridProps<TData extends object> {
95
101
  * Show the Global Search input expanded by default (instead of requiring a
96
102
  * click on the search icon to reveal it). The icon remains in the top-right
97
103
  * toolbar actions and can still be used to collapse/expand the input.
98
- * Default: `true`.
104
+ * Default: `false`.
99
105
  */
100
106
  showGlobalSearchByDefault?: boolean;
101
107
  /**
@@ -103,6 +109,18 @@ export interface GenericPaginatedGridProps<TData extends object> {
103
109
  * Accepts any valid CSS colour string.
104
110
  */
105
111
  headerBgColor?: string;
112
+ /**
113
+ * Background colour for the Excel export header row.
114
+ * Accepts a hex colour string (`#RRGGBB`).
115
+ * Falls back to `headerBgColor` when omitted, then to the default blue `#1565C0`.
116
+ */
117
+ exportHeaderBgColor?: string;
118
+ /**
119
+ * Text colour for the Excel export header row.
120
+ * Accepts a hex colour string (`#RRGGBB`).
121
+ * Defaults to white (`#FFFFFF`) when omitted.
122
+ */
123
+ exportHeaderTextColor?: string;
106
124
  /**
107
125
  * Minimum height of every header cell (CSS length or px number).
108
126
  * Default: `40px` (`34px` when `uiDensity="compact"`).
@@ -192,5 +210,5 @@ export interface GenericPaginatedGridProps<TData extends object> {
192
210
  */
193
211
  enableExpanding?: boolean;
194
212
  }
195
- declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, 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;
213
+ 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, exportHeaderBgColor, exportHeaderTextColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters, muiTableBodyCellProps: gridBodyCellProps, muiTableHeadCellProps: gridHeadCellProps, renderDetailPanel, enableExpanding, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
196
214
  export default GenericPaginatedGrid;