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
package/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# MUI Datatable library inspired by the gregnb/mui-datatables project, featuring an up-to-date implementation with Typescript Support
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
If not already installed, install the Material-UI library:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Then install the mui-datatables-updated package:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install mui-datatables-updated
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
import React from 'react';
|
|
21
|
+
import MUIDataTable from 'mui-datatables-updated';
|
|
22
|
+
|
|
23
|
+
const sampleData = [
|
|
24
|
+
{
|
|
25
|
+
"id": 1,
|
|
26
|
+
"name": "Cupcake",
|
|
27
|
+
"carbs": 67,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": 2,
|
|
31
|
+
"name": "Donut",
|
|
32
|
+
"carbs": 51,
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export default function App() {
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<MUITable title='Sample title' data={sampleData} />
|
|
40
|
+
</>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Options
|
|
46
|
+
|
|
47
|
+
| Name | Type | Description |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| title | string | Title of the table |
|
|
50
|
+
| data | array | Data to be displayed in the table |
|
|
51
|
+
| deactivateSelect | boolean | Disable row selection |
|
|
52
|
+
| defaultOrderBy | string -> must be a key in the data object | Default order by key to sort the table data on first load |
|
|
53
|
+
| defaultOrder | 'asc' or 'desc' | Default order to be used when sorting and displaying on first load |
|
|
54
|
+
| excludedColumns | array | Array of **keys** in data objects to be excluded from the table |
|
|
55
|
+
| columns | array | Array of objects to define the columns of the table |
|
|
56
|
+
| options | object | Object to define the options of the table |
|
|
57
|
+
|
|
58
|
+
### Example
|
|
59
|
+
|
|
60
|
+
```jsx
|
|
61
|
+
import MUITable from 'mui-datatables-updated'
|
|
62
|
+
|
|
63
|
+
const sampleColumns = [
|
|
64
|
+
{
|
|
65
|
+
name: 'name',
|
|
66
|
+
label: 'Name of dessert',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'healthy',
|
|
70
|
+
label: 'Is Healthy?',
|
|
71
|
+
options: {
|
|
72
|
+
sort: false,
|
|
73
|
+
customBodyRender: (value: boolean) => value ? ':)' : ':('
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'carbs',
|
|
78
|
+
label: 'Carbs (g)',
|
|
79
|
+
options: {
|
|
80
|
+
sort: false
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'id',
|
|
85
|
+
label: 'ID',
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
function App() {
|
|
90
|
+
return (
|
|
91
|
+
<>
|
|
92
|
+
<h1>MUI Table test</h1>
|
|
93
|
+
<MUITable
|
|
94
|
+
title='Sample Test'
|
|
95
|
+
data={sampleData}
|
|
96
|
+
defaultOrder='desc'
|
|
97
|
+
defaultOrderBy='name'
|
|
98
|
+
excludedColumns={['id']}
|
|
99
|
+
/>
|
|
100
|
+
<MUITable
|
|
101
|
+
title='Sample Test'
|
|
102
|
+
data={sampleData}
|
|
103
|
+
defaultOrder='desc'
|
|
104
|
+
defaultOrderBy='name'
|
|
105
|
+
columns={sampleColumns}
|
|
106
|
+
deactivateSelect
|
|
107
|
+
options={{
|
|
108
|
+
translations: {
|
|
109
|
+
filterTooltip: 'Filtrar',
|
|
110
|
+
filtersTitle: 'Filtros',
|
|
111
|
+
resetButtonText: 'Reiniciar',
|
|
112
|
+
searchPlaceholder: 'Buscar...',
|
|
113
|
+
rowsPerPageText: 'Filas por página',
|
|
114
|
+
selectedTextRenderer: (numSelected) => `${numSelected} seleccionado/s`,
|
|
115
|
+
labelDisplayedRows: ({ from, to, count }) => `${from}-${to} de ${count}`,
|
|
116
|
+
}
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
</>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Custom Columns
|
|
125
|
+
|
|
126
|
+
On each column object, you have the ability to customize columns to show the filtering options in the filter dropdown and to disable sorting when clicking on the column header. Values are set to true by default.
|
|
127
|
+
|
|
128
|
+
| Name | Type | Description |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| name | string | Key in the data object |
|
|
131
|
+
| label | string | Label to be displayed in the column header |
|
|
132
|
+
| options | object | Object to define the options of the column |
|
|
133
|
+
|
|
134
|
+
### Options Object
|
|
135
|
+
|
|
136
|
+
| Name | Type | Description |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| customBodyRender | function | Function to render custom content in the column |
|
|
139
|
+
| filter | boolean | Show filter dropdown in the column |
|
|
140
|
+
| sort | boolean | Enable sorting when clicking on the column header |
|
|
141
|
+
|
|
142
|
+
### Custom columns example
|
|
143
|
+
|
|
144
|
+
```jsx
|
|
145
|
+
const columns = [
|
|
146
|
+
{
|
|
147
|
+
name: "active",
|
|
148
|
+
label: "Is Active",
|
|
149
|
+
options: {
|
|
150
|
+
customBodyRender: (value: boolean) => value ? 'Yes' : 'No',
|
|
151
|
+
filter: true,
|
|
152
|
+
sort: false
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
...
|
|
156
|
+
];
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Localization
|
|
160
|
+
|
|
161
|
+
As well as gregnb original package, I decided that the cost of bringing in another library to perform localizations would be too expensive. Instead the ability to override most text labels (which aren't many) is offered through the translations property. The available customizations are:
|
|
162
|
+
|
|
163
|
+
```jsx
|
|
164
|
+
<MUITable
|
|
165
|
+
title='Sample Test'
|
|
166
|
+
data={sampleData}
|
|
167
|
+
options={{
|
|
168
|
+
translations: {
|
|
169
|
+
filterTooltip: 'Filtrar',
|
|
170
|
+
filtersTitle: 'Filtros',
|
|
171
|
+
resetButtonText: 'Reiniciar',
|
|
172
|
+
searchPlaceholder: 'Buscar...',
|
|
173
|
+
rowsPerPageText: 'Filas por página',
|
|
174
|
+
selectedTextRenderer: (numSelected) => `${numSelected} seleccionado/s`,
|
|
175
|
+
labelDisplayedRows: ({ from, to, count }) => `${from}-${to} de ${count}`,
|
|
176
|
+
}
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Contributing
|
|
182
|
+
|
|
183
|
+
Pull requests are welcome. I won't be able to implement new features in the short term, so feel free to contribute to the project by adding new features, fixing bugs, creating tests, and improving the documentation.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CustomSelectedToolbarProps } from './Toolbar';
|
|
3
|
+
import { Order } from './utils';
|
|
4
|
+
import { LabelDisplayedRowsArgs } from '@mui/material/TablePagination';
|
|
5
|
+
export interface Column {
|
|
6
|
+
name: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
options?: {
|
|
9
|
+
customBodyRender?: (value: any) => React.ReactNode;
|
|
10
|
+
filter?: boolean;
|
|
11
|
+
sort?: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface Options {
|
|
15
|
+
translations?: {
|
|
16
|
+
filterTooltip?: string;
|
|
17
|
+
searchTooltip?: string;
|
|
18
|
+
downloadTooltip?: string;
|
|
19
|
+
printTooltip?: string;
|
|
20
|
+
filtersTitle?: string;
|
|
21
|
+
resetButtonText?: string;
|
|
22
|
+
rowsPerPageText?: string;
|
|
23
|
+
searchPlaceholder?: string;
|
|
24
|
+
selectedTextRenderer?: (selected: number) => string;
|
|
25
|
+
labelDisplayedRows?: ({ from, to, count }: LabelDisplayedRowsArgs) => string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface EnhancedTableProps<T extends object> extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
|
|
29
|
+
title: string;
|
|
30
|
+
data: T[];
|
|
31
|
+
deactivateSelect?: boolean;
|
|
32
|
+
defaultOrderBy?: string;
|
|
33
|
+
defaultOrder?: Order;
|
|
34
|
+
excludedColumns?: (keyof T)[];
|
|
35
|
+
columns?: Column[];
|
|
36
|
+
options?: Options;
|
|
37
|
+
CustomToolbar?: React.FC;
|
|
38
|
+
CustomSelectedToolbar?: React.FC<CustomSelectedToolbarProps<T>>;
|
|
39
|
+
}
|
|
40
|
+
export declare const MUITable: <T extends object>({ title, data, deactivateSelect, defaultOrderBy, defaultOrder, excludedColumns, columns: passedColumns, CustomToolbar, CustomSelectedToolbar, options, ...rest }: EnhancedTableProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Column } from './MUITable';
|
|
2
|
+
import { Order } from './utils';
|
|
3
|
+
interface EnhancedTableProps<T> {
|
|
4
|
+
numSelected: number;
|
|
5
|
+
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof T) => void;
|
|
6
|
+
onSelectAllClick?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
7
|
+
order: Order;
|
|
8
|
+
orderBy: keyof T;
|
|
9
|
+
rowCount: number;
|
|
10
|
+
columns: Column[];
|
|
11
|
+
deactivateSelectAll?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function EnhancedTableHead<T>({ onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort, columns, deactivateSelectAll, }: EnhancedTableProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Column, Options } from "./MUITable";
|
|
3
|
+
import { UseReactToPrintFn } from "react-to-print";
|
|
4
|
+
export interface CustomSelectedToolbarProps<T> {
|
|
5
|
+
selected?: readonly T[];
|
|
6
|
+
data?: T[];
|
|
7
|
+
}
|
|
8
|
+
interface EnhancedTableToolbarProps<T> {
|
|
9
|
+
title: string;
|
|
10
|
+
numSelected: number;
|
|
11
|
+
selected: readonly T[];
|
|
12
|
+
onFilterChange: (filterFunc: (row: T) => boolean) => void;
|
|
13
|
+
onSearch: (query: string) => void;
|
|
14
|
+
printFn: UseReactToPrintFn;
|
|
15
|
+
columns: Column[];
|
|
16
|
+
CustomToolbar?: React.FC;
|
|
17
|
+
CustomSelectedToolbar?: React.FC<CustomSelectedToolbarProps<T>>;
|
|
18
|
+
data?: T[];
|
|
19
|
+
options?: Options;
|
|
20
|
+
}
|
|
21
|
+
export declare function EnhancedTableToolbar<T>(props: EnhancedTableToolbarProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const Table: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { HeadCell } from "./TableHead";
|
|
2
|
+
export interface Data {
|
|
3
|
+
id: number;
|
|
4
|
+
calories: number;
|
|
5
|
+
carbs: number;
|
|
6
|
+
fat: number;
|
|
7
|
+
name: string;
|
|
8
|
+
protein: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const rows: Data[];
|
|
11
|
+
export declare const headCells: HeadCell<typeof rows[0]>[];
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import Checkbox from '@mui/material/Checkbox';
|
|
3
|
+
import Paper from '@mui/material/Paper';
|
|
4
|
+
import Table from '@mui/material/Table';
|
|
5
|
+
import TableBody from '@mui/material/TableBody';
|
|
6
|
+
import TableCell from '@mui/material/TableCell';
|
|
7
|
+
import TableContainer from '@mui/material/TableContainer';
|
|
8
|
+
import TablePagination from '@mui/material/TablePagination';
|
|
9
|
+
import TableRow from '@mui/material/TableRow';
|
|
10
|
+
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
11
|
+
import { useReactToPrint } from 'react-to-print';
|
|
12
|
+
import Box from '@mui/material/Box';
|
|
13
|
+
import TableHead from '@mui/material/TableHead';
|
|
14
|
+
import TableSortLabel from '@mui/material/TableSortLabel';
|
|
15
|
+
import { visuallyHidden } from '@mui/utils';
|
|
16
|
+
import { Close, CloudDownload, Print } from '@mui/icons-material';
|
|
17
|
+
import FilterListIcon from '@mui/icons-material/FilterList';
|
|
18
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
19
|
+
import { Toolbar, Typography, Stack, TextField, IconButton, Tooltip, Popover, Button, Box as Box$1, Slider, Checkbox as Checkbox$1 } from '@mui/material';
|
|
20
|
+
import { alpha } from '@mui/material/styles';
|
|
21
|
+
|
|
22
|
+
/******************************************************************************
|
|
23
|
+
Copyright (c) Microsoft Corporation.
|
|
24
|
+
|
|
25
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
26
|
+
purpose with or without fee is hereby granted.
|
|
27
|
+
|
|
28
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
29
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
30
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
31
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
32
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
33
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
34
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
35
|
+
***************************************************************************** */
|
|
36
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
function __rest(s, e) {
|
|
40
|
+
var t = {};
|
|
41
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
42
|
+
t[p] = s[p];
|
|
43
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
44
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
45
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
46
|
+
t[p[i]] = s[p[i]];
|
|
47
|
+
}
|
|
48
|
+
return t;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
52
|
+
var e = new Error(message);
|
|
53
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function EnhancedTableHead({ onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort, columns, deactivateSelectAll, }) {
|
|
57
|
+
const createSortHandler = (property) => (event) => {
|
|
58
|
+
onRequestSort(event, property);
|
|
59
|
+
};
|
|
60
|
+
return (jsx(TableHead, { children: jsxs(TableRow, { children: [!deactivateSelectAll &&
|
|
61
|
+
jsx(TableCell, { padding: "checkbox", children: jsx(Checkbox, { color: "primary", indeterminate: numSelected > 0 && numSelected < rowCount, checked: rowCount > 0 && numSelected === rowCount, onChange: onSelectAllClick, inputProps: {
|
|
62
|
+
'aria-label': 'select all items',
|
|
63
|
+
} }) }), columns.map((column) => {
|
|
64
|
+
var _a;
|
|
65
|
+
return (jsx(TableCell, { padding: 'normal', sortDirection: orderBy === column.name ? order : false, children: ((_a = column.options) === null || _a === undefined ? undefined : _a.sort) !== false ? (jsxs(TableSortLabel, { active: orderBy === column.name, direction: orderBy === column.name ? order : 'asc', onClick: createSortHandler(column.name), sx: { fontWeight: 'bold' }, children: [column.label, orderBy === column.name ? (jsx(Box, { component: "span", sx: visuallyHidden, children: order === 'desc' ? 'sorted descending' : 'sorted ascending' })) : null] })) : (jsx("span", { style: { fontWeight: 700 }, children: column.label })) }, String(column.name)));
|
|
66
|
+
})] }) }));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function EnhancedTableToolbar(props) {
|
|
70
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
71
|
+
const { title, numSelected, selected, onFilterChange, onSearch, printFn, columns, CustomToolbar, CustomSelectedToolbar, data, options } = props;
|
|
72
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
73
|
+
const [filters, setFilters] = useState([]);
|
|
74
|
+
const [filterConfig, setFilterConfig] = useState([]);
|
|
75
|
+
// rest value to force re-render of filters
|
|
76
|
+
const [resetCounter, setResetCounter] = useState(0);
|
|
77
|
+
const [openSearch, setOpenSearch] = useState(false);
|
|
78
|
+
function downloadCSV(data, filename = "data.csv") {
|
|
79
|
+
// Base case
|
|
80
|
+
if (data.length === 0) {
|
|
81
|
+
console.warn("No data to export.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Create CSV content
|
|
85
|
+
const headers = Object.keys(data[0]);
|
|
86
|
+
const csvRows = data.map(obj => headers.map(field => { var _a; return JSON.stringify((_a = obj[field]) !== null && _a !== undefined ? _a : ""); }).join(","));
|
|
87
|
+
const csvContent = [headers.join(","), ...csvRows].join("\n");
|
|
88
|
+
// Create Blob and download
|
|
89
|
+
const blob = new Blob([csvContent], { type: "text/csv" });
|
|
90
|
+
const link = document.createElement("a");
|
|
91
|
+
link.href = URL.createObjectURL(blob);
|
|
92
|
+
link.download = filename;
|
|
93
|
+
document.body.appendChild(link);
|
|
94
|
+
link.click();
|
|
95
|
+
document.body.removeChild(link);
|
|
96
|
+
URL.revokeObjectURL(link.href);
|
|
97
|
+
}
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (data && data.length > 0) {
|
|
100
|
+
const inferredConfig = columns.map((column) => {
|
|
101
|
+
const key = column.name;
|
|
102
|
+
const values = data.map((row) => row[key]);
|
|
103
|
+
const isNumber = values.every((val) => typeof val === "number");
|
|
104
|
+
const inferredType = isNumber ? "number" : typeof values[0] === "boolean" ? "boolean" : "string";
|
|
105
|
+
return {
|
|
106
|
+
key,
|
|
107
|
+
type: inferredType,
|
|
108
|
+
min: isNumber ? Math.min(...values) : undefined,
|
|
109
|
+
max: isNumber ? Math.max(...values) : undefined,
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
setFilterConfig(inferredConfig);
|
|
113
|
+
}
|
|
114
|
+
}, [data, columns]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const newFilterFunc = (row) => {
|
|
117
|
+
if (filters.length === 0)
|
|
118
|
+
return true;
|
|
119
|
+
return filters.every((filter) => {
|
|
120
|
+
const rowValue = row[filter.key];
|
|
121
|
+
if (filter.type === "number") {
|
|
122
|
+
const [min, max] = filter.value;
|
|
123
|
+
return rowValue >= min && rowValue <= max;
|
|
124
|
+
}
|
|
125
|
+
if (filter.type === "string") {
|
|
126
|
+
return rowValue.toString().toLowerCase().includes(filter.value.toLowerCase());
|
|
127
|
+
}
|
|
128
|
+
if (filter.type === "boolean") {
|
|
129
|
+
return rowValue === filter.value;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
onFilterChange(newFilterFunc);
|
|
135
|
+
}, [filters, onFilterChange]);
|
|
136
|
+
const handleOpen = (event) => {
|
|
137
|
+
setAnchorEl(event.currentTarget);
|
|
138
|
+
};
|
|
139
|
+
const handleClose = () => {
|
|
140
|
+
setAnchorEl(null);
|
|
141
|
+
};
|
|
142
|
+
const handleSearchChange = () => {
|
|
143
|
+
setOpenSearch((prev) => !prev);
|
|
144
|
+
};
|
|
145
|
+
const open = Boolean(anchorEl);
|
|
146
|
+
const getFilter = (key) => filters.find((filter) => filter.key === key);
|
|
147
|
+
const updateFilter = (updatedFilter) => {
|
|
148
|
+
setFilters((prevFilters) => {
|
|
149
|
+
const existingIndex = prevFilters.findIndex((filter) => filter.key === updatedFilter.key);
|
|
150
|
+
if (existingIndex !== -1) {
|
|
151
|
+
// Update existing filter
|
|
152
|
+
const newFilters = [...prevFilters];
|
|
153
|
+
newFilters[existingIndex] = updatedFilter;
|
|
154
|
+
return newFilters;
|
|
155
|
+
}
|
|
156
|
+
// Add new filter
|
|
157
|
+
return [...prevFilters, updatedFilter];
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
const removeFilter = (key) => {
|
|
161
|
+
setFilters((prevFilters) => prevFilters.filter((filter) => filter.key !== key));
|
|
162
|
+
};
|
|
163
|
+
const handleFilterChange = (key, value) => {
|
|
164
|
+
if (value === undefined || value === null || value === "") {
|
|
165
|
+
removeFilter(key);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const config = filterConfig.find((config) => config.key === key);
|
|
169
|
+
if (!config)
|
|
170
|
+
return;
|
|
171
|
+
updateFilter({
|
|
172
|
+
key,
|
|
173
|
+
type: config.type,
|
|
174
|
+
value,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const resetFilters = () => {
|
|
179
|
+
setFilters([]);
|
|
180
|
+
setResetCounter((prev) => prev + 1);
|
|
181
|
+
};
|
|
182
|
+
return (jsxs(Toolbar, { sx: [
|
|
183
|
+
{ px: { sm: 2 }, borderBottom: 1, borderColor: "divider" },
|
|
184
|
+
numSelected > 0 && { bgcolor: (theme) => alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity) },
|
|
185
|
+
], children: [numSelected > 0 ? (jsxs(Fragment, { children: [jsx(Typography, { sx: { flex: "1 1 100%" }, color: "inherit", variant: "subtitle1", component: "div", children: ((_a = options === null || options === undefined ? undefined : options.translations) === null || _a === undefined ? undefined : _a.selectedTextRenderer)
|
|
186
|
+
? options.translations.selectedTextRenderer(numSelected)
|
|
187
|
+
: `${numSelected} selected` }), CustomSelectedToolbar && (jsx(CustomSelectedToolbar, { data: data, selected: selected }))] })) : (jsxs(Stack, { direction: "row", justifyContent: "space-between", width: "100%", alignItems: "center", children: [openSearch ? (jsxs(Stack, { direction: "row", alignItems: "center", children: [jsx(SearchIcon, {}), jsx(TextField, { placeholder: ((_b = options === null || options === undefined ? undefined : options.translations) === null || _b === undefined ? undefined : _b.searchPlaceholder) || "Search...", onChange: (e) => onSearch(e.target.value), variant: "standard", autoFocus: true, fullWidth: true, sx: { marginLeft: 1 } }), jsx(IconButton, { onClick: handleSearchChange, sx: { '&:hover': { color: 'error.main' } }, children: jsx(Close, {}) })] })) : (jsx(Typography, { sx: { flex: "1 1 100%", alignContent: "center", paddingLeft: 1 }, variant: "h6", id: "tableTitle", component: "div", children: title })), jsxs(Stack, { direction: "row", spacing: 0.5, children: [jsx(Tooltip, { title: ((_c = options === null || options === undefined ? undefined : options.translations) === null || _c === undefined ? undefined : _c.searchTooltip) || "Search", children: jsx(IconButton, { onClick: handleSearchChange, children: jsx(SearchIcon, {}) }) }), jsx(Tooltip, { title: ((_d = options === null || options === undefined ? undefined : options.translations) === null || _d === undefined ? undefined : _d.downloadTooltip) || "Download CSV", children: jsx(IconButton, { onClick: () => downloadCSV(data, "data.csv"), children: jsx(CloudDownload, {}) }) }), jsx(Tooltip, { title: ((_e = options === null || options === undefined ? undefined : options.translations) === null || _e === undefined ? undefined : _e.printTooltip) || "Print", children: jsx(IconButton, { onClick: () => printFn(), children: jsx(Print, {}) }) }), jsx(Tooltip, { title: ((_f = options === null || options === undefined ? undefined : options.translations) === null || _f === undefined ? undefined : _f.filterTooltip) || "Filter list", children: jsx(IconButton, { onClick: handleOpen, children: jsx(FilterListIcon, {}) }) }), CustomToolbar && jsx(CustomToolbar, {})] })] })), jsxs(Popover, { open: open, anchorEl: anchorEl, onClose: handleClose, anchorOrigin: {
|
|
188
|
+
vertical: "bottom",
|
|
189
|
+
horizontal: "right",
|
|
190
|
+
}, transformOrigin: {
|
|
191
|
+
vertical: "top",
|
|
192
|
+
horizontal: "right",
|
|
193
|
+
}, slotProps: {
|
|
194
|
+
paper: { sx: { width: "20%", padding: 2, boxShadow: 2 } }
|
|
195
|
+
}, children: [jsxs(Stack, { direction: "row", justifyContent: "space-between", children: [jsx(Typography, { variant: "h6", sx: { marginBottom: 2 }, children: ((_g = options === null || options === undefined ? undefined : options.translations) === null || _g === undefined ? undefined : _g.filtersTitle) || "Filters" }), jsx(Button, { variant: "contained", size: "small", sx: { height: "fit-content" }, onClick: resetFilters, children: ((_h = options === null || options === undefined ? undefined : options.translations) === null || _h === undefined ? undefined : _h.resetButtonText) || "Reset" })] }), jsx(Stack, { children: filterConfig.map(({ key, type, min, max }) => {
|
|
196
|
+
var _a, _b, _c, _d, _e, _f;
|
|
197
|
+
const currentFilter = getFilter(key);
|
|
198
|
+
return (jsxs(Box$1, { children: [jsx(Typography, { variant: "subtitle1", children: (_a = columns.find((cell) => cell.name === key)) === null || _a === undefined ? undefined : _a.label }), type === "number" && min !== undefined && max !== undefined && (jsx(Slider, { value: [
|
|
199
|
+
(_c = (_b = currentFilter === null || currentFilter === undefined ? undefined : currentFilter.value) === null || _b === undefined ? undefined : _b[0]) !== null && _c !== undefined ? _c : min,
|
|
200
|
+
(_e = (_d = currentFilter === null || currentFilter === undefined ? undefined : currentFilter.value) === null || _d === undefined ? undefined : _d[1]) !== null && _e !== undefined ? _e : max,
|
|
201
|
+
], onChange: (_, newValue) => {
|
|
202
|
+
const [newMin, newMax] = newValue;
|
|
203
|
+
handleFilterChange(key, [newMin, newMax]);
|
|
204
|
+
}, valueLabelDisplay: "auto", min: min || 0, max: max || 100, step: 1 })), type === "string" && (jsx(TextField, { placeholder: ((_f = options === null || options === undefined ? undefined : options.translations) === null || _f === undefined ? undefined : _f.searchPlaceholder) || "Search...", size: "small", value: (currentFilter === null || currentFilter === undefined ? undefined : currentFilter.value) || "", onChange: (e) => handleFilterChange(key, e.target.value), sx: {
|
|
205
|
+
marginBottom: 1,
|
|
206
|
+
paddingY: 0.5,
|
|
207
|
+
width: "100%",
|
|
208
|
+
} })), type === "boolean" && (jsxs(Box$1, { display: "flex", alignItems: "center", children: [jsx(Checkbox$1, { checked: (currentFilter === null || currentFilter === undefined ? undefined : currentFilter.value) === true, onChange: (e) => handleFilterChange(key, e.target.checked) }), currentFilter && (jsx(Close, { color: "error", sx: { cursor: "pointer", fontSize: 15 }, onClick: () => handleFilterChange(key, undefined) }))] }))] }, key));
|
|
209
|
+
}) }, resetCounter)] })] }));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function getComparator(order, orderBy) {
|
|
213
|
+
return order === 'desc'
|
|
214
|
+
? (a, b) => descendingComparator(a[orderBy], b[orderBy])
|
|
215
|
+
: (a, b) => -descendingComparator(a[orderBy], b[orderBy]);
|
|
216
|
+
}
|
|
217
|
+
function descendingComparator(a, b) {
|
|
218
|
+
if (b < a)
|
|
219
|
+
return -1;
|
|
220
|
+
if (b > a)
|
|
221
|
+
return 1;
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const MUITable = (_a) => {
|
|
226
|
+
var _b, _c;
|
|
227
|
+
var { title, data, deactivateSelect, defaultOrderBy, defaultOrder, excludedColumns, columns: passedColumns, CustomToolbar, CustomSelectedToolbar, options } = _a, rest = __rest(_a, ["title", "data", "deactivateSelect", "defaultOrderBy", "defaultOrder", "excludedColumns", "columns", "CustomToolbar", "CustomSelectedToolbar", "options"]);
|
|
228
|
+
const tableRef = useRef(null);
|
|
229
|
+
const reactToPrintFn = useReactToPrint({ contentRef: tableRef });
|
|
230
|
+
const getDefaultOrderByKey = React.useCallback(() => {
|
|
231
|
+
if (data.length === 0)
|
|
232
|
+
return "id";
|
|
233
|
+
const keys = data.length > 0 ? Object.keys(data[0]) : [];
|
|
234
|
+
if (defaultOrderBy) {
|
|
235
|
+
if (keys.includes(defaultOrderBy)) {
|
|
236
|
+
return defaultOrderBy;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
console.warn(`{defaultOrderBy}: "${defaultOrderBy}" not found among the object keys. Falling back to automatic key detection.`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return (keys.includes("id") ? "id" : keys[0]);
|
|
243
|
+
}, [data, defaultOrderBy]);
|
|
244
|
+
const [state, setState] = React.useState(() => {
|
|
245
|
+
const orderByKey = getDefaultOrderByKey();
|
|
246
|
+
return {
|
|
247
|
+
order: defaultOrder || 'asc',
|
|
248
|
+
orderBy: orderByKey,
|
|
249
|
+
selected: [],
|
|
250
|
+
page: 0,
|
|
251
|
+
rowsPerPage: 5,
|
|
252
|
+
searchQuery: "",
|
|
253
|
+
filterFunc: () => true,
|
|
254
|
+
currentData: data,
|
|
255
|
+
visibleRows: data,
|
|
256
|
+
emptyRows: 0,
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
const generateColumns = React.useCallback(() => {
|
|
260
|
+
if (data.length === 0)
|
|
261
|
+
return [];
|
|
262
|
+
return Object.keys(data[0])
|
|
263
|
+
.filter((key) => !(excludedColumns || []).includes(key))
|
|
264
|
+
.map((key) => ({
|
|
265
|
+
name: key,
|
|
266
|
+
label: key.charAt(0).toUpperCase() + key.slice(1),
|
|
267
|
+
}));
|
|
268
|
+
}, [data, excludedColumns]);
|
|
269
|
+
const columns = passedColumns || generateColumns();
|
|
270
|
+
const handleSearch = (query) => {
|
|
271
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { searchQuery: query.toLowerCase() })));
|
|
272
|
+
};
|
|
273
|
+
React.useEffect(() => {
|
|
274
|
+
const orderByKey = getDefaultOrderByKey();
|
|
275
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { orderBy: orderByKey })));
|
|
276
|
+
}, [data, getDefaultOrderByKey]);
|
|
277
|
+
// Data sorting, filtering, and pagination
|
|
278
|
+
React.useEffect(() => {
|
|
279
|
+
// Apply filters, search query, and sort data
|
|
280
|
+
const sortedData = [...data]
|
|
281
|
+
.filter(state.filterFunc)
|
|
282
|
+
.filter((row) => Object.values(row).some((value) => typeof value === "string" &&
|
|
283
|
+
value.toLowerCase().includes(state.searchQuery)))
|
|
284
|
+
.sort(getComparator(state.order, state.orderBy));
|
|
285
|
+
// Paginate the processed data
|
|
286
|
+
const startIndex = state.page * state.rowsPerPage;
|
|
287
|
+
const paginatedData = sortedData.slice(startIndex, startIndex + state.rowsPerPage);
|
|
288
|
+
const calculatedEmptyRows = Math.max(0, (1 + state.page) * state.rowsPerPage - sortedData.length);
|
|
289
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { visibleRows: paginatedData, emptyRows: calculatedEmptyRows, selected: prevState.selected.filter((selectedRow) => data.includes(selectedRow)) })));
|
|
290
|
+
}, [
|
|
291
|
+
state.filterFunc,
|
|
292
|
+
state.order,
|
|
293
|
+
state.orderBy,
|
|
294
|
+
state.page,
|
|
295
|
+
state.rowsPerPage,
|
|
296
|
+
state.searchQuery,
|
|
297
|
+
data,
|
|
298
|
+
]);
|
|
299
|
+
// Remove selected rows deleted from the data to prevent stale selected state
|
|
300
|
+
React.useEffect(() => {
|
|
301
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { selected: prevState.selected.filter((selectedRow) => data.some((row) => row === selectedRow)) })));
|
|
302
|
+
}, [data]);
|
|
303
|
+
const handleRequestSort = (_event, property) => {
|
|
304
|
+
const isAsc = state.orderBy === property && state.order === 'asc';
|
|
305
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { order: isAsc ? 'desc' : 'asc', orderBy: property })));
|
|
306
|
+
};
|
|
307
|
+
const handleSelectAllClick = (event) => {
|
|
308
|
+
if (event.target.checked) {
|
|
309
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { selected: [...state.currentData] })));
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { selected: [] })));
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const handleClick = (_event, row) => {
|
|
316
|
+
const selectedIndex = state.selected.findIndex((selectedRow) => selectedRow === row);
|
|
317
|
+
let newSelected = [];
|
|
318
|
+
if (selectedIndex === -1) {
|
|
319
|
+
newSelected = [...state.selected, row];
|
|
320
|
+
}
|
|
321
|
+
else if (selectedIndex === 0) {
|
|
322
|
+
newSelected = state.selected.slice(1);
|
|
323
|
+
}
|
|
324
|
+
else if (selectedIndex === state.selected.length - 1) {
|
|
325
|
+
newSelected = state.selected.slice(0, -1);
|
|
326
|
+
}
|
|
327
|
+
else if (selectedIndex > 0) {
|
|
328
|
+
newSelected = [
|
|
329
|
+
...state.selected.slice(0, selectedIndex),
|
|
330
|
+
...state.selected.slice(selectedIndex + 1),
|
|
331
|
+
];
|
|
332
|
+
}
|
|
333
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { selected: newSelected })));
|
|
334
|
+
};
|
|
335
|
+
const handleChangePage = (_event, newPage) => {
|
|
336
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { page: newPage })));
|
|
337
|
+
};
|
|
338
|
+
const handleChangeRowsPerPage = (event) => {
|
|
339
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { rowsPerPage: parseInt(event.target.value, 10), page: 0 })));
|
|
340
|
+
};
|
|
341
|
+
const handleFilterChange = useCallback((filterFunc) => {
|
|
342
|
+
setState((prevState) => (Object.assign(Object.assign({}, prevState), { filterFunc })));
|
|
343
|
+
}, []);
|
|
344
|
+
return (jsx("div", Object.assign({}, rest, { ref: tableRef, children: jsxs(Paper, { sx: { width: '100%', mb: 2 }, children: [jsx(EnhancedTableToolbar, { title: title, numSelected: state.selected.length, selected: state.selected, onFilterChange: handleFilterChange, onSearch: handleSearch, printFn: reactToPrintFn, columns: columns, CustomToolbar: CustomToolbar, CustomSelectedToolbar: CustomSelectedToolbar, data: data, options: options }), jsx(TableContainer, { children: jsxs(Table, { sx: { minWidth: 750 }, "aria-labelledby": "tableTitle", size: "small", children: [jsx(EnhancedTableHead, { columns: columns, numSelected: state.selected.length, order: state.order, orderBy: state.orderBy, onSelectAllClick: deactivateSelect ? undefined : handleSelectAllClick, onRequestSort: handleRequestSort, rowCount: state.currentData.length, deactivateSelectAll: deactivateSelect }), jsxs(TableBody, { children: [state.visibleRows.map((row, index) => {
|
|
345
|
+
const isItemSelected = state.selected.some((selectedRow) => selectedRow === row);
|
|
346
|
+
const labelId = `enhanced-table-checkbox-${index}`;
|
|
347
|
+
return (jsxs(TableRow, { hover: true, onClick: deactivateSelect ? undefined : (event) => handleClick(event, row), role: "checkbox", "aria-checked": isItemSelected, tabIndex: -1, selected: isItemSelected, sx: { cursor: 'pointer' }, children: [!deactivateSelect && (jsx(TableCell, { padding: "checkbox", children: jsx(Checkbox, { color: "primary", checked: isItemSelected, inputProps: {
|
|
348
|
+
'aria-labelledby': labelId,
|
|
349
|
+
} }) })), columns.map((column, cellIndex) => {
|
|
350
|
+
var _a;
|
|
351
|
+
return (jsx(TableCell, { component: cellIndex === 0 ? "th" : undefined, id: cellIndex === 0 ? labelId : undefined, scope: cellIndex === 0 ? "row" : undefined, padding: "normal", children: ((_a = column.options) === null || _a === undefined ? undefined : _a.customBodyRender)
|
|
352
|
+
? column.options.customBodyRender(row[column.name])
|
|
353
|
+
: String(row[column.name]) }, String(column.name)));
|
|
354
|
+
})] }, index));
|
|
355
|
+
}), state.emptyRows > 0 && (jsx(TableRow, { style: {
|
|
356
|
+
height: 33 * state.emptyRows,
|
|
357
|
+
}, children: jsx(TableCell, { colSpan: columns.length + 1 }) }))] })] }) }), jsx(TablePagination, { rowsPerPageOptions: [5, 10, 25], component: "div", count: state.currentData.length, rowsPerPage: state.rowsPerPage, page: state.page, onPageChange: handleChangePage, onRowsPerPageChange: handleChangeRowsPerPage, labelRowsPerPage: ((_b = options === null || options === undefined ? undefined : options.translations) === null || _b === undefined ? undefined : _b.rowsPerPageText) || "Rows per page", labelDisplayedRows: ((_c = options === null || options === undefined ? undefined : options.translations) === null || _c === undefined ? undefined : _c.labelDisplayedRows) || (({ from, to, count }) => `${from}-${to} of ${count}`) })] }) })));
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
export { MUITable as default };
|
|
361
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|