tp-react-elements 1.3.9 → 1.3.11
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 +24 -0
- package/dist/_virtual/index.esm6.js +2 -2
- package/dist/_virtual/index.esm7.js +2 -2
- package/dist/components/PaginatedGrid/DynamicFilterBar.d.ts +5 -1
- package/dist/components/PaginatedGrid/DynamicFilterBar.esm.js +33 -30
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.d.ts +58 -4
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +273 -88
- package/dist/components/PaginatedGrid/index.d.ts +2 -1
- package/dist/components/PaginatedGrid/mockData.d.ts +33 -0
- package/dist/index.d.ts +1 -0
- package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,9 +165,33 @@ The library provides a comprehensive set of form components that integrate seaml
|
|
|
165
165
|
- **RadioGroup** - Radio button selection groups
|
|
166
166
|
- **Switch** - Toggle switches for boolean values
|
|
167
167
|
- **Form** - Container component with built-in validation
|
|
168
|
+
- **GenericPaginatedGrid** - Server-paginated data grid with dynamic filter chips (see Storybook **Docs → Paginated Grid**)
|
|
168
169
|
|
|
169
170
|
Refer to our Storybook documentation for detailed API documentation and examples.
|
|
170
171
|
|
|
172
|
+
### Paginated grid (Storybook)
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm run storybook
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **Docs → Paginated Grid** — full API: grid height, header wrap (`headerMinHeight` / `headerMaxHeight`), `headerBgColor`, filters, troubleshooting
|
|
179
|
+
- **Components → PaginatedGrid → Sample Grids** — mock grids (no API): header wrap test, compact layout, viewport fill
|
|
180
|
+
- **Components → PaginatedGrid → Example Grid** — live invoice API demo
|
|
181
|
+
|
|
182
|
+
Example header styling:
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
<GenericPaginatedGrid
|
|
186
|
+
headerBgColor="#e8eaf6"
|
|
187
|
+
headerMinHeight="48px"
|
|
188
|
+
headerMaxHeight="96px"
|
|
189
|
+
tableHeight="calc(100vh - 200px)"
|
|
190
|
+
autoFillViewport={false}
|
|
191
|
+
{...gridProps}
|
|
192
|
+
/>
|
|
193
|
+
```
|
|
194
|
+
|
|
171
195
|
## 🏗️ Best Practices for Code Structure and Organization
|
|
172
196
|
|
|
173
197
|
### Component Organization
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRange as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DefinedRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DefinedRange as __exports };
|
|
@@ -82,6 +82,10 @@ export interface DynamicFilterBarProps {
|
|
|
82
82
|
availableColumns: AvailableColumn[];
|
|
83
83
|
/** Maximum number of chips that may be visible at once. Default: 4. */
|
|
84
84
|
maxVisibleFilters?: number;
|
|
85
|
+
uiDensity?: 'default' | 'compact';
|
|
86
|
+
filterChipFontSize?: number;
|
|
87
|
+
filterChipMinHeight?: number;
|
|
88
|
+
filterLabelFontSize?: number;
|
|
85
89
|
}
|
|
86
|
-
declare const DynamicFilterBar: ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters, }: DynamicFilterBarProps) => import("react/jsx-runtime").JSX.Element;
|
|
90
|
+
declare const DynamicFilterBar: ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters, uiDensity, filterChipFontSize, filterChipMinHeight, filterLabelFontSize, }: DynamicFilterBarProps) => import("react/jsx-runtime").JSX.Element;
|
|
87
91
|
export default DynamicFilterBar;
|
|
@@ -38,19 +38,17 @@ const COLUMN_TYPE_PILL = {
|
|
|
38
38
|
'searchable-select': { label: 'Search', color: '#bf360c', bg: '#fbe9e7' },
|
|
39
39
|
number: { label: 'Number', color: '#e65100', bg: '#fff3e0' },
|
|
40
40
|
};
|
|
41
|
-
|
|
42
|
-
const chipSx = (active) => ({
|
|
41
|
+
const chipSx = (active, opts) => ({
|
|
43
42
|
borderRadius: 5,
|
|
44
43
|
textTransform: 'none',
|
|
45
|
-
fontSize:
|
|
44
|
+
fontSize: opts.fontSize,
|
|
46
45
|
fontWeight: active ? 600 : 400,
|
|
47
46
|
borderColor: active ? 'primary.main' : 'divider',
|
|
48
47
|
color: active ? 'primary.main' : 'text.secondary',
|
|
49
48
|
bgcolor: active ? 'rgba(25,118,210,0.08)' : 'transparent',
|
|
50
49
|
whiteSpace: 'nowrap',
|
|
51
|
-
px: 1.5,
|
|
52
|
-
minHeight:
|
|
53
|
-
// Cap chip width so long option labels don't break the filter bar layout
|
|
50
|
+
px: opts.px ?? (opts.fontSize <= 11 ? 1 : 1.5),
|
|
51
|
+
minHeight: opts.minHeight,
|
|
54
52
|
maxWidth: 260,
|
|
55
53
|
overflow: 'hidden',
|
|
56
54
|
'&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(25,118,210,0.08)' },
|
|
@@ -78,7 +76,7 @@ const fmtDate = (iso) => {
|
|
|
78
76
|
const yearSuffix = y !== thisYear ? ` '${y.slice(2)}` : '';
|
|
79
77
|
return `${Number(d)} ${months[Number(m) - 1]}${yearSuffix}`;
|
|
80
78
|
};
|
|
81
|
-
const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove }) => {
|
|
79
|
+
const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove, chipStyle }) => {
|
|
82
80
|
// Inject react-date-range CSS via JS (not CSS import) to prevent it from being
|
|
83
81
|
// bundled into the library's extracted CSS file and leaking into consumer apps.
|
|
84
82
|
useRdrStyles();
|
|
@@ -120,14 +118,14 @@ const DateRangeChip = ({ label, fromDate, toDate, onApply, onRemove }) => {
|
|
|
120
118
|
setAnchor(null);
|
|
121
119
|
}
|
|
122
120
|
};
|
|
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), 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" }) }))] })] }));
|
|
121
|
+
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" }) }))] })] }));
|
|
124
122
|
};
|
|
125
|
-
const TextFilterChip = ({ label, value, onApply, onRemove, placeholder }) => {
|
|
123
|
+
const TextFilterChip = ({ label, value, onApply, onRemove, placeholder, chipStyle }) => {
|
|
126
124
|
const [anchor, setAnchor] = useState(null);
|
|
127
125
|
const [draft, setDraft] = useState('');
|
|
128
126
|
const active = Boolean(value);
|
|
129
127
|
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), 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) => {
|
|
128
|
+
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) => {
|
|
131
129
|
if (e.key === 'Enter') {
|
|
132
130
|
onApply(draft);
|
|
133
131
|
setAnchor(null);
|
|
@@ -136,7 +134,7 @@ const TextFilterChip = ({ label, value, onApply, onRemove, placeholder }) => {
|
|
|
136
134
|
setAnchor(null);
|
|
137
135
|
} }), 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
136
|
};
|
|
139
|
-
const DatePickerChip = ({ label, value, onApply, onRemove }) => {
|
|
137
|
+
const DatePickerChip = ({ label, value, onApply, onRemove, chipStyle }) => {
|
|
140
138
|
const [open, setOpen] = useState(false);
|
|
141
139
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
142
140
|
const active = Boolean(value);
|
|
@@ -160,7 +158,7 @@ const DatePickerChip = ({ label, value, onApply, onRemove }) => {
|
|
|
160
158
|
// LocalizationProvider must wrap DatePicker — scoped here so it only
|
|
161
159
|
// loads when the chip is actually used (DatePickerChip is tree-shaken
|
|
162
160
|
// 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), children: jsx(ChipLabel, { text: chipText }) }) }), jsx(DatePicker, { value: dayjsValue, open: open, onClose: () => setOpen(false), onChange: handleChange, slotProps: {
|
|
161
|
+
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: {
|
|
164
162
|
textField: { sx: { display: 'none' }, size: 'small' },
|
|
165
163
|
popper: {
|
|
166
164
|
anchorEl,
|
|
@@ -168,7 +166,7 @@ const DatePickerChip = ({ label, value, onApply, onRemove }) => {
|
|
|
168
166
|
},
|
|
169
167
|
} })] }));
|
|
170
168
|
};
|
|
171
|
-
const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
|
|
169
|
+
const DropdownFilterChip = ({ label, value, options, onApply, onRemove, chipStyle }) => {
|
|
172
170
|
const [anchor, setAnchor] = useState(null);
|
|
173
171
|
const active = Boolean(value);
|
|
174
172
|
const selectedLabel = options.find((o) => o.value === value)?.label ?? '';
|
|
@@ -199,7 +197,7 @@ const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
|
|
|
199
197
|
],
|
|
200
198
|
};
|
|
201
199
|
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), 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: {
|
|
200
|
+
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: {
|
|
203
201
|
item,
|
|
204
202
|
register,
|
|
205
203
|
control,
|
|
@@ -210,7 +208,7 @@ const DropdownFilterChip = ({ label, value, options, onApply, onRemove }) => {
|
|
|
210
208
|
watch,
|
|
211
209
|
}, variant: "outlined" }) })] }));
|
|
212
210
|
};
|
|
213
|
-
const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeholder = 'Search…', }) => {
|
|
211
|
+
const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeholder = 'Search…', chipStyle, }) => {
|
|
214
212
|
const [anchor, setAnchor] = useState(null);
|
|
215
213
|
const [search, setSearch] = useState('');
|
|
216
214
|
const active = Boolean(value);
|
|
@@ -228,7 +226,7 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
|
|
|
228
226
|
? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase()))
|
|
229
227
|
: options;
|
|
230
228
|
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), children: jsx(ChipLabel, { text: chipText }) }) }), jsxs(Popover, { open: Boolean(anchor), anchorEl: anchor, onClose: () => setAnchor(null), anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, PaperProps: {
|
|
229
|
+
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: {
|
|
232
230
|
sx: { mt: 0.75, borderRadius: 2, boxShadow: 6, width: 280, display: 'flex', flexDirection: 'column' },
|
|
233
231
|
}, 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
232
|
setAnchor(null); }, InputProps: {
|
|
@@ -246,7 +244,7 @@ const SearchableSelectChip = ({ label, value, options, onApply, onRemove, placeh
|
|
|
246
244
|
} }), isSelected && (jsx(CheckIcon, { sx: { fontSize: 15, color: 'primary.main', flexShrink: 0 } }))] }, opt.value));
|
|
247
245
|
}))] })] })] }));
|
|
248
246
|
};
|
|
249
|
-
const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, }) => {
|
|
247
|
+
const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, onToggle, maxVisible = 4, chipStyle, }) => {
|
|
250
248
|
const [anchor, setAnchor] = useState(null);
|
|
251
249
|
const atLimit = visibleIds.length >= maxVisible;
|
|
252
250
|
// Shared pill box renderer
|
|
@@ -262,9 +260,9 @@ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, o
|
|
|
262
260
|
lineHeight: 1.6,
|
|
263
261
|
}, children: label }));
|
|
264
262
|
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: {
|
|
265
|
-
borderRadius: 5, textTransform: 'none', fontSize:
|
|
263
|
+
borderRadius: 5, textTransform: 'none', fontSize: chipStyle.fontSize,
|
|
266
264
|
borderColor: 'divider', color: 'text.secondary',
|
|
267
|
-
px: 1.5, minHeight:
|
|
265
|
+
px: chipStyle.px ?? 1.5, minHeight: chipStyle.minHeight,
|
|
268
266
|
'&:hover': { borderColor: 'primary.main', color: 'primary.main' },
|
|
269
267
|
}, children: "Manage Filters" }) }), 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: [atLimit && (jsx(Box, { sx: {
|
|
270
268
|
px: 2, py: 0.75,
|
|
@@ -292,7 +290,12 @@ const ManageFiltersButton = ({ serverFilterDefs, visibleIds, availableColumns, o
|
|
|
292
290
|
} }), selected && jsx(CheckIcon, { sx: { fontSize: 16, color: 'primary.main', flexShrink: 0 } })] }) }) }, col.id));
|
|
293
291
|
}) })] }))] })] })] }));
|
|
294
292
|
};
|
|
295
|
-
const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, }) => {
|
|
293
|
+
const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter, filterValues, onFilterValuesChange, columnFilters, onColumnFilterChange, availableColumns, maxVisibleFilters = 4, uiDensity = 'default', filterChipFontSize, filterChipMinHeight, filterLabelFontSize, }) => {
|
|
294
|
+
const chipStyle = {
|
|
295
|
+
fontSize: filterChipFontSize ?? (uiDensity === 'compact' ? 11 : 12),
|
|
296
|
+
minHeight: filterChipMinHeight ?? (uiDensity === 'compact' ? 26 : 30),
|
|
297
|
+
};
|
|
298
|
+
const labelFontSize = filterLabelFontSize ?? (uiDensity === 'compact' ? 11 : 12);
|
|
296
299
|
const getColValue = (id) => columnFilters.find((f) => f.id === id)?.value ?? '';
|
|
297
300
|
const applyColFilter = (id, value) => {
|
|
298
301
|
const rest = columnFilters.filter((f) => f.id !== id);
|
|
@@ -316,36 +319,36 @@ const DynamicFilterBar = ({ serverFilterDefs, visibleFilterIds, onToggleFilter,
|
|
|
316
319
|
}
|
|
317
320
|
onToggleFilter(id);
|
|
318
321
|
};
|
|
319
|
-
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:
|
|
322
|
+
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) => {
|
|
320
323
|
const def = serverFilterDefs.find((d) => d.id === id);
|
|
321
324
|
if (def?.type === 'dateRange') {
|
|
322
|
-
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) }, id));
|
|
325
|
+
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));
|
|
323
326
|
}
|
|
324
327
|
if (def?.type === 'text') {
|
|
325
|
-
return (jsx(TextFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id), placeholder: def.placeholder }, id));
|
|
328
|
+
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));
|
|
326
329
|
}
|
|
327
330
|
if (def?.type === 'dropdown') {
|
|
328
|
-
return (jsx(DropdownFilterChip, { label: def.label, value: filterValues[def.filterKey] ?? '', options: def.options, onApply: (v) => onFilterValuesChange({ ...filterValues, [def.filterKey]: v }), onRemove: () => removeFilter(id) }, id));
|
|
331
|
+
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));
|
|
329
332
|
}
|
|
330
333
|
if (def?.type === 'searchableDropdown') {
|
|
331
|
-
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…' }, id));
|
|
334
|
+
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));
|
|
332
335
|
}
|
|
333
336
|
if (def?.type === 'date') {
|
|
334
|
-
return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id) }, id));
|
|
337
|
+
return (jsx(DatePickerChip, { label: def.label, value: getColValue(def.columnKey), onApply: (v) => applyColFilter(def.columnKey, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
|
|
335
338
|
}
|
|
336
339
|
// Column filter chip — render the variant that matches the column's filterType
|
|
337
340
|
const col = availableColumns.find((c) => c.id === id);
|
|
338
341
|
if (!col)
|
|
339
342
|
return null;
|
|
340
343
|
if (col.filterType === 'date') {
|
|
341
|
-
return (jsx(DatePickerChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id) }, id));
|
|
344
|
+
return (jsx(DatePickerChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
|
|
342
345
|
}
|
|
343
346
|
if (col.filterType === 'select' || col.filterType === 'searchable-select') {
|
|
344
|
-
return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id) }, id));
|
|
347
|
+
return (jsx(SearchableSelectChip, { label: col.label, value: getColValue(id), options: col.filterOptions ?? [], onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), chipStyle: chipStyle }, id));
|
|
345
348
|
}
|
|
346
349
|
// Default: free-text input
|
|
347
|
-
return (jsx(TextFilterChip, { label: col.label, value: getColValue(id), onApply: (v) => applyColFilter(id, v), onRemove: () => removeFilter(id), placeholder: `Filter by ${col.label}
|
|
348
|
-
}), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters })] }));
|
|
350
|
+
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));
|
|
351
|
+
}), jsx(ManageFiltersButton, { serverFilterDefs: serverFilterDefs, visibleIds: visibleFilterIds, availableColumns: availableColumns, onToggle: onToggleFilter, maxVisible: maxVisibleFilters, chipStyle: chipStyle })] }));
|
|
349
352
|
};
|
|
350
353
|
|
|
351
354
|
export { DynamicFilterBar as default };
|
|
@@ -7,6 +7,21 @@ export interface PagedResult<TData> {
|
|
|
7
7
|
data: TData[];
|
|
8
8
|
totalRecords: number;
|
|
9
9
|
}
|
|
10
|
+
/** Preset for toolbar, filter chips, and pagination footer sizing. */
|
|
11
|
+
export type GridUiDensity = 'default' | 'compact';
|
|
12
|
+
/** Fine-grained UI sizing overrides (merged on top of `uiDensity` preset). */
|
|
13
|
+
export interface GridUiDensityOptions {
|
|
14
|
+
footerMinHeight?: number;
|
|
15
|
+
footerFontSize?: number;
|
|
16
|
+
footerIconSize?: number;
|
|
17
|
+
footerPaddingY?: number;
|
|
18
|
+
filterChipFontSize?: number;
|
|
19
|
+
filterChipMinHeight?: number;
|
|
20
|
+
filterLabelFontSize?: number;
|
|
21
|
+
toolbarIconFontSize?: number | string;
|
|
22
|
+
/** MRT built-in toolbar control density (show/hide columns, density toggle, etc.). */
|
|
23
|
+
mrtDensity?: 'comfortable' | 'compact' | 'spacious';
|
|
24
|
+
}
|
|
10
25
|
export interface GenericPaginatedGridProps<TData extends object> {
|
|
11
26
|
/**
|
|
12
27
|
* Column definitions. Use `TPColumnDef<TData>` (a superset of `MRT_ColumnDef`)
|
|
@@ -36,11 +51,35 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
36
51
|
/**
|
|
37
52
|
* Height of the entire grid component.
|
|
38
53
|
* When omitted (default) the grid auto-sizes to fill the remaining viewport
|
|
39
|
-
* height from its top position
|
|
40
|
-
* Pass an explicit CSS value
|
|
41
|
-
* (e.g. "600px", "calc(100vh - 300px)").
|
|
54
|
+
* height from its top position on mount and window resize only (not on scroll).
|
|
55
|
+
* Pass an explicit CSS value to pin height (e.g. "600px", "calc(100vh - 300px)").
|
|
42
56
|
*/
|
|
43
57
|
tableHeight?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Minimum height of the grid wrapper (CSS length).
|
|
60
|
+
* Used in auto-height `clamp()` and as `minHeight` when `tableHeight` is set.
|
|
61
|
+
* Default: `300px`.
|
|
62
|
+
*/
|
|
63
|
+
tableMinHeight?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Maximum height of the grid wrapper (CSS length).
|
|
66
|
+
* Caps auto-calculated viewport height. Default: none (only viewport clamp).
|
|
67
|
+
*/
|
|
68
|
+
tableMaxHeight?: string;
|
|
69
|
+
/**
|
|
70
|
+
* When false, disables viewport auto-height; use `tableHeight` or `tableMinHeight` only.
|
|
71
|
+
* Default: true (unless `tableHeight` is provided).
|
|
72
|
+
*/
|
|
73
|
+
autoFillViewport?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* MRT column layout. Default `grid` — columns grow to fill width (no trailing spacer column).
|
|
76
|
+
* Use `grid-no-grow` only when you need fixed column widths with horizontal scroll.
|
|
77
|
+
*/
|
|
78
|
+
layoutMode?: 'grid' | 'semantic' | 'grid-no-grow';
|
|
79
|
+
/** Compact toolbar, filter chips, and footer. Default: `default`. */
|
|
80
|
+
uiDensity?: GridUiDensity;
|
|
81
|
+
/** Override individual sizes from the `uiDensity` preset. */
|
|
82
|
+
uiDensityOptions?: GridUiDensityOptions;
|
|
44
83
|
/** Choices shown in the "Rows per page" footer dropdown. Default: [1 000, 5 000, 10 000]. */
|
|
45
84
|
pageSizeOptions?: number[];
|
|
46
85
|
/**
|
|
@@ -48,6 +87,21 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
48
87
|
* Accepts any valid CSS colour string.
|
|
49
88
|
*/
|
|
50
89
|
headerBgColor?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Minimum height of every header cell (CSS length or px number).
|
|
92
|
+
* Default: `40px` (`34px` when `uiDensity="compact"`).
|
|
93
|
+
*/
|
|
94
|
+
headerMinHeight?: string | number;
|
|
95
|
+
/**
|
|
96
|
+
* Maximum height of the header row — **all columns use the same height**.
|
|
97
|
+
* Long titles wrap inside this box. Default: `72px` (`56px` compact).
|
|
98
|
+
*/
|
|
99
|
+
headerMaxHeight?: string | number;
|
|
100
|
+
/**
|
|
101
|
+
* Optional max lines for header text (`-webkit-line-clamp`).
|
|
102
|
+
* Omit to allow text to use the full `headerMaxHeight` (recommended for tall headers).
|
|
103
|
+
*/
|
|
104
|
+
headerMaxLines?: number;
|
|
51
105
|
/**
|
|
52
106
|
* Maximum number of filter chips that can be visible at once.
|
|
53
107
|
* Prevents the filter bar from wrapping to a second row on typical screens.
|
|
@@ -55,5 +109,5 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
55
109
|
*/
|
|
56
110
|
maxVisibleFilters?: number;
|
|
57
111
|
}
|
|
58
|
-
declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, tableHeight, pageSizeOptions, headerBgColor, maxVisibleFilters, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
112
|
+
declare function GenericPaginatedGrid<TData extends object>({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize, exportFilename, tableHeight, tableMinHeight, tableMaxHeight, autoFillViewport, layoutMode, uiDensity, uiDensityOptions, pageSizeOptions, headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, maxVisibleFilters, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
59
113
|
export default GenericPaginatedGrid;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useRef, useLayoutEffect
|
|
2
|
+
import { useMemo, useState, useRef, useLayoutEffect } from 'react';
|
|
3
3
|
import { useMaterialReactTable, MaterialReactTable } from 'material-react-table';
|
|
4
4
|
import { useQuery, keepPreviousData } from '@tanstack/react-query';
|
|
5
|
-
import { Box, Tooltip, IconButton,
|
|
5
|
+
import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, CircularProgress } from '@mui/material';
|
|
6
6
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
|
7
7
|
import FileDownloadIcon from '@mui/icons-material/FileDownload';
|
|
8
8
|
import FirstPageIcon from '@mui/icons-material/FirstPage';
|
|
@@ -11,6 +11,160 @@ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
|
11
11
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
12
12
|
import DynamicFilterBar from './DynamicFilterBar.esm.js';
|
|
13
13
|
|
|
14
|
+
const UI_DENSITY_PRESETS = {
|
|
15
|
+
default: {
|
|
16
|
+
footerMinHeight: 48,
|
|
17
|
+
footerFontSize: 14,
|
|
18
|
+
footerIconSize: 20,
|
|
19
|
+
footerPaddingY: 0.75,
|
|
20
|
+
filterChipFontSize: 12,
|
|
21
|
+
filterChipMinHeight: 30,
|
|
22
|
+
filterLabelFontSize: 12,
|
|
23
|
+
toolbarIconFontSize: 'small',
|
|
24
|
+
mrtDensity: 'comfortable',
|
|
25
|
+
},
|
|
26
|
+
compact: {
|
|
27
|
+
footerMinHeight: 32,
|
|
28
|
+
footerFontSize: 12,
|
|
29
|
+
footerIconSize: 18,
|
|
30
|
+
footerPaddingY: 0.25,
|
|
31
|
+
filterChipFontSize: 11,
|
|
32
|
+
filterChipMinHeight: 26,
|
|
33
|
+
filterLabelFontSize: 11,
|
|
34
|
+
toolbarIconFontSize: 'inherit',
|
|
35
|
+
mrtDensity: 'compact',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const toCssSize = (value, fallback) => {
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
return fallback;
|
|
41
|
+
return typeof value === 'number' ? `${value}px` : value;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Header overrides applied via GlobalStyles (literal prop values).
|
|
45
|
+
* MRT sets `backgroundColor: 'inherit'` and fixed density padding — must use !important.
|
|
46
|
+
*/
|
|
47
|
+
const buildHeaderGlobalStyles = (headerMin, headerMax, headerBgColor) => {
|
|
48
|
+
const headCell = {
|
|
49
|
+
height: `${headerMax} !important`,
|
|
50
|
+
minHeight: `${headerMin} !important`,
|
|
51
|
+
maxHeight: `${headerMax} !important`,
|
|
52
|
+
boxSizing: 'border-box !important',
|
|
53
|
+
alignSelf: 'stretch !important',
|
|
54
|
+
whiteSpace: 'normal !important',
|
|
55
|
+
verticalAlign: 'middle !important',
|
|
56
|
+
pt: '8px !important',
|
|
57
|
+
pb: '8px !important',
|
|
58
|
+
px: '14px !important',
|
|
59
|
+
};
|
|
60
|
+
if (headerBgColor) {
|
|
61
|
+
headCell.backgroundColor = `${headerBgColor} !important`;
|
|
62
|
+
headCell.backgroundImage = 'none !important';
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
'.tp-gpg-root .MuiTableHead-root .MuiTableRow-root': {
|
|
66
|
+
height: `${headerMax} !important`,
|
|
67
|
+
minHeight: `${headerMin} !important`,
|
|
68
|
+
maxHeight: `${headerMax} !important`,
|
|
69
|
+
alignItems: 'stretch !important',
|
|
70
|
+
...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
|
|
71
|
+
},
|
|
72
|
+
'.tp-gpg-root .MuiTableHead-root .MuiTableCell-head': headCell,
|
|
73
|
+
'.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content': {
|
|
74
|
+
height: '100% !important',
|
|
75
|
+
width: '100% !important',
|
|
76
|
+
minHeight: '0 !important',
|
|
77
|
+
alignItems: 'center !important',
|
|
78
|
+
},
|
|
79
|
+
'.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content-Labels': {
|
|
80
|
+
overflow: 'visible !important',
|
|
81
|
+
flexWrap: 'wrap !important',
|
|
82
|
+
whiteSpace: 'normal !important',
|
|
83
|
+
textOverflow: 'clip !important',
|
|
84
|
+
width: '100% !important',
|
|
85
|
+
maxWidth: '100% !important',
|
|
86
|
+
},
|
|
87
|
+
'.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content-Wrapper': {
|
|
88
|
+
overflow: 'visible !important',
|
|
89
|
+
whiteSpace: 'normal !important',
|
|
90
|
+
textOverflow: 'clip !important',
|
|
91
|
+
wordBreak: 'break-word !important',
|
|
92
|
+
overflowWrap: 'anywhere !important',
|
|
93
|
+
width: '100% !important',
|
|
94
|
+
maxWidth: '100% !important',
|
|
95
|
+
display: 'block !important',
|
|
96
|
+
WebkitLineClamp: 'unset !important',
|
|
97
|
+
WebkitBoxOrient: 'unset !important',
|
|
98
|
+
},
|
|
99
|
+
'.tp-gpg-root .MuiTableHead-root .MuiTableSortLabel-root': {
|
|
100
|
+
whiteSpace: 'normal !important',
|
|
101
|
+
wordBreak: 'break-word !important',
|
|
102
|
+
overflowWrap: 'anywhere !important',
|
|
103
|
+
width: '100% !important',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/** Per-cell sx merged into MRT (via muiTableHeadCellProps). */
|
|
108
|
+
const buildHeaderCellSx = (align, headerMin, headerMax, headerBgColor, headerMaxLines) => {
|
|
109
|
+
const textAlign = align;
|
|
110
|
+
const flexJustify = align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start';
|
|
111
|
+
const wrapperClamp = headerMaxLines != null && headerMaxLines > 0
|
|
112
|
+
? {
|
|
113
|
+
display: '-webkit-box',
|
|
114
|
+
WebkitLineClamp: headerMaxLines,
|
|
115
|
+
WebkitBoxOrient: 'vertical',
|
|
116
|
+
overflow: 'hidden',
|
|
117
|
+
}
|
|
118
|
+
: {
|
|
119
|
+
display: 'block',
|
|
120
|
+
WebkitLineClamp: 'unset',
|
|
121
|
+
overflow: 'visible',
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
...(headerBgColor
|
|
125
|
+
? { backgroundColor: `${headerBgColor} !important`, backgroundImage: 'none' }
|
|
126
|
+
: {}),
|
|
127
|
+
height: `${headerMax} !important`,
|
|
128
|
+
minHeight: `${headerMin} !important`,
|
|
129
|
+
maxHeight: `${headerMax} !important`,
|
|
130
|
+
boxSizing: 'border-box',
|
|
131
|
+
borderRight: '1px solid rgba(224,224,224,1)',
|
|
132
|
+
borderBottom: '2px solid rgba(0,0,0,0.15)',
|
|
133
|
+
'& button[aria-label="Move"]': { display: 'none !important' },
|
|
134
|
+
'& .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
135
|
+
'& .Mui-TableHeadCell-ResizeHandle-Wrapper': { height: '100%', top: 0 },
|
|
136
|
+
'& .Mui-TableHeadCell-ResizeHandle-Divider': { borderRight: 'none' },
|
|
137
|
+
'& .Mui-TableHeadCell-Content': {
|
|
138
|
+
alignItems: 'center',
|
|
139
|
+
justifyContent: flexJustify,
|
|
140
|
+
height: '100%',
|
|
141
|
+
width: '100%',
|
|
142
|
+
},
|
|
143
|
+
'& .Mui-TableHeadCell-Content-Labels': {
|
|
144
|
+
flexWrap: 'wrap',
|
|
145
|
+
whiteSpace: 'normal',
|
|
146
|
+
textAlign,
|
|
147
|
+
width: '100%',
|
|
148
|
+
justifyContent: flexJustify,
|
|
149
|
+
overflow: 'visible',
|
|
150
|
+
},
|
|
151
|
+
'& .Mui-TableHeadCell-Content-Wrapper': {
|
|
152
|
+
whiteSpace: 'normal',
|
|
153
|
+
wordBreak: 'break-word',
|
|
154
|
+
overflowWrap: 'anywhere',
|
|
155
|
+
lineHeight: 1.25,
|
|
156
|
+
width: '100%',
|
|
157
|
+
textAlign,
|
|
158
|
+
...wrapperClamp,
|
|
159
|
+
},
|
|
160
|
+
'& .MuiTableSortLabel-root': {
|
|
161
|
+
whiteSpace: 'normal',
|
|
162
|
+
wordBreak: 'break-word',
|
|
163
|
+
width: '100%',
|
|
164
|
+
margin: 0,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
};
|
|
14
168
|
// ─── Internal: overlay loader ─────────────────────────────────────────────────
|
|
15
169
|
const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
|
|
16
170
|
position: 'absolute', inset: 0,
|
|
@@ -19,31 +173,41 @@ const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
|
|
|
19
173
|
bgcolor: 'rgba(255,255,255,0.80)', backdropFilter: 'blur(2px)',
|
|
20
174
|
zIndex: 10, gap: 1.5,
|
|
21
175
|
}, children: [jsx(CircularProgress, { size: 50, thickness: 4 }), jsx(Typography, { variant: "body2", color: "text.secondary", fontWeight: 500, children: message })] }));
|
|
22
|
-
const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, }) => {
|
|
176
|
+
const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, density, }) => {
|
|
177
|
+
const fs = density.footerFontSize;
|
|
178
|
+
const selectH = density.footerMinHeight - 8;
|
|
23
179
|
const startRow = totalRecords === 0 ? 0 : pageIndex * pageSize + 1;
|
|
24
180
|
const endRow = Math.min((pageIndex + 1) * pageSize, totalRecords);
|
|
25
181
|
return (jsxs(Box, { sx: {
|
|
26
182
|
display: 'flex', alignItems: 'center', justifyContent: 'flex-end',
|
|
27
|
-
gap: { xs: 1
|
|
183
|
+
gap: { xs: 1, md: 2 }, px: 1.5, py: density.footerPaddingY,
|
|
28
184
|
borderTop: '1px solid', borderColor: 'divider',
|
|
29
|
-
bgcolor: 'background.paper', flexWrap: 'wrap',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
185
|
+
bgcolor: 'background.paper', flexWrap: 'wrap',
|
|
186
|
+
minHeight: density.footerMinHeight,
|
|
187
|
+
flexShrink: 0,
|
|
188
|
+
}, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.75 }, children: [jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: "Rows per page:" }), jsx(Select, { value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), size: "small", variant: "outlined", sx: {
|
|
189
|
+
height: selectH, fontSize: fs - 1, minWidth: 72,
|
|
190
|
+
'.MuiSelect-select': { py: '2px !important' },
|
|
33
191
|
'.MuiOutlinedInput-notchedOutline': { borderColor: 'divider' },
|
|
34
|
-
}, children: pageSizeOptions.map((opt) => (jsx(MenuItem, { value: opt, sx: { fontSize:
|
|
192
|
+
}, children: pageSizeOptions.map((opt) => (jsx(MenuItem, { value: opt, sx: { fontSize: fs - 1 }, children: opt.toLocaleString() }, opt))) })] }), jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: isLoading || (isFetching && totalRecords === 0)
|
|
35
193
|
? 'Loading…'
|
|
36
194
|
: totalRecords === 0
|
|
37
195
|
? 'No records'
|
|
38
|
-
: `${startRow.toLocaleString()}–${endRow.toLocaleString()} of ${totalRecords.toLocaleString()}` }), jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.25 }, children: [jsx(Tooltip, { title: "First page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onFirst, disabled: !canPrev, children: jsx(FirstPageIcon, { sx: { fontSize:
|
|
39
|
-
mx: 0.5,
|
|
196
|
+
: `${startRow.toLocaleString()}–${endRow.toLocaleString()} of ${totalRecords.toLocaleString()}` }), jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.25 }, children: [jsx(Tooltip, { title: "First page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onFirst, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(FirstPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Previous page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onPrev, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(ChevronLeftIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Typography, { fontWeight: 600, sx: {
|
|
197
|
+
mx: 0.5,
|
|
198
|
+
minWidth: density.footerMinHeight - 12,
|
|
199
|
+
height: density.footerMinHeight - 12,
|
|
40
200
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
41
201
|
borderRadius: 1, border: '1px solid', borderColor: 'primary.main',
|
|
42
|
-
color: 'primary.main', bgcolor: 'rgba(25,118,210,0.07)', fontSize:
|
|
43
|
-
}, children: pageIndex + 1 }), jsx(Tooltip, { title: "Next page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onNext, disabled: !canNext, children: jsx(ChevronRightIcon, { sx: { fontSize:
|
|
202
|
+
color: 'primary.main', bgcolor: 'rgba(25,118,210,0.07)', fontSize: fs - 1,
|
|
203
|
+
}, children: pageIndex + 1 }), jsx(Tooltip, { title: "Next page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onNext, disabled: !canNext, sx: { p: 0.5 }, children: jsx(ChevronRightIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Last page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onLast, disabled: !canNext, sx: { p: 0.5 }, children: jsx(LastPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) })] })] }));
|
|
44
204
|
};
|
|
45
205
|
// ─── GenericPaginatedGrid ─────────────────────────────────────────────────────
|
|
46
|
-
function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, pageSizeOptions = [1000, 5000, 10000], headerBgColor, maxVisibleFilters = 4, }) {
|
|
206
|
+
function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity = 'default', uiDensityOptions, pageSizeOptions = [1000, 5000, 10000], headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, maxVisibleFilters = 4, }) {
|
|
207
|
+
const densityTokens = useMemo(() => ({ ...UI_DENSITY_PRESETS[uiDensity], ...uiDensityOptions }), [uiDensity, uiDensityOptions]);
|
|
208
|
+
const resolvedHeaderMinHeight = toCssSize(headerMinHeight, uiDensity === 'compact' ? '34px' : '40px');
|
|
209
|
+
const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight, uiDensity === 'compact' ? '56px' : '72px');
|
|
210
|
+
const headerGlobalStyleOverrides = useMemo(() => buildHeaderGlobalStyles(resolvedHeaderMinHeight, resolvedHeaderMaxHeight, headerBgColor), [resolvedHeaderMinHeight, resolvedHeaderMaxHeight, headerBgColor]);
|
|
47
211
|
const defaultVisible = defaultVisibleFilterIds
|
|
48
212
|
?? (serverFilterDefs.length > 0 ? [serverFilterDefs[0].id] : []);
|
|
49
213
|
// ── State ──────────────────────────────────────────────────────────────────
|
|
@@ -57,35 +221,35 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
57
221
|
const [columnFilters, setColumnFilters] = useState([]);
|
|
58
222
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
59
223
|
const [columnPinning, setColumnPinning] = useState({});
|
|
60
|
-
// ── Viewport-fill
|
|
224
|
+
// ── Viewport-fill height (mount + resize only — never on scroll) ─────────────
|
|
61
225
|
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// bottom margin). A ResizeObserver on <html> keeps the value in sync if the
|
|
65
|
-
// layout shifts (sidebar collapses, font loads, etc.).
|
|
66
|
-
//
|
|
67
|
-
// The MRT Paper + TableContainer flex chain distributes that height between
|
|
68
|
-
// the toolbar, sticky column headers, scrollable rows, and pagination footer —
|
|
69
|
-
// so the grid fills the window exactly and the page never needs to scroll.
|
|
226
|
+
// Previously a scroll listener recalculated height as the grid moved within the
|
|
227
|
+
// viewport, which grew the grid while scrolling and caused double scrollbars.
|
|
70
228
|
const containerRef = useRef(null);
|
|
71
229
|
const [autoHeight, setAutoHeight] = useState('');
|
|
230
|
+
const shouldAutoFill = autoFillViewport && !tableHeight;
|
|
72
231
|
useLayoutEffect(() => {
|
|
73
|
-
if (
|
|
74
|
-
return;
|
|
232
|
+
if (!shouldAutoFill)
|
|
233
|
+
return;
|
|
75
234
|
const el = containerRef.current;
|
|
76
235
|
if (!el)
|
|
77
236
|
return;
|
|
78
237
|
const update = () => {
|
|
79
238
|
const top = el.getBoundingClientRect().top;
|
|
80
|
-
|
|
239
|
+
const preferred = `calc(100vh - ${Math.ceil(top) + 8}px)`;
|
|
240
|
+
const maxPart = tableMaxHeight ?? preferred;
|
|
241
|
+
setAutoHeight(`clamp(${tableMinHeight}, ${preferred}, ${maxPart})`);
|
|
81
242
|
};
|
|
82
243
|
update();
|
|
83
244
|
const ro = new ResizeObserver(update);
|
|
84
|
-
ro.observe(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
245
|
+
ro.observe(el);
|
|
246
|
+
window.addEventListener('resize', update, { passive: true });
|
|
247
|
+
return () => {
|
|
248
|
+
ro.disconnect();
|
|
249
|
+
window.removeEventListener('resize', update);
|
|
250
|
+
};
|
|
251
|
+
}, [shouldAutoFill, tableMinHeight, tableMaxHeight]);
|
|
252
|
+
const resolvedHeight = tableHeight || (shouldAutoFill ? autoHeight : undefined) || tableMinHeight;
|
|
89
253
|
// ── Data fetching ──────────────────────────────────────────────────────────
|
|
90
254
|
const { data, isLoading, isError, isFetching, refetch } = useQuery({
|
|
91
255
|
queryKey: [...queryKey, filterValues, pagination.pageIndex],
|
|
@@ -101,9 +265,13 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
101
265
|
// MRT doesn't know these props, so we strip them here. We also inject the
|
|
102
266
|
// appropriate `filterFn` automatically so callers never have to write one.
|
|
103
267
|
const processedColumns = useMemo(() => {
|
|
268
|
+
const allowGrow = layoutMode === 'grid' || layoutMode === 'semantic';
|
|
104
269
|
return columns.map((col) => {
|
|
105
270
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
106
271
|
const { filterType, options, ...mrtCol } = col;
|
|
272
|
+
if (allowGrow && mrtCol.grow === undefined) {
|
|
273
|
+
mrtCol.grow = true;
|
|
274
|
+
}
|
|
107
275
|
// Auto-inject filterFn only when the user hasn't already supplied one.
|
|
108
276
|
if (!mrtCol.filterFn && filterType) {
|
|
109
277
|
if (filterType === 'date') {
|
|
@@ -122,7 +290,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
122
290
|
}
|
|
123
291
|
return mrtCol;
|
|
124
292
|
});
|
|
125
|
-
}, [columns]);
|
|
293
|
+
}, [columns, layoutMode]);
|
|
126
294
|
// ── Derive column list for filter chip picker ──────────────────────────────
|
|
127
295
|
//
|
|
128
296
|
// A column is excluded from the "Column Filters" section of Manage Filters
|
|
@@ -184,12 +352,26 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
184
352
|
a.click();
|
|
185
353
|
URL.revokeObjectURL(url);
|
|
186
354
|
};
|
|
187
|
-
const hasAnyValue = Object.values(filterValues).some(Boolean) ||
|
|
188
|
-
columnFilters.some((f) => f.value);
|
|
189
355
|
// ── Table instance ─────────────────────────────────────────────────────────
|
|
190
356
|
const table = useMaterialReactTable({
|
|
191
357
|
columns: processedColumns,
|
|
192
358
|
data: rows,
|
|
359
|
+
layoutMode,
|
|
360
|
+
displayColumnDefOptions: {
|
|
361
|
+
'mrt-row-spacer': {
|
|
362
|
+
size: 0,
|
|
363
|
+
minSize: 0,
|
|
364
|
+
maxSize: 0,
|
|
365
|
+
grow: false,
|
|
366
|
+
enableResizing: false,
|
|
367
|
+
muiTableHeadCellProps: {
|
|
368
|
+
sx: { display: 'none !important', width: 0, maxWidth: 0, p: 0, border: 'none' },
|
|
369
|
+
},
|
|
370
|
+
muiTableBodyCellProps: {
|
|
371
|
+
sx: { display: 'none !important', width: 0, maxWidth: 0, p: 0, border: 'none' },
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
193
375
|
manualPagination: true,
|
|
194
376
|
rowCount: totalRecords,
|
|
195
377
|
manualSorting: false,
|
|
@@ -225,71 +407,76 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
225
407
|
enableHiding: true,
|
|
226
408
|
enableStickyHeader: true,
|
|
227
409
|
enableRowVirtualization: true,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
410
|
+
initialState: {
|
|
411
|
+
density: densityTokens.mrtDensity,
|
|
412
|
+
},
|
|
413
|
+
muiTableProps: {
|
|
414
|
+
sx: { width: '100%' },
|
|
415
|
+
},
|
|
231
416
|
muiTableContainerProps: {
|
|
232
417
|
sx: {
|
|
233
418
|
flex: '1 1 0',
|
|
234
419
|
minHeight: 0,
|
|
420
|
+
maxHeight: '100%',
|
|
421
|
+
overflow: 'auto',
|
|
235
422
|
},
|
|
236
423
|
},
|
|
237
|
-
// Paper fills the wrapper Box height and acts as the flex column host for
|
|
238
|
-
// toolbar → table-container → pagination.
|
|
239
424
|
muiTablePaperProps: {
|
|
240
425
|
sx: {
|
|
241
426
|
height: '100%',
|
|
427
|
+
minHeight: 0,
|
|
242
428
|
display: 'flex',
|
|
243
429
|
flexDirection: 'column',
|
|
430
|
+
overflow: 'hidden',
|
|
431
|
+
boxShadow: 'none',
|
|
244
432
|
},
|
|
245
433
|
},
|
|
246
434
|
muiTopToolbarProps: {
|
|
247
435
|
sx: {
|
|
248
436
|
alignItems: 'center',
|
|
249
437
|
flexWrap: 'nowrap',
|
|
438
|
+
minHeight: densityTokens.filterChipMinHeight + 12,
|
|
439
|
+
py: uiDensity === 'compact' ? 0.25 : 0.5,
|
|
440
|
+
'& .MuiIconButton-root': { p: uiDensity === 'compact' ? 0.35 : 0.5 },
|
|
441
|
+
'& .MuiIconButton-root svg': { fontSize: densityTokens.toolbarIconFontSize === 'small' ? 18 : 20 },
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
muiTableHeadProps: {
|
|
445
|
+
sx: {
|
|
446
|
+
position: 'sticky',
|
|
447
|
+
top: 0,
|
|
448
|
+
zIndex: 2,
|
|
449
|
+
...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
muiTableHeadRowProps: {
|
|
453
|
+
sx: {
|
|
454
|
+
height: resolvedHeaderMaxHeight,
|
|
455
|
+
minHeight: resolvedHeaderMinHeight,
|
|
456
|
+
maxHeight: resolvedHeaderMaxHeight,
|
|
457
|
+
alignItems: 'stretch',
|
|
458
|
+
...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
|
|
250
459
|
},
|
|
251
460
|
},
|
|
252
461
|
muiTableHeadCellProps: ({ column }) => {
|
|
253
462
|
const colProps = column.columnDef.muiTableHeadCellProps;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
463
|
+
let align = 'left';
|
|
464
|
+
let colSx;
|
|
465
|
+
if (typeof colProps === 'function') {
|
|
466
|
+
const resolved = colProps({ column });
|
|
467
|
+
align = resolved?.align ?? 'left';
|
|
468
|
+
colSx = resolved?.sx;
|
|
469
|
+
}
|
|
470
|
+
else if (colProps) {
|
|
471
|
+
align = colProps.align ?? 'left';
|
|
472
|
+
colSx = colProps.sx;
|
|
473
|
+
}
|
|
260
474
|
return {
|
|
261
|
-
align
|
|
262
|
-
sx:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
borderRight: '1px solid rgba(224,224,224,1)',
|
|
267
|
-
borderBottom: '2px solid rgba(0,0,0,0.15)',
|
|
268
|
-
'& button[aria-label="Move"]': { display: 'none !important' },
|
|
269
|
-
'& .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
270
|
-
'& .Mui-TableHeadCell-ResizeHandle-Wrapper': {
|
|
271
|
-
height: '100%',
|
|
272
|
-
top: 0,
|
|
273
|
-
},
|
|
274
|
-
'& .Mui-TableHeadCell-ResizeHandle-Divider': {
|
|
275
|
-
borderRight: 'none',
|
|
276
|
-
},
|
|
277
|
-
'& .Mui-TableHeadCell-Content': {
|
|
278
|
-
alignItems: 'flex-end',
|
|
279
|
-
justifyContent,
|
|
280
|
-
},
|
|
281
|
-
'& .Mui-TableHeadCell-Content-Labels': {
|
|
282
|
-
flexWrap: 'wrap',
|
|
283
|
-
whiteSpace: 'normal',
|
|
284
|
-
overflowWrap: 'normal',
|
|
285
|
-
lineHeight: 1.3,
|
|
286
|
-
justifyContent,
|
|
287
|
-
},
|
|
288
|
-
'& .Mui-TableHeadCell-Content-Wrapper': {
|
|
289
|
-
whiteSpace: 'normal',
|
|
290
|
-
overflow: 'visible',
|
|
291
|
-
},
|
|
292
|
-
},
|
|
475
|
+
align,
|
|
476
|
+
sx: [
|
|
477
|
+
buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, headerBgColor, headerMaxLines),
|
|
478
|
+
...(colSx ? [colSx] : []),
|
|
479
|
+
],
|
|
293
480
|
};
|
|
294
481
|
},
|
|
295
482
|
muiTableBodyCellProps: {
|
|
@@ -301,27 +488,25 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
301
488
|
muiToolbarAlertBannerProps: isError
|
|
302
489
|
? { color: 'error', children: 'Failed to load data. Please try again.' }
|
|
303
490
|
: undefined,
|
|
304
|
-
renderTopToolbarCustomActions: () => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0, pr: 1, gap: 0.5 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexShrink: 0, gap: 0.25 }, children: [jsx(Tooltip, { title: "
|
|
305
|
-
|
|
306
|
-
textTransform: 'none',
|
|
307
|
-
fontSize: 12,
|
|
308
|
-
color: 'error.main',
|
|
309
|
-
minWidth: 'auto',
|
|
310
|
-
px: 1,
|
|
311
|
-
'&:hover': { bgcolor: 'rgba(211,47,47,0.06)' },
|
|
312
|
-
}, children: "Clear All" }))] })),
|
|
313
|
-
renderBottomToolbar: ({ table: t }) => (jsx(PaginationFooter, { pageIndex: t.getState().pagination.pageIndex, pageSize: t.getState().pagination.pageSize, totalRecords: totalRecords, isFetching: isFetching, isLoading: isLoading, canPrev: t.getCanPreviousPage() && !isFetching, canNext: t.getCanNextPage() && !isFetching, onFirst: () => t.firstPage(), onPrev: () => t.previousPage(), onNext: () => t.nextPage(), onLast: () => t.lastPage(), onPageSizeChange: (s) => t.setPageSize(s), pageSizeOptions: pageSizeOptions })),
|
|
491
|
+
renderTopToolbarCustomActions: () => (jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0, pr: 1, gap: 0.5 }, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', flexShrink: 0, gap: 0.25 }, children: [jsx(Tooltip, { title: "Clear filters & refresh", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: () => { handleClearAll(); refetch(); }, disabled: isFetching, sx: { p: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(RefreshIcon, { sx: { fontSize: uiDensity === 'compact' ? 18 : 20 } }) }) }) }), jsx(Tooltip, { title: "Export batch as CSV", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: handleExport, disabled: rows.length === 0 || isFetching, sx: { p: uiDensity === 'compact' ? 0.35 : 0.5 }, children: jsx(FileDownloadIcon, { sx: { fontSize: uiDensity === 'compact' ? 18 : 20 } }) }) }) })] }), jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: jsx(DynamicFilterBar, { serverFilterDefs: serverFilterDefs, visibleFilterIds: visibleFilterIds, onToggleFilter: handleToggleFilter, filterValues: filterValues, onFilterValuesChange: handleFilterValuesChange, columnFilters: columnFilters, onColumnFilterChange: setColumnFilters, availableColumns: availableColumns, maxVisibleFilters: maxVisibleFilters, uiDensity: uiDensity, filterChipFontSize: densityTokens.filterChipFontSize, filterChipMinHeight: densityTokens.filterChipMinHeight, filterLabelFontSize: densityTokens.filterLabelFontSize }) })] })),
|
|
492
|
+
renderBottomToolbar: ({ table: t }) => (jsx(PaginationFooter, { pageIndex: t.getState().pagination.pageIndex, pageSize: t.getState().pagination.pageSize, totalRecords: totalRecords, isFetching: isFetching, isLoading: isLoading, canPrev: t.getCanPreviousPage() && !isFetching, canNext: t.getCanNextPage() && !isFetching, onFirst: () => t.firstPage(), onPrev: () => t.previousPage(), onNext: () => t.nextPage(), onLast: () => t.lastPage(), onPageSizeChange: (s) => t.setPageSize(s), pageSizeOptions: pageSizeOptions, density: densityTokens })),
|
|
314
493
|
});
|
|
315
494
|
return (jsxs(Box, { ref: containerRef, className: "tp-gpg-root", sx: {
|
|
316
495
|
position: 'relative',
|
|
317
496
|
height: resolvedHeight,
|
|
497
|
+
minHeight: tableMinHeight,
|
|
498
|
+
maxHeight: tableMaxHeight,
|
|
318
499
|
display: 'flex',
|
|
319
500
|
flexDirection: 'column',
|
|
501
|
+
overflow: 'hidden',
|
|
320
502
|
}, children: [jsx(GlobalStyles, { styles: {
|
|
321
503
|
'button[aria-label="Pin to right"]': { display: 'none' },
|
|
322
504
|
'.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
323
505
|
'.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
|
|
324
|
-
|
|
506
|
+
'.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
|
|
507
|
+
'.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
|
|
508
|
+
...headerGlobalStyleOverrides,
|
|
509
|
+
} }), isLoading && jsx(OverlayLoader, { message: "Loading data\u2026" }), !isLoading && isFetching && jsx(OverlayLoader, { message: "Updating data\u2026" }), jsx(Box, { sx: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: jsx(MaterialReactTable, { table: table }) })] }));
|
|
325
510
|
}
|
|
326
511
|
|
|
327
512
|
export { GenericPaginatedGrid as default };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as GenericPaginatedGrid } from './GenericPaginatedGrid';
|
|
2
|
-
export type { GenericPaginatedGridProps, PagedResult } from './GenericPaginatedGrid';
|
|
2
|
+
export type { GenericPaginatedGridProps, PagedResult, GridUiDensity, GridUiDensityOptions, } from './GenericPaginatedGrid';
|
|
3
3
|
export { default as DynamicFilterBar } from './DynamicFilterBar';
|
|
4
4
|
export type { ServerFilterDef } from './DynamicFilterBar';
|
|
5
|
+
export type { TPColumnDef, ColumnFilterType } from './types';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock data + fetch adapters for Storybook sample grids (no API required).
|
|
3
|
+
*/
|
|
4
|
+
import type { TPColumnDef } from './types';
|
|
5
|
+
import type { PagedResult } from './GenericPaginatedGrid';
|
|
6
|
+
import type { ServerFilterDef } from './DynamicFilterBar';
|
|
7
|
+
export interface EmployeeRow {
|
|
8
|
+
id: number;
|
|
9
|
+
name: string;
|
|
10
|
+
department: string;
|
|
11
|
+
costCenter: string;
|
|
12
|
+
workLocation: string;
|
|
13
|
+
reportingManager: string;
|
|
14
|
+
employmentType: string;
|
|
15
|
+
status: string;
|
|
16
|
+
joinedOn: string;
|
|
17
|
+
}
|
|
18
|
+
/** Columns with long headers + narrow widths to exercise header wrapping in Storybook. */
|
|
19
|
+
export declare const employeeColumns: TPColumnDef<EmployeeRow>[];
|
|
20
|
+
export declare const employeeFilterDefs: ServerFilterDef[];
|
|
21
|
+
export declare const employeeDefaultFilters: Record<string, string>;
|
|
22
|
+
export declare function fetchEmployeePage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<EmployeeRow>>;
|
|
23
|
+
export interface ProductRow {
|
|
24
|
+
sku: string;
|
|
25
|
+
title: string;
|
|
26
|
+
category: string;
|
|
27
|
+
price: number;
|
|
28
|
+
inStock: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare const productColumns: TPColumnDef<ProductRow>[];
|
|
31
|
+
export declare const productFilterDefs: ServerFilterDef[];
|
|
32
|
+
export declare const productDefaultFilters: Record<string, string>;
|
|
33
|
+
export declare function fetchProductPage(filters: Record<string, string>, pageIndex: number, pageSize?: number): Promise<PagedResult<ProductRow>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,3 +15,4 @@ export * as Theme from './theme';
|
|
|
15
15
|
export type { FormRenderProps } from './utils';
|
|
16
16
|
export type { FormRenderWrapperProps } from './components/Form/FormRenderWrapper';
|
|
17
17
|
export type { FormSectionPropsItem, VariantProps } from './utils';
|
|
18
|
+
export type { GenericPaginatedGridProps, GridUiDensity, GridUiDensityOptions, PagedResult, ServerFilterDef, TPColumnDef, } from './components/PaginatedGrid';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DateRange } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DateRange } from '../../../../../_virtual/index.esm6.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireCalendar } from '../Calendar/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DefinedRange } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DefinedRange } from '../../../../../_virtual/index.esm7.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireStyles } from '../../styles.esm.js';
|