@applica-software-guru/react-admin 1.5.364 → 1.5.365
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/ra-buttons/ExportButton.d.ts +46 -0
- package/dist/components/ra-buttons/ExportButton.d.ts.map +1 -0
- package/dist/components/ra-buttons/index.d.ts +1 -0
- package/dist/components/ra-buttons/index.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 +2 -1
- package/dist/components/ra-lists/Datagrid/DatagridHeader.d.ts.map +1 -1
- package/dist/components/ra-lists/ListView.d.ts.map +1 -1
- package/dist/react-admin.cjs.js +61 -61
- package/dist/react-admin.cjs.js.gz +0 -0
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +9860 -9785
- package/dist/react-admin.es.js.gz +0 -0
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +63 -63
- package/dist/react-admin.umd.js.gz +0 -0
- package/dist/react-admin.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ra-buttons/ExportButton.tsx +104 -0
- package/src/components/ra-buttons/index.ts +1 -0
- package/src/components/ra-lists/Datagrid/Datagrid.tsx +46 -29
- package/src/components/ra-lists/Datagrid/DatagridHeader.tsx +7 -4
- package/src/components/ra-lists/ListView.tsx +16 -3
package/package.json
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import DownloadIcon from '@mui/icons-material/GetApp';
|
|
5
|
+
import {
|
|
6
|
+
Exporter,
|
|
7
|
+
FilterPayload,
|
|
8
|
+
fetchRelatedRecords,
|
|
9
|
+
useDataProvider,
|
|
10
|
+
useListContext,
|
|
11
|
+
useNotify,
|
|
12
|
+
useResourceContext
|
|
13
|
+
} from 'ra-core';
|
|
14
|
+
import { Button, ButtonProps } from './Button';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Custom ExportButton that exports data.
|
|
18
|
+
*
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* import { ExportButton } from '@applica-software-guru/react-admin';
|
|
22
|
+
* import { TopToolbar } from 'react-admin';
|
|
23
|
+
*
|
|
24
|
+
* const ListActions = () => (
|
|
25
|
+
* <TopToolbar>
|
|
26
|
+
* <ExportButton />
|
|
27
|
+
* </TopToolbar>
|
|
28
|
+
* );
|
|
29
|
+
*/
|
|
30
|
+
function _ExportButton(props: ExportButtonProps) {
|
|
31
|
+
const {
|
|
32
|
+
maxResults = 1000,
|
|
33
|
+
onClick,
|
|
34
|
+
label = 'ra.action.export',
|
|
35
|
+
icon = defaultIcon,
|
|
36
|
+
exporter: customExporter,
|
|
37
|
+
meta,
|
|
38
|
+
sort,
|
|
39
|
+
...rest
|
|
40
|
+
} = props;
|
|
41
|
+
const { filter, filterValues, exporter: exportContext, total } = useListContext(props);
|
|
42
|
+
const resource = useResourceContext(props);
|
|
43
|
+
const exporter = customExporter || exportContext;
|
|
44
|
+
const dataProvider = useDataProvider();
|
|
45
|
+
const notify = useNotify();
|
|
46
|
+
|
|
47
|
+
const handleClick = useCallback(
|
|
48
|
+
(event: React.MouseEvent) => {
|
|
49
|
+
dataProvider
|
|
50
|
+
.getList(resource, {
|
|
51
|
+
sort: { field: '_id', order: 'ASC' },
|
|
52
|
+
filter: filter ? { ...filterValues, ...filter } : filterValues,
|
|
53
|
+
pagination: { page: 1, perPage: maxResults },
|
|
54
|
+
meta
|
|
55
|
+
})
|
|
56
|
+
.then(({ data }) => exporter && exporter(data, fetchRelatedRecords(dataProvider), dataProvider, resource))
|
|
57
|
+
.catch((error) => {
|
|
58
|
+
console.error(error);
|
|
59
|
+
notify('ra.notification.http_error', { type: 'error' });
|
|
60
|
+
});
|
|
61
|
+
if (typeof onClick === 'function') {
|
|
62
|
+
onClick(event as any);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[dataProvider, exporter, filter, filterValues, maxResults, notify, onClick, resource, meta]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Button onClick={handleClick} label={label} disabled={total === 0} {...rest}>
|
|
70
|
+
{icon}
|
|
71
|
+
</Button>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface Props {
|
|
76
|
+
exporter?: Exporter;
|
|
77
|
+
filterValues?: FilterPayload;
|
|
78
|
+
icon?: JSX.Element;
|
|
79
|
+
label?: string;
|
|
80
|
+
maxResults?: number;
|
|
81
|
+
onClick?: (e: Event) => void;
|
|
82
|
+
resource?: string;
|
|
83
|
+
meta?: any;
|
|
84
|
+
sort?: any;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type ExportButtonProps = Props & ButtonProps;
|
|
88
|
+
|
|
89
|
+
_ExportButton.propTypes = {
|
|
90
|
+
exporter: PropTypes.func,
|
|
91
|
+
filterValues: PropTypes.object,
|
|
92
|
+
label: PropTypes.string,
|
|
93
|
+
maxResults: PropTypes.number,
|
|
94
|
+
resource: PropTypes.string,
|
|
95
|
+
icon: PropTypes.element,
|
|
96
|
+
meta: PropTypes.any
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const defaultIcon = <DownloadIcon />;
|
|
100
|
+
|
|
101
|
+
const ExportButton = React.memo(_ExportButton);
|
|
102
|
+
|
|
103
|
+
export { ExportButton };
|
|
104
|
+
export type { ExportButtonProps };
|
|
@@ -4,5 +4,6 @@ export * from './CreateButton';
|
|
|
4
4
|
export * from './CreateInDialogButton';
|
|
5
5
|
export * from './DeleteWithConfirmButton';
|
|
6
6
|
export * from './EditInDialogButton';
|
|
7
|
+
export * from './ExportButton';
|
|
7
8
|
export * from './GoogleLoginButton';
|
|
8
9
|
export * from './ImpersonateUserButton';
|
|
@@ -5,17 +5,8 @@ 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 {
|
|
9
|
-
|
|
10
|
-
cloneElement,
|
|
11
|
-
createElement,
|
|
12
|
-
isValidElement,
|
|
13
|
-
useCallback,
|
|
14
|
-
useEffect,
|
|
15
|
-
useMemo,
|
|
16
|
-
useRef,
|
|
17
|
-
useState
|
|
18
|
-
} from 'react';
|
|
8
|
+
import { FC, cloneElement, createElement, isValidElement, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
9
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
19
10
|
import * as React from 'react';
|
|
20
11
|
import {
|
|
21
12
|
BulkDeleteButton,
|
|
@@ -134,29 +125,54 @@ const Datagrid = React.forwardRef((props, ref) => {
|
|
|
134
125
|
} = props;
|
|
135
126
|
|
|
136
127
|
const translate = useTranslate();
|
|
137
|
-
const
|
|
138
|
-
const
|
|
128
|
+
const listContext = useListContext(props);
|
|
129
|
+
const { sort, data, isLoading, onSelect, onToggleItem, selectedIds, setSort, total } = listContext;
|
|
130
|
+
const navigate = useNavigate();
|
|
131
|
+
const location = useLocation();
|
|
139
132
|
|
|
140
|
-
const
|
|
141
|
-
const multiSortKey = useMemo(() => multiSort.map((s) => `${s.field}:${s.order}`).join('|'), [multiSort]);
|
|
133
|
+
const clickedFields = useRef<Set<string>>(new Set());
|
|
142
134
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
135
|
+
function handleHeaderSortClick(field: string) {
|
|
136
|
+
if (field) clickedFields.current.add(field);
|
|
137
|
+
}
|
|
146
138
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
});
|
|
139
|
+
const multiSort = useMemo<SortItem[]>(() => {
|
|
140
|
+
if (!sort || !sort.field) return [];
|
|
141
|
+
if (sort.field === 'id' && !clickedFields.current.has('id')) {
|
|
142
|
+
return [];
|
|
152
143
|
}
|
|
144
|
+
const fields = sort.field.split(',');
|
|
145
|
+
const orders = (sort.order || '').split(',');
|
|
146
|
+
return fields.map((field, i) => ({
|
|
147
|
+
field,
|
|
148
|
+
order: (orders[i] || 'ASC') as 'ASC' | 'DESC'
|
|
149
|
+
}));
|
|
150
|
+
}, [sort]);
|
|
151
|
+
|
|
152
|
+
const onMultiSortChange = useCallback(
|
|
153
|
+
(newMultiSort: SortItem[]) => {
|
|
154
|
+
const newField = newMultiSort.map((s) => s.field).join(',');
|
|
155
|
+
const newOrder = newMultiSort.length > 0 ? newMultiSort.map((s) => s.order).join(',') : 'ASC';
|
|
156
|
+
|
|
157
|
+
if (newMultiSort.length > 1) {
|
|
158
|
+
const searchParams = new URLSearchParams(
|
|
159
|
+
location.search.startsWith('?') ? location.search.slice(1) : location.search
|
|
160
|
+
);
|
|
153
161
|
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
searchParams.set('sort', newField);
|
|
163
|
+
searchParams.set('order', newOrder);
|
|
156
164
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
const url = searchParams.toString();
|
|
166
|
+
navigate(`${location.pathname}${url ? `?${url}` : ''}`, { replace: true });
|
|
167
|
+
} else {
|
|
168
|
+
setSort({
|
|
169
|
+
field: newField,
|
|
170
|
+
order: newOrder as 'ASC' | 'DESC'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
[location, navigate, setSort]
|
|
175
|
+
);
|
|
160
176
|
|
|
161
177
|
const hasBulkActions = !!bulkActionButtons !== false;
|
|
162
178
|
const contextValue = useMemo(() => ({ isRowExpandable, expandSingle }), [isRowExpandable, expandSingle]);
|
|
@@ -260,7 +276,8 @@ const Datagrid = React.forwardRef((props, ref) => {
|
|
|
260
276
|
onSelect,
|
|
261
277
|
resource,
|
|
262
278
|
selectedIds,
|
|
263
|
-
setSort
|
|
279
|
+
setSort,
|
|
280
|
+
onSortClick: handleHeaderSortClick
|
|
264
281
|
},
|
|
265
282
|
children
|
|
266
283
|
)}
|
|
@@ -9,8 +9,9 @@ type SortItem = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
interface MultiSortDatagridHeaderProps extends Omit<DatagridHeaderProps, 'setSort'> {
|
|
12
|
-
setMultiSort?: (sort: SortItem[]) => void;
|
|
13
12
|
multiSort?: SortItem[];
|
|
13
|
+
onSortClick?: (field: string) => void;
|
|
14
|
+
setMultiSort?: (sort: SortItem[]) => void;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function DatagridHeader(props: MultiSortDatagridHeaderProps): ReactElement {
|
|
@@ -24,6 +25,7 @@ function DatagridHeader(props: MultiSortDatagridHeaderProps): ReactElement {
|
|
|
24
25
|
data = [],
|
|
25
26
|
multiSort = [],
|
|
26
27
|
setMultiSort,
|
|
28
|
+
onSortClick,
|
|
27
29
|
resource
|
|
28
30
|
} = props;
|
|
29
31
|
const { setSort } = useListContext();
|
|
@@ -31,9 +33,10 @@ function DatagridHeader(props: MultiSortDatagridHeaderProps): ReactElement {
|
|
|
31
33
|
|
|
32
34
|
const updateSort = useCallback(
|
|
33
35
|
(field: string, isMultiSort: boolean = false) => {
|
|
36
|
+
if (onSortClick) onSortClick(field);
|
|
34
37
|
if (!setMultiSort) {
|
|
35
|
-
const
|
|
36
|
-
const newOrder =
|
|
38
|
+
const currentSort = multiSort.find((s) => s.field === field);
|
|
39
|
+
const newOrder = currentSort?.order === 'ASC' ? 'DESC' : 'ASC';
|
|
37
40
|
setSort({ field, order: newOrder });
|
|
38
41
|
return;
|
|
39
42
|
}
|
|
@@ -69,7 +72,7 @@ function DatagridHeader(props: MultiSortDatagridHeaderProps): ReactElement {
|
|
|
69
72
|
|
|
70
73
|
setMultiSort(newMultiSort);
|
|
71
74
|
},
|
|
72
|
-
[multiSort, setMultiSort, setSort]
|
|
75
|
+
[multiSort, setMultiSort, setSort, onSortClick]
|
|
73
76
|
);
|
|
74
77
|
|
|
75
78
|
const handleSelectAll = useCallback(
|
|
@@ -8,18 +8,31 @@ import { RaRecord, useListContext } from 'ra-core';
|
|
|
8
8
|
import { ElementType, ReactElement, ReactNode, cloneElement } from 'react';
|
|
9
9
|
import * as React from 'react';
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
CreateButton,
|
|
12
12
|
Pagination as DefaultPagination,
|
|
13
|
+
FilterButton,
|
|
13
14
|
SavedQueriesListClasses,
|
|
14
|
-
Title
|
|
15
|
+
Title,
|
|
16
|
+
TopToolbar
|
|
15
17
|
} from 'react-admin';
|
|
18
|
+
import { ExportButton } from '@/components/ra-buttons/ExportButton';
|
|
16
19
|
import { ListToolbar } from './ListToolbar';
|
|
17
20
|
import { FilterSidebar } from './FilterSidebar';
|
|
18
21
|
import { MainCard } from '@/components/MainCard';
|
|
19
22
|
import { Empty } from '@/components/ra-lists/Empty';
|
|
20
23
|
import { type ListTabToolbarConfig } from './ListTabsToolbar';
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
function DefaultListActions() {
|
|
26
|
+
return (
|
|
27
|
+
<TopToolbar>
|
|
28
|
+
<FilterButton />
|
|
29
|
+
<CreateButton />
|
|
30
|
+
<ExportButton />
|
|
31
|
+
</TopToolbar>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultActions = <DefaultListActions />;
|
|
23
36
|
const defaultPagination = <DefaultPagination />;
|
|
24
37
|
const defaultEmpty = <Empty />;
|
|
25
38
|
const DefaultComponent = Card;
|