@applica-software-guru/react-admin 1.5.280 → 1.5.282

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/package.json CHANGED
@@ -106,5 +106,5 @@
106
106
  "type": "module",
107
107
  "types": "dist/index.d.ts",
108
108
  "typings": "dist/index.d.ts",
109
- "version": "1.5.280"
109
+ "version": "1.5.282"
110
110
  }
@@ -0,0 +1,184 @@
1
+ import { FC, ReactElement, memo, useCallback, useMemo, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { TablePagination, TablePaginationBaseProps, Theme, Toolbar, useMediaQuery } from '@mui/material';
4
+ import {
5
+ ComponentPropType,
6
+ ListPaginationContextValue,
7
+ sanitizeListRestProps,
8
+ useListPaginationContext,
9
+ useTranslate
10
+ } from 'ra-core';
11
+
12
+ import { PaginationActions, PaginationActionsProps } from './PaginationActions';
13
+ import { debounce } from 'lodash';
14
+
15
+ const Pagination: FC<PaginationProps> = memo((props) => {
16
+ const { rowsPerPageOptions = DefaultRowsPerPageOptions, actions, limit = null, ...rest } = props;
17
+ const { isLoading, hasNextPage, page, perPage, total, setPage, setPerPage } = useListPaginationContext(props);
18
+ const translate = useTranslate();
19
+ const isSmall = useMediaQuery((theme: Theme) => theme.breakpoints.down('md'));
20
+ const [currentPage, setCurrentPage] = useState(0); // Stato per la UI
21
+
22
+ const totalPages = useMemo(() => {
23
+ return total != null ? Math.ceil(total / perPage) : undefined;
24
+ }, [perPage, total]);
25
+
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ const debouncedPageChange = useCallback(
28
+ debounce((page) => {
29
+ setPage(page + 1);
30
+ }, 500),
31
+ [setPage]
32
+ );
33
+
34
+ /**
35
+ * Warning: Material UI's page is 0-based
36
+ */
37
+ const handlePageChange = useCallback(
38
+ (event: React.MouseEvent<HTMLButtonElement, MouseEvent> | null, page: number) => {
39
+ if (!event) {
40
+ return;
41
+ }
42
+
43
+ event.preventDefault();
44
+ if (page < 0 || (totalPages !== undefined && page > totalPages - 1)) {
45
+ throw new Error(
46
+ translate('ra.navigation.page_out_of_boundaries', {
47
+ page: page + 1
48
+ })
49
+ );
50
+ }
51
+
52
+ const arrowSelected =
53
+ ((event.target as HTMLElement).dataset?.testid || (event.target as HTMLElement).classList?.value) ?? '';
54
+ // check if user is clicking on the arrows or on the numbers
55
+ const isArrowClick =
56
+ arrowSelected.includes('MuiPaginationItem-previousNext') ||
57
+ arrowSelected.includes('NavigateBeforeIcon') ||
58
+ arrowSelected.includes('NavigateNextIcon') ||
59
+ arrowSelected.includes('KeyboardArrowLeftIcon') ||
60
+ arrowSelected.includes('KeyboardArrowRightIcon');
61
+
62
+ setCurrentPage(page);
63
+
64
+ if (isArrowClick) {
65
+ // apply debounced API call for arrows clicks
66
+ debouncedPageChange(page);
67
+ } else {
68
+ // apply immediate API call for number clicks
69
+ setPage(page + 1);
70
+ }
71
+ },
72
+ [debouncedPageChange, setPage, translate, totalPages]
73
+ );
74
+
75
+ const handlePerPageChange = useCallback(
76
+ (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
77
+ setPerPage(Number(event.target.value));
78
+ },
79
+ [setPerPage]
80
+ );
81
+
82
+ const labelDisplayedRows = useCallback(
83
+ ({ from, to, count }: { from: number; to: number; count: number }) =>
84
+ count === -1 && hasNextPage
85
+ ? translate('ra.navigation.partial_page_range_info', {
86
+ offsetBegin: from,
87
+ offsetEnd: to,
88
+ _: `%{from}-%{to} of more than %{to}`
89
+ })
90
+ : translate('ra.navigation.page_range_info', {
91
+ offsetBegin: from,
92
+ offsetEnd: to,
93
+ total: count === -1 ? to : count,
94
+ _: `%{from}-%{to} of %{count === -1 ? to : count}`
95
+ }),
96
+ [translate, hasNextPage]
97
+ );
98
+
99
+ const labelItem = useCallback(
100
+ (type: string) => translate(`ra.navigation.${type}`, { _: `Go to ${type} page` }),
101
+ [translate]
102
+ );
103
+
104
+ if (isLoading) {
105
+ return <Toolbar variant="dense" />;
106
+ }
107
+
108
+ // Avoid rendering TablePagination if "page" value is invalid
109
+ if (total === 0 || page < 1 || (totalPages !== undefined && page > totalPages)) {
110
+ if (limit != null && process.env.NODE_ENV === 'development') {
111
+ console.warn(
112
+ 'The Pagination limit prop is deprecated. Empty state should be handled by the component displaying data (Datagrid, SimpleList).'
113
+ );
114
+ }
115
+ return null;
116
+ }
117
+
118
+ if (isSmall) {
119
+ return (
120
+ <TablePagination
121
+ count={total == null ? -1 : total}
122
+ rowsPerPage={perPage}
123
+ page={currentPage}
124
+ onPageChange={handlePageChange}
125
+ rowsPerPageOptions={emptyArray}
126
+ component="span"
127
+ labelDisplayedRows={labelDisplayedRows}
128
+ {...sanitizeListRestProps(rest)}
129
+ />
130
+ );
131
+ }
132
+
133
+ const ActionsComponent = actions
134
+ ? actions // overridden by caller
135
+ : !isLoading && total != null
136
+ ? PaginationActions // regular navigation
137
+ : undefined; // partial navigation (uses default TablePaginationActions)
138
+
139
+ return (
140
+ <TablePagination
141
+ count={total == null ? -1 : total}
142
+ rowsPerPage={perPage}
143
+ page={currentPage}
144
+ onPageChange={handlePageChange}
145
+ onRowsPerPageChange={handlePerPageChange}
146
+ // @ts-ignore
147
+ ActionsComponent={ActionsComponent}
148
+ nextIconButtonProps={{
149
+ disabled: !hasNextPage
150
+ }}
151
+ component="span"
152
+ labelRowsPerPage={translate('ra.navigation.page_rows_per_page')}
153
+ labelDisplayedRows={labelDisplayedRows}
154
+ getItemAriaLabel={labelItem}
155
+ rowsPerPageOptions={rowsPerPageOptions}
156
+ {...sanitizeListRestProps(rest)}
157
+ />
158
+ );
159
+ });
160
+
161
+ Pagination.propTypes = {
162
+ actions: ComponentPropType,
163
+ limit: PropTypes.element,
164
+ rowsPerPageOptions: PropTypes.arrayOf(
165
+ PropTypes.oneOfType([
166
+ PropTypes.number,
167
+ PropTypes.shape({
168
+ label: PropTypes.string.isRequired,
169
+ value: PropTypes.number.isRequired
170
+ })
171
+ ])
172
+ ) as PropTypes.Validator<(number | { label: string; value: number })[] | null | undefined>
173
+ };
174
+
175
+ const DefaultRowsPerPageOptions = [5, 10, 25, 50];
176
+ const emptyArray: any[] = [];
177
+
178
+ interface PaginationProps extends TablePaginationBaseProps, Partial<ListPaginationContextValue> {
179
+ rowsPerPageOptions?: Array<number | { label: string; value: number }>;
180
+ actions?: FC<PaginationActionsProps>;
181
+ limit?: ReactElement;
182
+ }
183
+
184
+ export { Pagination };
@@ -0,0 +1,84 @@
1
+ import { FC, memo } from 'react';
2
+ import { styled } from '@mui/material/styles';
3
+ import { Pagination, PaginationProps } from '@mui/material';
4
+ import PropTypes from 'prop-types';
5
+ import { useTranslate } from 'ra-core';
6
+
7
+ const PaginationActions: FC<PaginationActionsProps> = memo((props) => {
8
+ const { page, rowsPerPage, count, onPageChange, size = 'small', className, ...rest } = props;
9
+ const translate = useTranslate();
10
+
11
+ const nbPages = Math.ceil(count / rowsPerPage) || 1;
12
+
13
+ if (nbPages === 1) {
14
+ return <Root className={className} />;
15
+ }
16
+
17
+ function getItemAriaLabel(type: 'page' | 'first' | 'last' | 'next' | 'previous', page: number, selected: boolean) {
18
+ if (type === 'page') {
19
+ return selected
20
+ ? translate('ra.navigation.current_page', {
21
+ page,
22
+ _: `page ${page}`
23
+ })
24
+ : translate('ra.navigation.page', {
25
+ page,
26
+ _: `Go to page ${page}`
27
+ });
28
+ }
29
+ return translate(`ra.navigation.${type}`, { _: `Go to ${type} page` });
30
+ }
31
+
32
+ return (
33
+ <Root className={className}>
34
+ <Pagination
35
+ size={size}
36
+ count={nbPages}
37
+ // <TablePagination>, the parent, uses 0-based pagination
38
+ // while <Pagination> uses 1-based pagination
39
+ page={page + 1}
40
+ onChange={(e: any, page) => onPageChange(e, page - 1)}
41
+ {...sanitizeRestProps(rest)}
42
+ getItemAriaLabel={getItemAriaLabel}
43
+ />
44
+ </Root>
45
+ );
46
+ });
47
+
48
+ interface PaginationActionsProps extends PaginationProps {
49
+ page: number;
50
+ rowsPerPage: number;
51
+ count: number;
52
+ onPageChange: (event: MouseEvent, page: number) => void;
53
+ }
54
+ /**
55
+ * PaginationActions propTypes are copied over from Material UI’s
56
+ * TablePaginationActions propTypes. See
57
+ * https://github.com/mui/material-ui/blob/869692ecf3812bc4577ed4dde81a9911c5949695/packages/material-ui/src/TablePaginationActions/TablePaginationActions.js#L53-L85
58
+ * for reference.
59
+ */
60
+ PaginationActions.propTypes = {
61
+ count: PropTypes.number.isRequired,
62
+ onPageChange: PropTypes.func.isRequired,
63
+ page: PropTypes.number.isRequired,
64
+ rowsPerPage: PropTypes.number.isRequired,
65
+ color: PropTypes.oneOf(['primary', 'secondary', 'standard']),
66
+ size: PropTypes.oneOf(['small', 'medium', 'large'])
67
+ };
68
+
69
+ const PREFIX = 'RaPaginationActions';
70
+
71
+ const Root = styled('div', {
72
+ name: PREFIX,
73
+ overridesResolver: (styles) => styles.root
74
+ })(() => ({
75
+ flexShrink: 0,
76
+ ml: 4
77
+ }));
78
+
79
+ function sanitizeRestProps({ nextIconButtonProps, backIconButtonProps, slotProps, ...rest }: any) {
80
+ return rest;
81
+ }
82
+
83
+ export { PaginationActions };
84
+ export type { PaginationActionsProps };
@@ -0,0 +1,2 @@
1
+ export * from './Pagination';
2
+ export * from './PaginationActions';
@@ -66,11 +66,11 @@ type EditInDialogButtonProps = PropsWithChildren &
66
66
  * console.log(values); // { title: 'Hello World' }
67
67
  * closeDialog();
68
68
  * }
