tp-react-elements 1.3.10 → 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.esm12.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/_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 +270 -98
- 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/Calendar/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/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
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 Month = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Month as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var InputRangeField = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { InputRangeField as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Calendar = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Calendar as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRangePicker = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRangePicker as __exports };
|
|
@@ -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,5 +1,5 @@
|
|
|
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
5
|
import { Box, Tooltip, IconButton, GlobalStyles, Typography, Select, MenuItem, CircularProgress } from '@mui/material';
|
|
@@ -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,58 +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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// the grid to zero height.
|
|
84
|
-
setAutoHeight(`max(300px, calc(100vh - ${Math.ceil(top) + 8}px))`);
|
|
239
|
+
const preferred = `calc(100vh - ${Math.ceil(top) + 8}px)`;
|
|
240
|
+
const maxPart = tableMaxHeight ?? preferred;
|
|
241
|
+
setAutoHeight(`clamp(${tableMinHeight}, ${preferred}, ${maxPart})`);
|
|
85
242
|
};
|
|
86
243
|
update();
|
|
87
|
-
// Re-measure on any document resize (window resize, sidebar toggle, etc.)
|
|
88
244
|
const ro = new ResizeObserver(update);
|
|
89
|
-
ro.observe(
|
|
90
|
-
|
|
91
|
-
// viewport-relative top offset shifts with scroll position, so the
|
|
92
|
-
// height must be recalculated to fill the remaining visible space.
|
|
93
|
-
const findScrollParent = (node) => {
|
|
94
|
-
let p = node.parentElement;
|
|
95
|
-
while (p && p !== document.documentElement) {
|
|
96
|
-
const { overflow, overflowY } = window.getComputedStyle(p);
|
|
97
|
-
if (/auto|scroll/.test(overflow) || /auto|scroll/.test(overflowY))
|
|
98
|
-
return p;
|
|
99
|
-
p = p.parentElement;
|
|
100
|
-
}
|
|
101
|
-
return window;
|
|
102
|
-
};
|
|
103
|
-
const scrollParent = findScrollParent(el);
|
|
104
|
-
scrollParent.addEventListener('scroll', update, { passive: true });
|
|
245
|
+
ro.observe(el);
|
|
246
|
+
window.addEventListener('resize', update, { passive: true });
|
|
105
247
|
return () => {
|
|
106
248
|
ro.disconnect();
|
|
107
|
-
|
|
249
|
+
window.removeEventListener('resize', update);
|
|
108
250
|
};
|
|
109
|
-
}, [
|
|
110
|
-
|
|
111
|
-
const resolvedHeight = tableHeight || autoHeight || '60vh';
|
|
251
|
+
}, [shouldAutoFill, tableMinHeight, tableMaxHeight]);
|
|
252
|
+
const resolvedHeight = tableHeight || (shouldAutoFill ? autoHeight : undefined) || tableMinHeight;
|
|
112
253
|
// ── Data fetching ──────────────────────────────────────────────────────────
|
|
113
254
|
const { data, isLoading, isError, isFetching, refetch } = useQuery({
|
|
114
255
|
queryKey: [...queryKey, filterValues, pagination.pageIndex],
|
|
@@ -124,9 +265,13 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
124
265
|
// MRT doesn't know these props, so we strip them here. We also inject the
|
|
125
266
|
// appropriate `filterFn` automatically so callers never have to write one.
|
|
126
267
|
const processedColumns = useMemo(() => {
|
|
268
|
+
const allowGrow = layoutMode === 'grid' || layoutMode === 'semantic';
|
|
127
269
|
return columns.map((col) => {
|
|
128
270
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
129
271
|
const { filterType, options, ...mrtCol } = col;
|
|
272
|
+
if (allowGrow && mrtCol.grow === undefined) {
|
|
273
|
+
mrtCol.grow = true;
|
|
274
|
+
}
|
|
130
275
|
// Auto-inject filterFn only when the user hasn't already supplied one.
|
|
131
276
|
if (!mrtCol.filterFn && filterType) {
|
|
132
277
|
if (filterType === 'date') {
|
|
@@ -145,7 +290,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
145
290
|
}
|
|
146
291
|
return mrtCol;
|
|
147
292
|
});
|
|
148
|
-
}, [columns]);
|
|
293
|
+
}, [columns, layoutMode]);
|
|
149
294
|
// ── Derive column list for filter chip picker ──────────────────────────────
|
|
150
295
|
//
|
|
151
296
|
// A column is excluded from the "Column Filters" section of Manage Filters
|
|
@@ -211,6 +356,22 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
211
356
|
const table = useMaterialReactTable({
|
|
212
357
|
columns: processedColumns,
|
|
213
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
|
+
},
|
|
214
375
|
manualPagination: true,
|
|
215
376
|
rowCount: totalRecords,
|
|
216
377
|
manualSorting: false,
|
|
@@ -246,71 +407,76 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
246
407
|
enableHiding: true,
|
|
247
408
|
enableStickyHeader: true,
|
|
248
409
|
enableRowVirtualization: true,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
410
|
+
initialState: {
|
|
411
|
+
density: densityTokens.mrtDensity,
|
|
412
|
+
},
|
|
413
|
+
muiTableProps: {
|
|
414
|
+
sx: { width: '100%' },
|
|
415
|
+
},
|
|
252
416
|
muiTableContainerProps: {
|
|
253
417
|
sx: {
|
|
254
418
|
flex: '1 1 0',
|
|
255
|
-
minHeight:
|
|
419
|
+
minHeight: 0,
|
|
420
|
+
maxHeight: '100%',
|
|
421
|
+
overflow: 'auto',
|
|
256
422
|
},
|
|
257
423
|
},
|
|
258
|
-
// Paper fills the wrapper Box height and acts as the flex column host for
|
|
259
|
-
// toolbar → table-container → pagination.
|
|
260
424
|
muiTablePaperProps: {
|
|
261
425
|
sx: {
|
|
262
426
|
height: '100%',
|
|
427
|
+
minHeight: 0,
|
|
263
428
|
display: 'flex',
|
|
264
429
|
flexDirection: 'column',
|
|
430
|
+
overflow: 'hidden',
|
|
431
|
+
boxShadow: 'none',
|
|
265
432
|
},
|
|
266
433
|
},
|
|
267
434
|
muiTopToolbarProps: {
|
|
268
435
|
sx: {
|
|
269
436
|
alignItems: 'center',
|
|
270
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 } : {}),
|
|
271
459
|
},
|
|
272
460
|
},
|
|
273
461
|
muiTableHeadCellProps: ({ column }) => {
|
|
274
462
|
const colProps = column.columnDef.muiTableHeadCellProps;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
+
}
|
|
281
474
|
return {
|
|
282
|
-
align
|
|
283
|
-
sx:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
borderRight: '1px solid rgba(224,224,224,1)',
|
|
288
|
-
borderBottom: '2px solid rgba(0,0,0,0.15)',
|
|
289
|
-
'& button[aria-label="Move"]': { display: 'none !important' },
|
|
290
|
-
'& .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
291
|
-
'& .Mui-TableHeadCell-ResizeHandle-Wrapper': {
|
|
292
|
-
height: '100%',
|
|
293
|
-
top: 0,
|
|
294
|
-
},
|
|
295
|
-
'& .Mui-TableHeadCell-ResizeHandle-Divider': {
|
|
296
|
-
borderRight: 'none',
|
|
297
|
-
},
|
|
298
|
-
'& .Mui-TableHeadCell-Content': {
|
|
299
|
-
alignItems: 'flex-end',
|
|
300
|
-
justifyContent,
|
|
301
|
-
},
|
|
302
|
-
'& .Mui-TableHeadCell-Content-Labels': {
|
|
303
|
-
flexWrap: 'wrap',
|
|
304
|
-
whiteSpace: 'normal',
|
|
305
|
-
overflowWrap: 'normal',
|
|
306
|
-
lineHeight: 1.3,
|
|
307
|
-
justifyContent,
|
|
308
|
-
},
|
|
309
|
-
'& .Mui-TableHeadCell-Content-Wrapper': {
|
|
310
|
-
whiteSpace: 'normal',
|
|
311
|
-
overflow: 'visible',
|
|
312
|
-
},
|
|
313
|
-
},
|
|
475
|
+
align,
|
|
476
|
+
sx: [
|
|
477
|
+
buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, headerBgColor, headerMaxLines),
|
|
478
|
+
...(colSx ? [colSx] : []),
|
|
479
|
+
],
|
|
314
480
|
};
|
|
315
481
|
},
|
|
316
482
|
muiTableBodyCellProps: {
|
|
@@ -322,19 +488,25 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
322
488
|
muiToolbarAlertBannerProps: isError
|
|
323
489
|
? { color: 'error', children: 'Failed to load data. Please try again.' }
|
|
324
490
|
: undefined,
|
|
325
|
-
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, children: jsx(RefreshIcon, { fontSize:
|
|
326
|
-
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 })),
|
|
327
493
|
});
|
|
328
494
|
return (jsxs(Box, { ref: containerRef, className: "tp-gpg-root", sx: {
|
|
329
495
|
position: 'relative',
|
|
330
496
|
height: resolvedHeight,
|
|
497
|
+
minHeight: tableMinHeight,
|
|
498
|
+
maxHeight: tableMaxHeight,
|
|
331
499
|
display: 'flex',
|
|
332
500
|
flexDirection: 'column',
|
|
501
|
+
overflow: 'hidden',
|
|
333
502
|
}, children: [jsx(GlobalStyles, { styles: {
|
|
334
503
|
'button[aria-label="Pin to right"]': { display: 'none' },
|
|
335
504
|
'.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
336
505
|
'.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
|
|
337
|
-
|
|
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 }) })] }));
|
|
338
510
|
}
|
|
339
511
|
|
|
340
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 Calendar } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as Calendar } from '../../../../../_virtual/index.esm4.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDayCell } from '../DayCell/index.esm.js';
|
|
@@ -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 DateRangePicker } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DateRangePicker } from '../../../../../_virtual/index.esm5.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDateRange } from '../DateRange/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';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as InputRangeField } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as InputRangeField } from '../../../../../_virtual/index.esm14.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as Month } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as Month } from '../../../../../_virtual/index.esm12.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDayCell } from '../DayCell/index.esm.js';
|