@applica-software-guru/react-admin 1.5.301 → 1.5.303

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.
Files changed (77) hide show
  1. package/.eslintrc +1 -1
  2. package/dist/components/Layout/Sidebar/Content.d.ts +8 -0
  3. package/dist/components/Layout/Sidebar/Content.d.ts.map +1 -0
  4. package/dist/components/Layout/Sidebar/Drawer.d.ts +11 -0
  5. package/dist/components/Layout/Sidebar/Drawer.d.ts.map +1 -0
  6. package/dist/components/Layout/Sidebar/Footer.d.ts +7 -0
  7. package/dist/components/Layout/Sidebar/Footer.d.ts.map +1 -0
  8. package/dist/components/Layout/Sidebar/Header.d.ts +16 -0
  9. package/dist/components/Layout/Sidebar/Header.d.ts.map +1 -0
  10. package/dist/components/Layout/Sidebar/index.d.ts +14 -0
  11. package/dist/components/Layout/Sidebar/index.d.ts.map +1 -0
  12. package/dist/components/Layout/TopToolbar.d.ts +11 -0
  13. package/dist/components/Layout/TopToolbar.d.ts.map +1 -0
  14. package/dist/components/Layout/index.d.ts +2 -0
  15. package/dist/components/Layout/index.d.ts.map +1 -1
  16. package/dist/components/Pagination/Pagination.d.ts.map +1 -1
  17. package/dist/components/ra-lists/FilterSidebar/ActiveFiltersChips.d.ts +4 -0
  18. package/dist/components/ra-lists/FilterSidebar/ActiveFiltersChips.d.ts.map +1 -0
  19. package/dist/components/ra-lists/FilterSidebar/FilterSidebar.d.ts +7 -0
  20. package/dist/components/ra-lists/FilterSidebar/FilterSidebar.d.ts.map +1 -0
  21. package/dist/components/ra-lists/FilterSidebar/FilterSidebarButton.d.ts +3 -0
  22. package/dist/components/ra-lists/FilterSidebar/FilterSidebarButton.d.ts.map +1 -0
  23. package/dist/components/ra-lists/FilterSidebar/FilterSidebarContext.d.ts +13 -0
  24. package/dist/components/ra-lists/FilterSidebar/FilterSidebarContext.d.ts.map +1 -0
  25. package/dist/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.d.ts +11 -0
  26. package/dist/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.d.ts.map +1 -0
  27. package/dist/components/ra-lists/FilterSidebar/SaveFiltersButton.d.ts +3 -0
  28. package/dist/components/ra-lists/FilterSidebar/SaveFiltersButton.d.ts.map +1 -0
  29. package/dist/components/ra-lists/FilterSidebar/SaveFiltersDialog.d.ts +7 -0
  30. package/dist/components/ra-lists/FilterSidebar/SaveFiltersDialog.d.ts.map +1 -0
  31. package/dist/components/ra-lists/FilterSidebar/SavedFiltersList.d.ts +6 -0
  32. package/dist/components/ra-lists/FilterSidebar/SavedFiltersList.d.ts.map +1 -0
  33. package/dist/components/ra-lists/FilterSidebar/index.d.ts +9 -0
  34. package/dist/components/ra-lists/FilterSidebar/index.d.ts.map +1 -0
  35. package/dist/components/ra-lists/List.d.ts.map +1 -1
  36. package/dist/components/ra-lists/ListToolbar.d.ts +25 -0
  37. package/dist/components/ra-lists/ListToolbar.d.ts.map +1 -0
  38. package/dist/components/ra-lists/ListView.d.ts.map +1 -1
  39. package/dist/components/ra-lists/ListViewProvider.d.ts +11 -0
  40. package/dist/components/ra-lists/ListViewProvider.d.ts.map +1 -0
  41. package/dist/components/ra-lists/index.d.ts +3 -0
  42. package/dist/components/ra-lists/index.d.ts.map +1 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/react-admin.cjs.js +58 -58
  46. package/dist/react-admin.cjs.js.gz +0 -0
  47. package/dist/react-admin.cjs.js.map +1 -1
  48. package/dist/react-admin.es.js +12072 -11478
  49. package/dist/react-admin.es.js.gz +0 -0
  50. package/dist/react-admin.es.js.map +1 -1
  51. package/dist/react-admin.umd.js +58 -58
  52. package/dist/react-admin.umd.js.gz +0 -0
  53. package/dist/react-admin.umd.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/components/Layout/Sidebar/Content.tsx +13 -0
  56. package/src/components/Layout/Sidebar/Drawer.tsx +50 -0
  57. package/src/components/Layout/Sidebar/Footer.tsx +20 -0
  58. package/src/components/Layout/Sidebar/Header.tsx +54 -0
  59. package/src/components/Layout/Sidebar/index.ts +22 -0
  60. package/src/components/Layout/TopToolbar.tsx +43 -0
  61. package/src/components/Layout/index.ts +2 -0
  62. package/src/components/Pagination/Pagination.tsx +3 -0
  63. package/src/components/ra-lists/FilterSidebar/ActiveFiltersChips.tsx +108 -0
  64. package/src/components/ra-lists/FilterSidebar/FilterSidebar.tsx +164 -0
  65. package/src/components/ra-lists/FilterSidebar/FilterSidebarButton.tsx +44 -0
  66. package/src/components/ra-lists/FilterSidebar/FilterSidebarContext.tsx +53 -0
  67. package/src/components/ra-lists/FilterSidebar/RemoveSavedQueryDialog.tsx +72 -0
  68. package/src/components/ra-lists/FilterSidebar/SaveFiltersButton.tsx +28 -0
  69. package/src/components/ra-lists/FilterSidebar/SaveFiltersDialog.tsx +138 -0
  70. package/src/components/ra-lists/FilterSidebar/SavedFiltersList.tsx +118 -0
  71. package/src/components/ra-lists/FilterSidebar/index.ts +8 -0
  72. package/src/components/ra-lists/List.tsx +23 -6
  73. package/src/components/ra-lists/ListToolbar.tsx +101 -0
  74. package/src/components/ra-lists/ListView.tsx +18 -8
  75. package/src/components/ra-lists/ListViewProvider.tsx +23 -0
  76. package/src/components/ra-lists/index.ts +3 -0
  77. package/src/index.ts +3 -2
