@applica-software-guru/react-admin 1.5.362-alpha1 → 1.5.363
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/dist/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/components/Pagination/PaginationActions.d.ts.map +1 -1
- package/dist/components/ra-lists/Datagrid/Datagrid.d.ts.map +1 -1
- package/dist/components/ra-lists/Datagrid/DatagridHeader.d.ts +14 -0
- package/dist/components/ra-lists/Datagrid/DatagridHeader.d.ts.map +1 -0
- package/dist/components/ra-lists/ListTabsToolbar.d.ts +1 -1
- package/dist/components/ra-lists/ListTabsToolbar.d.ts.map +1 -1
- package/dist/i18n/it.json.gz +0 -0
- package/dist/react-admin.cjs.js +54 -54
- package/dist/react-admin.cjs.js.gz +0 -0
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +7792 -7716
- package/dist/react-admin.es.js.gz +0 -0
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +54 -54
- package/dist/react-admin.umd.js.gz +0 -0
- package/dist/react-admin.umd.js.map +1 -1
- package/dist/style.css.gz +0 -0
- package/package.json +1 -1
- package/src/components/Pagination/Pagination.tsx +8 -34
- package/src/components/Pagination/PaginationActions.tsx +6 -21
- package/src/components/ra-lists/Datagrid/Datagrid.tsx +36 -3
- package/src/components/ra-lists/Datagrid/DatagridHeader.tsx +179 -0
- package/src/components/ra-lists/ListTabsToolbar.tsx +110 -116
package/dist/style.css.gz
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
ComponentPropType,
|
|
5
5
|
ListPaginationContextValue,
|
|
6
6
|
sanitizeListRestProps,
|
|
7
|
-
useListContext,
|
|
8
7
|
useListPaginationContext,
|
|
9
8
|
useResourceDefinition,
|
|
10
9
|
useTranslate
|
|
@@ -17,8 +16,6 @@ import { PaginationActions, PaginationActionsProps } from './PaginationActions';
|
|
|
17
16
|
const Pagination: FC<PaginationProps> = memo((props) => {
|
|
18
17
|
const { rowsPerPageOptions = DefaultRowsPerPageOptions, actions, limit = null, ...rest } = props;
|
|
19
18
|
const { isLoading, hasNextPage, page, perPage, total, setPage, setPerPage } = useListPaginationContext(props);
|
|
20
|
-
// Recupera i dati della pagina corrente
|
|
21
|
-
const { data } = useListContext();
|
|
22
19
|
const translate = useTranslate();
|
|
23
20
|
const isSmall = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
|
|
24
21
|
const [currentPage, setCurrentPage] = useState(page - 1); // Stato per la UI
|
|
@@ -36,12 +33,7 @@ const Pagination: FC<PaginationProps> = memo((props) => {
|
|
|
36
33
|
setIsSelectedPage(false);
|
|
37
34
|
}, [page]);
|
|
38
35
|
|
|
39
|
-
// Modalità infinito: total === -1
|
|
40
36
|
const totalPages = useMemo(() => {
|
|
41
|
-
if (total === -1) {
|
|
42
|
-
// In modalità infinito, non conosciamo il totale
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
37
|
return total != null ? Math.ceil(total / perPage) : undefined;
|
|
46
38
|
}, [perPage, total]);
|
|
47
39
|
|
|
@@ -63,30 +55,12 @@ const Pagination: FC<PaginationProps> = memo((props) => {
|
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
event.preventDefault();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
throw new Error(
|
|
73
|
-
translate('ra.navigation.page_out_of_boundaries', {
|
|
74
|
-
page: page + 1
|
|
75
|
-
})
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
if (page > currentPage && !canGoForward) {
|
|
79
|
-
// Non puoi andare avanti se la risposta è minore di rowsPerPage
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
if (page < 0 || (totalPages !== undefined && page > totalPages - 1)) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
translate('ra.navigation.page_out_of_boundaries', {
|
|
86
|
-
page: page + 1
|
|
87
|
-
})
|
|
88
|
-
);
|
|
89
|
-
}
|
|
58
|
+
if (page < 0 || (totalPages !== undefined && page > totalPages - 1)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
translate('ra.navigation.page_out_of_boundaries', {
|
|
61
|
+
page: page + 1
|
|
62
|
+
})
|
|
63
|
+
);
|
|
90
64
|
}
|
|
91
65
|
|
|
92
66
|
const arrowSelected =
|
|
@@ -110,7 +84,7 @@ const Pagination: FC<PaginationProps> = memo((props) => {
|
|
|
110
84
|
setPage(page + 1);
|
|
111
85
|
}
|
|
112
86
|
},
|
|
113
|
-
[debouncedPageChange, setPage, translate, totalPages, setIsSelectedPage
|
|
87
|
+
[debouncedPageChange, setPage, translate, totalPages, setIsSelectedPage]
|
|
114
88
|
);
|
|
115
89
|
|
|
116
90
|
const handlePerPageChange = useCallback(
|
|
@@ -202,7 +176,7 @@ const Pagination: FC<PaginationProps> = memo((props) => {
|
|
|
202
176
|
// @ts-ignore
|
|
203
177
|
ActionsComponent={ActionsComponent}
|
|
204
178
|
nextIconButtonProps={{
|
|
205
|
-
disabled:
|
|
179
|
+
disabled: !hasNextPage
|
|
206
180
|
}}
|
|
207
181
|
component="span"
|
|
208
182
|
labelRowsPerPage={translate('ra.navigation.page_rows_per_page')}
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import { FC, memo } from 'react';
|
|
2
2
|
import { styled } from '@mui/material/styles';
|
|
3
3
|
import { Pagination, PaginationProps } from '@mui/material';
|
|
4
|
-
import { useListContext, useTranslate } from 'ra-core';
|
|
5
4
|
import PropTypes from 'prop-types';
|
|
5
|
+
import { useTranslate } from 'ra-core';
|
|
6
6
|
|
|
7
7
|
const PaginationActions: FC<PaginationActionsProps> = memo((props) => {
|
|
8
8
|
const { page, rowsPerPage, count, onPageChange, size = 'small', className, ...rest } = props;
|
|
9
|
-
const { data } = useListContext();
|
|
10
9
|
const translate = useTranslate();
|
|
11
10
|
|
|
12
11
|
const nbPages = Math.ceil(count / rowsPerPage) || 1;
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
const isInfinite = count === -1;
|
|
16
|
-
// Disabilita avanti se i record sono meno di rowsPerPage
|
|
17
|
-
const disableNext = isInfinite ? !(data && Array.isArray(data) && data.length === rowsPerPage) : page + 1 >= nbPages;
|
|
18
|
-
|
|
19
|
-
if (nbPages === 1 && !isInfinite) {
|
|
13
|
+
if (nbPages === 1) {
|
|
20
14
|
return <Root className={className} />;
|
|
21
15
|
}
|
|
22
16
|
|
|
@@ -39,22 +33,13 @@ const PaginationActions: FC<PaginationActionsProps> = memo((props) => {
|
|
|
39
33
|
<Root className={className}>
|
|
40
34
|
<Pagination
|
|
41
35
|
size={size}
|
|
42
|
-
count={
|
|
36
|
+
count={nbPages}
|
|
37
|
+
// <TablePagination>, the parent, uses 0-based pagination
|
|
38
|
+
// while <Pagination> uses 1-based pagination
|
|
43
39
|
page={page + 1}
|
|
44
|
-
onChange={(e: any,
|
|
45
|
-
// Disabilita avanti in modalità infinito
|
|
46
|
-
if (isInfinite && newPage - 1 > page && disableNext) return;
|
|
47
|
-
onPageChange(e, newPage - 1);
|
|
48
|
-
}}
|
|
40
|
+
onChange={(e: any, page) => onPageChange(e, page - 1)}
|
|
49
41
|
{...sanitizeRestProps(rest)}
|
|
50
42
|
getItemAriaLabel={getItemAriaLabel}
|
|
51
|
-
renderItem={(item) => {
|
|
52
|
-
// Disabilita il bottone avanti in modalità infinito
|
|
53
|
-
if (isInfinite && item.type === 'next') {
|
|
54
|
-
return { ...item, disabled: disableNext };
|
|
55
|
-
}
|
|
56
|
-
return item;
|
|
57
|
-
}}
|
|
58
43
|
/>
|
|
59
44
|
</Root>
|
|
60
45
|
);
|
|
@@ -5,19 +5,29 @@ import clsx from 'clsx';
|
|
|
5
5
|
import difference from 'lodash/difference';
|
|
6
6
|
import union from 'lodash/union';
|
|
7
7
|
import { Identifier, sanitizeListRestProps, useListContext, useTranslate } from 'ra-core';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
FC,
|
|
10
|
+
cloneElement,
|
|
11
|
+
createElement,
|
|
12
|
+
isValidElement,
|
|
13
|
+
useCallback,
|
|
14
|
+
useEffect,
|
|
15
|
+
useMemo,
|
|
16
|
+
useRef,
|
|
17
|
+
useState
|
|
18
|
+
} from 'react';
|
|
9
19
|
import * as React from 'react';
|
|
10
20
|
import {
|
|
11
21
|
BulkDeleteButton,
|
|
12
22
|
DatagridBody,
|
|
13
23
|
DatagridClasses,
|
|
14
|
-
DatagridHeader,
|
|
15
24
|
DatagridLoading,
|
|
16
25
|
DatagridRoot,
|
|
17
26
|
PureDatagridBody,
|
|
18
27
|
DatagridProps as RaDatagridProps
|
|
19
28
|
} from 'react-admin';
|
|
20
29
|
import { Empty } from '@/components/ra-lists/Empty';
|
|
30
|
+
import { DatagridHeader, SortItem } from './DatagridHeader';
|
|
21
31
|
|
|
22
32
|
const defaultBulkActionButtons = <BulkDeleteButton />;
|
|
23
33
|
|
|
@@ -125,6 +135,29 @@ const Datagrid = React.forwardRef((props, ref) => {
|
|
|
125
135
|
|
|
126
136
|
const translate = useTranslate();
|
|
127
137
|
const { sort, data, isLoading, onSelect, onToggleItem, selectedIds, setSort, total } = useListContext(props);
|
|
138
|
+
const [multiSort, setMultiSort] = useState<SortItem[]>([]);
|
|
139
|
+
|
|
140
|
+
const prevMultiSortKeyRef = useRef<string>('');
|
|
141
|
+
const multiSortKey = useMemo(() => multiSort.map((s) => `${s.field}:${s.order}`).join('|'), [multiSort]);
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const prevKey = prevMultiSortKeyRef.current;
|
|
145
|
+
if (prevKey === multiSortKey) return;
|
|
146
|
+
|
|
147
|
+
if (multiSort.length > 0) {
|
|
148
|
+
setSort({
|
|
149
|
+
field: multiSort.map((s) => s.field).join(','),
|
|
150
|
+
order: multiSort.map((s) => s.order).join(',') as 'ASC' | 'DESC'
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
prevMultiSortKeyRef.current = multiSortKey;
|
|
155
|
+
}, [multiSortKey, multiSort, setSort]);
|
|
156
|
+
|
|
157
|
+
const onMultiSortChange = useCallback((newMultiSort: SortItem[]) => {
|
|
158
|
+
setMultiSort(newMultiSort);
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
128
161
|
const hasBulkActions = !!bulkActionButtons !== false;
|
|
129
162
|
const contextValue = useMemo(() => ({ isRowExpandable, expandSingle }), [isRowExpandable, expandSingle]);
|
|
130
163
|
const lastSelected = useRef(null);
|
|
@@ -216,7 +249,7 @@ const Datagrid = React.forwardRef((props, ref) => {
|
|
|
216
249
|
<div className={DatagridClasses.tableWrapper}>
|
|
217
250
|
<Table ref={ref} className={DatagridClasses.table} size={size} {...sanitizeRestProps(rest)}>
|
|
218
251
|
{createOrCloneElement(
|
|
219
|
-
|
|
252
|
+
<DatagridHeader multiSort={multiSort} setMultiSort={onMultiSortChange} />,
|
|
220
253
|
{
|
|
221
254
|
children,
|
|
222
255
|
sort,
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Checkbox, TableCell, TableHead, TableRow, TableSortLabel, Typography } from '@mui/material';
|
|
2
|
+
import { RaRecord, useListContext, useTranslateLabel } from 'ra-core';
|
|
3
|
+
import React, { ReactElement, ReactNode, useCallback, useMemo } from 'react';
|
|
4
|
+
import { DatagridHeaderProps } from 'react-admin';
|
|
5
|
+
|
|
6
|
+
type SortItem = {
|
|
7
|
+
field: string;
|
|
8
|
+
order: 'ASC' | 'DESC';
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
interface MultiSortDatagridHeaderProps extends Omit<DatagridHeaderProps, 'setSort'> {
|
|
12
|
+
setMultiSort?: (sort: SortItem[]) => void;
|
|
13
|
+
multiSort?: SortItem[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function DatagridHeader(props: MultiSortDatagridHeaderProps): ReactElement {
|
|
17
|
+
const {
|
|
18
|
+
children,
|
|
19
|
+
hasExpand,
|
|
20
|
+
hasBulkActions,
|
|
21
|
+
isRowSelectable,
|
|
22
|
+
onSelect,
|
|
23
|
+
selectedIds = [],
|
|
24
|
+
data = [],
|
|
25
|
+
multiSort = [],
|
|
26
|
+
setMultiSort,
|
|
27
|
+
resource
|
|
28
|
+
} = props;
|
|
29
|
+
const { setSort } = useListContext();
|
|
30
|
+
const translateLabel = useTranslateLabel();
|
|
31
|
+
|
|
32
|
+
const updateSort = useCallback(
|
|
33
|
+
(field: string, isMultiSort: boolean = false) => {
|
|
34
|
+
if (!setMultiSort) {
|
|
35
|
+
const currentSortForField = multiSort.find((s) => s.field === field);
|
|
36
|
+
const newOrder = currentSortForField?.order === 'ASC' ? 'DESC' : 'ASC';
|
|
37
|
+
setSort({ field, order: newOrder });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const existingIndex = multiSort.findIndex((s) => s.field === field);
|
|
42
|
+
|
|
43
|
+
if (!isMultiSort) {
|
|
44
|
+
if (existingIndex >= 0) {
|
|
45
|
+
const currentOrder = multiSort[existingIndex].order;
|
|
46
|
+
if (currentOrder === 'ASC') {
|
|
47
|
+
setMultiSort([{ field, order: 'DESC' }]);
|
|
48
|
+
} else {
|
|
49
|
+
setMultiSort([]);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
setMultiSort([{ field, order: 'ASC' }]);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const newMultiSort = [...multiSort];
|
|
58
|
+
|
|
59
|
+
if (existingIndex >= 0) {
|
|
60
|
+
const currentOrder = newMultiSort[existingIndex].order;
|
|
61
|
+
if (currentOrder === 'ASC') {
|
|
62
|
+
newMultiSort[existingIndex] = { field, order: 'DESC' };
|
|
63
|
+
} else {
|
|
64
|
+
newMultiSort.splice(existingIndex, 1);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
newMultiSort.push({ field, order: 'ASC' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setMultiSort(newMultiSort);
|
|
71
|
+
},
|
|
72
|
+
[multiSort, setMultiSort, setSort]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const handleSelectAll = useCallback(
|
|
76
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
77
|
+
if (!onSelect) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (event.target.checked) {
|
|
81
|
+
const allIds = data
|
|
82
|
+
.filter((record: RaRecord) => (isRowSelectable ? isRowSelectable(record) : true))
|
|
83
|
+
.map((record: RaRecord) => record.id);
|
|
84
|
+
onSelect(allIds);
|
|
85
|
+
} else {
|
|
86
|
+
onSelect([]);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
[data, isRowSelectable, onSelect]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const getSortForField = useCallback(
|
|
93
|
+
(field: string): { order: 'ASC' | 'DESC'; index: number } | null => {
|
|
94
|
+
const index = multiSort.findIndex((s) => s.field === field);
|
|
95
|
+
if (index >= 0) {
|
|
96
|
+
return { order: multiSort[index].order, index };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
},
|
|
100
|
+
[multiSort]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const childrenArray = useMemo(() => React.Children.toArray(children), [children]);
|
|
104
|
+
|
|
105
|
+
const selectableCount = useMemo(
|
|
106
|
+
() => data.filter((record: RaRecord) => (isRowSelectable ? isRowSelectable(record) : true)).length,
|
|
107
|
+
[data, isRowSelectable]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const handleSortClick = useCallback(
|
|
111
|
+
(source: string) => (event: React.MouseEvent<unknown>) => {
|
|
112
|
+
updateSort(source, event.ctrlKey || event.metaKey);
|
|
113
|
+
},
|
|
114
|
+
[updateSort]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<TableHead>
|
|
119
|
+
<TableRow>
|
|
120
|
+
{!!hasExpand && <TableCell />}
|
|
121
|
+
{!!hasBulkActions && (
|
|
122
|
+
<TableCell padding="checkbox">
|
|
123
|
+
<Checkbox
|
|
124
|
+
color="primary"
|
|
125
|
+
checked={selectedIds.length > 0 && selectedIds.length === selectableCount}
|
|
126
|
+
onChange={handleSelectAll}
|
|
127
|
+
/>
|
|
128
|
+
</TableCell>
|
|
129
|
+
)}
|
|
130
|
+
{childrenArray.length > 0 ? (
|
|
131
|
+
childrenArray.map((child: ReactNode, index) => {
|
|
132
|
+
if (!child || typeof child !== 'object' || !('props' in child)) {
|
|
133
|
+
return <TableCell key={index} />;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const source =
|
|
137
|
+
child.props.source ?? (typeof child.props.label === 'string' ? child.props.label : undefined);
|
|
138
|
+
const sortable = child.props.sortable !== false && !!source;
|
|
139
|
+
const sortInfo = sortable ? getSortForField(source) : null;
|
|
140
|
+
|
|
141
|
+
const translated = translateLabel({
|
|
142
|
+
...child.props,
|
|
143
|
+
source: child.props.source ?? source,
|
|
144
|
+
resource
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const displayLabel: ReactNode = translated ?? child.props.label ?? null;
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<TableCell
|
|
151
|
+
key={child.props.source ?? index}
|
|
152
|
+
align={child.props.textAlign || 'left'}
|
|
153
|
+
sx={{ cursor: sortable ? 'pointer' : 'default' }}
|
|
154
|
+
>
|
|
155
|
+
{sortable ? (
|
|
156
|
+
<TableSortLabel
|
|
157
|
+
active={!!sortInfo}
|
|
158
|
+
direction={(sortInfo?.order.toLowerCase() as 'asc' | 'desc') || 'asc'}
|
|
159
|
+
onClick={handleSortClick(source)}
|
|
160
|
+
>
|
|
161
|
+
<Typography>{displayLabel}</Typography>
|
|
162
|
+
</TableSortLabel>
|
|
163
|
+
) : (
|
|
164
|
+
displayLabel
|
|
165
|
+
)}
|
|
166
|
+
</TableCell>
|
|
167
|
+
);
|
|
168
|
+
})
|
|
169
|
+
) : (
|
|
170
|
+
<TableCell />
|
|
171
|
+
)}
|
|
172
|
+
</TableRow>
|
|
173
|
+
</TableHead>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { DatagridHeader };
|
|
178
|
+
|
|
179
|
+
export type { MultiSortDatagridHeaderProps, SortItem };
|
|
@@ -19,147 +19,141 @@ interface ListTabsToolbarProps {
|
|
|
19
19
|
tabs?: ListTabToolbarConfig[];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const theme = useTheme();
|
|
27
|
-
const [searchParams, setSearchParams] = useSearchParams();
|
|
22
|
+
interface TabFilterState {
|
|
23
|
+
filterValues: Record<string, any>;
|
|
24
|
+
displayedFilters: Record<string, any>;
|
|
25
|
+
}
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
function useListTabs(tabs: ListTabToolbarConfig[]) {
|
|
28
|
+
const [searchParams] = useSearchParams();
|
|
29
|
+
const resource = useResourceContext();
|
|
30
|
+
const { setCurrentTabKey } = useListViewContext();
|
|
31
|
+
const { filterValues, displayedFilters, setFilters } = useListContext();
|
|
32
32
|
|
|
33
|
-
const defaultTabIndex = useMemo(() => tabs.findIndex((tab) => tab.default) ?? 0, [tabs]);
|
|
34
33
|
const resourceKey = useMemo(() => resource.replace(/^entities\//, ''), [resource]);
|
|
35
|
-
const tabGroupKey =
|
|
36
|
-
const tabFiltersKey =
|
|
34
|
+
const tabGroupKey = `${resourceKey}-tab-group`;
|
|
35
|
+
const tabFiltersKey = `${resourceKey}-tab-filters`;
|
|
37
36
|
|
|
38
|
-
const
|
|
39
|
-
const [tabFilterStates, setTabFilterStates] = useSessionStorage(tabFiltersKey, {});
|
|
40
|
-
const initialized = useRef(false);
|
|
41
|
-
|
|
42
|
-
const saveCurrentTabFilters = useMemo(
|
|
43
|
-
() =>
|
|
44
|
-
_.debounce((tabKey: string, filters: any, displayed: any) => {
|
|
45
|
-
setTabFilterStates((prev: any) => ({
|
|
46
|
-
...prev,
|
|
47
|
-
[tabKey]: {
|
|
48
|
-
filterValues: { ...filters },
|
|
49
|
-
displayedFilters: { ...displayed }
|
|
50
|
-
}
|
|
51
|
-
}));
|
|
52
|
-
}, 500),
|
|
53
|
-
[setTabFilterStates]
|
|
54
|
-
);
|
|
37
|
+
const defaultTabIndex = useMemo(() => tabs.findIndex((tab) => tab.default) ?? 0, [tabs]);
|
|
55
38
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
const [currentTab, setCurrentTab] = useSessionStorage(tabGroupKey, defaultTabIndex) as [number, (v: number) => void];
|
|
40
|
+
const [tabFilterStates, setTabFilterStates] = useSessionStorage(tabFiltersKey, {}) as [
|
|
41
|
+
Record<string, TabFilterState>,
|
|
42
|
+
(v: any) => void
|
|
43
|
+
];
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} else {
|
|
63
|
-
setFilters(tabFilter, {}, debounce);
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
[tabFilterStates, setFilters]
|
|
67
|
-
);
|
|
45
|
+
const initialized = useRef(false);
|
|
46
|
+
const switchingTab = useRef(false);
|
|
68
47
|
|
|
69
|
-
const
|
|
70
|
-
(index: number
|
|
71
|
-
const
|
|
48
|
+
const handleTabChange = useCallback(
|
|
49
|
+
(index: number) => {
|
|
50
|
+
const tab = tabs[index];
|
|
51
|
+
if (!tab) return;
|
|
72
52
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
76
|
-
[tabs, setCurrentTabKey, restoreTabFilters]
|
|
77
|
-
);
|
|
53
|
+
setCurrentTab(index);
|
|
54
|
+
setCurrentTabKey?.(tab.key);
|
|
78
55
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
newParams.set(tabGroupKey, String(index));
|
|
83
|
-
setSearchParams(newParams, { replace: true });
|
|
84
|
-
},
|
|
85
|
-
[searchParams, setSearchParams, tabGroupKey]
|
|
86
|
-
);
|
|
56
|
+
const savedState = tabFilterStates?.[tab.key];
|
|
57
|
+
const nextFilters = savedState?.filterValues ?? tab.filter;
|
|
58
|
+
const nextDisplayed = savedState?.displayedFilters ?? {};
|
|
87
59
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
60
|
+
if (!_.isEqual(nextFilters, filterValues) || !_.isEqual(nextDisplayed, displayedFilters)) {
|
|
61
|
+
switchingTab.current = true;
|
|
62
|
+
setFilters(nextFilters, nextDisplayed);
|
|
63
|
+
}
|
|
92
64
|
},
|
|
93
|
-
[
|
|
65
|
+
[tabs, tabFilterStates, filterValues, displayedFilters, setCurrentTab, setCurrentTabKey, setFilters]
|
|
94
66
|
);
|
|
95
67
|
|
|
96
68
|
useEffect(() => {
|
|
97
|
-
if (initialized.current
|
|
98
|
-
|
|
99
|
-
if (currentTabKey) {
|
|
100
|
-
saveCurrentTabFilters(currentTabKey, filterValues, displayedFilters);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}, [filterValues, displayedFilters, currentTab, tabs, saveCurrentTabFilters]);
|
|
69
|
+
if (initialized.current) return;
|
|
70
|
+
initialized.current = true;
|
|
104
71
|
|
|
105
|
-
useEffect(() => {
|
|
106
72
|
const param = searchParams.get(tabGroupKey);
|
|
107
|
-
|
|
108
|
-
|
|
73
|
+
const initialIndex = param != null ? Number(param) : Number(currentTab);
|
|
74
|
+
const hasUrlFilter = searchParams.get('filter') !== null;
|
|
75
|
+
|
|
76
|
+
if (hasUrlFilter) {
|
|
77
|
+
setCurrentTab(initialIndex);
|
|
78
|
+
setCurrentTabKey?.(tabs[initialIndex]?.key);
|
|
79
|
+
} else {
|
|
80
|
+
handleTabChange(initialIndex);
|
|
109
81
|
}
|
|
110
|
-
|
|
111
|
-
}, [filterValues]);
|
|
82
|
+
}, [searchParams, tabGroupKey, currentTab, setCurrentTab, setCurrentTabKey, handleTabChange, tabs]);
|
|
112
83
|
|
|
113
84
|
useEffect(() => {
|
|
114
|
-
if (!initialized.current) {
|
|
115
|
-
|
|
116
|
-
|
|
85
|
+
if (!initialized.current || switchingTab.current) {
|
|
86
|
+
switchingTab.current = false;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const currentTabKey = tabs[Number(currentTab)]?.key;
|
|
91
|
+
if (!currentTabKey) return;
|
|
117
92
|
|
|
118
|
-
|
|
119
|
-
|
|
93
|
+
setTabFilterStates((prev: Record<string, TabFilterState>) => {
|
|
94
|
+
const prevState = prev?.[currentTabKey];
|
|
95
|
+
if (
|
|
96
|
+
_.isEqual(prevState?.filterValues, filterValues) &&
|
|
97
|
+
_.isEqual(prevState?.displayedFilters, displayedFilters)
|
|
98
|
+
) {
|
|
99
|
+
return prev;
|
|
120
100
|
}
|
|
101
|
+
return {
|
|
102
|
+
...prev,
|
|
103
|
+
[currentTabKey]: { filterValues, displayedFilters }
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}, [filterValues, displayedFilters, currentTab, tabs, setTabFilterStates]);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
currentTab,
|
|
110
|
+
handleTabChange: (_: any, val: number) => handleTabChange(val)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
121
113
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
114
|
+
function ListTabsToolbar({ tabs = [] }: ListTabsToolbarProps) {
|
|
115
|
+
const theme = useTheme();
|
|
116
|
+
const { currentTab, handleTabChange } = useListTabs(tabs);
|
|
117
|
+
|
|
118
|
+
if (tabs.length > 0 && tabs.some((tab) => !tab.key)) {
|
|
119
|
+
throw new Error('ListTabsToolbar: Each tab must have a unique key.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (tabs.length === 0) return null;
|
|
127
123
|
|
|
128
124
|
return (
|
|
129
125
|
<Box sx={{ width: '100%' }}>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
</Tabs>
|
|
162
|
-
) : null}
|
|
126
|
+
<Tabs
|
|
127
|
+
value={Number(currentTab)}
|
|
128
|
+
onChange={handleTabChange}
|
|
129
|
+
allowScrollButtonsMobile
|
|
130
|
+
variant="scrollable"
|
|
131
|
+
scrollButtons="auto"
|
|
132
|
+
sx={{
|
|
133
|
+
width: '100%',
|
|
134
|
+
'.MuiTabs-scrollButtons.Mui-disabled': {
|
|
135
|
+
opacity: 0.2
|
|
136
|
+
}
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{tabs.map((tab) => (
|
|
140
|
+
<Tab
|
|
141
|
+
icon={tab.icon}
|
|
142
|
+
key={tab.key}
|
|
143
|
+
label={tab.label}
|
|
144
|
+
sx={{
|
|
145
|
+
flex: '0 0 auto',
|
|
146
|
+
lineHeight: 1,
|
|
147
|
+
whiteSpace: 'nowrap',
|
|
148
|
+
overflow: 'hidden',
|
|
149
|
+
textOverflow: 'ellipsis',
|
|
150
|
+
'& .MuiTab-iconWrapper': {
|
|
151
|
+
marginRight: theme.spacing(0.5)
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
155
|
+
))}
|
|
156
|
+
</Tabs>
|
|
163
157
|
</Box>
|
|
164
158
|
);
|
|
165
159
|
}
|