@applica-software-guru/react-admin 1.5.302 → 1.5.304
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/.eslintrc +1 -1
- package/dist/components/Layout/Sidebar/Content.d.ts +8 -0
- package/dist/components/Layout/Sidebar/Content.d.ts.map +1 -0
- package/dist/components/Layout/Sidebar/Drawer.d.ts +11 -0
- package/dist/components/Layout/Sidebar/Drawer.d.ts.map +1 -0
- package/dist/components/Layout/Sidebar/Footer.d.ts +7 -0
- package/dist/components/Layout/Sidebar/Footer.d.ts.map +1 -0
- package/dist/components/Layout/Sidebar/Header.d.ts +16 -0
- package/dist/components/Layout/Sidebar/Header.d.ts.map +1 -0
- package/dist/components/Layout/Sidebar/index.d.ts +14 -0
- package/dist/components/Layout/Sidebar/index.d.ts.map +1 -0
- package/dist/components/Layout/TopToolbar.d.ts +11 -0
- package/dist/components/Layout/TopToolbar.d.ts.map +1 -0
- package/dist/components/Layout/index.d.ts +2 -0
- package/dist/components/Layout/index.d.ts.map +1 -1
- package/dist/components/Pagination/Pagination.d.ts.map +1 -1
- package/dist/components/ra-inputs/LabeledInput.d.ts +1 -0
- package/dist/components/ra-inputs/LabeledInput.d.ts.map +1 -1
- package/dist/components/ra-lists/FilterSidebar/ActiveFiltersChips.d.ts +4 -0
- package/dist/components/ra-lists/FilterSidebar/ActiveFiltersChips.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/FilterSidebar.d.ts +7 -0
- package/dist/components/ra-lists/FilterSidebar/FilterSidebar.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/FilterSidebarButton.d.ts +3 -0
- package/dist/components/ra-lists/FilterSidebar/FilterSidebarButton.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/FilterSidebarContext.d.ts +13 -0
- package/dist/components/ra-lists/FilterSidebar/FilterSidebarContext.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.d.ts +11 -0
- package/dist/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/SaveFiltersButton.d.ts +3 -0
- package/dist/components/ra-lists/FilterSidebar/SaveFiltersButton.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/SaveFiltersDialog.d.ts +7 -0
- package/dist/components/ra-lists/FilterSidebar/SaveFiltersDialog.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/SavedFiltersList.d.ts +6 -0
- package/dist/components/ra-lists/FilterSidebar/SavedFiltersList.d.ts.map +1 -0
- package/dist/components/ra-lists/FilterSidebar/index.d.ts +9 -0
- package/dist/components/ra-lists/FilterSidebar/index.d.ts.map +1 -0
- package/dist/components/ra-lists/List.d.ts.map +1 -1
- package/dist/components/ra-lists/ListToolbar.d.ts +25 -0
- package/dist/components/ra-lists/ListToolbar.d.ts.map +1 -0
- package/dist/components/ra-lists/ListView.d.ts.map +1 -1
- package/dist/components/ra-lists/ListViewProvider.d.ts +11 -0
- package/dist/components/ra-lists/ListViewProvider.d.ts.map +1 -0
- package/dist/components/ra-lists/index.d.ts +3 -0
- package/dist/components/ra-lists/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/react-admin.cjs.js +58 -58
- package/dist/react-admin.cjs.js.gz +0 -0
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +12033 -11439
- package/dist/react-admin.es.js.gz +0 -0
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +59 -59
- 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/Layout/Sidebar/Content.tsx +13 -0
- package/src/components/Layout/Sidebar/Drawer.tsx +50 -0
- package/src/components/Layout/Sidebar/Footer.tsx +20 -0
- package/src/components/Layout/Sidebar/Header.tsx +54 -0
- package/src/components/Layout/Sidebar/index.ts +22 -0
- package/src/components/Layout/TopToolbar.tsx +43 -0
- package/src/components/Layout/index.ts +2 -0
- package/src/components/Pagination/Pagination.tsx +3 -0
- package/src/components/ra-inputs/LabeledInput.tsx +3 -1
- package/src/components/ra-lists/FilterSidebar/ActiveFiltersChips.tsx +108 -0
- package/src/components/ra-lists/FilterSidebar/FilterSidebar.tsx +164 -0
- package/src/components/ra-lists/FilterSidebar/FilterSidebarButton.tsx +44 -0
- package/src/components/ra-lists/FilterSidebar/FilterSidebarContext.tsx +56 -0
- package/src/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.tsx +72 -0
- package/src/components/ra-lists/FilterSidebar/SaveFiltersButton.tsx +28 -0
- package/src/components/ra-lists/FilterSidebar/SaveFiltersDialog.tsx +138 -0
- package/src/components/ra-lists/FilterSidebar/SavedFiltersList.tsx +118 -0
- package/src/components/ra-lists/FilterSidebar/index.ts +8 -0
- package/src/components/ra-lists/List.tsx +23 -6
- package/src/components/ra-lists/ListToolbar.tsx +101 -0
- package/src/components/ra-lists/ListView.tsx +18 -8
- package/src/components/ra-lists/ListViewProvider.tsx +23 -0
- package/src/components/ra-lists/index.ts +3 -0
- package/src/index.ts +2 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import isEqual from 'lodash/isEqual';
|
|
3
|
+
import { useListContext, useTranslate } from 'ra-core';
|
|
4
|
+
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from '@mui/material';
|
|
5
|
+
|
|
6
|
+
import { SavedQuery, extractValidSavedQueries, useSavedQueries } from 'react-admin';
|
|
7
|
+
|
|
8
|
+
interface RemoveSavedQueryDialogProps {
|
|
9
|
+
open: boolean;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
filterToRemove?: SavedQuery;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function RemoveSavedQueryDialog({ open, onClose, filterToRemove }: RemoveSavedQueryDialogProps): ReactElement {
|
|
15
|
+
const translate = useTranslate();
|
|
16
|
+
const { resource, filterValues, sort, perPage, displayedFilters } = useListContext();
|
|
17
|
+
const filterValuesToUse = filterToRemove?.value?.filter || filterValues;
|
|
18
|
+
const label = filterToRemove?.label ?? '';
|
|
19
|
+
|
|
20
|
+
const [savedQueries, setSavedQueries] = useSavedQueries(resource);
|
|
21
|
+
|
|
22
|
+
function removeQuery() {
|
|
23
|
+
const savedQueryToRemove = {
|
|
24
|
+
filter: filterValuesToUse,
|
|
25
|
+
sort,
|
|
26
|
+
perPage,
|
|
27
|
+
displayedFilters
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const newSavedQueries = extractValidSavedQueries(savedQueries);
|
|
31
|
+
const index = newSavedQueries.findIndex((savedFilter) => isEqual(savedFilter.value, savedQueryToRemove));
|
|
32
|
+
setSavedQueries([...newSavedQueries.slice(0, index), ...newSavedQueries.slice(index + 1)]);
|
|
33
|
+
onClose();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Dialog
|
|
38
|
+
open={open}
|
|
39
|
+
onClose={onClose}
|
|
40
|
+
aria-labelledby="alert-dialog-title"
|
|
41
|
+
aria-describedby="alert-dialog-description"
|
|
42
|
+
>
|
|
43
|
+
<DialogTitle id="alert-dialog-title">
|
|
44
|
+
{translate('ra.saved_queries.remove_dialog_title', {
|
|
45
|
+
label: label,
|
|
46
|
+
_: 'Remove saved query?'
|
|
47
|
+
})}
|
|
48
|
+
</DialogTitle>
|
|
49
|
+
<DialogContent>
|
|
50
|
+
<DialogContentText>
|
|
51
|
+
{translate('ra.saved_queries.remove_message', {
|
|
52
|
+
_: 'Are you sure you want to remove that item from your list of saved queries?'
|
|
53
|
+
})}
|
|
54
|
+
</DialogContentText>
|
|
55
|
+
</DialogContent>
|
|
56
|
+
<DialogActions>
|
|
57
|
+
<Button onClick={onClose}>{translate('ra.action.cancel')}</Button>
|
|
58
|
+
<Button
|
|
59
|
+
onClick={removeQuery}
|
|
60
|
+
color="primary"
|
|
61
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
62
|
+
autoFocus
|
|
63
|
+
>
|
|
64
|
+
{translate('ra.action.confirm')}
|
|
65
|
+
</Button>
|
|
66
|
+
</DialogActions>
|
|
67
|
+
</Dialog>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { RemoveSavedQueryDialog };
|
|
72
|
+
export type { RemoveSavedQueryDialogProps };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Button } from '@mui/material';
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { SaveFiltersDialog } from './SaveFiltersDialog';
|
|
4
|
+
import { useTranslate } from 'ra-core';
|
|
5
|
+
|
|
6
|
+
function SaveFiltersButton() {
|
|
7
|
+
const [open, setOpen] = useState(false);
|
|
8
|
+
const translate = useTranslate();
|
|
9
|
+
|
|
10
|
+
const onClose = useCallback(() => {
|
|
11
|
+
setOpen(false);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
const openDialog = useCallback(() => {
|
|
15
|
+
setOpen(true);
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<Button sx={{ height: '36px' }} variant="contained" onClick={openDialog}>
|
|
21
|
+
{translate('ra.action.save_filter')}
|
|
22
|
+
</Button>
|
|
23
|
+
<SaveFiltersDialog open={open} onClose={onClose} />
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { SaveFiltersButton };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { SimpleForm } from '@/components/ra-forms';
|
|
2
|
+
import { TextInput } from '@/components/ra-inputs';
|
|
3
|
+
import { Button, Dialog, Divider, List, ListItem, ListItemText, Stack, Toolbar, Typography } from '@mui/material';
|
|
4
|
+
import { required, useListContext, useTranslate } from 'ra-core';
|
|
5
|
+
import { extractValidSavedQueries, useSavedQueries } from 'ra-ui-materialui';
|
|
6
|
+
import { useCallback } from 'react';
|
|
7
|
+
import { useFormContext } from 'react-hook-form';
|
|
8
|
+
import { useGetChipValue } from './FilterSidebarContext';
|
|
9
|
+
|
|
10
|
+
interface SelectedFiltersItemProps {
|
|
11
|
+
value: unknown;
|
|
12
|
+
source: string;
|
|
13
|
+
lastItem: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function SelectedFiltersItem(props: SelectedFiltersItemProps): JSX.Element {
|
|
17
|
+
const { value, source, lastItem } = props;
|
|
18
|
+
const label = useGetChipValue({ source, value });
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Stack>
|
|
22
|
+
<ListItem alignItems="flex-start" sx={{ pl: 0 }}>
|
|
23
|
+
<ListItemText primary={label} sx={{ my: 0 }} />
|
|
24
|
+
</ListItem>
|
|
25
|
+
{lastItem === source ? null : <Divider />}
|
|
26
|
+
</Stack>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function SelectedFiltersList(): JSX.Element {
|
|
31
|
+
const { filterValues } = useListContext();
|
|
32
|
+
const translate = useTranslate();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Stack mt={4} width={'100%'}>
|
|
36
|
+
<Typography variant="h6" fontWeight={'bold'} color={'secondary'}>
|
|
37
|
+
{translate('ra.title.selected_filters')}
|
|
38
|
+
</Typography>
|
|
39
|
+
<List sx={{ bgcolor: 'background.paper', pt: 0 }}>
|
|
40
|
+
{filterValues
|
|
41
|
+
? Object.entries(filterValues).map(([key, value]) => {
|
|
42
|
+
const lastEntry = Object.entries(filterValues).pop();
|
|
43
|
+
const lastItem = lastEntry ? lastEntry[0] : '';
|
|
44
|
+
return <SelectedFiltersItem key={key} lastItem={lastItem} value={value} source={key} />;
|
|
45
|
+
})
|
|
46
|
+
: null}
|
|
47
|
+
</List>
|
|
48
|
+
</Stack>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface CustomToolbarProps {
|
|
53
|
+
onClose: () => void;
|
|
54
|
+
addQuery: (queryName: string) => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function CustomToolbar(props: CustomToolbarProps): JSX.Element {
|
|
58
|
+
const { onClose, addQuery } = props;
|
|
59
|
+
const translate = useTranslate();
|
|
60
|
+
const { handleSubmit } = useFormContext();
|
|
61
|
+
|
|
62
|
+
const onFormSubmit = useCallback(
|
|
63
|
+
(data: Record<string, any>) => {
|
|
64
|
+
addQuery(data.queryName);
|
|
65
|
+
},
|
|
66
|
+
[addQuery]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Toolbar sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
|
71
|
+
<Button onClick={onClose}>{translate('ra.action.cancel')}</Button>
|
|
72
|
+
<Button variant="contained" onClick={handleSubmit(onFormSubmit)}>
|
|
73
|
+
{translate('ra.action.save')}
|
|
74
|
+
</Button>
|
|
75
|
+
</Toolbar>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface SaveFiltersFormProps {
|
|
80
|
+
open: boolean;
|
|
81
|
+
onClose: () => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function SaveFiltersDialog(props: SaveFiltersFormProps) {
|
|
85
|
+
const { open, onClose } = props;
|
|
86
|
+
const { filterValues, sort, perPage, displayedFilters, resource } = useListContext();
|
|
87
|
+
const [savedQueries, setSavedQueries] = useSavedQueries(resource);
|
|
88
|
+
const translate = useTranslate();
|
|
89
|
+
|
|
90
|
+
const addQuery = useCallback(
|
|
91
|
+
(queryName: string): void => {
|
|
92
|
+
const newSavedQuery = {
|
|
93
|
+
label: queryName.trim(),
|
|
94
|
+
value: {
|
|
95
|
+
filter: filterValues,
|
|
96
|
+
sort,
|
|
97
|
+
perPage,
|
|
98
|
+
displayedFilters
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const newSavedQueries = extractValidSavedQueries(savedQueries);
|
|
102
|
+
setSavedQueries(newSavedQueries.concat(newSavedQuery));
|
|
103
|
+
onClose();
|
|
104
|
+
},
|
|
105
|
+
[filterValues, sort, perPage, displayedFilters, savedQueries, setSavedQueries, onClose]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const checkAlreadyExists = useCallback(
|
|
109
|
+
() => (value: string) => {
|
|
110
|
+
if (savedQueries.some((query) => query.label === value?.trim())) {
|
|
111
|
+
return 'ra.validation.already_exists';
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
},
|
|
115
|
+
[savedQueries]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
|
120
|
+
<SimpleForm
|
|
121
|
+
toolbar={<CustomToolbar addQuery={addQuery} onClose={onClose} />}
|
|
122
|
+
title={translate('ra.title.save_favorite_filter')}
|
|
123
|
+
modal={true}
|
|
124
|
+
>
|
|
125
|
+
<TextInput
|
|
126
|
+
fullWidth
|
|
127
|
+
source={'queryName'}
|
|
128
|
+
label={translate('ra.input.query_name')}
|
|
129
|
+
placeholder={translate('ra.input.query_name_placeholder')}
|
|
130
|
+
validate={[required(), checkAlreadyExists()]}
|
|
131
|
+
/>
|
|
132
|
+
<SelectedFiltersList />
|
|
133
|
+
</SimpleForm>
|
|
134
|
+
</Dialog>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { SaveFiltersDialog };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Stack, Typography } from '@mui/material';
|
|
2
|
+
import { useListContext, useTranslate } from 'ra-core';
|
|
3
|
+
import { SavedQuery, extractValidSavedQueries, useSavedQueries } from 'ra-ui-materialui';
|
|
4
|
+
import { useCallback, useState } from 'react';
|
|
5
|
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
6
|
+
import { useTheme } from '@mui/material/styles';
|
|
7
|
+
import { RemoveSavedQueryDialog } from './RemoveSavedQueryDialog';
|
|
8
|
+
|
|
9
|
+
interface RemoveSavedQueryButtonProps {
|
|
10
|
+
query?: SavedQuery;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function RemoveSavedQueryButton(props: RemoveSavedQueryButtonProps) {
|
|
14
|
+
const { query } = props;
|
|
15
|
+
const [open, setOpen] = useState(false);
|
|
16
|
+
const toggleDialog = useCallback(() => {
|
|
17
|
+
setOpen((prev) => !prev);
|
|
18
|
+
}, []);
|
|
19
|
+
const translate = useTranslate();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<Button size="small" color="primary" onClick={toggleDialog}>
|
|
24
|
+
{translate('ra.action.delete')}
|
|
25
|
+
</Button>
|
|
26
|
+
<RemoveSavedQueryDialog open={open} onClose={toggleDialog} filterToRemove={query} />
|
|
27
|
+
</>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface SavedFiltersListProps {
|
|
32
|
+
closeSidebar: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function SavedFiltersList(props: SavedFiltersListProps) {
|
|
36
|
+
const { closeSidebar } = props;
|
|
37
|
+
const { resource, setFilters, setSort, setPage, setPerPage } = useListContext();
|
|
38
|
+
const [expanded, setExpanded] = useState(false);
|
|
39
|
+
const theme = useTheme();
|
|
40
|
+
const [savedQueries] = useSavedQueries(resource);
|
|
41
|
+
const validSavedQueries = extractValidSavedQueries(savedQueries);
|
|
42
|
+
const translate = useTranslate();
|
|
43
|
+
|
|
44
|
+
const toggleExpanded = useCallback(() => {
|
|
45
|
+
setExpanded((prev) => !prev);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const handleApplyFilter = useCallback(
|
|
49
|
+
(props: any) => {
|
|
50
|
+
const { value } = props;
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
setFilters(value.filter);
|
|
53
|
+
setSort({ field: value.sort.field, order: value.sort.order });
|
|
54
|
+
setPage(1);
|
|
55
|
+
setPerPage(value.perPage);
|
|
56
|
+
closeSidebar();
|
|
57
|
+
},
|
|
58
|
+
[setFilters, setSort, setPage, setPerPage, closeSidebar]
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return savedQueries && savedQueries.length > 0 ? (
|
|
62
|
+
<Box
|
|
63
|
+
sx={{
|
|
64
|
+
mt: '-1px',
|
|
65
|
+
mb: 2,
|
|
66
|
+
'& .MuiAccordion-root': {
|
|
67
|
+
borderColor: theme.palette.divider,
|
|
68
|
+
'& .Mui-expanded:last-of-type': {
|
|
69
|
+
marginBottom: 'inherit'
|
|
70
|
+
},
|
|
71
|
+
'& .MuiAccordionSummary-root': {
|
|
72
|
+
bgcolor: 'transparent',
|
|
73
|
+
flexDirection: 'row'
|
|
74
|
+
},
|
|
75
|
+
'& .MuiAccordionSummary-content': {
|
|
76
|
+
justifyContent: 'space-between',
|
|
77
|
+
marginLeft: 0
|
|
78
|
+
},
|
|
79
|
+
'& .MuiAccordionDetails-root': {
|
|
80
|
+
borderColor: theme.palette.divider
|
|
81
|
+
},
|
|
82
|
+
'& .MuiAccordionSummary-expandIconWrapper': {
|
|
83
|
+
color: theme.palette.text.primary,
|
|
84
|
+
'&.Mui-expanded': {
|
|
85
|
+
transform: 'rotate(180deg)'
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<Accordion expanded={expanded} onChange={toggleExpanded}>
|
|
92
|
+
<AccordionSummary sx={{ px: '20px' }} expandIcon={<ExpandMoreIcon fontSize="small" />}>
|
|
93
|
+
<Typography variant="h5" fontWeight="bold">
|
|
94
|
+
{translate('ra.action.saved_filters')} ({validSavedQueries.length})
|
|
95
|
+
</Typography>
|
|
96
|
+
</AccordionSummary>
|
|
97
|
+
{validSavedQueries?.map((query, i) => (
|
|
98
|
+
<AccordionDetails
|
|
99
|
+
key={i}
|
|
100
|
+
sx={{ display: 'flex', justifyContent: 'space-between', px: '20px', alignItems: 'center' }}
|
|
101
|
+
>
|
|
102
|
+
<Typography variant="body1">{query.label}</Typography>
|
|
103
|
+
<Stack flexDirection="row" alignItems={'center'}>
|
|
104
|
+
<RemoveSavedQueryButton query={query} />
|
|
105
|
+
<Button size="small" color="primary" onClick={() => handleApplyFilter({ value: query.value })}>
|
|
106
|
+
{translate('ra.action.apply')}
|
|
107
|
+
</Button>
|
|
108
|
+
</Stack>
|
|
109
|
+
</AccordionDetails>
|
|
110
|
+
))}
|
|
111
|
+
</Accordion>
|
|
112
|
+
</Box>
|
|
113
|
+
) : (
|
|
114
|
+
<Box height={16} />
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { SavedFiltersList };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './ActiveFiltersChips';
|
|
2
|
+
export * from './FilterSidebar';
|
|
3
|
+
export * from './FilterSidebarButton';
|
|
4
|
+
export * from './FilterSidebarContext';
|
|
5
|
+
export * from './RemoveSavedQueryDialog';
|
|
6
|
+
export * from './SaveFiltersButton';
|
|
7
|
+
export * from './SaveFiltersDialog';
|
|
8
|
+
export * from './SavedFiltersList';
|
|
@@ -2,9 +2,11 @@ import { MainCard } from '@/components/MainCard';
|
|
|
2
2
|
import { ListView } from '@/components/ra-lists/ListView';
|
|
3
3
|
import { styled } from '@mui/system';
|
|
4
4
|
import { ListBase, RaRecord } from 'ra-core';
|
|
5
|
-
import React, { ReactElement } from 'react';
|
|
5
|
+
import React, { ReactElement, isValidElement } from 'react';
|
|
6
6
|
import { ListProps } from 'react-admin';
|
|
7
7
|
import { Pagination } from '@/components/Pagination/Pagination';
|
|
8
|
+
import { FilterSidebar } from './FilterSidebar';
|
|
9
|
+
import { ListViewProvider } from './ListViewProvider';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* List page component
|
|
@@ -56,6 +58,7 @@ import { Pagination } from '@/components/Pagination/Pagination';
|
|
|
56
58
|
* </List>
|
|
57
59
|
* );
|
|
58
60
|
*/
|
|
61
|
+
|
|
59
62
|
function RaList<RecordType extends RaRecord = any>({
|
|
60
63
|
debounce,
|
|
61
64
|
disableAuthentication,
|
|
@@ -69,7 +72,7 @@ function RaList<RecordType extends RaRecord = any>({
|
|
|
69
72
|
sort,
|
|
70
73
|
storeKey,
|
|
71
74
|
...rest
|
|
72
|
-
}: ListProps
|
|
75
|
+
}: ListProps): ReactElement {
|
|
73
76
|
return (
|
|
74
77
|
<ListBase<RecordType>
|
|
75
78
|
debounce={debounce}
|
|
@@ -84,7 +87,9 @@ function RaList<RecordType extends RaRecord = any>({
|
|
|
84
87
|
sort={sort}
|
|
85
88
|
storeKey={storeKey}
|
|
86
89
|
>
|
|
87
|
-
<
|
|
90
|
+
<ListViewProvider>
|
|
91
|
+
<ListView<RecordType> {...rest} />
|
|
92
|
+
</ListViewProvider>
|
|
88
93
|
</ListBase>
|
|
89
94
|
);
|
|
90
95
|
}
|
|
@@ -193,12 +198,24 @@ const StyledList = styled(RaList, { slot: 'root' })(({ theme }) => ({
|
|
|
193
198
|
}));
|
|
194
199
|
|
|
195
200
|
function List(props: ListProps): ReactElement {
|
|
196
|
-
const
|
|
201
|
+
const { aside } = props;
|
|
202
|
+
|
|
203
|
+
// Check if 'aside' is a valid React element
|
|
204
|
+
const isAsideValid = isValidElement(aside);
|
|
205
|
+
|
|
206
|
+
// Determine if 'aside' is a FilterSidebar component
|
|
207
|
+
const isFilterSidebar = isAsideValid && aside?.type === FilterSidebar;
|
|
208
|
+
|
|
209
|
+
// Use React.Fragment if there's a generic 'aside', otherwise use MainCard
|
|
210
|
+
const ListWrapper = isAsideValid && !isFilterSidebar ? React.Fragment : MainCard;
|
|
211
|
+
|
|
212
|
+
// Define props for the ListWrapper based on the presence of 'aside'
|
|
213
|
+
const listWrapperProps = !aside || isFilterSidebar ? { content: false } : undefined;
|
|
197
214
|
|
|
198
215
|
return (
|
|
199
|
-
<
|
|
216
|
+
<ListWrapper {...listWrapperProps}>
|
|
200
217
|
<StyledList {...props} pagination={<Pagination />} />
|
|
201
|
-
</
|
|
218
|
+
</ListWrapper>
|
|
202
219
|
);
|
|
203
220
|
}
|
|
204
221
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React, { ReactElement, memo } from 'react';
|
|
2
|
+
import { styled, useTheme } from '@mui/material/styles';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { Divider, ToolbarProps, useMediaQuery } from '@mui/material';
|
|
5
|
+
import { Exporter, useListContext } from 'ra-core';
|
|
6
|
+
import { FilterContext, FilterForm } from 'react-admin';
|
|
7
|
+
import { ActiveFiltersChips, FilterSidebarContext } from './FilterSidebar';
|
|
8
|
+
import { isEmpty } from 'lodash';
|
|
9
|
+
|
|
10
|
+
interface ListToolbarPropsExtended extends ListToolbarProps {
|
|
11
|
+
hasFilterSidebar?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ListToolbarComp(props: ListToolbarPropsExtended): ReactElement | null {
|
|
15
|
+
const { filters, actions, className, hasFilterSidebar, ...rest } = props;
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
|
18
|
+
const Actions = actions ? React.cloneElement(actions, { ...rest, ...actions.props }) : null;
|
|
19
|
+
const { filterValues } = useListContext();
|
|
20
|
+
|
|
21
|
+
return Array.isArray(filters) ? (
|
|
22
|
+
<FilterSidebarContext.Provider value={{ hasFilterSidebar }}>
|
|
23
|
+
<FilterContext.Provider value={filters}>
|
|
24
|
+
{isMobile && hasFilterSidebar ? (
|
|
25
|
+
<div>
|
|
26
|
+
{Actions}
|
|
27
|
+
{!isEmpty(filterValues) ? (
|
|
28
|
+
<div>
|
|
29
|
+
<Divider />
|
|
30
|
+
<ActiveFiltersChips />
|
|
31
|
+
</div>
|
|
32
|
+
) : null}
|
|
33
|
+
</div>
|
|
34
|
+
) : (
|
|
35
|
+
<Root className={className}>
|
|
36
|
+
{hasFilterSidebar ? <ActiveFiltersChips /> : <FilterForm />}
|
|
37
|
+
<span />
|
|
38
|
+
{Actions}
|
|
39
|
+
</Root>
|
|
40
|
+
)}
|
|
41
|
+
</FilterContext.Provider>
|
|
42
|
+
</FilterSidebarContext.Provider>
|
|
43
|
+
) : (
|
|
44
|
+
<Root className={className}>
|
|
45
|
+
{filters
|
|
46
|
+
? React.cloneElement(filters, {
|
|
47
|
+
...rest,
|
|
48
|
+
context: 'form'
|
|
49
|
+
})
|
|
50
|
+
: null}
|
|
51
|
+
<span />
|
|
52
|
+
{actions
|
|
53
|
+
? React.cloneElement(actions, {
|
|
54
|
+
...rest,
|
|
55
|
+
filters,
|
|
56
|
+
...actions.props
|
|
57
|
+
})
|
|
58
|
+
: null}
|
|
59
|
+
</Root>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ListToolbarComp.propTypes = {
|
|
64
|
+
filters: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
actions: PropTypes.oneOfType([PropTypes.bool, PropTypes.element]),
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
exporter: PropTypes.oneOfType([PropTypes.func, PropTypes.bool])
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
interface ListToolbarProps extends Omit<ToolbarProps, 'classes' | 'onSelect'> {
|
|
72
|
+
actions?: ReactElement | false;
|
|
73
|
+
exporter?: Exporter | false;
|
|
74
|
+
filters?: ReactElement | ReactElement[];
|
|
75
|
+
hasCreate?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const PREFIX = 'RaListToolbar';
|
|
79
|
+
|
|
80
|
+
const Root = styled('div', {
|
|
81
|
+
name: PREFIX,
|
|
82
|
+
overridesResolver: (styles) => styles.root
|
|
83
|
+
})(({ theme }) => ({
|
|
84
|
+
display: 'flex',
|
|
85
|
+
position: 'relative',
|
|
86
|
+
justifyContent: 'space-between',
|
|
87
|
+
alignItems: 'flex-end',
|
|
88
|
+
width: '100%',
|
|
89
|
+
// [theme.breakpoints.down('md')]: {
|
|
90
|
+
// flexWrap: 'wrap'
|
|
91
|
+
// },
|
|
92
|
+
[theme.breakpoints.down('sm')]: {
|
|
93
|
+
backgroundColor: theme.palette.background.paper,
|
|
94
|
+
flexWrap: 'inherit',
|
|
95
|
+
flexDirection: 'column-reverse'
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
const ListToolbar = memo(ListToolbarComp);
|
|
100
|
+
export { ListToolbar };
|
|
101
|
+
export type { ListToolbarProps };
|
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
ListActions as DefaultActions,
|
|
12
12
|
Pagination as DefaultPagination,
|
|
13
13
|
Empty,
|
|
14
|
-
ListToolbar,
|
|
15
14
|
SavedQueriesListClasses,
|
|
16
15
|
Title
|
|
17
16
|
} from 'react-admin';
|
|
17
|
+
import { ListToolbar } from './ListToolbar';
|
|
18
|
+
import { FilterSidebar } from './FilterSidebar';
|
|
18
19
|
const defaultActions = <DefaultActions />;
|
|
19
20
|
const defaultPagination = <DefaultPagination />;
|
|
20
21
|
const defaultEmpty = <Empty />;
|
|
@@ -38,7 +39,8 @@ function ListView<RecordType extends RaRecord = any>(props: ListViewProps): Reac
|
|
|
38
39
|
} = props;
|
|
39
40
|
const { defaultTitle, data, error, isLoading, filterValues, resource } = useListContext<RecordType>(props);
|
|
40
41
|
const theme = useTheme() as any;
|
|
41
|
-
const
|
|
42
|
+
const hasAsideSidebar = React.isValidElement(aside) && aside?.type !== FilterSidebar;
|
|
43
|
+
const hasFilterSidebar = React.isValidElement(aside) && aside?.type === FilterSidebar;
|
|
42
44
|
|
|
43
45
|
if (!children || (!data && isLoading && emptyWhileLoading)) {
|
|
44
46
|
return null;
|
|
@@ -49,18 +51,24 @@ function ListView<RecordType extends RaRecord = any>(props: ListViewProps): Reac
|
|
|
49
51
|
<div
|
|
50
52
|
className={ListClasses.main}
|
|
51
53
|
style={{
|
|
52
|
-
border:
|
|
53
|
-
borderRadius:
|
|
54
|
-
borderColor:
|
|
54
|
+
border: hasAsideSidebar ? '1px solid' : 'none',
|
|
55
|
+
borderRadius: hasAsideSidebar ? 4 : 0,
|
|
56
|
+
borderColor: hasAsideSidebar
|
|
55
57
|
? theme.palette.mode === 'dark'
|
|
56
58
|
? theme.palette.divider
|
|
57
59
|
: theme.palette.grey.A800
|
|
58
60
|
: 'transparent',
|
|
59
|
-
backgroundColor:
|
|
61
|
+
backgroundColor: hasAsideSidebar ? theme.palette.background.paper : 'transparent'
|
|
60
62
|
}}
|
|
61
63
|
>
|
|
62
64
|
{filters || actions ? (
|
|
63
|
-
<ListToolbar
|
|
65
|
+
<ListToolbar
|
|
66
|
+
className={ListClasses.actions}
|
|
67
|
+
filters={filters}
|
|
68
|
+
actions={actions}
|
|
69
|
+
hasCreate={hasCreate}
|
|
70
|
+
hasFilterSidebar={hasFilterSidebar}
|
|
71
|
+
/>
|
|
64
72
|
) : null}
|
|
65
73
|
{!error && (
|
|
66
74
|
<Content className={ListClasses.content}>
|
|
@@ -97,7 +105,9 @@ function ListView<RecordType extends RaRecord = any>(props: ListViewProps): Reac
|
|
|
97
105
|
<Root className={clsx('list-page', className)} {...rest}>
|
|
98
106
|
<Title title={title} defaultTitle={defaultTitle} preferenceKey={`${resource}.list.title`} />
|
|
99
107
|
{shouldRenderEmptyPage ? renderEmpty() : renderList()}
|
|
100
|
-
{
|
|
108
|
+
{hasAsideSidebar ? <AsideCard aside={aside} /> : null}
|
|
109
|
+
{/* @ts-ignore */}
|
|
110
|
+
{hasFilterSidebar ? React.cloneElement(aside, { filters }) : null}
|
|
101
111
|
</Root>
|
|
102
112
|
);
|
|
103
113
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ListViewContextType {
|
|
4
|
+
sidebarOpen?: boolean;
|
|
5
|
+
setSidebarOpen?: (open: boolean) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const ListViewContext = createContext<ListViewContextType>({
|
|
9
|
+
sidebarOpen: false,
|
|
10
|
+
setSidebarOpen: () => {}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function ListViewProvider(props: { children: React.ReactNode }) {
|
|
14
|
+
const [sidebarOpen, setSidebarOpen] = React.useState(false);
|
|
15
|
+
|
|
16
|
+
return <ListViewContext.Provider value={{ sidebarOpen, setSidebarOpen }}>{props.children}</ListViewContext.Provider>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function useListViewContext() {
|
|
20
|
+
return useContext(ListViewContext);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { ListViewProvider, useListViewContext };
|
|
@@ -2,6 +2,9 @@ export * from './BulkActionsToolbar';
|
|
|
2
2
|
export * from './BulkFloatingActionsToolbar';
|
|
3
3
|
export * from './Datagrid';
|
|
4
4
|
export * from './Empty';
|
|
5
|
+
export * from './FilterSidebar';
|
|
5
6
|
export * from './List';
|
|
7
|
+
export * from './ListToolbar';
|
|
8
|
+
export * from './ListViewProvider';
|
|
6
9
|
export * from './NotificationList';
|
|
7
10
|
export * from './SimpleList';
|
package/src/index.ts
CHANGED
|
@@ -30,7 +30,6 @@ export {
|
|
|
30
30
|
HttpError,
|
|
31
31
|
I18nContextProvider,
|
|
32
32
|
ListBase,
|
|
33
|
-
ListToolbar,
|
|
34
33
|
LoadingIndicator,
|
|
35
34
|
SimpleFormIterator as RaSimpleFormIterator,
|
|
36
35
|
RecordContextProvider,
|
|
@@ -44,9 +43,9 @@ export {
|
|
|
44
43
|
SimpleShowLayout,
|
|
45
44
|
SingleFieldList,
|
|
46
45
|
TabbedFormTabs,
|
|
47
|
-
TopToolbar,
|
|
48
46
|
UrlField,
|
|
49
47
|
ValidationError,
|
|
48
|
+
WithListContext,
|
|
50
49
|
choices,
|
|
51
50
|
email,
|
|
52
51
|
maxLength,
|
|
@@ -84,6 +83,7 @@ export {
|
|
|
84
83
|
useNotify,
|
|
85
84
|
usePermissions,
|
|
86
85
|
useRecordContext,
|
|
86
|
+
useReferenceArrayFieldController,
|
|
87
87
|
useRefresh,
|
|
88
88
|
useRemoveFromStore,
|
|
89
89
|
useResetStore,
|