tp-react-elements 1.3.10 → 1.3.12
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 +42 -0
- package/dist/_virtual/index.esm4.js +2 -2
- package/dist/_virtual/index.esm5.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 +72 -4
- package/dist/components/PaginatedGrid/GenericPaginatedGrid.esm.js +320 -103
- 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,51 @@ 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** — API reference plus **four copy-paste examples** (embedded panel, wrapped headers, typography, invoice filters)
|
|
179
|
+
- **Components → PaginatedGrid → Sample Grids** — mock grids (no API): header wrap, compact layout, viewport fill
|
|
180
|
+
- **Components → PaginatedGrid → Example Grid** — live invoice API demo
|
|
181
|
+
|
|
182
|
+
**Example — compact embedded grid with smaller fonts:**
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
<GenericPaginatedGrid
|
|
186
|
+
tableHeight="calc(100vh - 240px)"
|
|
187
|
+
autoFillViewport={false}
|
|
188
|
+
uiDensity="compact"
|
|
189
|
+
headerBgColor="#f5f5f5"
|
|
190
|
+
headerMaxHeight={64}
|
|
191
|
+
headerFontSize={11}
|
|
192
|
+
bodyFontSize={10}
|
|
193
|
+
{...gridProps}
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Example — wrapped header “Department / Business Unit”:**
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
<GenericPaginatedGrid
|
|
201
|
+
headerBgColor="#e8eaf6"
|
|
202
|
+
headerMinHeight="48px"
|
|
203
|
+
headerMaxHeight="96px"
|
|
204
|
+
headerFontSize={12}
|
|
205
|
+
bodyFontSize={12}
|
|
206
|
+
layoutMode="grid"
|
|
207
|
+
{...gridProps}
|
|
208
|
+
/>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Full examples: Storybook **Docs → Paginated Grid → Examples**.
|
|
212
|
+
|
|
171
213
|
## 🏗️ Best Practices for Code Structure and Organization
|
|
172
214
|
|
|
173
215
|
### 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,25 @@ 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
|
+
/** Header column title font size (px number). */
|
|
23
|
+
headerFontSize?: number;
|
|
24
|
+
/** Body cell data font size (px number). */
|
|
25
|
+
bodyFontSize?: number;
|
|
26
|
+
/** MRT built-in toolbar control density (show/hide columns, density toggle, etc.). */
|
|
27
|
+
mrtDensity?: 'comfortable' | 'compact' | 'spacious';
|
|
28
|
+
}
|
|
10
29
|
export interface GenericPaginatedGridProps<TData extends object> {
|
|
11
30
|
/**
|
|
12
31
|
* Column definitions. Use `TPColumnDef<TData>` (a superset of `MRT_ColumnDef`)
|
|
@@ -36,11 +55,35 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
36
55
|
/**
|
|
37
56
|
* Height of the entire grid component.
|
|
38
57
|
* 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)").
|
|
58
|
+
* height from its top position on mount and window resize only (not on scroll).
|
|
59
|
+
* Pass an explicit CSS value to pin height (e.g. "600px", "calc(100vh - 300px)").
|
|
42
60
|
*/
|
|
43
61
|
tableHeight?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Minimum height of the grid wrapper (CSS length).
|
|
64
|
+
* Used in auto-height `clamp()` and as `minHeight` when `tableHeight` is set.
|
|
65
|
+
* Default: `300px`.
|
|
66
|
+
*/
|
|
67
|
+
tableMinHeight?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Maximum height of the grid wrapper (CSS length).
|
|
70
|
+
* Caps auto-calculated viewport height. Default: none (only viewport clamp).
|
|
71
|
+
*/
|
|
72
|
+
tableMaxHeight?: string;
|
|
73
|
+
/**
|
|
74
|
+
* When false, disables viewport auto-height; use `tableHeight` or `tableMinHeight` only.
|
|
75
|
+
* Default: true (unless `tableHeight` is provided).
|
|
76
|
+
*/
|
|
77
|
+
autoFillViewport?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* MRT column layout. Default `grid` — columns grow to fill width (no trailing spacer column).
|
|
80
|
+
* Use `grid-no-grow` only when you need fixed column widths with horizontal scroll.
|
|
81
|
+
*/
|
|
82
|
+
layoutMode?: 'grid' | 'semantic' | 'grid-no-grow';
|
|
83
|
+
/** Compact toolbar, filter chips, and footer. Default: `default`. */
|
|
84
|
+
uiDensity?: GridUiDensity;
|
|
85
|
+
/** Override individual sizes from the `uiDensity` preset. */
|
|
86
|
+
uiDensityOptions?: GridUiDensityOptions;
|
|
44
87
|
/** Choices shown in the "Rows per page" footer dropdown. Default: [1 000, 5 000, 10 000]. */
|
|
45
88
|
pageSizeOptions?: number[];
|
|
46
89
|
/**
|
|
@@ -48,6 +91,31 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
48
91
|
* Accepts any valid CSS colour string.
|
|
49
92
|
*/
|
|
50
93
|
headerBgColor?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Minimum height of every header cell (CSS length or px number).
|
|
96
|
+
* Default: `40px` (`34px` when `uiDensity="compact"`).
|
|
97
|
+
*/
|
|
98
|
+
headerMinHeight?: string | number;
|
|
99
|
+
/**
|
|
100
|
+
* Maximum height of the header row — **all columns use the same height**.
|
|
101
|
+
* Long titles wrap inside this box. Default: `72px` (`56px` compact).
|
|
102
|
+
*/
|
|
103
|
+
headerMaxHeight?: string | number;
|
|
104
|
+
/**
|
|
105
|
+
* Optional max lines for header text (`-webkit-line-clamp`).
|
|
106
|
+
* Omit to allow text to use the full `headerMaxHeight` (recommended for tall headers).
|
|
107
|
+
*/
|
|
108
|
+
headerMaxLines?: number;
|
|
109
|
+
/**
|
|
110
|
+
* Font size for header column titles (CSS length or px number).
|
|
111
|
+
* Default: `12px` (`11px` when `uiDensity="compact"`). Overridden by `uiDensityOptions.headerFontSize`.
|
|
112
|
+
*/
|
|
113
|
+
headerFontSize?: string | number;
|
|
114
|
+
/**
|
|
115
|
+
* Font size for body/data cells (CSS length or px number).
|
|
116
|
+
* Default: `12px` (`11px` compact). Overridden by `uiDensityOptions.bodyFontSize`.
|
|
117
|
+
*/
|
|
118
|
+
bodyFontSize?: string | number;
|
|
51
119
|
/**
|
|
52
120
|
* Maximum number of filter chips that can be visible at once.
|
|
53
121
|
* Prevents the filter bar from wrapping to a second row on typical screens.
|
|
@@ -55,5 +123,5 @@ export interface GenericPaginatedGridProps<TData extends object> {
|
|
|
55
123
|
*/
|
|
56
124
|
maxVisibleFilters?: number;
|
|
57
125
|
}
|
|
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;
|
|
126
|
+
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, headerFontSize, bodyFontSize, maxVisibleFilters, }: GenericPaginatedGridProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
59
127
|
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,178 @@ 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
|
+
headerFontSize: 12,
|
|
25
|
+
bodyFontSize: 12,
|
|
26
|
+
mrtDensity: 'comfortable',
|
|
27
|
+
},
|
|
28
|
+
compact: {
|
|
29
|
+
footerMinHeight: 32,
|
|
30
|
+
footerFontSize: 12,
|
|
31
|
+
footerIconSize: 18,
|
|
32
|
+
footerPaddingY: 0.25,
|
|
33
|
+
filterChipFontSize: 11,
|
|
34
|
+
filterChipMinHeight: 26,
|
|
35
|
+
filterLabelFontSize: 11,
|
|
36
|
+
toolbarIconFontSize: 'inherit',
|
|
37
|
+
headerFontSize: 11,
|
|
38
|
+
bodyFontSize: 11,
|
|
39
|
+
mrtDensity: 'compact',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
const toCssSize = (value, fallback) => {
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
return fallback;
|
|
45
|
+
return typeof value === 'number' ? `${value}px` : value;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Header overrides applied via GlobalStyles (literal prop values).
|
|
49
|
+
* MRT sets `backgroundColor: 'inherit'` and fixed density padding — must use !important.
|
|
50
|
+
*/
|
|
51
|
+
const buildBodyGlobalStyles = (bodyFont) => ({
|
|
52
|
+
'.tp-gpg-root .MuiTableBody-root .MuiTableCell-root': {
|
|
53
|
+
fontSize: `${bodyFont} !important`,
|
|
54
|
+
lineHeight: 1.35,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const buildHeaderGlobalStyles = (headerMin, headerMax, headerFont, headerBgColor) => {
|
|
58
|
+
const headCell = {
|
|
59
|
+
height: `${headerMax} !important`,
|
|
60
|
+
minHeight: `${headerMin} !important`,
|
|
61
|
+
maxHeight: `${headerMax} !important`,
|
|
62
|
+
boxSizing: 'border-box !important',
|
|
63
|
+
alignSelf: 'stretch !important',
|
|
64
|
+
whiteSpace: 'normal !important',
|
|
65
|
+
verticalAlign: 'middle !important',
|
|
66
|
+
pt: '8px !important',
|
|
67
|
+
pb: '8px !important',
|
|
68
|
+
px: '14px !important',
|
|
69
|
+
};
|
|
70
|
+
if (headerBgColor) {
|
|
71
|
+
headCell.backgroundColor = `${headerBgColor} !important`;
|
|
72
|
+
headCell.backgroundImage = 'none !important';
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
'.tp-gpg-root .MuiTableHead-root .MuiTableRow-root': {
|
|
76
|
+
height: `${headerMax} !important`,
|
|
77
|
+
minHeight: `${headerMin} !important`,
|
|
78
|
+
maxHeight: `${headerMax} !important`,
|
|
79
|
+
alignItems: 'stretch !important',
|
|
80
|
+
...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
|
|
81
|
+
},
|
|
82
|
+
'.tp-gpg-root .MuiTableHead-root .MuiTableCell-head': {
|
|
83
|
+
...headCell,
|
|
84
|
+
fontSize: `${headerFont} !important`,
|
|
85
|
+
},
|
|
86
|
+
'.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content': {
|
|
87
|
+
height: '100% !important',
|
|
88
|
+
width: '100% !important',
|
|
89
|
+
minHeight: '0 !important',
|
|
90
|
+
alignItems: 'center !important',
|
|
91
|
+
},
|
|
92
|
+
'.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content-Labels': {
|
|
93
|
+
overflow: 'visible !important',
|
|
94
|
+
flexWrap: 'wrap !important',
|
|
95
|
+
whiteSpace: 'normal !important',
|
|
96
|
+
textOverflow: 'clip !important',
|
|
97
|
+
width: '100% !important',
|
|
98
|
+
maxWidth: '100% !important',
|
|
99
|
+
},
|
|
100
|
+
'.tp-gpg-root .MuiTableHead-root .Mui-TableHeadCell-Content-Wrapper': {
|
|
101
|
+
overflow: 'visible !important',
|
|
102
|
+
whiteSpace: 'normal !important',
|
|
103
|
+
textOverflow: 'clip !important',
|
|
104
|
+
wordBreak: 'break-word !important',
|
|
105
|
+
overflowWrap: 'anywhere !important',
|
|
106
|
+
width: '100% !important',
|
|
107
|
+
maxWidth: '100% !important',
|
|
108
|
+
fontSize: `${headerFont} !important`,
|
|
109
|
+
display: 'block !important',
|
|
110
|
+
WebkitLineClamp: 'unset !important',
|
|
111
|
+
WebkitBoxOrient: 'unset !important',
|
|
112
|
+
},
|
|
113
|
+
'.tp-gpg-root .MuiTableHead-root .MuiTableSortLabel-root': {
|
|
114
|
+
whiteSpace: 'normal !important',
|
|
115
|
+
wordBreak: 'break-word !important',
|
|
116
|
+
overflowWrap: 'anywhere !important',
|
|
117
|
+
width: '100% !important',
|
|
118
|
+
fontSize: `${headerFont} !important`,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
/** Per-cell sx merged into MRT (via muiTableHeadCellProps). */
|
|
123
|
+
const buildHeaderCellSx = (align, headerMin, headerMax, headerFont, headerBgColor, headerMaxLines) => {
|
|
124
|
+
const textAlign = align;
|
|
125
|
+
const flexJustify = align === 'right' ? 'flex-end' : align === 'center' ? 'center' : 'flex-start';
|
|
126
|
+
const wrapperClamp = headerMaxLines != null && headerMaxLines > 0
|
|
127
|
+
? {
|
|
128
|
+
display: '-webkit-box',
|
|
129
|
+
WebkitLineClamp: headerMaxLines,
|
|
130
|
+
WebkitBoxOrient: 'vertical',
|
|
131
|
+
overflow: 'hidden',
|
|
132
|
+
}
|
|
133
|
+
: {
|
|
134
|
+
display: 'block',
|
|
135
|
+
WebkitLineClamp: 'unset',
|
|
136
|
+
overflow: 'visible',
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
...(headerBgColor
|
|
140
|
+
? { backgroundColor: `${headerBgColor} !important`, backgroundImage: 'none' }
|
|
141
|
+
: {}),
|
|
142
|
+
height: `${headerMax} !important`,
|
|
143
|
+
minHeight: `${headerMin} !important`,
|
|
144
|
+
maxHeight: `${headerMax} !important`,
|
|
145
|
+
fontSize: headerFont,
|
|
146
|
+
boxSizing: 'border-box',
|
|
147
|
+
borderRight: '1px solid rgba(224,224,224,1)',
|
|
148
|
+
borderBottom: '2px solid rgba(0,0,0,0.15)',
|
|
149
|
+
'& button[aria-label="Move"]': { display: 'none !important' },
|
|
150
|
+
'& .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
151
|
+
'& .Mui-TableHeadCell-ResizeHandle-Wrapper': { height: '100%', top: 0 },
|
|
152
|
+
'& .Mui-TableHeadCell-ResizeHandle-Divider': { borderRight: 'none' },
|
|
153
|
+
'& .Mui-TableHeadCell-Content': {
|
|
154
|
+
alignItems: 'center',
|
|
155
|
+
justifyContent: flexJustify,
|
|
156
|
+
height: '100%',
|
|
157
|
+
width: '100%',
|
|
158
|
+
},
|
|
159
|
+
'& .Mui-TableHeadCell-Content-Labels': {
|
|
160
|
+
flexWrap: 'wrap',
|
|
161
|
+
whiteSpace: 'normal',
|
|
162
|
+
textAlign,
|
|
163
|
+
width: '100%',
|
|
164
|
+
justifyContent: flexJustify,
|
|
165
|
+
overflow: 'visible',
|
|
166
|
+
},
|
|
167
|
+
'& .Mui-TableHeadCell-Content-Wrapper': {
|
|
168
|
+
whiteSpace: 'normal',
|
|
169
|
+
wordBreak: 'break-word',
|
|
170
|
+
overflowWrap: 'anywhere',
|
|
171
|
+
lineHeight: 1.25,
|
|
172
|
+
fontSize: headerFont,
|
|
173
|
+
width: '100%',
|
|
174
|
+
textAlign,
|
|
175
|
+
...wrapperClamp,
|
|
176
|
+
},
|
|
177
|
+
'& .MuiTableSortLabel-root': {
|
|
178
|
+
whiteSpace: 'normal',
|
|
179
|
+
wordBreak: 'break-word',
|
|
180
|
+
fontSize: headerFont,
|
|
181
|
+
width: '100%',
|
|
182
|
+
margin: 0,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
};
|
|
14
186
|
// ─── Internal: overlay loader ─────────────────────────────────────────────────
|
|
15
187
|
const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
|
|
16
188
|
position: 'absolute', inset: 0,
|
|
@@ -19,31 +191,52 @@ const OverlayLoader = ({ message }) => (jsxs(Box, { sx: {
|
|
|
19
191
|
bgcolor: 'rgba(255,255,255,0.80)', backdropFilter: 'blur(2px)',
|
|
20
192
|
zIndex: 10, gap: 1.5,
|
|
21
193
|
}, 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, }) => {
|
|
194
|
+
const PaginationFooter = ({ pageIndex, pageSize, totalRecords, isFetching, isLoading, canPrev, canNext, onFirst, onPrev, onNext, onLast, onPageSizeChange, pageSizeOptions, density, }) => {
|
|
195
|
+
const fs = density.footerFontSize;
|
|
196
|
+
const selectH = density.footerMinHeight - 8;
|
|
23
197
|
const startRow = totalRecords === 0 ? 0 : pageIndex * pageSize + 1;
|
|
24
198
|
const endRow = Math.min((pageIndex + 1) * pageSize, totalRecords);
|
|
25
199
|
return (jsxs(Box, { sx: {
|
|
26
200
|
display: 'flex', alignItems: 'center', justifyContent: 'flex-end',
|
|
27
|
-
gap: { xs: 1
|
|
201
|
+
gap: { xs: 1, md: 2 }, px: 1.5, py: density.footerPaddingY,
|
|
28
202
|
borderTop: '1px solid', borderColor: 'divider',
|
|
29
|
-
bgcolor: 'background.paper', flexWrap: 'wrap',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
203
|
+
bgcolor: 'background.paper', flexWrap: 'wrap',
|
|
204
|
+
minHeight: density.footerMinHeight,
|
|
205
|
+
flexShrink: 0,
|
|
206
|
+
}, children: [jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.75 }, children: [jsx(Typography, { sx: { fontSize: fs }, color: "text.secondary", noWrap: true, children: "Rows per page:" }), jsx(Select, { value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), size: "small", variant: "outlined", sx: {
|
|
207
|
+
height: selectH, fontSize: fs - 1, minWidth: 72,
|
|
208
|
+
'.MuiSelect-select': { py: '2px !important' },
|
|
33
209
|
'.MuiOutlinedInput-notchedOutline': { borderColor: 'divider' },
|
|
34
|
-
}, children: pageSizeOptions.map((opt) => (jsx(MenuItem, { value: opt, sx: { fontSize:
|
|
210
|
+
}, 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
211
|
? 'Loading…'
|
|
36
212
|
: totalRecords === 0
|
|
37
213
|
? '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,
|
|
214
|
+
: `${startRow.toLocaleString()}–${endRow.toLocaleString()} of ${totalRecords.toLocaleString()}` }), jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.25 }, children: [jsx(Tooltip, { title: "First page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onFirst, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(FirstPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Previous page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onPrev, disabled: !canPrev, sx: { p: 0.5 }, children: jsx(ChevronLeftIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Typography, { fontWeight: 600, sx: {
|
|
215
|
+
mx: 0.5,
|
|
216
|
+
minWidth: density.footerMinHeight - 12,
|
|
217
|
+
height: density.footerMinHeight - 12,
|
|
40
218
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
41
219
|
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:
|
|
220
|
+
color: 'primary.main', bgcolor: 'rgba(25,118,210,0.07)', fontSize: fs - 1,
|
|
221
|
+
}, children: pageIndex + 1 }), jsx(Tooltip, { title: "Next page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onNext, disabled: !canNext, sx: { p: 0.5 }, children: jsx(ChevronRightIcon, { sx: { fontSize: density.footerIconSize } }) }) }) }), jsx(Tooltip, { title: "Last page", children: jsx("span", { children: jsx(IconButton, { size: "small", onClick: onLast, disabled: !canNext, sx: { p: 0.5 }, children: jsx(LastPageIcon, { sx: { fontSize: density.footerIconSize } }) }) }) })] })] }));
|
|
44
222
|
};
|
|
45
223
|
// ─── GenericPaginatedGrid ─────────────────────────────────────────────────────
|
|
46
|
-
function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, pageSizeOptions = [1000, 5000, 10000], headerBgColor, maxVisibleFilters = 4, }) {
|
|
224
|
+
function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs, defaultFilters, defaultVisibleFilterIds, batchSize = 10000, exportFilename = 'export', tableHeight, tableMinHeight = '300px', tableMaxHeight, autoFillViewport = true, layoutMode = 'grid', uiDensity = 'default', uiDensityOptions, pageSizeOptions = [1000, 5000, 10000], headerBgColor, headerMinHeight, headerMaxHeight, headerMaxLines, headerFontSize, bodyFontSize, maxVisibleFilters = 4, }) {
|
|
225
|
+
const densityTokens = useMemo(() => ({ ...UI_DENSITY_PRESETS[uiDensity], ...uiDensityOptions }), [uiDensity, uiDensityOptions]);
|
|
226
|
+
const resolvedHeaderMinHeight = toCssSize(headerMinHeight, uiDensity === 'compact' ? '34px' : '40px');
|
|
227
|
+
const resolvedHeaderMaxHeight = toCssSize(headerMaxHeight, uiDensity === 'compact' ? '56px' : '72px');
|
|
228
|
+
const resolvedHeaderFontSize = toCssSize(headerFontSize ?? densityTokens.headerFontSize, uiDensity === 'compact' ? '11px' : '12px');
|
|
229
|
+
const resolvedBodyFontSize = toCssSize(bodyFontSize ?? densityTokens.bodyFontSize, uiDensity === 'compact' ? '11px' : '12px');
|
|
230
|
+
const tableStyleOverrides = useMemo(() => ({
|
|
231
|
+
...buildHeaderGlobalStyles(resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor),
|
|
232
|
+
...buildBodyGlobalStyles(resolvedBodyFontSize),
|
|
233
|
+
}), [
|
|
234
|
+
resolvedHeaderMinHeight,
|
|
235
|
+
resolvedHeaderMaxHeight,
|
|
236
|
+
resolvedHeaderFontSize,
|
|
237
|
+
resolvedBodyFontSize,
|
|
238
|
+
headerBgColor,
|
|
239
|
+
]);
|
|
47
240
|
const defaultVisible = defaultVisibleFilterIds
|
|
48
241
|
?? (serverFilterDefs.length > 0 ? [serverFilterDefs[0].id] : []);
|
|
49
242
|
// ── State ──────────────────────────────────────────────────────────────────
|
|
@@ -57,58 +250,35 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
57
250
|
const [columnFilters, setColumnFilters] = useState([]);
|
|
58
251
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
59
252
|
const [columnPinning, setColumnPinning] = useState({});
|
|
60
|
-
// ── Viewport-fill
|
|
61
|
-
//
|
|
62
|
-
// When tableHeight is not provided, we measure how far the wrapper sits from
|
|
63
|
-
// the top of the viewport and set height = 100vh − topOffset − 8px (tiny
|
|
64
|
-
// bottom margin). A ResizeObserver on <html> keeps the value in sync if the
|
|
65
|
-
// layout shifts (sidebar collapses, font loads, etc.).
|
|
253
|
+
// ── Viewport-fill height (mount + resize only — never on scroll) ─────────────
|
|
66
254
|
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// so the grid fills the window exactly and the page never needs to scroll.
|
|
255
|
+
// Previously a scroll listener recalculated height as the grid moved within the
|
|
256
|
+
// viewport, which grew the grid while scrolling and caused double scrollbars.
|
|
70
257
|
const containerRef = useRef(null);
|
|
71
258
|
const [autoHeight, setAutoHeight] = useState('');
|
|
259
|
+
const shouldAutoFill = autoFillViewport && !tableHeight;
|
|
72
260
|
useLayoutEffect(() => {
|
|
73
|
-
if (
|
|
74
|
-
return;
|
|
261
|
+
if (!shouldAutoFill)
|
|
262
|
+
return;
|
|
75
263
|
const el = containerRef.current;
|
|
76
264
|
if (!el)
|
|
77
265
|
return;
|
|
78
266
|
const update = () => {
|
|
79
267
|
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))`);
|
|
268
|
+
const preferred = `calc(100vh - ${Math.ceil(top) + 8}px)`;
|
|
269
|
+
const maxPart = tableMaxHeight ?? preferred;
|
|
270
|
+
setAutoHeight(`clamp(${tableMinHeight}, ${preferred}, ${maxPart})`);
|
|
85
271
|
};
|
|
86
272
|
update();
|
|
87
|
-
// Re-measure on any document resize (window resize, sidebar toggle, etc.)
|
|
88
273
|
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 });
|
|
274
|
+
ro.observe(el);
|
|
275
|
+
window.addEventListener('resize', update, { passive: true });
|
|
105
276
|
return () => {
|
|
106
277
|
ro.disconnect();
|
|
107
|
-
|
|
278
|
+
window.removeEventListener('resize', update);
|
|
108
279
|
};
|
|
109
|
-
}, [
|
|
110
|
-
|
|
111
|
-
const resolvedHeight = tableHeight || autoHeight || '60vh';
|
|
280
|
+
}, [shouldAutoFill, tableMinHeight, tableMaxHeight]);
|
|
281
|
+
const resolvedHeight = tableHeight || (shouldAutoFill ? autoHeight : undefined) || tableMinHeight;
|
|
112
282
|
// ── Data fetching ──────────────────────────────────────────────────────────
|
|
113
283
|
const { data, isLoading, isError, isFetching, refetch } = useQuery({
|
|
114
284
|
queryKey: [...queryKey, filterValues, pagination.pageIndex],
|
|
@@ -124,9 +294,13 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
124
294
|
// MRT doesn't know these props, so we strip them here. We also inject the
|
|
125
295
|
// appropriate `filterFn` automatically so callers never have to write one.
|
|
126
296
|
const processedColumns = useMemo(() => {
|
|
297
|
+
const allowGrow = layoutMode === 'grid' || layoutMode === 'semantic';
|
|
127
298
|
return columns.map((col) => {
|
|
128
299
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
129
300
|
const { filterType, options, ...mrtCol } = col;
|
|
301
|
+
if (allowGrow && mrtCol.grow === undefined) {
|
|
302
|
+
mrtCol.grow = true;
|
|
303
|
+
}
|
|
130
304
|
// Auto-inject filterFn only when the user hasn't already supplied one.
|
|
131
305
|
if (!mrtCol.filterFn && filterType) {
|
|
132
306
|
if (filterType === 'date') {
|
|
@@ -145,7 +319,7 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
145
319
|
}
|
|
146
320
|
return mrtCol;
|
|
147
321
|
});
|
|
148
|
-
}, [columns]);
|
|
322
|
+
}, [columns, layoutMode]);
|
|
149
323
|
// ── Derive column list for filter chip picker ──────────────────────────────
|
|
150
324
|
//
|
|
151
325
|
// A column is excluded from the "Column Filters" section of Manage Filters
|
|
@@ -211,6 +385,22 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
211
385
|
const table = useMaterialReactTable({
|
|
212
386
|
columns: processedColumns,
|
|
213
387
|
data: rows,
|
|
388
|
+
layoutMode,
|
|
389
|
+
displayColumnDefOptions: {
|
|
390
|
+
'mrt-row-spacer': {
|
|
391
|
+
size: 0,
|
|
392
|
+
minSize: 0,
|
|
393
|
+
maxSize: 0,
|
|
394
|
+
grow: false,
|
|
395
|
+
enableResizing: false,
|
|
396
|
+
muiTableHeadCellProps: {
|
|
397
|
+
sx: { display: 'none !important', width: 0, maxWidth: 0, p: 0, border: 'none' },
|
|
398
|
+
},
|
|
399
|
+
muiTableBodyCellProps: {
|
|
400
|
+
sx: { display: 'none !important', width: 0, maxWidth: 0, p: 0, border: 'none' },
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
},
|
|
214
404
|
manualPagination: true,
|
|
215
405
|
rowCount: totalRecords,
|
|
216
406
|
manualSorting: false,
|
|
@@ -246,95 +436,122 @@ function GenericPaginatedGrid({ columns, fetchPage, queryKey, serverFilterDefs,
|
|
|
246
436
|
enableHiding: true,
|
|
247
437
|
enableStickyHeader: true,
|
|
248
438
|
enableRowVirtualization: true,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
439
|
+
initialState: {
|
|
440
|
+
density: densityTokens.mrtDensity,
|
|
441
|
+
},
|
|
442
|
+
muiTableProps: {
|
|
443
|
+
sx: { width: '100%' },
|
|
444
|
+
},
|
|
252
445
|
muiTableContainerProps: {
|
|
253
446
|
sx: {
|
|
254
447
|
flex: '1 1 0',
|
|
255
|
-
minHeight:
|
|
448
|
+
minHeight: 0,
|
|
449
|
+
maxHeight: '100%',
|
|
450
|
+
overflow: 'auto',
|
|
256
451
|
},
|
|
257
452
|
},
|
|
258
|
-
// Paper fills the wrapper Box height and acts as the flex column host for
|
|
259
|
-
// toolbar → table-container → pagination.
|
|
260
453
|
muiTablePaperProps: {
|
|
261
454
|
sx: {
|
|
262
455
|
height: '100%',
|
|
456
|
+
minHeight: 0,
|
|
263
457
|
display: 'flex',
|
|
264
458
|
flexDirection: 'column',
|
|
459
|
+
overflow: 'hidden',
|
|
460
|
+
boxShadow: 'none',
|
|
265
461
|
},
|
|
266
462
|
},
|
|
267
463
|
muiTopToolbarProps: {
|
|
268
464
|
sx: {
|
|
269
465
|
alignItems: 'center',
|
|
270
466
|
flexWrap: 'nowrap',
|
|
467
|
+
minHeight: densityTokens.filterChipMinHeight + 12,
|
|
468
|
+
py: uiDensity === 'compact' ? 0.25 : 0.5,
|
|
469
|
+
'& .MuiIconButton-root': { p: uiDensity === 'compact' ? 0.35 : 0.5 },
|
|
470
|
+
'& .MuiIconButton-root svg': { fontSize: densityTokens.toolbarIconFontSize === 'small' ? 18 : 20 },
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
muiTableHeadProps: {
|
|
474
|
+
sx: {
|
|
475
|
+
position: 'sticky',
|
|
476
|
+
top: 0,
|
|
477
|
+
zIndex: 2,
|
|
478
|
+
...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
muiTableHeadRowProps: {
|
|
482
|
+
sx: {
|
|
483
|
+
height: resolvedHeaderMaxHeight,
|
|
484
|
+
minHeight: resolvedHeaderMinHeight,
|
|
485
|
+
maxHeight: resolvedHeaderMaxHeight,
|
|
486
|
+
alignItems: 'stretch',
|
|
487
|
+
...(headerBgColor ? { backgroundColor: headerBgColor } : {}),
|
|
271
488
|
},
|
|
272
489
|
},
|
|
273
490
|
muiTableHeadCellProps: ({ column }) => {
|
|
274
491
|
const colProps = column.columnDef.muiTableHeadCellProps;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
492
|
+
let align = 'left';
|
|
493
|
+
let colSx;
|
|
494
|
+
if (typeof colProps === 'function') {
|
|
495
|
+
const resolved = colProps({ column });
|
|
496
|
+
align = resolved?.align ?? 'left';
|
|
497
|
+
colSx = resolved?.sx;
|
|
498
|
+
}
|
|
499
|
+
else if (colProps) {
|
|
500
|
+
align = colProps.align ?? 'left';
|
|
501
|
+
colSx = colProps.sx;
|
|
502
|
+
}
|
|
281
503
|
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
|
-
},
|
|
504
|
+
align,
|
|
505
|
+
sx: [
|
|
506
|
+
buildHeaderCellSx(align, resolvedHeaderMinHeight, resolvedHeaderMaxHeight, resolvedHeaderFontSize, headerBgColor, headerMaxLines),
|
|
507
|
+
...(colSx ? [colSx] : []),
|
|
508
|
+
],
|
|
314
509
|
};
|
|
315
510
|
},
|
|
316
|
-
muiTableBodyCellProps: {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
511
|
+
muiTableBodyCellProps: ({ column }) => {
|
|
512
|
+
const colProps = column.columnDef.muiTableBodyCellProps;
|
|
513
|
+
let colSx;
|
|
514
|
+
if (typeof colProps === 'function') {
|
|
515
|
+
const resolved = colProps({ column });
|
|
516
|
+
colSx = resolved?.sx;
|
|
517
|
+
}
|
|
518
|
+
else if (colProps) {
|
|
519
|
+
colSx = colProps.sx;
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
sx: [
|
|
523
|
+
{
|
|
524
|
+
whiteSpace: 'nowrap',
|
|
525
|
+
fontSize: resolvedBodyFontSize,
|
|
526
|
+
lineHeight: 1.35,
|
|
527
|
+
borderRight: '1px solid rgba(224,224,224,1)',
|
|
528
|
+
},
|
|
529
|
+
...(colSx ? [colSx] : []),
|
|
530
|
+
],
|
|
531
|
+
};
|
|
321
532
|
},
|
|
322
533
|
muiToolbarAlertBannerProps: isError
|
|
323
534
|
? { color: 'error', children: 'Failed to load data. Please try again.' }
|
|
324
535
|
: 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 })),
|
|
536
|
+
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 }) })] })),
|
|
537
|
+
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
538
|
});
|
|
328
539
|
return (jsxs(Box, { ref: containerRef, className: "tp-gpg-root", sx: {
|
|
329
540
|
position: 'relative',
|
|
330
541
|
height: resolvedHeight,
|
|
542
|
+
minHeight: tableMinHeight,
|
|
543
|
+
maxHeight: tableMaxHeight,
|
|
331
544
|
display: 'flex',
|
|
332
545
|
flexDirection: 'column',
|
|
546
|
+
overflow: 'hidden',
|
|
333
547
|
}, children: [jsx(GlobalStyles, { styles: {
|
|
334
548
|
'button[aria-label="Pin to right"]': { display: 'none' },
|
|
335
549
|
'.tp-gpg-root .MuiTableSortLabel-icon': { display: 'none !important' },
|
|
336
550
|
'.tp-gpg-root button[aria-label="Move"]': { display: 'none !important' },
|
|
337
|
-
|
|
551
|
+
'.tp-gpg-root [data-column-id="mrt-row-spacer"]': { display: 'none !important' },
|
|
552
|
+
'.tp-gpg-root .Mui-TableHeadCell-ColumnSpacer': { display: 'none !important' },
|
|
553
|
+
...tableStyleOverrides,
|
|
554
|
+
} }), 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
555
|
}
|
|
339
556
|
|
|
340
557
|
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.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 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.esm5.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireStyles } from '../../styles.esm.js';
|