package/package.json CHANGED
@@ -107,5 +107,5 @@
107
107
  "type": "module",
108
108
  "types": "dist/index.d.ts",
109
109
  "typings": "dist/index.d.ts",
110
- "version": "1.5.301"
110
+ "version": "1.5.303"
111
111
  }
@@ -0,0 +1,13 @@
1
+ import { Box } from '@mui/material';
2
+
3
+ type ContentProps = {
4
+ children: React.ReactNode;
5
+ sx?: Record<string, any>;
6
+ };
7
+
8
+ function Content(props: ContentProps) {
9
+ const { sx } = props;
10
+ return <Box sx={{ ...sx, flex: '1 1 auto', backgroundColor: 'background.paper' }}>{props.children}</Box>;
11
+ }
12
+
13
+ export { Content };
@@ -0,0 +1,50 @@
1
+ import { Drawer as MUIDrawer, useMediaQuery, useTheme } from '@mui/material';
2
+ import { useCallback } from 'react';
3
+ import { useSx } from '@/hooks';
4
+ import { useListViewContext } from '@/components/ra-lists';
5
+ import { isFunction } from 'lodash';
6
+
7
+ type DrawerProps = {
8
+ PaperProps?: Record<string, any>;
9
+ anchor?: 'left' | 'right' | 'top' | 'bottom';
10
+ variant?: 'temporary' | 'persistent' | 'permanent';
11
+ children: React.ReactNode;
12
+ open?: boolean;
13
+ };
14
+
15
+ function Drawer(props: DrawerProps) {
16
+ const { PaperProps, anchor = 'right', variant = 'temporary', open = false } = props;
17
+ const theme = useTheme();
18
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
19
+ const anchorPosition = isMobile ? 'bottom' : anchor;
20
+ const { setSidebarOpen } = useListViewContext();
21
+ const handleClose = useCallback(() => {
22
+ if (isFunction(setSidebarOpen)) {
23
+ setSidebarOpen(false);
24
+ }
25
+ }, [setSidebarOpen]);
26
+ const sx = useSx(PaperProps ?? {}, {
27
+ backgroundColor: 'background.default',
28
+ boxShadow: '-10px 4px 42px 0px rgba(0, 0, 0, 0.25)',
29
+ width: {
30
+ xs: window.innerWidth,
31
+ sm: 480,
32
+ md: 600
33
+ }
34
+ });
35
+
36
+ return (
37
+ <MUIDrawer
38
+ {...props}
39
+ anchor={anchorPosition}
40
+ onClose={handleClose}
41
+ open={open}
42
+ PaperProps={{ sx: sx }}
43
+ variant={variant}
44
+ >
45
+ {props.children}
46
+ </MUIDrawer>
47
+ );
48
+ }
49
+
50
+ export { Drawer };
@@ -0,0 +1,20 @@
1
+ import { Box, Divider } from '@mui/material';
2
+
3
+ import { ReactNode } from 'react';
4
+
5
+ interface FooterProps {
6
+ children: ReactNode;
7
+ }
8
+
9
+ function Footer(props: FooterProps) {
10
+ return (
11
+ <Box>
12
+ <Divider />
13
+ <Box bgcolor={'background.paper'} px={2} py={2.5}>
14
+ {props.children}
15
+ </Box>
16
+ </Box>
17
+ );
18
+ }
19
+
20
+ export { Footer };
@@ -0,0 +1,54 @@
1
+ import { Box, Divider, Stack, Typography } from '@mui/material';
2
+
3
+ interface HeaderProps {
4
+ children: React.ReactNode;
5
+ }
6
+
7
+ function Header(props: HeaderProps) {
8
+ return (
9
+ <Box>
10
+ <Stack
11
+ alignItems="center"
12
+ bgcolor="background.paper"
13
+ direction="row"
14
+ px={2.5}
15
+ py={2}
16
+ spacing={2}
17
+ minHeight={'64px'}
18
+ >
19
+ {props.children}
20
+ </Stack>
21
+ <Divider sx={{ opacity: 0.6 }} />
22
+ </Box>
23
+ );
24
+ }
25
+
26
+ interface HeaderTextProps {
27
+ primary?: React.ReactNode;
28
+ secondary?: React.ReactNode;
29
+ }
30
+
31
+ function HeaderText(props: HeaderTextProps) {
32
+ const { primary, secondary } = props;
33
+
34
+ return (
35
+ <Box display="inline-flex" flexGrow={1} flexShrink={1} flexDirection={'column'}>
36
+ {primary ? <Typography variant="h5">{primary}</Typography> : null}
37
+ {secondary ? <Typography variant="h6">{secondary}</Typography> : null}
38
+ </Box>
39
+ );
40
+ }
41
+
42
+ interface HeaderActionProps {
43
+ children: React.ReactNode;
44
+ }
45
+
46
+ function HeaderAction(props: HeaderActionProps) {
47
+ return (
48
+ <Box display="inline-flex" flexGrow={0} flexShrink={0}>
49
+ {props.children}
50
+ </Box>
51
+ );
52
+ }
53
+
54
+ export { Header, HeaderAction, HeaderText };
@@ -0,0 +1,22 @@
1
+ import { Content } from './Content';
2
+ import { Drawer } from './Drawer';
3
+ import { Footer } from './Footer';
4
+ import { Header, HeaderAction, HeaderText } from './Header';
5
+
6
+ type ISidebar = typeof Drawer & {
7
+ Content: typeof Content;
8
+ Footer: typeof Footer;
9
+ Header: typeof Header;
10
+ HeaderAction: typeof HeaderAction;
11
+ HeaderText: typeof HeaderText;
12
+ };
13
+
14
+ const DefaultSidebar = Drawer as ISidebar;
15
+
16
+ DefaultSidebar.Content = Content;
17
+ DefaultSidebar.Footer = Footer;
18
+ DefaultSidebar.Header = Header;
19
+ DefaultSidebar.HeaderText = HeaderText;
20
+ DefaultSidebar.HeaderAction = HeaderAction;
21
+
22
+ export { DefaultSidebar as Sidebar };
@@ -0,0 +1,43 @@
1
+ import { Theme, Toolbar, ToolbarProps, styled, useMediaQuery } from '@mui/material';
2
+ import PropTypes from 'prop-types';
3
+
4
+ function TopToolbar(props: ToolbarProps) {
5
+ const isXSmall = useMediaQuery<Theme>((theme) => theme.breakpoints.down('sm'));
6
+ return <StyledToolbar disableGutters variant={isXSmall ? 'regular' : 'dense'} {...sanitizeToolbarRestProps(props)} />;
7
+ }
8
+
9
+ TopToolbar.propTypes = {
10
+ children: PropTypes.node,
11
+ className: PropTypes.string
12
+ };
13
+
14
+ const PREFIX = 'RaTopToolbar';
15
+
16
+ const StyledToolbar = styled(Toolbar, {
17
+ name: PREFIX,
18
+ overridesResolver: (styles) => styles.root
19
+ })(({ theme }) => ({
20
+ display: 'flex',
21
+ justifyContent: 'flex-end',
22
+ alignItems: 'flex-end',
23
+ gap: theme.spacing(1),
24
+ whiteSpace: 'nowrap',
25
+ flex: '0 1 auto',
26
+ paddingTop: theme.spacing(0.5),
27
+ paddingBottom: theme.spacing(0.5),
28
+ paddingInline: theme.spacing(2),
29
+ [theme.breakpoints.down('md')]: {
30
+ flex: '0 1 100%'
31
+ },
32
+ [theme.breakpoints.down('sm')]: {
33
+ backgroundColor: theme.palette.background.paper,
34
+ justifyContent: 'space-between',
35
+ alignItems: 'center'
36
+ }
37
+ }));
38
+
39
+ function sanitizeToolbarRestProps({ hasCreate, ...props }: any): any {
40
+ return props;
41
+ }
42
+
43
+ export { TopToolbar };
@@ -9,7 +9,9 @@ export * from './MenuProvider';
9
9
  export * from './NavMenu';
