@applica-software-guru/react-admin 1.3.144 → 1.3.146
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-lists/BulkActionsToolbar.d.ts +26 -0
- package/dist/components/ra-lists/BulkActionsToolbar.d.ts.map +1 -0
- package/dist/components/ra-lists/BulkFloatingActionsToolbar.d.ts +13 -0
- package/dist/components/ra-lists/BulkFloatingActionsToolbar.d.ts.map +1 -0
- package/dist/components/ra-lists/Datagrid/Datagrid.d.ts +114 -0
- package/dist/components/ra-lists/Datagrid/Datagrid.d.ts.map +1 -0
- package/dist/components/ra-lists/Datagrid/DatagridContext.d.ts +9 -0
- package/dist/components/ra-lists/Datagrid/DatagridContext.d.ts.map +1 -0
- package/dist/components/ra-lists/Datagrid/DatagridContextProvider.d.ts +8 -0
- package/dist/components/ra-lists/Datagrid/DatagridContextProvider.d.ts.map +1 -0
- package/dist/components/ra-lists/Datagrid/index.d.ts +15 -0
- package/dist/components/ra-lists/Datagrid/index.d.ts.map +1 -0
- package/dist/components/ra-lists/index.d.ts +6 -5
- package/dist/components/ra-lists/index.d.ts.map +1 -1
- package/dist/react-admin.cjs.js +59 -59
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +10034 -9399
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +59 -59
- package/dist/react-admin.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ra-lists/BulkActionsToolbar.tsx +141 -0
- package/src/components/ra-lists/BulkFloatingActionsToolbar.tsx +100 -0
- package/src/components/ra-lists/Datagrid/Datagrid.tsx +345 -0
- package/src/components/ra-lists/Datagrid/DatagridContext.ts +13 -0
- package/src/components/ra-lists/Datagrid/DatagridContextProvider.tsx +8 -0
- package/src/components/ra-lists/{Datagrid.tsx → Datagrid/index.tsx} +6 -9
- package/src/components/ra-lists/index.ts +6 -5
- package/src/playground/components/ra-forms/TestModelForm.jsx +18 -0
- package/dist/components/ra-lists/Datagrid.d.ts +0 -638
- package/dist/components/ra-lists/Datagrid.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Children, ReactNode, cloneElement, isValidElement, useCallback } from 'react';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { styled, lighten } from '@mui/material/styles';
|
|
5
|
+
import clsx from 'clsx';
|
|
6
|
+
import Toolbar from '@mui/material/Toolbar';
|
|
7
|
+
import Typography from '@mui/material/Typography';
|
|
8
|
+
|
|
9
|
+
import IconButton from '@mui/material/IconButton';
|
|
10
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
11
|
+
import { useTranslate, sanitizeListRestProps, useListContext, Identifier } from 'ra-core';
|
|
12
|
+
import { TopToolbar } from 'react-admin';
|
|
13
|
+
|
|
14
|
+
const BulkActionsToolbar = (props: BulkActionsToolbarProps) => {
|
|
15
|
+
const { label = 'ra.action.bulk_actions', children, className, ...rest } = props;
|
|
16
|
+
const { filterValues, resource, selectedIds = [], onUnselectItems } = useListContext(props);
|
|
17
|
+
|
|
18
|
+
const translate = useTranslate();
|
|
19
|
+
|
|
20
|
+
const handleUnselectAllClick = useCallback(() => {
|
|
21
|
+
onUnselectItems();
|
|
22
|
+
}, [onUnselectItems]);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Root className={className}>
|
|
26
|
+
<Toolbar
|
|
27
|
+
data-test="bulk-actions-toolbar"
|
|
28
|
+
className={clsx(BulkActionsToolbarClasses.toolbar, {
|
|
29
|
+
[BulkActionsToolbarClasses.collapsed]: selectedIds.length === 0
|
|
30
|
+
})}
|
|
31
|
+
{...sanitizeListRestProps(rest)}
|
|
32
|
+
>
|
|
33
|
+
<div className={BulkActionsToolbarClasses.title}>
|
|
34
|
+
<IconButton
|
|
35
|
+
className={BulkActionsToolbarClasses.icon}
|
|
36
|
+
aria-label={translate('ra.action.unselect')}
|
|
37
|
+
title={translate('ra.action.unselect')}
|
|
38
|
+
onClick={handleUnselectAllClick}
|
|
39
|
+
size="small"
|
|
40
|
+
>
|
|
41
|
+
<CloseIcon fontSize="small" />
|
|
42
|
+
</IconButton>
|
|
43
|
+
<Typography color="inherit" variant="subtitle1">
|
|
44
|
+
{translate(label, {
|
|
45
|
+
_: label,
|
|
46
|
+
// eslint-disable-next-line camelcase
|
|
47
|
+
smart_count: selectedIds.length
|
|
48
|
+
})}
|
|
49
|
+
</Typography>
|
|
50
|
+
</div>
|
|
51
|
+
<TopToolbar className={BulkActionsToolbarClasses.topToolbar}>
|
|
52
|
+
{Children.map(children, (child) =>
|
|
53
|
+
isValidElement<any>(child)
|
|
54
|
+
? cloneElement(child, {
|
|
55
|
+
filterValues,
|
|
56
|
+
resource,
|
|
57
|
+
selectedIds
|
|
58
|
+
})
|
|
59
|
+
: null
|
|
60
|
+
)}
|
|
61
|
+
</TopToolbar>
|
|
62
|
+
</Toolbar>
|
|
63
|
+
</Root>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
BulkActionsToolbar.propTypes = {
|
|
68
|
+
children: PropTypes.node,
|
|
69
|
+
label: PropTypes.string
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export interface BulkActionsToolbarProps {
|
|
73
|
+
children?: ReactNode;
|
|
74
|
+
label?: string;
|
|
75
|
+
selectedIds?: Identifier[];
|
|
76
|
+
className?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const PREFIX = 'RaBulkActionsToolbar';
|
|
80
|
+
|
|
81
|
+
export const BulkActionsToolbarClasses = {
|
|
82
|
+
toolbar: `${PREFIX}-toolbar`,
|
|
83
|
+
topToolbar: `${PREFIX}-topToolbar`,
|
|
84
|
+
buttons: `${PREFIX}-buttons`,
|
|
85
|
+
collapsed: `${PREFIX}-collapsed`,
|
|
86
|
+
title: `${PREFIX}-title`,
|
|
87
|
+
icon: `${PREFIX}-icon`
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const Root = styled('div', {
|
|
91
|
+
name: PREFIX,
|
|
92
|
+
overridesResolver: (props, styles) => styles.root
|
|
93
|
+
})(({ theme }) => ({
|
|
94
|
+
position: 'relative',
|
|
95
|
+
[`& .${BulkActionsToolbarClasses.toolbar}`]: {
|
|
96
|
+
position: 'absolute',
|
|
97
|
+
left: 0,
|
|
98
|
+
right: 0,
|
|
99
|
+
zIndex: 3,
|
|
100
|
+
color: theme.palette.mode === 'light' ? theme.palette.primary.main : theme.palette.text.primary,
|
|
101
|
+
justifyContent: 'space-between',
|
|
102
|
+
backgroundColor: theme.palette.mode === 'light' ? lighten(theme.palette.primary.light, 0.8) : theme.palette.primary.dark,
|
|
103
|
+
minHeight: theme.spacing(6),
|
|
104
|
+
height: theme.spacing(6),
|
|
105
|
+
transform: `translateY(-${theme.spacing(6)})`,
|
|
106
|
+
transition: `${theme.transitions.create('height')}, ${theme.transitions.create('min-height')}, ${theme.transitions.create(
|
|
107
|
+
'transform'
|
|
108
|
+
)}`,
|
|
109
|
+
borderTopLeftRadius: theme.shape.borderRadius,
|
|
110
|
+
borderTopRightRadius: theme.shape.borderRadius
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
[`& .${BulkActionsToolbarClasses.topToolbar}`]: {
|
|
114
|
+
paddingBottom: theme.spacing(1),
|
|
115
|
+
minHeight: 'auto',
|
|
116
|
+
[theme.breakpoints.down('sm')]: {
|
|
117
|
+
backgroundColor: 'transparent'
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
[`& .${BulkActionsToolbarClasses.buttons}`]: {},
|
|
122
|
+
|
|
123
|
+
[`& .${BulkActionsToolbarClasses.collapsed}`]: {
|
|
124
|
+
minHeight: 0,
|
|
125
|
+
height: 0,
|
|
126
|
+
transform: `translateY(0)`,
|
|
127
|
+
overflowY: 'hidden'
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
[`& .${BulkActionsToolbarClasses.title}`]: {
|
|
131
|
+
display: 'flex',
|
|
132
|
+
flex: '0 0 auto'
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
[`& .${BulkActionsToolbarClasses.icon}`]: {
|
|
136
|
+
marginLeft: '-0.5em',
|
|
137
|
+
marginRight: '0.5em'
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
export default BulkActionsToolbar;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useListContext, Button } from 'react-admin';
|
|
4
|
+
import { Box, Fab, Fade, List, ListItem, Popover, Theme, useMediaQuery } from '@mui/material';
|
|
5
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
6
|
+
const noSpaceOutside = {
|
|
7
|
+
p: 0,
|
|
8
|
+
m: 0
|
|
9
|
+
};
|
|
10
|
+
const moreSpaceInside = {
|
|
11
|
+
p: 2,
|
|
12
|
+
m: 0,
|
|
13
|
+
borderRadius: 0,
|
|
14
|
+
width: '100%'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const BulkFloatingActionsToolbar = (props: BulkFloatingActionsToolbarProps) => {
|
|
18
|
+
const { children } = props;
|
|
19
|
+
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
|
|
20
|
+
|
|
21
|
+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
22
|
+
setAnchorEl(event.currentTarget);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleClose = () => {
|
|
26
|
+
setAnchorEl(null);
|
|
27
|
+
};
|
|
28
|
+
const isSmall = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
|
|
29
|
+
const open = Boolean(anchorEl);
|
|
30
|
+
const id = open ? 'simple-popover' : undefined;
|
|
31
|
+
const { filterValues, resource, selectedIds = [], onUnselectItems } = useListContext(props);
|
|
32
|
+
|
|
33
|
+
const handleUnselectAllClick = React.useCallback(() => {
|
|
34
|
+
onUnselectItems();
|
|
35
|
+
setAnchorEl(null);
|
|
36
|
+
}, [onUnselectItems, setAnchorEl]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Fade in={selectedIds !== undefined && selectedIds.length > 0}>
|
|
40
|
+
<Box
|
|
41
|
+
sx={{
|
|
42
|
+
position: 'fixed',
|
|
43
|
+
bottom: '16px',
|
|
44
|
+
right: '16px'
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<Fab color="primary" aria-label="manage" onClick={handleClick}>
|
|
48
|
+
{selectedIds?.length}
|
|
49
|
+
</Fab>
|
|
50
|
+
<Popover
|
|
51
|
+
id={id}
|
|
52
|
+
open={open}
|
|
53
|
+
anchorEl={anchorEl}
|
|
54
|
+
onClose={handleClose}
|
|
55
|
+
anchorOrigin={{
|
|
56
|
+
vertical: 'top',
|
|
57
|
+
horizontal: isSmall ? 'center' : 'right'
|
|
58
|
+
}}
|
|
59
|
+
transformOrigin={{
|
|
60
|
+
vertical: 'bottom',
|
|
61
|
+
horizontal: isSmall ? 'center' : 'right'
|
|
62
|
+
}}
|
|
63
|
+
disableScrollLock
|
|
64
|
+
elevation={10}
|
|
65
|
+
>
|
|
66
|
+
<List sx={noSpaceOutside}>
|
|
67
|
+
<ListItem sx={noSpaceOutside}>
|
|
68
|
+
<Button onClick={handleUnselectAllClick} sx={moreSpaceInside} label="ra.action.unselect" startIcon={<CloseIcon />}>
|
|
69
|
+
<CloseIcon />
|
|
70
|
+
</Button>
|
|
71
|
+
</ListItem>
|
|
72
|
+
{React.Children.map(children, (child) => (
|
|
73
|
+
<ListItem sx={noSpaceOutside}>
|
|
74
|
+
{React.isValidElement<any>(child)
|
|
75
|
+
? React.cloneElement(child, {
|
|
76
|
+
filterValues,
|
|
77
|
+
resource,
|
|
78
|
+
selectedIds,
|
|
79
|
+
sx: moreSpaceInside,
|
|
80
|
+
...(child.props?.popover ? { closePopover: handleClose } : {})
|
|
81
|
+
})
|
|
82
|
+
: null}
|
|
83
|
+
</ListItem>
|
|
84
|
+
))}
|
|
85
|
+
</List>
|
|
86
|
+
</Popover>
|
|
87
|
+
</Box>
|
|
88
|
+
</Fade>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
BulkFloatingActionsToolbar.propTypes = {
|
|
93
|
+
children: PropTypes.node
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export interface BulkFloatingActionsToolbarProps {
|
|
97
|
+
children?: React.ReactNode | React.ReactNode[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default BulkFloatingActionsToolbar;
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatagridProps as RaDatagridProps,
|
|
3
|
+
DatagridHeader,
|
|
4
|
+
DatagridLoading,
|
|
5
|
+
DatagridBody,
|
|
6
|
+
PureDatagridBody,
|
|
7
|
+
BulkDeleteButton,
|
|
8
|
+
ListNoResults,
|
|
9
|
+
DatagridClasses,
|
|
10
|
+
DatagridRoot
|
|
11
|
+
} from 'react-admin';
|
|
12
|
+
|
|
13
|
+
import * as React from 'react';
|
|
14
|
+
import { cloneElement, createElement, isValidElement, useCallback, useRef, useEffect, FC, useMemo } from 'react';
|
|
15
|
+
import PropTypes from 'prop-types';
|
|
16
|
+
import { sanitizeListRestProps, useListContext, Identifier } from 'ra-core';
|
|
17
|
+
import { Table } from '@mui/material';
|
|
18
|
+
import clsx from 'clsx';
|
|
19
|
+
import union from 'lodash/union';
|
|
20
|
+
import difference from 'lodash/difference';
|
|
21
|
+
import DatagridContextProvider from './DatagridContextProvider';
|
|
22
|
+
import BulkActionsToolbar from '../BulkActionsToolbar';
|
|
23
|
+
|
|
24
|
+
const defaultBulkActionButtons = <BulkDeleteButton />;
|
|
25
|
+
|
|
26
|
+
export type DatagridProps = RaDatagridProps & {
|
|
27
|
+
/**
|
|
28
|
+
* An alternative to bulkActionButtons, to be used when the actions are too complex to be expressed.
|
|
29
|
+
* In this case you are free to fully control the rendering of the bulk actions toolbar.
|
|
30
|
+
* @see BulkActionsToolbar
|
|
31
|
+
* @example <caption>Using the BulkActionsToolbar component</caption>
|
|
32
|
+
* import { BulkActionsToolbar } from '@applica-software-guru/react-admin';
|
|
33
|
+
* import { BulkDeleteButton } from 'react-admin';
|
|
34
|
+
*
|
|
35
|
+
* const PostBulkActionButtons = props => (
|
|
36
|
+
* <BulkActionsToolbar {...props}>
|
|
37
|
+
* <BulkDeleteButton />
|
|
38
|
+
* // add your custom actions here
|
|
39
|
+
* </BulkActionsToolbar>
|
|
40
|
+
* );
|
|
41
|
+
*
|
|
42
|
+
* @example <caption>Using BulkFloatingActionsToolbar component</caption>
|
|
43
|
+
* import { BulkActionsToolbar } from '@applica-software-guru/react-admin';
|
|
44
|
+
* import { BulkDeleteButton } from 'react-admin';
|
|
45
|
+
*
|
|
46
|
+
* const PostBulkActionButtons = props => (
|
|
47
|
+
* <BulkActionsToolbar {...props}>
|
|
48
|
+
* <BulkDeleteButton />
|
|
49
|
+
* // add your custom actions here
|
|
50
|
+
* </BulkActionsToolbar>
|
|
51
|
+
* );
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
54
|
+
bulkActionsToolbar: React.ReactNode | React.FC | boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The Datagrid component renders a list of records as a table.
|
|
59
|
+
* It is usually used as a child of the <List> and <ReferenceManyField> components.
|
|
60
|
+
*
|
|
61
|
+
* Props:
|
|
62
|
+
* - body
|
|
63
|
+
* - bulkActionButtons
|
|
64
|
+
* - children
|
|
65
|
+
* - empty
|
|
66
|
+
* - expand
|
|
67
|
+
* - header
|
|
68
|
+
* - hover
|
|
69
|
+
* - isRowExpandable
|
|
70
|
+
* - isRowSelectable
|
|
71
|
+
* - optimized
|
|
72
|
+
* - rowClick
|
|
73
|
+
* - rowSx
|
|
74
|
+
* - size
|
|
75
|
+
* - sx
|
|
76
|
+
*
|
|
77
|
+
* @example // Display all posts as a datagrid
|
|
78
|
+
* const postRowSx = (record, index) => ({
|
|
79
|
+
* backgroundColor: record.nb_views >= 500 ? '#efe' : 'white',
|
|
80
|
+
* });
|
|
81
|
+
* export const PostList = () => (
|
|
82
|
+
* <List>
|
|
83
|
+
* <Datagrid rowSx={postRowSx}>
|
|
84
|
+
* <TextField source="id" />
|
|
85
|
+
* <TextField source="title" />
|
|
86
|
+
* <TextField source="body" />
|
|
87
|
+
* <EditButton />
|
|
88
|
+
* </Datagrid>
|
|
89
|
+
* </List>
|
|
90
|
+
* );
|
|
91
|
+
*
|
|
92
|
+
* @example // Display all the comments of the current post as a datagrid
|
|
93
|
+
* <ReferenceManyField reference="comments" target="post_id">
|
|
94
|
+
* <Datagrid>
|
|
95
|
+
* <TextField source="id" />
|
|
96
|
+
* <TextField source="body" />
|
|
97
|
+
* <DateField source="created_at" />
|
|
98
|
+
* <EditButton />
|
|
99
|
+
* </Datagrid>
|
|
100
|
+
* </ReferenceManyField>
|
|
101
|
+
*
|
|
102
|
+
* @example // Usage outside of a <List> or a <ReferenceManyField>.
|
|
103
|
+
*
|
|
104
|
+
* const sort = { field: 'published_at', order: 'DESC' };
|
|
105
|
+
*
|
|
106
|
+
* export const MyCustomList = (props) => {
|
|
107
|
+
* const { data, total, isLoading } = useGetList(
|
|
108
|
+
* 'posts',
|
|
109
|
+
* { pagination: { page: 1, perPage: 10 }, sort: sort }
|
|
110
|
+
* );
|
|
111
|
+
*
|
|
112
|
+
* return (
|
|
113
|
+
* <Datagrid
|
|
114
|
+
* data={data}
|
|
115
|
+
* total={total}
|
|
116
|
+
* isLoading={isLoading}
|
|
117
|
+
* sort={sort}
|
|
118
|
+
* selectedIds={[]}
|
|
119
|
+
* setSort={() => {
|
|
120
|
+
* console.log('set sort');
|
|
121
|
+
* }}
|
|
122
|
+
* onSelect={() => {
|
|
123
|
+
* console.log('on select');
|
|
124
|
+
* }}
|
|
125
|
+
* onToggleItem={() => {
|
|
126
|
+
* console.log('on toggle item');
|
|
127
|
+
* }}
|
|
128
|
+
* >
|
|
129
|
+
* <TextField source="id" />
|
|
130
|
+
* <TextField source="title" />
|
|
131
|
+
* </Datagrid>
|
|
132
|
+
* );
|
|
133
|
+
* }
|
|
134
|
+
*/
|
|
135
|
+
export const Datagrid: FC<DatagridProps> = React.forwardRef((props, ref) => {
|
|
136
|
+
const {
|
|
137
|
+
optimized = false,
|
|
138
|
+
body = optimized ? PureDatagridBody : DatagridBody,
|
|
139
|
+
header = DatagridHeader,
|
|
140
|
+
children,
|
|
141
|
+
className,
|
|
142
|
+
empty = DefaultEmpty,
|
|
143
|
+
expand,
|
|
144
|
+
bulkActionsToolbar = false,
|
|
145
|
+
bulkActionButtons = defaultBulkActionButtons,
|
|
146
|
+
hover,
|
|
147
|
+
isRowSelectable,
|
|
148
|
+
isRowExpandable,
|
|
149
|
+
resource,
|
|
150
|
+
rowClick,
|
|
151
|
+
rowSx,
|
|
152
|
+
rowStyle,
|
|
153
|
+
size = 'small',
|
|
154
|
+
sx,
|
|
155
|
+
expandSingle = false,
|
|
156
|
+
...rest
|
|
157
|
+
} = props;
|
|
158
|
+
|
|
159
|
+
const { sort, data, isLoading, onSelect, onToggleItem, selectedIds, setSort, total } = useListContext(props);
|
|
160
|
+
|
|
161
|
+
const hasBulkActions = !!bulkActionButtons !== false;
|
|
162
|
+
|
|
163
|
+
const contextValue = useMemo(() => ({ isRowExpandable, expandSingle }), [isRowExpandable, expandSingle]);
|
|
164
|
+
|
|
165
|
+
const lastSelected = useRef(null);
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!selectedIds || selectedIds.length === 0) {
|
|
169
|
+
lastSelected.current = null;
|
|
170
|
+
}
|
|
171
|
+
}, [JSON.stringify(selectedIds)]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
172
|
+
|
|
173
|
+
// we manage row selection at the datagrid level to allow shift+click to select an array of rows
|
|
174
|
+
const handleToggleItem = useCallback(
|
|
175
|
+
// @ts-ignore
|
|
176
|
+
(id, event) => {
|
|
177
|
+
const ids = data.map((record) => record.id);
|
|
178
|
+
const lastSelectedIndex = ids.indexOf(lastSelected.current);
|
|
179
|
+
lastSelected.current = event.target.checked ? id : null;
|
|
180
|
+
|
|
181
|
+
if (event.shiftKey && lastSelectedIndex !== -1) {
|
|
182
|
+
const index = ids.indexOf(id);
|
|
183
|
+
const idsBetweenSelections = ids.slice(Math.min(lastSelectedIndex, index), Math.max(lastSelectedIndex, index) + 1);
|
|
184
|
+
|
|
185
|
+
const newSelectedIds = event.target.checked
|
|
186
|
+
? union(selectedIds, idsBetweenSelections)
|
|
187
|
+
: difference(selectedIds, idsBetweenSelections);
|
|
188
|
+
|
|
189
|
+
onSelect(
|
|
190
|
+
isRowSelectable
|
|
191
|
+
? newSelectedIds.filter((id: Identifier) => isRowSelectable(data.find((record) => record.id === id)))
|
|
192
|
+
: newSelectedIds
|
|
193
|
+
);
|
|
194
|
+
} else {
|
|
195
|
+
onToggleItem(id);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
[data, isRowSelectable, onSelect, onToggleItem, selectedIds]
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (isLoading === true) {
|
|
202
|
+
return (
|
|
203
|
+
<DatagridLoading
|
|
204
|
+
className={className}
|
|
205
|
+
expand={expand}
|
|
206
|
+
hasBulkActions={hasBulkActions}
|
|
207
|
+
nbChildren={React.Children.count(children)}
|
|
208
|
+
size={size}
|
|
209
|
+
/>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Once loaded, the data for the list may be empty. Instead of
|
|
215
|
+
* displaying the table header with zero data rows,
|
|
216
|
+
* the Datagrid displays the empty component.
|
|
217
|
+
*/
|
|
218
|
+
if (data == null || data.length === 0 || total === 0) {
|
|
219
|
+
if (empty) {
|
|
220
|
+
return empty;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* After the initial load, if the data for the list isn't empty,
|
|
228
|
+
* and even if the data is refreshing (e.g. after a filter change),
|
|
229
|
+
* the datagrid displays the current data.
|
|
230
|
+
*/
|
|
231
|
+
return (
|
|
232
|
+
<DatagridContextProvider value={contextValue}>
|
|
233
|
+
<DatagridRoot sx={sx} className={clsx(DatagridClasses.root, className)}>
|
|
234
|
+
{bulkActionsToolbar != null && isValidElement(bulkActionsToolbar) ? (
|
|
235
|
+
cloneElement<any>(bulkActionsToolbar, {
|
|
236
|
+
selectedIds,
|
|
237
|
+
total
|
|
238
|
+
})
|
|
239
|
+
) : bulkActionButtons !== false ? (
|
|
240
|
+
<BulkActionsToolbar>{isValidElement(bulkActionButtons) ? bulkActionButtons : defaultBulkActionButtons}</BulkActionsToolbar>
|
|
241
|
+
) : null}
|
|
242
|
+
<div className={DatagridClasses.tableWrapper}>
|
|
243
|
+
<Table ref={ref} className={DatagridClasses.table} size={size} {...sanitizeRestProps(rest)}>
|
|
244
|
+
{createOrCloneElement(
|
|
245
|
+
header,
|
|
246
|
+
{
|
|
247
|
+
children,
|
|
248
|
+
sort,
|
|
249
|
+
data,
|
|
250
|
+
hasExpand: !!expand,
|
|
251
|
+
hasBulkActions,
|
|
252
|
+
isRowSelectable,
|
|
253
|
+
onSelect,
|
|
254
|
+
resource,
|
|
255
|
+
selectedIds,
|
|
256
|
+
setSort
|
|
257
|
+
},
|
|
258
|
+
children
|
|
259
|
+
)}
|
|
260
|
+
{createOrCloneElement(
|
|
261
|
+
body,
|
|
262
|
+
{
|
|
263
|
+
expand,
|
|
264
|
+
rowClick,
|
|
265
|
+
data,
|
|
266
|
+
hasBulkActions,
|
|
267
|
+
hover,
|
|
268
|
+
onToggleItem: handleToggleItem,
|
|
269
|
+
resource,
|
|
270
|
+
rowSx,
|
|
271
|
+
rowStyle,
|
|
272
|
+
selectedIds,
|
|
273
|
+
isRowSelectable
|
|
274
|
+
},
|
|
275
|
+
children
|
|
276
|
+
)}
|
|
277
|
+
</Table>
|
|
278
|
+
</div>
|
|
279
|
+
</DatagridRoot>
|
|
280
|
+
</DatagridContextProvider>
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// @ts-ignore
|
|
285
|
+
const createOrCloneElement = (element, props, children) =>
|
|
286
|
+
isValidElement(element) ? cloneElement(element, props, children) : createElement(element, props, children);
|
|
287
|
+
|
|
288
|
+
Datagrid.propTypes = {
|
|
289
|
+
// @ts-ignore
|
|
290
|
+
body: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
|
|
291
|
+
// @ts-ignore-line
|
|
292
|
+
bulkActionButtons: PropTypes.oneOfType([PropTypes.bool, PropTypes.element]),
|
|
293
|
+
children: PropTypes.node.isRequired,
|
|
294
|
+
className: PropTypes.string,
|
|
295
|
+
// @ts-ignore
|
|
296
|
+
sort: PropTypes.exact({
|
|
297
|
+
field: PropTypes.string,
|
|
298
|
+
order: PropTypes.oneOf(['ASC', 'DESC'] as const)
|
|
299
|
+
}),
|
|
300
|
+
data: PropTypes.arrayOf(PropTypes.any),
|
|
301
|
+
empty: PropTypes.element,
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
expand: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
|
|
304
|
+
// @ts-ignore
|
|
305
|
+
header: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]),
|
|
306
|
+
hover: PropTypes.bool,
|
|
307
|
+
isLoading: PropTypes.bool,
|
|
308
|
+
onSelect: PropTypes.func,
|
|
309
|
+
onToggleItem: PropTypes.func,
|
|
310
|
+
resource: PropTypes.string,
|
|
311
|
+
// @ts-ignore
|
|
312
|
+
rowClick: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.bool]),
|
|
313
|
+
rowSx: PropTypes.func,
|
|
314
|
+
rowStyle: PropTypes.func,
|
|
315
|
+
selectedIds: PropTypes.arrayOf(PropTypes.any),
|
|
316
|
+
setSort: PropTypes.func,
|
|
317
|
+
total: PropTypes.number,
|
|
318
|
+
isRowSelectable: PropTypes.func,
|
|
319
|
+
isRowExpandable: PropTypes.func,
|
|
320
|
+
expandSingle: PropTypes.bool
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const injectedProps = [
|
|
324
|
+
'isRequired',
|
|
325
|
+
'setFilter',
|
|
326
|
+
'setPagination',
|
|
327
|
+
'limitChoicesToValue',
|
|
328
|
+
'translateChoice',
|
|
329
|
+
// Datagrid may be used as an alternative to SelectInput
|
|
330
|
+
'field',
|
|
331
|
+
'fieldState',
|
|
332
|
+
'formState'
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
// @ts-ignore
|
|
336
|
+
const sanitizeRestProps = (props) =>
|
|
337
|
+
Object.keys(sanitizeListRestProps(props))
|
|
338
|
+
.filter((propName) => !injectedProps.includes(propName))
|
|
339
|
+
.reduce((acc, key) => ({ ...acc, [key]: props[key] }), {});
|
|
340
|
+
|
|
341
|
+
Datagrid.displayName = 'Datagrid';
|
|
342
|
+
|
|
343
|
+
const DefaultEmpty = <ListNoResults />;
|
|
344
|
+
|
|
345
|
+
export default Datagrid;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import { RaRecord } from 'ra-core';
|
|
3
|
+
|
|
4
|
+
const DatagridContext = createContext<DatagridContextValue>({});
|
|
5
|
+
|
|
6
|
+
DatagridContext.displayName = 'DatagridContext';
|
|
7
|
+
|
|
8
|
+
export type DatagridContextValue = {
|
|
9
|
+
isRowExpandable?: (record: RaRecord) => boolean;
|
|
10
|
+
expandSingle?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default DatagridContext;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React, { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import DatagridContext, { DatagridContextValue } from './DatagridContext';
|
|
3
|
+
|
|
4
|
+
const DatagridContextProvider = ({ children, value }: { children: ReactNode; value: DatagridContextValue }): ReactElement => (
|
|
5
|
+
<DatagridContext.Provider value={value}>{children}</DatagridContext.Provider>
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
export default DatagridContextProvider;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Datagrid as RaDatagrid } from './Datagrid';
|
|
2
|
+
import { DatagridProps } from 'react-admin';
|
|
2
3
|
|
|
3
4
|
import { styled } from '@mui/material/styles';
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
+
const StyledDatagrid = styled(RaDatagrid, {
|
|
6
7
|
name: 'ApplicaDatagrid',
|
|
7
8
|
slot: 'root'
|
|
8
9
|
})(() => ({
|
|
@@ -40,13 +41,9 @@ const ApplicaStyledDatagrid = styled(RaDatagrid, {
|
|
|
40
41
|
* @param {DatagridProps} props
|
|
41
42
|
* @returns {JSX.Element}
|
|
42
43
|
*/
|
|
43
|
-
const Datagrid = (props: DatagridProps): JSX.Element =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
...RaDatagrid.propTypes
|
|
47
|
-
};
|
|
48
|
-
Datagrid.defaultProps = {
|
|
49
|
-
...RaDatagrid.defaultProps
|
|
44
|
+
const Datagrid = (props: DatagridProps): JSX.Element => {
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
return <StyledDatagrid {...props} />;
|
|
50
47
|
};
|
|
51
48
|
|
|
52
49
|
export default Datagrid;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export {
|
|
1
|
+
export { default as Datagrid } from './Datagrid';
|
|
2
|
+
export { default as Empty } from './Empty';
|
|
3
|
+
export { default as NotificationList } from './NotificationList';
|
|
4
|
+
export { default as List } from './List';
|
|
5
|
+
export { default as BulkActionsToolbar } from './BulkActionsToolbar';
|
|
6
|
+
export { default as BulkFloatingActionsToolbar } from './BulkFloatingActionsToolbar';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LongForm, MainCard, TextInput } from '@applica-software-guru/react-admin';
|
|
2
|
+
import { Grid } from '@mui/material';
|
|
3
|
+
|
|
4
|
+
const TestModelForm = () => (
|
|
5
|
+
<LongForm>
|
|
6
|
+
<LongForm.Tab id="root" label="Root">
|
|
7
|
+
<MainCard title="Info">
|
|
8
|
+
<Grid container spacing={2}>
|
|
9
|
+
<Grid item xs={12}>
|
|
10
|
+
<TextInput source="name" fullWidth />
|
|
11
|
+
</Grid>
|
|
12
|
+
</Grid>
|
|
13
|
+
</MainCard>
|
|
14
|
+
</LongForm.Tab>
|
|
15
|
+
</LongForm>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default TestModelForm;
|