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.
- package/README.md +6 -2
- 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.esm5.js +2 -2
- package/dist/_virtual/index.esm6.js +2 -2
- package/dist/components/DateRangePicker/DateRangePicker.esm.js +49 -72
- package/dist/components/Form/FormRender.esm.js +1 -1
- 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/FormComponents/index.esm.js +2 -2
- 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 +17 -2
- package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +161 -61
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +21 -3
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +133 -92
- 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/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/DefinedRange/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';
|
|
@@ -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
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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:
|
|
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: [
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
375
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
/**
|
|
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: `
|
|
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;
|