10
10
  export * from './Navigation';
11
11
  export * from './Provider';
12
+ export * as Sidebar from './Sidebar';
12
13
  export * from './ThemeColor';
13
14
  export * from './ThemeProvider';
14
15
  export * from './ThemeToggler';
16
+ export * from './TopToolbar';
15
17
  export * from './Wrapper';
@@ -6,6 +6,7 @@ import {
6
6
  ListPaginationContextValue,
7
7
  sanitizeListRestProps,
8
8
  useListPaginationContext,
9
+ useResourceDefinition,
9
10
  useTranslate
10
11
  } from 'ra-core';
11
12
 
@@ -18,6 +19,7 @@ const Pagination: FC<PaginationProps> = memo((props) => {
18
19
  const translate = useTranslate();
19
20
  const isSmall = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
20
21
  const [currentPage, setCurrentPage] = useState(page - 1); // Stato per la UI
22
+ const { hasCreate } = useResourceDefinition(props);
21
23
 
22
24
  const totalPages = useMemo(() => {
23
25
  return total != null ? Math.ceil(total / perPage) : undefined;
@@ -125,6 +127,7 @@ const Pagination: FC<PaginationProps> = memo((props) => {
125
127
  rowsPerPageOptions={emptyArray}
126
128
  component="span"
127
129
  labelDisplayedRows={labelDisplayedRows}
130
+ sx={{ justifyContent: hasCreate ? 'flex-start' : 'flex-end', display: hasCreate ? 'flex' : 'block' }}
128
131
  {...sanitizeListRestProps(rest)}
129
132
  />
130
133
  );
@@ -0,0 +1,108 @@
1
+ import { Button, Chip, Stack, Typography, useMediaQuery, useTheme } from '@mui/material';
2
+ import { useListContext, useTranslate } from 'ra-core';
3
+ import { Close } from '@mui/icons-material';
4
+ import { ReactElement, useCallback } from 'react';
5
+ import { isEmpty } from 'lodash';
6
+ import { SaveFiltersButton } from './SaveFiltersButton';
7
+ import { useGetChipValue } from './FilterSidebarContext';
8
+
9
+ interface ChipItemProps {
10
+ onDelete: () => void;
11
+ value: unknown;
12
+ source: string;
13
+ }
14
+
15
+ function ChipItem(props: ChipItemProps) {
16
+ const { onDelete, value, source } = props;
17
+ const label = useGetChipValue({ source, value });
18
+
19
+ return (
20
+ label && (
21
+ <Chip
22
+ label={label}
23
+ // @ts-ignore
24
+ variant="combined"
25
+ color="primary"
26
+ sx={{
27
+ height: '36px',
28
+ '& .MuiChip-deleteIcon': {
29
+ fontSize: '1rem !important',
30
+ mr: 1
31
+ }
32
+ }}
33
+ deleteIcon={<Close />}
34
+ onDelete={onDelete}
35
+ />
36
+ )
37
+ );
38
+ }
39
+
40
+ function Chips(): ReactElement {
41
+ const { filterValues, setFilters } = useListContext();
42
+ const theme = useTheme();
43
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
44
+ const translate = useTranslate();
45
+
46
+ const resetAllFilters = useCallback(() => {
47
+ // @ts-ignore
48
+ setFilters({});
49
+ }, [setFilters]);
50
+
51
+ const removeFilter = useCallback(
52
+ (key: string) => {
53
+ const newFilters = { ...filterValues };
54
+ delete newFilters[key];
55
+ // @ts-ignore
56
+ setFilters(newFilters);
57
+ },
58
+ [filterValues, setFilters]
59
+ );
60
+
61
+ const Buttons = useCallback(() => {
62
+ return (
63
+ <Stack flexDirection={'row'} gap={2}>
64
+ <SaveFiltersButton />
65
+ <Button variant="outlined" onClick={resetAllFilters}>
66
+ {translate('ra.action.reset_filters')}
67
+ </Button>
68
+ </Stack>
69
+ );
70
+ }, [resetAllFilters, translate]);
71
+
72
+ return (
73
+ <Stack gap={2} flexDirection={isMobile ? 'column' : 'row'}>
74
+ <Stack
75
+ flexDirection={'row'}
76
+ flexWrap={isMobile ? 'nowrap' : 'wrap'}
77
+ gap={2}
78
+ pb={0.5}
79
+ overflow={'auto'}
80
+ width={isMobile ? window.innerWidth - (24 + 34) : 'auto'}
81
+ >
82
+ {Object.entries(filterValues).map(([key, value]) => {
83
+ return <ChipItem key={key} source={key} value={value} onDelete={() => removeFilter(key)} />;
84
+ })}
85
+ {!isMobile ? <Buttons /> : null}
86
+ </Stack>
87
+ {isMobile ? <Buttons /> : null}
88
+ </Stack>
89
+ );
90
+ }
91
+
92
+ function ActiveFiltersChips(): ReactElement | null {
93
+ const { filterValues } = useListContext();
94
+ const theme = useTheme();
95
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
96
+ const translate = useTranslate();
97
+
98
+ return filterValues && !isEmpty(filterValues) ? (
99
+ <Stack flexDirection={isMobile ? 'column' : 'row'} alignItems={'baseline'} gap={2} m={isMobile ? 2 : 0}>
100
+ <Typography variant="h6" color="secondary" whiteSpace={'nowrap'}>
101
+ {translate('ra.title.active_filters')}
102
+ </Typography>
103
+ <Chips />
104
+ </Stack>
105
+ ) : null;
106
+ }
107
+
108
+ export { ActiveFiltersChips };
@@ -0,0 +1,164 @@
1
+ import { LoadingButton } from '@/components/@extended';
2
+ import { Box, Button, Stack } from '@mui/material';
3
+ import { useListContext, useResourceContext, useTranslate } from 'ra-core';
4
+ import React, { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { FilterContext, getFilterFormValues } from 'react-admin';
6
+ import { FormProvider, useForm } from 'react-hook-form';
7
+ import { ReloadOutlined } from '@ant-design/icons';
8
+ import { isEmpty, isEqual, isFunction } from 'lodash';
9
+ import { SavedFiltersList } from './SavedFiltersList';
10
+ import { useListViewContext } from '@/components/ra-lists';
11
+ import { Sidebar } from '@/components/Layout/Sidebar';
12
+
13
+ interface FiltersInputProps {
14
+ form: ReturnType<typeof useForm>;
15
+ filters?: ReactElement | ReactElement[];
16
+ }
17
+
18
+ function FiltersInput(props: FiltersInputProps): ReactElement {
19
+ const { form, filters } = props;
20
+ const resource = useResourceContext();
21
+
22
+ return (
23
+ <FilterContext.Provider value={Array.isArray(filters) ? filters : [filters]}>
24
+ <FormProvider {...form}>
25
+ <Box px={'20px'}>
26
+ {filters
27
+ ? (Array.isArray(filters) ? filters : [filters]).map((filter: ReactElement, i) => {
28
+ const props = { ...filter.props, display: 'label' };
29
+ return React.isValidElement(filter) ? (
30
+ <Box key={i} sx={{ mb: 1 }}>
31
+ {React.cloneElement(filter, { ...props, resource })}
32
+ </Box>
33
+ ) : null;
34
+ })
35
+ : null}
36
+ </Box>
37
+ </FormProvider>
38
+ </FilterContext.Provider>
39
+ );
40
+ }
41
+
42
+ interface FilterSidebarProps {
43
+ filters?: ReactElement | ReactElement[];
44
+ }
45
+
46
+ function FilterSidebar(props: FilterSidebarProps): ReactElement {
47
+ const { filters } = props;
48
+ const [loading, setLoading] = useState(false);
49
+ const { setSidebarOpen, sidebarOpen } = useListViewContext();
50
+ const { filterValues, setFilters } = useListContext();
51
+ const translate = useTranslate();
52
+ const form = useForm({
53
+ defaultValues: filterValues
54
+ });
55
+ const { handleSubmit, getValues, reset, watch } = form;
56
+ const watchValues = watch();
57
+ // values containes all the filters values and the default ones
58
+ const values = useMemo(() => {
59
+ const values: any = {};
60
+ if (filters) {
61
+ (Array.isArray(filters) ? filters : [filters]).forEach((filter) => {
62
+ values[filter.props.source] = filterValues?.[filter.props.source] ?? '';
63
+ });
64
+ }
65
+ return values;
66
+ }, [filters, filterValues]);
67
+
68
+ // Reapply filterValues when the URL changes or a user removes a filter
69
+ useEffect(() => {
70
+ const newValues = getFilterFormValues(getValues(), filterValues);
71
+ const previousValues = getValues();
72
+ if (!isEqual(newValues, previousValues)) {
73
+ reset(newValues);
74
+ }
75
+ // The reference to the filterValues object is not updated when it changes,
76
+ // so we must stringify it to compare it by value and also compare the reference.
77
+ // This makes it work for both input values and filters applied directly through
78
+ // the ListContext.setFilter (e.g. QuickFilter in the simple example)
79
+ // eslint-disable-next-line react-hooks/exhaustive-deps
80
+ }, [JSON.stringify(filterValues), filterValues, getValues, reset]);
81
+
82
+ const closeSidebar = useCallback(() => {
83
+ if (isFunction(setSidebarOpen)) {
84
+ setSidebarOpen(false);
85
+ }
86
+ }, [setSidebarOpen]);
87
+
88
+ const onSubmit = React.useCallback(
89
+ (data: any) => {
90
+ // @ts-ignore
91
+ setFilters(data);
92
+ closeSidebar();
93
+ },
94
+ [setFilters, closeSidebar]
95
+ );
96
+
97
+ const resetAllFields = useCallback(() => {
98
+ reset({});
99
+ // @ts-ignore
100
+ setFilters({});
101
+ }, [reset, setFilters]);
102
+
103
+ const toggleLoading = useCallback(() => {
104
+ setLoading((prev) => !prev);
105
+ resetAllFields();
106
+ setTimeout(() => {
107
+ setLoading(false);
108
+ }, 1000);
109
+ }, [resetAllFields]);
110
+
111
+ const isApplyDisabled = useMemo(() => {
112
+ const currentFormValues = getValues();
113
+ return isEmpty(values) || isEqual(currentFormValues, values);
114
+ // eslint-disable-next-line react-hooks/exhaustive-deps
115
+ }, [values, getValues, watchValues]);
116
+
117
+ return (
118
+ <Sidebar PaperProps={{ sx: { height: '100%' } }} open={sidebarOpen}>
119
+ <Sidebar.Header>
120
+ <Sidebar.HeaderText primary={translate('ra.action.add_filter')} />
121
+ <Sidebar.HeaderAction>
122
+ <LoadingButton
123
+ variant="text"
124
+ loading={loading}
125
+ loadingPosition="start"
126
+ // @ts-ignore
127
+ startIcon={<ReloadOutlined />}
128
+ onClick={toggleLoading}
129
+ >
130
+ {translate('ra.action.reset_filters')}
131
+ </LoadingButton>
132
+ </Sidebar.HeaderAction>
133
+ </Sidebar.Header>
134
+ <Sidebar.Content>
135
+ <SavedFiltersList closeSidebar={closeSidebar} />
136
+ <FiltersInput form={form} filters={filters} />
137
+ </Sidebar.Content>
138
+ <Sidebar.Footer>
139
+ <Stack direction="row" spacing={2}>
140
+ <Button
141
+ fullWidth
142
+ onClick={() => {
143
+ reset({});
144
+ closeSidebar();
145
+ }}
146
+ >
147
+ {translate('ra.action.close')}
148
+ </Button>
149
+ <Button
150
+ fullWidth
151
+ disabled={isApplyDisabled}
152
+ variant="contained"
153
+ color="primary"
154
+ onClick={handleSubmit(onSubmit)}
155
+ >
156
+ {translate('ra.action.apply_filters')}
157
+ </Button>
158
+ </Stack>
159
+ </Sidebar.Footer>
160
+ </Sidebar>
161
+ );
162
+ }
163
+
164
+ export { FilterSidebar };
@@ -0,0 +1,44 @@
1
+ import { useCallback, useContext } from 'react';
2
+ import { FilterList } from '@mui/icons-material';
3
+ import { FilterContext, useTranslate } from 'react-admin';
4
+ import { useIsEnabledSidebarFilter } from '@/components/ra-lists/FilterSidebar';
5
+ import { Button as MUIButton, Typography, useMediaQuery, useTheme } from '@mui/material';
6
+ import { Button } from '@/components/ra-buttons';
7
+ import { useListViewContext } from '@/components/ra-lists';
8
+ import { isFunction } from 'lodash';
9
+
10
+ function FilterSidebarButton() {
11
+ const { setSidebarOpen } = useListViewContext();
12
+ const filters = useContext(FilterContext);
13
+ const hasFilterSidebar = useIsEnabledSidebarFilter();
14
+ const translate = useTranslate();
15
+ const theme = useTheme();
16
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
17
+
18
+ if (!hasFilterSidebar) {
19
+ throw new Error('The <FilterSidebarButton> component requires the <List aside={<FilterSidebar />}> prop to be set');
20
+ }
21
+
22
+ if (filters === undefined) {
23
+ throw new Error('The <FilterSidebarButton> component requires the <List filters> prop to be set');
24
+ }
25
+
26
+ const handleFilterClick = useCallback(() => {
27
+ if (isFunction(setSidebarOpen)) {
28
+ setSidebarOpen(true);
29
+ }
30
+ }, [setSidebarOpen]);
31
+
32
+ return isMobile ? (
33
+ <MUIButton className="add-filter" onClick={handleFilterClick}>
34
+ <FilterList sx={{ fontSize: '1rem', mr: 0.5 }} />
35
+ <Typography variant="h6">{translate('ra.action.add_filter')}</Typography>
36
+ </MUIButton>
37
+ ) : (
38
+ <Button label="ra.action.add_filter" onClick={handleFilterClick}>
39
+ <FilterList />
40
+ </Button>
41
+ );
42
+ }
43
+
44
+ export { FilterSidebarButton };
@@ -0,0 +1,53 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+ import { FilterContext, useResourceContext } from 'react-admin';
3
+
4
+ interface FiltersContextType {
5
+ hasFilterSidebar?: boolean;
6
+ }
7
+
8
+ const FilterSidebarContext = createContext<FiltersContextType>({
9
+ hasFilterSidebar: false
10
+ });
11
+
12
+ function useIsEnabledSidebarFilter(): boolean {
13
+ return useContext(FilterSidebarContext)?.hasFilterSidebar ?? false;
14
+ }
15
+
16
+ interface ChipItemProps {
17
+ source: string;
18
+ value: unknown;
19
+ }
20
+
21
+ function useGetChipValue({ source, value }: ChipItemProps): JSX.Element | null {
22
+ const filters = useContext(FilterContext);
23
+ const resource = useResourceContext();
24
+
25
+ const currentSourceFilter = useMemo(() => {
26
+ return filters.find((filter) => React.isValidElement(filter) && filter.props?.source === source);
27
+ }, [filters, source]);
28
+ const currentSourceFilterProps = useMemo(
29
+ () => (React.isValidElement(currentSourceFilter) ? currentSourceFilter.props : {}),
30
+ [currentSourceFilter]
31
+ );
32
+
33
+ const ChipComponent = React.isValidElement(currentSourceFilter) ? currentSourceFilter.props?.chip : null;
34
+
35
+ if (!ChipComponent) {
36
+ throw new Error(
37
+ `No chip component found for filter source "${source}". Ensure your filter components define a 'chip' prop.`
38
+ );
39
+ }
40
+
41
+ const label = useMemo(() => {
42
+ return React.cloneElement(ChipComponent as React.ReactElement<any>, {
43
+ ...currentSourceFilterProps,
44
+ record: { [source]: value },
45
+ value,
46
+ resource
47
+ });
48
+ }, [value, ChipComponent, source, resource, currentSourceFilterProps]);
49
+
50
+ return label;
51
+ }
52
+
53
+ export { FilterSidebarContext, useGetChipValue, useIsEnabledSidebarFilter };