mui-datatables-updated 1.0.2 → 1.0.3
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/LICENSE +21 -21
- package/README.md +185 -183
- package/dist/components/MUITable.d.ts +4 -3
- package/dist/components/TableHead.d.ts +2 -2
- package/dist/components/Toolbar.d.ts +3 -3
- package/dist/components/utils.d.ts +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +112 -65
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +112 -65
- package/dist/index.js.map +1 -1
- package/dist/tests/MUITable.performance.test.d.ts +1 -0
- package/dist/tests/MUITable.test.d.ts +1 -0
- package/dist/tests/setup.d.ts +1 -0
- package/dist/tests/test-data.d.ts +2 -2
- package/dist/tests/utils.test.d.ts +1 -0
- package/package.json +82 -58
- package/dist/components/table.d.ts +0 -1
- package/dist/components/test-data.d.ts +0 -11
- package/rollup.config.mjs +0 -23
- package/src/components/MUITable.tsx +0 -344
- package/src/components/TableHead.tsx +0 -81
- package/src/components/Toolbar.tsx +0 -340
- package/src/components/utils.ts +0 -17
- package/src/index.ts +0 -3
- package/tsconfig.json +0 -24
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import { Close, CloudDownload, Print } from "@mui/icons-material";
|
|
2
|
-
import FilterListIcon from "@mui/icons-material/FilterList";
|
|
3
|
-
import SearchIcon from "@mui/icons-material/Search";
|
|
4
|
-
import { Box, Button, Checkbox, IconButton, Popover, Slider, Stack, TextField, Toolbar, Tooltip, Typography } from "@mui/material";
|
|
5
|
-
import { alpha } from "@mui/material/styles";
|
|
6
|
-
import React, { useEffect, useState } from "react";
|
|
7
|
-
import { Column, Options } from "./MUITable";
|
|
8
|
-
import { UseReactToPrintFn } from "react-to-print";
|
|
9
|
-
|
|
10
|
-
interface Filter {
|
|
11
|
-
key: string;
|
|
12
|
-
type: "number" | "string" | "boolean";
|
|
13
|
-
value?: string | boolean | number[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface FilterConfig {
|
|
17
|
-
key: string;
|
|
18
|
-
type: "number" | "string" | "boolean";
|
|
19
|
-
min?: number;
|
|
20
|
-
max?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface CustomSelectedToolbarProps<T> {
|
|
24
|
-
selected?: readonly T[];
|
|
25
|
-
data?: T[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface EnhancedTableToolbarProps<T> {
|
|
29
|
-
title: string;
|
|
30
|
-
numSelected: number;
|
|
31
|
-
selected: readonly T[];
|
|
32
|
-
onFilterChange: (filterFunc: (row: T) => boolean) => void;
|
|
33
|
-
onSearch: (query: string) => void;
|
|
34
|
-
printFn: UseReactToPrintFn;
|
|
35
|
-
columns: Column[];
|
|
36
|
-
CustomToolbar?: React.FC;
|
|
37
|
-
CustomSelectedToolbar?: React.FC<CustomSelectedToolbarProps<T>>;
|
|
38
|
-
data?: T[];
|
|
39
|
-
options?: Options;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
43
|
-
const { title, numSelected, selected, onFilterChange, onSearch, printFn, columns, CustomToolbar, CustomSelectedToolbar, data, options } = props;
|
|
44
|
-
|
|
45
|
-
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
|
46
|
-
const [filters, setFilters] = useState<Filter[]>([]);
|
|
47
|
-
const [filterConfig, setFilterConfig] = useState<FilterConfig[]>([]);
|
|
48
|
-
// rest value to force re-render of filters
|
|
49
|
-
const [resetCounter, setResetCounter] = useState(0);
|
|
50
|
-
const [openSearch, setOpenSearch] = useState(false);
|
|
51
|
-
|
|
52
|
-
function downloadCSV(data: Record<string, any>[], filename: string = "data.csv"): void {
|
|
53
|
-
// Base case
|
|
54
|
-
if (data.length === 0) {
|
|
55
|
-
console.warn("No data to export.");
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Create CSV content
|
|
60
|
-
const headers = Object.keys(data[0] as Record<string, any>);
|
|
61
|
-
const csvRows = data.map(obj =>
|
|
62
|
-
headers.map(field => JSON.stringify(obj[field] ?? "")).join(",")
|
|
63
|
-
);
|
|
64
|
-
const csvContent = [headers.join(","), ...csvRows].join("\n");
|
|
65
|
-
|
|
66
|
-
// Create Blob and download
|
|
67
|
-
const blob = new Blob([csvContent], { type: "text/csv" });
|
|
68
|
-
const link = document.createElement("a");
|
|
69
|
-
link.href = URL.createObjectURL(blob);
|
|
70
|
-
link.download = filename;
|
|
71
|
-
document.body.appendChild(link);
|
|
72
|
-
link.click();
|
|
73
|
-
document.body.removeChild(link);
|
|
74
|
-
URL.revokeObjectURL(link.href);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
if (data && data.length > 0) {
|
|
79
|
-
const inferredConfig = columns.map((column) => {
|
|
80
|
-
const key = column.name;
|
|
81
|
-
const values = data.map((row) => (row as Record<string, any>)[key]);
|
|
82
|
-
const isNumber = values.every((val) => typeof val === "number");
|
|
83
|
-
const inferredType = isNumber ? "number" : typeof values[0] === "boolean" ? "boolean" : "string";
|
|
84
|
-
return {
|
|
85
|
-
key,
|
|
86
|
-
type: inferredType as "number" | "string" | "boolean",
|
|
87
|
-
min: isNumber ? Math.min(...values) : undefined,
|
|
88
|
-
max: isNumber ? Math.max(...values) : undefined,
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
setFilterConfig(inferredConfig);
|
|
92
|
-
}
|
|
93
|
-
}, [data, columns]);
|
|
94
|
-
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
const newFilterFunc = (row: T) => {
|
|
97
|
-
if (filters.length === 0) return true;
|
|
98
|
-
return filters.every((filter) => {
|
|
99
|
-
const rowValue = (row as Record<string, any>)[filter.key];
|
|
100
|
-
if (filter.type === "number") {
|
|
101
|
-
const [min, max] = filter.value as number[];
|
|
102
|
-
return rowValue >= min && rowValue <= max;
|
|
103
|
-
}
|
|
104
|
-
if (filter.type === "string") {
|
|
105
|
-
return rowValue.toString().toLowerCase().includes((filter.value as string).toLowerCase());
|
|
106
|
-
}
|
|
107
|
-
if (filter.type === "boolean") {
|
|
108
|
-
return rowValue === filter.value;
|
|
109
|
-
}
|
|
110
|
-
return true;
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
|
-
onFilterChange(newFilterFunc);
|
|
114
|
-
}, [filters, onFilterChange]);
|
|
115
|
-
|
|
116
|
-
const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
117
|
-
setAnchorEl(event.currentTarget);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const handleClose = () => {
|
|
121
|
-
setAnchorEl(null);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const handleSearchChange = () => {
|
|
125
|
-
setOpenSearch((prev) => !prev);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const open = Boolean(anchorEl);
|
|
129
|
-
|
|
130
|
-
const getFilter = (key: string): Filter | undefined =>
|
|
131
|
-
filters.find((filter) => filter.key === key);
|
|
132
|
-
|
|
133
|
-
const updateFilter = (updatedFilter: Filter) => {
|
|
134
|
-
setFilters((prevFilters) => {
|
|
135
|
-
const existingIndex = prevFilters.findIndex((filter) => filter.key === updatedFilter.key);
|
|
136
|
-
if (existingIndex !== -1) {
|
|
137
|
-
// Update existing filter
|
|
138
|
-
const newFilters = [...prevFilters];
|
|
139
|
-
newFilters[existingIndex] = updatedFilter;
|
|
140
|
-
return newFilters;
|
|
141
|
-
}
|
|
142
|
-
// Add new filter
|
|
143
|
-
return [...prevFilters, updatedFilter];
|
|
144
|
-
});
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const removeFilter = (key: string) => {
|
|
148
|
-
setFilters((prevFilters) => prevFilters.filter((filter) => filter.key !== key));
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const handleFilterChange = (key: string, value: any) => {
|
|
152
|
-
if (value === undefined || value === null || value === "") {
|
|
153
|
-
removeFilter(key);
|
|
154
|
-
} else {
|
|
155
|
-
const config = filterConfig.find((config) => config.key === key);
|
|
156
|
-
if (!config) return;
|
|
157
|
-
|
|
158
|
-
updateFilter({
|
|
159
|
-
key,
|
|
160
|
-
type: config.type,
|
|
161
|
-
value,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const resetFilters = () => {
|
|
167
|
-
setFilters([]);
|
|
168
|
-
setResetCounter((prev) => prev + 1);
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
return (
|
|
172
|
-
<Toolbar
|
|
173
|
-
sx={[
|
|
174
|
-
{ px: { sm: 2 }, borderBottom: 1, borderColor: "divider" },
|
|
175
|
-
numSelected > 0 && { bgcolor: (theme) => alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity) },
|
|
176
|
-
]}
|
|
177
|
-
>
|
|
178
|
-
{numSelected > 0 ? (
|
|
179
|
-
<>
|
|
180
|
-
<Typography
|
|
181
|
-
sx={{ flex: "1 1 100%" }}
|
|
182
|
-
color="inherit"
|
|
183
|
-
variant="subtitle1"
|
|
184
|
-
component="div"
|
|
185
|
-
>
|
|
186
|
-
{options?.translations?.selectedTextRenderer
|
|
187
|
-
? options.translations.selectedTextRenderer(numSelected)
|
|
188
|
-
: `${numSelected} selected`}
|
|
189
|
-
</Typography>
|
|
190
|
-
{CustomSelectedToolbar && (
|
|
191
|
-
<CustomSelectedToolbar data={data} selected={selected} />
|
|
192
|
-
)}
|
|
193
|
-
</>
|
|
194
|
-
) : (
|
|
195
|
-
<Stack
|
|
196
|
-
direction="row"
|
|
197
|
-
justifyContent="space-between"
|
|
198
|
-
width="100%"
|
|
199
|
-
alignItems="center"
|
|
200
|
-
>
|
|
201
|
-
{openSearch ? (
|
|
202
|
-
<Stack direction="row" alignItems="center">
|
|
203
|
-
<SearchIcon />
|
|
204
|
-
<TextField
|
|
205
|
-
placeholder={options?.translations?.searchPlaceholder || "Search..."}
|
|
206
|
-
onChange={(e) => onSearch(e.target.value)}
|
|
207
|
-
variant="standard"
|
|
208
|
-
autoFocus
|
|
209
|
-
fullWidth
|
|
210
|
-
sx={{ marginLeft: 1 }}
|
|
211
|
-
/>
|
|
212
|
-
<IconButton
|
|
213
|
-
onClick={handleSearchChange}
|
|
214
|
-
sx={{ '&:hover': { color: 'error.main' } }}
|
|
215
|
-
>
|
|
216
|
-
<Close />
|
|
217
|
-
</IconButton>
|
|
218
|
-
</Stack>
|
|
219
|
-
) : (
|
|
220
|
-
|
|
221
|
-
<Typography
|
|
222
|
-
sx={{ flex: "1 1 100%", alignContent: "center", paddingLeft: 1 }}
|
|
223
|
-
variant="h6"
|
|
224
|
-
id="tableTitle"
|
|
225
|
-
component="div"
|
|
226
|
-
>
|
|
227
|
-
{title}
|
|
228
|
-
</Typography>
|
|
229
|
-
)}
|
|
230
|
-
<Stack direction="row" spacing={0.5}>
|
|
231
|
-
<Tooltip title={options?.translations?.searchTooltip || "Search"}>
|
|
232
|
-
<IconButton onClick={handleSearchChange}>
|
|
233
|
-
<SearchIcon />
|
|
234
|
-
</IconButton>
|
|
235
|
-
</Tooltip>
|
|
236
|
-
<Tooltip title={options?.translations?.downloadTooltip || "Download CSV"}>
|
|
237
|
-
<IconButton onClick={() => downloadCSV(data as Record<string, any>[], "data.csv")}>
|
|
238
|
-
<CloudDownload />
|
|
239
|
-
</IconButton>
|
|
240
|
-
</Tooltip>
|
|
241
|
-
<Tooltip title={options?.translations?.printTooltip || "Print"}>
|
|
242
|
-
<IconButton onClick={() => printFn()}>
|
|
243
|
-
<Print />
|
|
244
|
-
</IconButton>
|
|
245
|
-
</Tooltip>
|
|
246
|
-
<Tooltip title={options?.translations?.filterTooltip || "Filter list"}>
|
|
247
|
-
<IconButton onClick={handleOpen}>
|
|
248
|
-
<FilterListIcon />
|
|
249
|
-
</IconButton>
|
|
250
|
-
</Tooltip>
|
|
251
|
-
{CustomToolbar && <CustomToolbar />}
|
|
252
|
-
</Stack>
|
|
253
|
-
</Stack>
|
|
254
|
-
)}
|
|
255
|
-
<Popover
|
|
256
|
-
open={open}
|
|
257
|
-
anchorEl={anchorEl}
|
|
258
|
-
onClose={handleClose}
|
|
259
|
-
anchorOrigin={{
|
|
260
|
-
vertical: "bottom",
|
|
261
|
-
horizontal: "right",
|
|
262
|
-
}}
|
|
263
|
-
transformOrigin={{
|
|
264
|
-
vertical: "top",
|
|
265
|
-
horizontal: "right",
|
|
266
|
-
}}
|
|
267
|
-
slotProps={{
|
|
268
|
-
paper: { sx: { width: "20%", padding: 2, boxShadow: 2 } }
|
|
269
|
-
}}
|
|
270
|
-
>
|
|
271
|
-
<Stack direction="row" justifyContent="space-between">
|
|
272
|
-
<Typography variant="h6" sx={{ marginBottom: 2 }}>
|
|
273
|
-
{options?.translations?.filtersTitle || "Filters"}
|
|
274
|
-
</Typography>
|
|
275
|
-
<Button variant="contained" size="small" sx={{ height: "fit-content" }} onClick={resetFilters}>
|
|
276
|
-
{options?.translations?.resetButtonText || "Reset"}
|
|
277
|
-
</Button>
|
|
278
|
-
</Stack>
|
|
279
|
-
<Stack key={resetCounter}>
|
|
280
|
-
{filterConfig.map(({ key, type, min, max }) => {
|
|
281
|
-
const currentFilter = getFilter(key);
|
|
282
|
-
return (
|
|
283
|
-
<Box key={key}>
|
|
284
|
-
<Typography variant="subtitle1">
|
|
285
|
-
{columns.find((cell) => cell.name === key)?.label}
|
|
286
|
-
</Typography>
|
|
287
|
-
{type === "number" && min !== undefined && max !== undefined && (
|
|
288
|
-
<Slider
|
|
289
|
-
value={[
|
|
290
|
-
(currentFilter?.value as number[] | undefined)?.[0] ?? min,
|
|
291
|
-
(currentFilter?.value as number[] | undefined)?.[1] ?? max,
|
|
292
|
-
]}
|
|
293
|
-
onChange={(_, newValue) => {
|
|
294
|
-
const [newMin, newMax] = newValue as number[];
|
|
295
|
-
handleFilterChange(key, [newMin, newMax]);
|
|
296
|
-
}}
|
|
297
|
-
valueLabelDisplay="auto"
|
|
298
|
-
min={min || 0}
|
|
299
|
-
max={max || 100}
|
|
300
|
-
step={1}
|
|
301
|
-
/>
|
|
302
|
-
)}
|
|
303
|
-
{type === "string" && (
|
|
304
|
-
<TextField
|
|
305
|
-
placeholder={options?.translations?.searchPlaceholder || "Search..."}
|
|
306
|
-
size="small"
|
|
307
|
-
value={(currentFilter?.value as string) || ""}
|
|
308
|
-
onChange={(e) => handleFilterChange(key, e.target.value)}
|
|
309
|
-
sx={{
|
|
310
|
-
marginBottom: 1,
|
|
311
|
-
paddingY: 0.5,
|
|
312
|
-
width: "100%",
|
|
313
|
-
}}
|
|
314
|
-
/>
|
|
315
|
-
)}
|
|
316
|
-
{type === "boolean" && (
|
|
317
|
-
<Box display="flex" alignItems="center">
|
|
318
|
-
<Checkbox
|
|
319
|
-
checked={currentFilter?.value === true}
|
|
320
|
-
onChange={(e) =>
|
|
321
|
-
handleFilterChange(key, e.target.checked)
|
|
322
|
-
}
|
|
323
|
-
/>
|
|
324
|
-
{currentFilter && (
|
|
325
|
-
<Close
|
|
326
|
-
color="error"
|
|
327
|
-
sx={{ cursor: "pointer", fontSize: 15 }}
|
|
328
|
-
onClick={() => handleFilterChange(key, undefined)}
|
|
329
|
-
/>
|
|
330
|
-
)}
|
|
331
|
-
</Box>
|
|
332
|
-
)}
|
|
333
|
-
</Box>
|
|
334
|
-
);
|
|
335
|
-
})}
|
|
336
|
-
</Stack>
|
|
337
|
-
</Popover>
|
|
338
|
-
</Toolbar>
|
|
339
|
-
);
|
|
340
|
-
}
|
package/src/components/utils.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type Order = 'asc' | 'desc';
|
|
2
|
-
|
|
3
|
-
export function getComparator<T, Key extends keyof T>(
|
|
4
|
-
order: Order,
|
|
5
|
-
orderBy: Key,
|
|
6
|
-
): (a: T, b: T) => number {
|
|
7
|
-
return order === 'desc'
|
|
8
|
-
? (a, b) => descendingComparator(a[orderBy], b[orderBy])
|
|
9
|
-
: (a, b) => -descendingComparator(a[orderBy], b[orderBy]);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function descendingComparator<T>(a: T, b: T): number {
|
|
13
|
-
if (b < a) return -1;
|
|
14
|
-
if (b > a) return 1;
|
|
15
|
-
return 0;
|
|
16
|
-
}
|
|
17
|
-
|
package/src/index.ts
DELETED
package/tsconfig.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2017",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"jsx": "react-jsx",
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"declarationDir": "./dist/types",
|
|
13
|
-
"outDir": "./dist",
|
|
14
|
-
"isolatedModules": true
|
|
15
|
-
},
|
|
16
|
-
"include": [
|
|
17
|
-
"src/**/*.ts",
|
|
18
|
-
"src/**/*.tsx"
|
|
19
|
-
],
|
|
20
|
-
"exclude": [
|
|
21
|
-
"node_modules",
|
|
22
|
-
"dist"
|
|
23
|
-
]
|
|
24
|
-
}
|