69
- * <CreateInDialogButton onSubmit={handleSubmit}>
69
+ * <EditInDialogButton onSubmit={handleSubmit}>
70
70
  * <SimpleForm>
71
71
  * <TextInput source="title" />
72
72
  * </SimpleForm>
73
- * </CreateInDialogButton>
73
+ * </EditInDialogButton>
74
74
  *
75
75
  */
76
76
  onSubmit?: SubmitFunction;
@@ -4,6 +4,7 @@ import { styled } from '@mui/system';
4
4
  import { ListBase, RaRecord } from 'ra-core';
5
5
  import { ReactElement } from 'react';
6
6
  import { ListProps } from 'react-admin';
7
+ import { Pagination } from '@/components/Pagination/Pagination';
7
8
 
8
9
  /**
9
10
  * List page component
@@ -191,7 +192,7 @@ const StyledList = styled(RaList, { slot: 'root' })(({ theme }) => ({
191
192
  function List(props: ListProps): ReactElement {
192
193
  return (
193
194
  <MainCard content={false}>
194
- <StyledList {...props} />
195
+ <StyledList {...props} pagination={<Pagination />} />
195
196
  </MainCard>
196
197
  );
197
198
  }
@@ -9,7 +9,7 @@ import { ApplicaDataProvider, createAttachmentsParser } from '@applica-software-
9
9
  import { ApplicaAuthProvider, LocalStorage } from '@applica-software-guru/iam-client';
10
10
  import { CustomRoutes } from 'ra-core';
11
11
  import { Route } from 'react-router-dom';
12
- import { createJsonI18nProvider } from '@/i18n';
12
+ import { createI18nProvider, createJsonI18nProvider } from '@/i18n';
13
13
 
14
14
  const authProvider = new ApplicaAuthProvider(API_URL, new LocalStorage());
15
15
  const dataProvider = new ApplicaDataProvider({
@@ -20,7 +20,7 @@ const dataProvider = new ApplicaDataProvider({
20
20
  attachmentsParser: createAttachmentsParser(),
21
21
  HttpErrorClass: HttpError
22
22
  });
23
- const i18nProvider = createJsonI18nProvider({ path: `//${document.location.host}/i18n`, defaultLocale: 'en' });
23
+ const i18nProvider = createI18nProvider({ apiUrl: API_URL, allowMissing: true, defaultLocale: 'it' });
24
24
  function App() {
25
25
  return (
26
26
  <ApplicaAdmin