mui-datatables-updated 1.0.0 → 1.0.2
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 +183 -0
- package/dist/components/MUITable.d.ts +40 -0
- package/dist/components/TableHead.d.ts +14 -0
- package/dist/components/Toolbar.d.ts +22 -0
- package/dist/components/table.d.ts +1 -0
- package/dist/components/test-data.d.ts +11 -0
- package/dist/components/utils.d.ts +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +361 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +363 -0
- package/dist/index.js.map +1 -0
- package/dist/tests/test-data.d.ts +11 -0
- package/package.json +4 -1
- package/rollup.config.mjs +1 -1
- package/src/components/MUITable.tsx +80 -56
- package/src/components/TableHead.tsx +23 -24
- package/src/components/Toolbar.tsx +106 -55
- package/src/index.ts +3 -1
- package/src/components/test-data.ts +0 -89
|
@@ -6,25 +6,47 @@ import TableCell from '@mui/material/TableCell';
|
|
|
6
6
|
import TableContainer from '@mui/material/TableContainer';
|
|
7
7
|
import TablePagination from '@mui/material/TablePagination';
|
|
8
8
|
import TableRow from '@mui/material/TableRow';
|
|
9
|
-
import React, { useCallback } from 'react';
|
|
10
|
-
import {
|
|
9
|
+
import React, { useCallback, useRef } from 'react';
|
|
10
|
+
import { useReactToPrint } from "react-to-print";
|
|
11
|
+
import { EnhancedTableHead } from './TableHead';
|
|
11
12
|
import { CustomSelectedToolbarProps, EnhancedTableToolbar } from './Toolbar';
|
|
12
13
|
import { getComparator, Order } from './utils';
|
|
14
|
+
import { LabelDisplayedRowsArgs } from '@mui/material/TablePagination';
|
|
13
15
|
|
|
14
|
-
export interface
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
export interface Column {
|
|
17
|
+
name: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
options?: {
|
|
20
|
+
customBodyRender?: (value: any) => React.ReactNode;
|
|
21
|
+
filter?: boolean;
|
|
22
|
+
sort?: boolean;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Options {
|
|
27
|
+
translations?: {
|
|
28
|
+
filterTooltip?: string;
|
|
29
|
+
searchTooltip?: string;
|
|
30
|
+
downloadTooltip?: string;
|
|
31
|
+
printTooltip?: string;
|
|
32
|
+
filtersTitle?: string;
|
|
33
|
+
resetButtonText?: string;
|
|
34
|
+
rowsPerPageText?: string;
|
|
35
|
+
searchPlaceholder?: string;
|
|
36
|
+
selectedTextRenderer?: (selected: number) => string;
|
|
37
|
+
labelDisplayedRows?: ({ from, to, count }: LabelDisplayedRowsArgs) => string;
|
|
38
|
+
}
|
|
17
39
|
}
|
|
18
40
|
|
|
19
41
|
export interface EnhancedTableProps<T extends object> extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
|
20
42
|
title: string;
|
|
21
43
|
data: T[];
|
|
22
|
-
headCells?: HeadCell<T>[];
|
|
23
44
|
deactivateSelect?: boolean;
|
|
24
45
|
defaultOrderBy?: string;
|
|
25
46
|
defaultOrder?: Order;
|
|
26
47
|
excludedColumns?: (keyof T)[];
|
|
27
|
-
|
|
48
|
+
columns?: Column[];
|
|
49
|
+
options?: Options;
|
|
28
50
|
CustomToolbar?: React.FC;
|
|
29
51
|
CustomSelectedToolbar?: React.FC<CustomSelectedToolbarProps<T>>;
|
|
30
52
|
}
|
|
@@ -37,7 +59,6 @@ interface EnhancedTableState<T> {
|
|
|
37
59
|
rowsPerPage: number;
|
|
38
60
|
searchQuery: string;
|
|
39
61
|
filterFunc: (row: T) => boolean;
|
|
40
|
-
filteredData: T[];
|
|
41
62
|
currentData: T[];
|
|
42
63
|
visibleRows: T[];
|
|
43
64
|
emptyRows: number;
|
|
@@ -46,16 +67,18 @@ interface EnhancedTableState<T> {
|
|
|
46
67
|
export const MUITable = <T extends object>({
|
|
47
68
|
title,
|
|
48
69
|
data,
|
|
49
|
-
headCells: passedHeadCells,
|
|
50
70
|
deactivateSelect,
|
|
51
71
|
defaultOrderBy,
|
|
52
72
|
defaultOrder,
|
|
53
73
|
excludedColumns,
|
|
54
|
-
|
|
74
|
+
columns: passedColumns,
|
|
55
75
|
CustomToolbar,
|
|
56
76
|
CustomSelectedToolbar,
|
|
77
|
+
options,
|
|
57
78
|
...rest
|
|
58
79
|
}: EnhancedTableProps<T>) => {
|
|
80
|
+
const tableRef = useRef<HTMLDivElement>(null);
|
|
81
|
+
const reactToPrintFn = useReactToPrint({ contentRef: tableRef });
|
|
59
82
|
|
|
60
83
|
const getDefaultOrderByKey = React.useCallback((): keyof T => {
|
|
61
84
|
if (data.length === 0) return "id" as keyof T;
|
|
@@ -83,25 +106,23 @@ export const MUITable = <T extends object>({
|
|
|
83
106
|
rowsPerPage: 5,
|
|
84
107
|
searchQuery: "",
|
|
85
108
|
filterFunc: () => true,
|
|
86
|
-
filteredData: data,
|
|
87
109
|
currentData: data,
|
|
88
110
|
visibleRows: data,
|
|
89
111
|
emptyRows: 0,
|
|
90
112
|
};
|
|
91
113
|
});
|
|
92
114
|
|
|
93
|
-
const
|
|
115
|
+
const generateColumns = React.useCallback((): Column[] => {
|
|
94
116
|
if (data.length === 0) return [];
|
|
95
117
|
return Object.keys(data[0] as object)
|
|
96
118
|
.filter((key) => !(excludedColumns || []).includes(key as keyof T))
|
|
97
119
|
.map((key) => ({
|
|
98
|
-
|
|
120
|
+
name: key,
|
|
99
121
|
label: key.charAt(0).toUpperCase() + key.slice(1),
|
|
100
|
-
numeric: typeof data[0][key as keyof T] === 'number',
|
|
101
122
|
}));
|
|
102
123
|
}, [data, excludedColumns]);
|
|
103
124
|
|
|
104
|
-
const
|
|
125
|
+
const columns = passedColumns || generateColumns();
|
|
105
126
|
|
|
106
127
|
const handleSearch = (query: string) => {
|
|
107
128
|
setState((prevState) => ({ ...prevState, searchQuery: query.toLowerCase() }));
|
|
@@ -112,46 +133,49 @@ export const MUITable = <T extends object>({
|
|
|
112
133
|
setState((prevState) => ({ ...prevState, orderBy: orderByKey }));
|
|
113
134
|
}, [data, getDefaultOrderByKey]);
|
|
114
135
|
|
|
115
|
-
//
|
|
116
|
-
React.useEffect(() => {
|
|
117
|
-
const newFilteredData = data.filter(state.filterFunc);
|
|
118
|
-
|
|
119
|
-
setState((prevState) => ({
|
|
120
|
-
...prevState,
|
|
121
|
-
filteredData: newFilteredData,
|
|
122
|
-
currentData: newFilteredData,
|
|
123
|
-
}));
|
|
124
|
-
}, [data, state.filterFunc]);
|
|
125
|
-
|
|
126
|
-
// Search data based on search query
|
|
136
|
+
// Data sorting, filtering, and pagination
|
|
127
137
|
React.useEffect(() => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
// Apply filters, search query, and sort data
|
|
139
|
+
const sortedData = [...data]
|
|
140
|
+
.filter(state.filterFunc)
|
|
141
|
+
.filter((row) =>
|
|
142
|
+
Object.values(row as Record<string, unknown>).some((value) =>
|
|
143
|
+
typeof value === "string" &&
|
|
144
|
+
value.toLowerCase().includes(state.searchQuery)
|
|
145
|
+
)
|
|
131
146
|
)
|
|
132
|
-
|
|
133
|
-
setState((prevState) => ({ ...prevState, currentData: searchResults }));
|
|
134
|
-
}, [state.filteredData, state.searchQuery]);
|
|
147
|
+
.sort(getComparator<T, keyof T>(state.order, state.orderBy));
|
|
135
148
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const sortedData = [...state.currentData].sort(
|
|
139
|
-
getComparator<T, keyof T>(state.order, state.orderBy)
|
|
140
|
-
);
|
|
149
|
+
// Paginate the processed data
|
|
150
|
+
const startIndex = state.page * state.rowsPerPage;
|
|
141
151
|
const paginatedData = sortedData.slice(
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
startIndex,
|
|
153
|
+
startIndex + state.rowsPerPage
|
|
144
154
|
);
|
|
155
|
+
|
|
145
156
|
const calculatedEmptyRows = Math.max(
|
|
146
157
|
0,
|
|
147
|
-
(1 + state.page) * state.rowsPerPage -
|
|
158
|
+
(1 + state.page) * state.rowsPerPage - sortedData.length
|
|
148
159
|
);
|
|
160
|
+
|
|
149
161
|
setState((prevState) => ({
|
|
150
162
|
...prevState,
|
|
151
163
|
visibleRows: paginatedData,
|
|
152
164
|
emptyRows: calculatedEmptyRows,
|
|
165
|
+
selected: prevState.selected.filter((selectedRow) =>
|
|
166
|
+
data.includes(selectedRow)
|
|
167
|
+
),
|
|
153
168
|
}));
|
|
154
|
-
}, [
|
|
169
|
+
}, [
|
|
170
|
+
state.filterFunc,
|
|
171
|
+
state.order,
|
|
172
|
+
state.orderBy,
|
|
173
|
+
state.page,
|
|
174
|
+
state.rowsPerPage,
|
|
175
|
+
state.searchQuery,
|
|
176
|
+
data,
|
|
177
|
+
]);
|
|
178
|
+
|
|
155
179
|
|
|
156
180
|
// Remove selected rows deleted from the data to prevent stale selected state
|
|
157
181
|
React.useEffect(() => {
|
|
@@ -217,7 +241,7 @@ export const MUITable = <T extends object>({
|
|
|
217
241
|
}, []);
|
|
218
242
|
|
|
219
243
|
return (
|
|
220
|
-
<div {...rest}>
|
|
244
|
+
<div {...rest} ref={tableRef}>
|
|
221
245
|
<Paper sx={{ width: '100%', mb: 2 }}>
|
|
222
246
|
<EnhancedTableToolbar
|
|
223
247
|
title={title}
|
|
@@ -225,10 +249,12 @@ export const MUITable = <T extends object>({
|
|
|
225
249
|
selected={state.selected}
|
|
226
250
|
onFilterChange={handleFilterChange}
|
|
227
251
|
onSearch={handleSearch}
|
|
228
|
-
|
|
252
|
+
printFn={reactToPrintFn}
|
|
253
|
+
columns={columns}
|
|
229
254
|
CustomToolbar={CustomToolbar}
|
|
230
255
|
CustomSelectedToolbar={CustomSelectedToolbar}
|
|
231
256
|
data={data}
|
|
257
|
+
options={options}
|
|
232
258
|
/>
|
|
233
259
|
<TableContainer>
|
|
234
260
|
<Table
|
|
@@ -237,7 +263,7 @@ export const MUITable = <T extends object>({
|
|
|
237
263
|
size="small"
|
|
238
264
|
>
|
|
239
265
|
<EnhancedTableHead
|
|
240
|
-
|
|
266
|
+
columns={columns}
|
|
241
267
|
numSelected={state.selected.length}
|
|
242
268
|
order={state.order}
|
|
243
269
|
orderBy={state.orderBy}
|
|
@@ -248,7 +274,6 @@ export const MUITable = <T extends object>({
|
|
|
248
274
|
/>
|
|
249
275
|
<TableBody>
|
|
250
276
|
{state.visibleRows.map((row, index) => {
|
|
251
|
-
const selectionKey = state.orderBy;
|
|
252
277
|
const isItemSelected = state.selected.some((selectedRow) => selectedRow === row);
|
|
253
278
|
const labelId = `enhanced-table-checkbox-${index}`;
|
|
254
279
|
|
|
@@ -259,7 +284,7 @@ export const MUITable = <T extends object>({
|
|
|
259
284
|
role="checkbox"
|
|
260
285
|
aria-checked={isItemSelected}
|
|
261
286
|
tabIndex={-1}
|
|
262
|
-
key={
|
|
287
|
+
key={index}
|
|
263
288
|
selected={isItemSelected}
|
|
264
289
|
sx={{ cursor: 'pointer' }}
|
|
265
290
|
>
|
|
@@ -274,20 +299,17 @@ export const MUITable = <T extends object>({
|
|
|
274
299
|
/>
|
|
275
300
|
</TableCell>
|
|
276
301
|
)}
|
|
277
|
-
{
|
|
302
|
+
{columns.map((column, cellIndex) => (
|
|
278
303
|
<TableCell
|
|
279
|
-
key={String(
|
|
304
|
+
key={String(column.name)}
|
|
280
305
|
component={cellIndex === 0 ? "th" : undefined}
|
|
281
306
|
id={cellIndex === 0 ? labelId : undefined}
|
|
282
307
|
scope={cellIndex === 0 ? "row" : undefined}
|
|
283
308
|
padding="normal"
|
|
284
309
|
>
|
|
285
|
-
{
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
.find((renderer) => renderer.id === headCell.id)
|
|
289
|
-
?.render(row, headCell.id)
|
|
290
|
-
: String(row[headCell.id])}
|
|
310
|
+
{column.options?.customBodyRender
|
|
311
|
+
? column.options.customBodyRender((row as Record<string, any>)[column.name])
|
|
312
|
+
: String((row as Record<string, any>)[column.name])}
|
|
291
313
|
</TableCell>
|
|
292
314
|
))}
|
|
293
315
|
</TableRow>
|
|
@@ -299,7 +321,7 @@ export const MUITable = <T extends object>({
|
|
|
299
321
|
height: 33 * state.emptyRows,
|
|
300
322
|
}}
|
|
301
323
|
>
|
|
302
|
-
<TableCell colSpan={
|
|
324
|
+
<TableCell colSpan={columns.length + 1} />
|
|
303
325
|
</TableRow>
|
|
304
326
|
)}
|
|
305
327
|
</TableBody>
|
|
@@ -313,6 +335,8 @@ export const MUITable = <T extends object>({
|
|
|
313
335
|
page={state.page}
|
|
314
336
|
onPageChange={handleChangePage}
|
|
315
337
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
|
338
|
+
labelRowsPerPage={options?.translations?.rowsPerPageText || "Rows per page"}
|
|
339
|
+
labelDisplayedRows={options?.translations?.labelDisplayedRows || (({ from, to, count }) => `${from}-${to} of ${count}`)}
|
|
316
340
|
/>
|
|
317
341
|
</Paper>
|
|
318
342
|
</div>
|
|
@@ -5,14 +5,9 @@ import TableHead from '@mui/material/TableHead';
|
|
|
5
5
|
import TableRow from '@mui/material/TableRow';
|
|
6
6
|
import TableSortLabel from '@mui/material/TableSortLabel';
|
|
7
7
|
import { visuallyHidden } from '@mui/utils';
|
|
8
|
+
import { Column } from './MUITable';
|
|
8
9
|
import { Order } from './utils';
|
|
9
10
|
|
|
10
|
-
export interface HeadCell<T> {
|
|
11
|
-
id: keyof T;
|
|
12
|
-
label: string;
|
|
13
|
-
numeric: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
interface EnhancedTableProps<T> {
|
|
17
12
|
numSelected: number;
|
|
18
13
|
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof T) => void;
|
|
@@ -20,7 +15,7 @@ interface EnhancedTableProps<T> {
|
|
|
20
15
|
order: Order;
|
|
21
16
|
orderBy: keyof T;
|
|
22
17
|
rowCount: number;
|
|
23
|
-
|
|
18
|
+
columns: Column[];
|
|
24
19
|
deactivateSelectAll?: boolean;
|
|
25
20
|
}
|
|
26
21
|
|
|
@@ -31,7 +26,7 @@ export function EnhancedTableHead<T>({
|
|
|
31
26
|
numSelected,
|
|
32
27
|
rowCount,
|
|
33
28
|
onRequestSort,
|
|
34
|
-
|
|
29
|
+
columns,
|
|
35
30
|
deactivateSelectAll,
|
|
36
31
|
}: EnhancedTableProps<T>) {
|
|
37
32
|
const createSortHandler =
|
|
@@ -55,25 +50,29 @@ export function EnhancedTableHead<T>({
|
|
|
55
50
|
/>
|
|
56
51
|
</TableCell>
|
|
57
52
|
}
|
|
58
|
-
{
|
|
53
|
+
{columns.map((column) => (
|
|
59
54
|
<TableCell
|
|
60
|
-
key={String(
|
|
55
|
+
key={String(column.name)}
|
|
61
56
|
padding='normal'
|
|
62
|
-
sortDirection={orderBy ===
|
|
57
|
+
sortDirection={orderBy === column.name ? order : false}
|
|
63
58
|
>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
{column.options?.sort !== false ? (
|
|
60
|
+
<TableSortLabel
|
|
61
|
+
active={orderBy === column.name}
|
|
62
|
+
direction={orderBy === column.name ? order : 'asc'}
|
|
63
|
+
onClick={createSortHandler(column.name as keyof T)}
|
|
64
|
+
sx={{ fontWeight: 'bold' }}
|
|
65
|
+
>
|
|
66
|
+
{column.label}
|
|
67
|
+
{orderBy === column.name ? (
|
|
68
|
+
<Box component="span" sx={visuallyHidden}>
|
|
69
|
+
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
|
70
|
+
</Box>
|
|
71
|
+
) : null}
|
|
72
|
+
</TableSortLabel>
|
|
73
|
+
) : (
|
|
74
|
+
<span style={{ fontWeight: 700 }}>{column.label}</span>
|
|
75
|
+
)}
|
|
77
76
|
</TableCell>
|
|
78
77
|
))}
|
|
79
78
|
</TableRow>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { Close, CloudDownload, Print } from "@mui/icons-material";
|
|
1
2
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
|
2
3
|
import SearchIcon from "@mui/icons-material/Search";
|
|
3
|
-
import { Box, Button, Checkbox, IconButton,
|
|
4
|
+
import { Box, Button, Checkbox, IconButton, Popover, Slider, Stack, TextField, Toolbar, Tooltip, Typography } from "@mui/material";
|
|
4
5
|
import { alpha } from "@mui/material/styles";
|
|
5
6
|
import React, { useEffect, useState } from "react";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { Column, Options } from "./MUITable";
|
|
8
|
+
import { UseReactToPrintFn } from "react-to-print";
|
|
8
9
|
|
|
9
10
|
interface Filter {
|
|
10
11
|
key: string;
|
|
@@ -30,24 +31,53 @@ interface EnhancedTableToolbarProps<T> {
|
|
|
30
31
|
selected: readonly T[];
|
|
31
32
|
onFilterChange: (filterFunc: (row: T) => boolean) => void;
|
|
32
33
|
onSearch: (query: string) => void;
|
|
33
|
-
|
|
34
|
+
printFn: UseReactToPrintFn;
|
|
35
|
+
columns: Column[];
|
|
34
36
|
CustomToolbar?: React.FC;
|
|
35
37
|
CustomSelectedToolbar?: React.FC<CustomSelectedToolbarProps<T>>;
|
|
36
38
|
data?: T[];
|
|
39
|
+
options?: Options;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
40
|
-
const { title, numSelected, selected, onFilterChange, onSearch,
|
|
43
|
+
const { title, numSelected, selected, onFilterChange, onSearch, printFn, columns, CustomToolbar, CustomSelectedToolbar, data, options } = props;
|
|
41
44
|
|
|
42
45
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
|
43
46
|
const [filters, setFilters] = useState<Filter[]>([]);
|
|
44
47
|
const [filterConfig, setFilterConfig] = useState<FilterConfig[]>([]);
|
|
45
48
|
// rest value to force re-render of filters
|
|
46
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
|
+
}
|
|
47
76
|
|
|
48
77
|
useEffect(() => {
|
|
49
78
|
if (data && data.length > 0) {
|
|
50
|
-
const inferredConfig =
|
|
79
|
+
const inferredConfig = columns.map((column) => {
|
|
80
|
+
const key = column.name;
|
|
51
81
|
const values = data.map((row) => (row as Record<string, any>)[key]);
|
|
52
82
|
const isNumber = values.every((val) => typeof val === "number");
|
|
53
83
|
const inferredType = isNumber ? "number" : typeof values[0] === "boolean" ? "boolean" : "string";
|
|
@@ -60,7 +90,7 @@ export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
|
60
90
|
});
|
|
61
91
|
setFilterConfig(inferredConfig);
|
|
62
92
|
}
|
|
63
|
-
}, [data]);
|
|
93
|
+
}, [data, columns]);
|
|
64
94
|
|
|
65
95
|
useEffect(() => {
|
|
66
96
|
const newFilterFunc = (row: T) => {
|
|
@@ -91,6 +121,10 @@ export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
|
91
121
|
setAnchorEl(null);
|
|
92
122
|
};
|
|
93
123
|
|
|
124
|
+
const handleSearchChange = () => {
|
|
125
|
+
setOpenSearch((prev) => !prev);
|
|
126
|
+
};
|
|
127
|
+
|
|
94
128
|
const open = Boolean(anchorEl);
|
|
95
129
|
|
|
96
130
|
const getFilter = (key: string): Filter | undefined =>
|
|
@@ -137,11 +171,8 @@ export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
|
137
171
|
return (
|
|
138
172
|
<Toolbar
|
|
139
173
|
sx={[
|
|
140
|
-
{
|
|
141
|
-
numSelected > 0 && {
|
|
142
|
-
bgcolor: (theme) =>
|
|
143
|
-
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
|
|
144
|
-
},
|
|
174
|
+
{ px: { sm: 2 }, borderBottom: 1, borderColor: "divider" },
|
|
175
|
+
numSelected > 0 && { bgcolor: (theme) => alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity) },
|
|
145
176
|
]}
|
|
146
177
|
>
|
|
147
178
|
{numSelected > 0 ? (
|
|
@@ -152,7 +183,9 @@ export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
|
152
183
|
variant="subtitle1"
|
|
153
184
|
component="div"
|
|
154
185
|
>
|
|
155
|
-
{
|
|
186
|
+
{options?.translations?.selectedTextRenderer
|
|
187
|
+
? options.translations.selectedTextRenderer(numSelected)
|
|
188
|
+
: `${numSelected} selected`}
|
|
156
189
|
</Typography>
|
|
157
190
|
{CustomSelectedToolbar && (
|
|
158
191
|
<CustomSelectedToolbar data={data} selected={selected} />
|
|
@@ -165,39 +198,58 @@ export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
|
165
198
|
width="100%"
|
|
166
199
|
alignItems="center"
|
|
167
200
|
>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
variant="h6"
|
|
171
|
-
id="tableTitle"
|
|
172
|
-
component="div"
|
|
173
|
-
>
|
|
174
|
-
{title}
|
|
175
|
-
</Typography>
|
|
176
|
-
<Box
|
|
177
|
-
sx={{
|
|
178
|
-
flexGrow: 1,
|
|
179
|
-
display: "flex",
|
|
180
|
-
alignItems: "center",
|
|
181
|
-
border: 1,
|
|
182
|
-
borderColor: "lightgray",
|
|
183
|
-
borderRadius: 1,
|
|
184
|
-
}}
|
|
185
|
-
>
|
|
186
|
-
<InputBase
|
|
187
|
-
placeholder="Search…"
|
|
188
|
-
onChange={(e) => onSearch(e.target.value)}
|
|
189
|
-
sx={{ marginLeft: 2, flex: 1, width: 180 }}
|
|
190
|
-
/>
|
|
191
|
-
<IconButton>
|
|
201
|
+
{openSearch ? (
|
|
202
|
+
<Stack direction="row" alignItems="center">
|
|
192
203
|
<SearchIcon />
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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>
|
|
201
253
|
</Stack>
|
|
202
254
|
)}
|
|
203
255
|
<Popover
|
|
@@ -218,19 +270,19 @@ export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
|
218
270
|
>
|
|
219
271
|
<Stack direction="row" justifyContent="space-between">
|
|
220
272
|
<Typography variant="h6" sx={{ marginBottom: 2 }}>
|
|
221
|
-
Filters
|
|
273
|
+
{options?.translations?.filtersTitle || "Filters"}
|
|
222
274
|
</Typography>
|
|
223
275
|
<Button variant="contained" size="small" sx={{ height: "fit-content" }} onClick={resetFilters}>
|
|
224
|
-
Reset
|
|
276
|
+
{options?.translations?.resetButtonText || "Reset"}
|
|
225
277
|
</Button>
|
|
226
278
|
</Stack>
|
|
227
|
-
<Stack key={resetCounter}
|
|
279
|
+
<Stack key={resetCounter}>
|
|
228
280
|
{filterConfig.map(({ key, type, min, max }) => {
|
|
229
281
|
const currentFilter = getFilter(key);
|
|
230
282
|
return (
|
|
231
283
|
<Box key={key}>
|
|
232
284
|
<Typography variant="subtitle1">
|
|
233
|
-
{
|
|
285
|
+
{columns.find((cell) => cell.name === key)?.label}
|
|
234
286
|
</Typography>
|
|
235
287
|
{type === "number" && min !== undefined && max !== undefined && (
|
|
236
288
|
<Slider
|
|
@@ -249,15 +301,14 @@ export function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>) {
|
|
|
249
301
|
/>
|
|
250
302
|
)}
|
|
251
303
|
{type === "string" && (
|
|
252
|
-
<
|
|
253
|
-
placeholder="Search..."
|
|
304
|
+
<TextField
|
|
305
|
+
placeholder={options?.translations?.searchPlaceholder || "Search..."}
|
|
306
|
+
size="small"
|
|
254
307
|
value={(currentFilter?.value as string) || ""}
|
|
255
308
|
onChange={(e) => handleFilterChange(key, e.target.value)}
|
|
256
309
|
sx={{
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
borderColor: "lightgray",
|
|
260
|
-
borderRadius: 1,
|
|
310
|
+
marginBottom: 1,
|
|
311
|
+
paddingY: 0.5,
|
|
261
312
|
width: "100%",
|
|
262
313
|
}}
|
|
263
314
|
/>
|
package/src/index.ts
CHANGED