@applica-software-guru/react-admin 1.3.126 → 1.3.128

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 (49) hide show
  1. package/dist/ApplicaAdmin.d.ts +14 -29
  2. package/dist/ApplicaAdmin.d.ts.map +1 -1
  3. package/dist/components/Layout/Content.d.ts +4 -1
  4. package/dist/components/Layout/Content.d.ts.map +1 -1
  5. package/dist/components/Layout/Error.d.ts +27 -0
  6. package/dist/components/Layout/Error.d.ts.map +1 -0
  7. package/dist/components/Layout/Layout.d.ts +3 -0
  8. package/dist/components/Layout/Layout.d.ts.map +1 -1
  9. package/dist/components/Layout/index.d.ts +1 -0
  10. package/dist/components/Layout/index.d.ts.map +1 -1
  11. package/dist/components/ra-forms/LongForm/utils.d.ts.map +1 -1
  12. package/dist/components/ra-lists/List.d.ts +19 -18
  13. package/dist/components/ra-lists/List.d.ts.map +1 -1
  14. package/dist/components/ra-lists/ListView.d.ts +257 -0
  15. package/dist/components/ra-lists/ListView.d.ts.map +1 -0
  16. package/dist/dev/CatchResult.d.ts +17 -0
  17. package/dist/dev/CatchResult.d.ts.map +1 -0
  18. package/dist/dev/ErrorEventHandler.d.ts +13 -0
  19. package/dist/dev/ErrorEventHandler.d.ts.map +1 -0
  20. package/dist/dev/index.d.ts +4 -2
  21. package/dist/dev/index.d.ts.map +1 -1
  22. package/dist/dev/useErrorEventCatcher.d.ts +12 -0
  23. package/dist/dev/useErrorEventCatcher.d.ts.map +1 -0
  24. package/dist/react-admin.cjs.js +63 -63
  25. package/dist/react-admin.cjs.js.map +1 -1
  26. package/dist/react-admin.es.js +9563 -8992
  27. package/dist/react-admin.es.js.map +1 -1
  28. package/dist/react-admin.umd.js +66 -66
  29. package/dist/react-admin.umd.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/ApplicaAdmin.tsx +19 -33
  32. package/src/components/Layout/Content.tsx +37 -16
  33. package/src/components/Layout/Error.tsx +81 -0
  34. package/src/components/Layout/Layout.tsx +3 -2
  35. package/src/components/Layout/index.ts +1 -0
  36. package/src/components/ra-forms/LongForm/hooks.tsx +1 -1
  37. package/src/components/ra-forms/LongForm/utils.ts +3 -1
  38. package/src/components/ra-lists/List.tsx +117 -2
  39. package/src/components/ra-lists/ListView.tsx +369 -0
  40. package/src/components/ra-pages/GenericErrorPage.tsx +1 -1
  41. package/src/dev/CatchResult.ts +32 -0
  42. package/src/dev/ErrorEventHandler.ts +51 -0
  43. package/src/dev/index.ts +4 -2
  44. package/src/dev/useErrorEventCatcher.ts +50 -0
  45. package/src/playground/components/pages/CustomPage.jsx +6 -0
  46. package/dist/dev/useCliErrorCatcher.d.ts +0 -59
  47. package/dist/dev/useCliErrorCatcher.d.ts.map +0 -1
  48. package/src/dev/useCliErrorCatcher.ts +0 -142
  49. /package/src/assets/{genericError.png → generic-error.png} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applica-software-guru/react-admin",
3
- "version": "1.3.126",
3
+ "version": "1.3.128",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  import { AdminProps, AuthProvider, DataProvider } from 'react-admin';
2
2
  import { AppConfigProvider, MenuConfigProvider, MenuPropTypes, ThemeConfig, ThemeConfigProvider } from './contexts';
3
- import { CatchResult, useCliErrorCatcher } from './dev';
3
+ import { CatchResult, useErrorEventCatcher } from './dev';
4
4
  import { GenericErrorPage, Layout, LoginPage, MainIcon, Notification, SmallIcon } from './components';
5
5
  import React, { useMemo } from 'react';
6
6
  import { useI18nProvider } from './i18n';
@@ -9,7 +9,7 @@ import { MenuProps } from './types';
9
9
  import PropTypes from 'prop-types';
10
10
  import { QueryClient } from 'react-query';
11
11
  import { ThemeCustomization } from './themes';
12
-
12
+ import { IErrorEventHandler } from './dev/ErrorEventHandler';
13
13
  const queryClient = new QueryClient({
14
14
  defaultOptions: {
15
15
  queries: {
@@ -80,6 +80,16 @@ export type ApplicaAdminProps = AdminProps & {
80
80
  * Il provider di dati da utilizzare nell'applicazione.
81
81
  */
82
82
  authProvider: AuthProvider;
83
+ /**
84
+ * Indica l'eventuale handler da utilizzare per la gestione degli errori.
85
+ * Di default è eseguita una chiamata PUT /api/ui/error con il messaggio di errore "error".
86
+ * Puoi implementare un tuo handler per gestire gli errori in modo diverso.
87
+ */
88
+ errorHandler?: IErrorEventHandler | undefined;
89
+ /**
90
+ * Indica il componente da visualizzare in caso di errore.
91
+ */
92
+ error: React.Component;
83
93
  /**
84
94
  * Indica il nome della risorsa REST da utilizzare per la gestione delle notifiche.
85
95
  * @default "entities/notification"
@@ -123,34 +133,7 @@ export type ApplicaAdminProps = AdminProps & {
123
133
  };
124
134
 
125
135
  /**
126
- * Definisce un'applicazione super figa basata su React Admi, Mantis Theme ed il nostro stile.
127
- * Ogni applicazione che crei dovrebbe partire con un 'C'era una volta un ApplicaAdmin'.
128
- *
129
- * @example
130
- * // Esempio di utilizzo
131
- * import { ApplicaAdmin } from '@applica-software-guru/react-admin';
132
- * import { createAuthProvider } from '@applica-software-guru/iam-client';
133
- * import { createDataProvider } from '@applica-software-guru/crud-client';
134
- * import * as entities from './entities';
135
- *
136
- * const apiUrl = "https://bimbobruno.applica.guru/api";
137
- * const authProvider = createAuthProvider({ apiUrl });
138
- * const getToken = async () => await authProvider.getToken();
139
- * const getHeaders = async () => await authProvider.getHeaders();
140
- *
141
- * const dataProvider = createDataProvider({ apiUrl, getToken, getHeaders });
142
- *
143
- * const App = () => (
144
- * <ApplicaAdmin
145
- * apiUrl="https://api.applica.software-guru.it"
146
- * dataProvider={dataProvider}
147
- * authProvider={authProvider}
148
- * menu={menu}
149
- * name="Applica Admin"
150
- * version="1.0.0">
151
- * <Resource name="entities/user" {...entities.user} />
152
- * </ApplicaAdmin>
153
- *
136
+ * Definisce un'applicazione super figa basata su React Admin, Mantis Theme ed il nostro stile.
154
137
  * @param {ApplicaAdminProps}
155
138
  * @returns {React.ReactElement}
156
139
  */
@@ -169,15 +152,17 @@ const ApplicaAdmin = ({
169
152
  version,
170
153
  dataProvider,
171
154
  authProvider,
155
+ errorHandler,
156
+ error,
172
157
  notification,
173
158
  enableNotification,
174
159
  enableRegistration,
175
160
  enablePasswordRecover,
176
161
  ...props
177
162
  }: ApplicaAdminProps) => {
178
- useCliErrorCatcher({
163
+ useErrorEventCatcher({
179
164
  apiUrl,
180
- endpoint: '/cli/error',
165
+ errorHandler,
181
166
  catcherFn: (error: any): CatchResult => {
182
167
  const errorMessage = error?.toString();
183
168
  const knownErrors = [
@@ -224,10 +209,11 @@ const ApplicaAdmin = ({
224
209
  logoIcon={_logoIcon}
225
210
  notification={notification}
226
211
  enableNotification={enableNotification}
212
+ error={error}
227
213
  />
228
214
  );
229
215
  },
230
- [logoMain, logoIcon, name, version, notification, enableNotification]
216
+ [logoMain, logoIcon, name, version, error, notification, enableNotification]
231
217
  );
232
218
  const i18nProvider = useI18nProvider({
233
219
  apiUrl: apiUrl,
@@ -1,29 +1,50 @@
1
1
  import { Box, Container, Toolbar } from '@mui/material';
2
2
  import { useLayoutDrawerState, useLayoutMediaState } from './Provider';
3
3
  import { useThemeConfig } from '../../hooks';
4
+ import { Loading } from 'ra-ui-materialui';
5
+ import { ErrorInfo, Suspense, useState } from 'react';
6
+ import { ErrorBoundary } from 'react-error-boundary';
7
+ import Error, { ErrorProps } from './Error';
4
8
 
5
- type ILayoutContentProps = React.PropsWithChildren;
9
+ type ILayoutContentProps = React.PropsWithChildren & {
10
+ error?: React.ComponentType<ErrorProps>;
11
+ };
6
12
 
7
13
  function LayoutContent(props: ILayoutContentProps) {
8
14
  const { width } = useLayoutDrawerState(),
9
15
  { horizontal } = useLayoutMediaState(),
10
16
  { container } = useThemeConfig();
17
+ // @ts-ignore
18
+ const [errorInfo, setErrorInfo] = useState<ErrorInfo>(null);
19
+ const handleError = (error: Error, info: ErrorInfo) => {
20
+ setErrorInfo(info);
21
+ };
22
+
11
23
  return (
12
- <Box component="main" sx={{ width: `calc(100% - ${width}px)`, flexGrow: 1, p: { xs: 0, sm: 2, lg: 1 }, pt: { lg: 2 } }}>
13
- <Toolbar sx={{ mt: horizontal ? 8 : 'inherit' }} /> {/*Hugh...*/}
14
- <Container
15
- maxWidth={container ? 'xl' : false}
16
- sx={{
17
- ...(container && { px: { xs: 0, sm: 2 } }),
18
- position: 'relative',
19
- minHeight: 'calc(100vh - 110px)',
20
- display: 'flex',
21
- flexDirection: 'column'
22
- }}
23
- >
24
- {props.children}
25
- </Container>
26
- </Box>
24
+ <ErrorBoundary
25
+ onError={handleError}
26
+ fallbackRender={({ error, resetErrorBoundary }) => (
27
+ <Error error={error} errorComponent={props.error} errorInfo={errorInfo} resetErrorBoundary={resetErrorBoundary} />
28
+ )}
29
+ >
30
+ <Suspense fallback={<Loading />}>
31
+ <Box component="main" sx={{ width: `calc(100% - ${width}px)`, flexGrow: 1, p: { xs: 0, sm: 2, lg: 1 }, pt: { lg: 2 } }}>
32
+ <Toolbar sx={{ mt: horizontal ? 8 : 'inherit' }} /> {/*Hugh...*/}
33
+ <Container
34
+ maxWidth={container ? 'xl' : false}
35
+ sx={{
36
+ ...(container && { px: { xs: 0, sm: 2 } }),
37
+ position: 'relative',
38
+ minHeight: 'calc(100vh - 110px)',
39
+ display: 'flex',
40
+ flexDirection: 'column'
41
+ }}
42
+ >
43
+ {props.children}
44
+ </Container>
45
+ </Box>
46
+ </Suspense>
47
+ </ErrorBoundary>
27
48
  );
28
49
  }
29
50
 
@@ -0,0 +1,81 @@
1
+ import { Container, Stack, Typography, Button, useTheme } from '@mui/material';
2
+ import PropTypes from 'prop-types';
3
+ // @ts-ignore
4
+ import imgSrc from '../../assets/generic-error.png';
5
+ import { Title, TitlePropType, useResetErrorBoundaryOnLocationChange } from 'ra-ui-materialui';
6
+ import { ComponentType, ErrorInfo, HtmlHTMLAttributes, useCallback } from 'react';
7
+ import { TitleComponent, useTranslate } from 'ra-core';
8
+ import { FallbackProps } from 'react-error-boundary';
9
+ import History from '@mui/icons-material/History';
10
+
11
+ export type ErrorBoundaryProps = InternalErrorProps & {
12
+ errorComponent?: ComponentType<ErrorProps>;
13
+ title?: TitleComponent;
14
+ resetErrorBoundary?: null | (() => void);
15
+ };
16
+
17
+ function goBack() {
18
+ window.history.go(-1);
19
+ }
20
+ function Error(props: ErrorBoundaryProps) {
21
+ const { error, errorComponent: ErrorComponent, errorInfo, resetErrorBoundary, title } = props;
22
+ const translate = useTranslate();
23
+ const theme = useTheme();
24
+ const isProduction = process.env.NODE_ENV === 'production';
25
+
26
+ useResetErrorBoundaryOnLocationChange(resetErrorBoundary);
27
+
28
+ const handleBack = useCallback(() => {
29
+ goBack();
30
+ }, []);
31
+
32
+ if (ErrorComponent) {
33
+ return <ErrorComponent error={error} errorInfo={errorInfo} title={title} />;
34
+ }
35
+
36
+ return (
37
+ <Container>
38
+ <Stack alignItems="center" px={2} py={{ xs: 4, sm: 8 }} spacing={2} textAlign="center">
39
+ {title && <Title title={title} />}
40
+ <img src={imgSrc} style={{ width: '100%', maxWidth: 450, marginTop: theme.spacing(4) }} />
41
+ <Typography variant="h2">{translate(isProduction ? 'ra.page.error' : error.message, { _: error.message })}</Typography>
42
+ {!isProduction && <Typography variant="caption">{translate('ra.message.error_more_info')}</Typography>}
43
+ {errorInfo && (
44
+ <Typography
45
+ variant="body1"
46
+ component="pre"
47
+ textAlign={isProduction ? 'center' : 'start'}
48
+ fontFamily={isProduction ? 'inherit' : 'monospace'}
49
+ sx={{
50
+ whiteSpace: 'pre-wrap',
51
+ wordBreak: 'break-word'
52
+ }}
53
+ >
54
+ {isProduction ? translate('ra.message.error') : errorInfo?.componentStack?.substring(0, 250) + '...'}
55
+ </Typography>
56
+ )}
57
+
58
+ <Button variant="contained" startIcon={<History />} onClick={handleBack}>
59
+ {translate('ra.action.back')}
60
+ </Button>
61
+ </Stack>
62
+ </Container>
63
+ );
64
+ }
65
+ Error.propTypes = {
66
+ className: PropTypes.string,
67
+ error: PropTypes.object.isRequired,
68
+ errorInfo: PropTypes.object,
69
+ title: TitlePropType
70
+ };
71
+
72
+ interface InternalErrorProps extends Omit<HtmlHTMLAttributes<HTMLDivElement>, 'title'>, FallbackProps, ErrorProps {
73
+ className?: string;
74
+ }
75
+
76
+ export interface ErrorProps extends Pick<FallbackProps, 'error'> {
77
+ errorInfo?: ErrorInfo;
78
+ title?: TitleComponent;
79
+ }
80
+
81
+ export default Error;
@@ -4,7 +4,7 @@ import { Breadcrumbs } from '../@extended';
4
4
  import { Footer } from './Footer';
5
5
  import { LayoutContent } from './Content';
6
6
  import { LayoutWrapper } from './Wrapper';
7
- import { LoadingIndicator } from 'ra-ui-materialui';
7
+ import { ErrorProps, LoadingIndicator } from 'ra-ui-materialui';
8
8
  import { NavMenu } from './NavMenu';
9
9
  import { Navigation } from './Navigation';
10
10
  import { Outlet } from 'react-router-dom';
@@ -15,6 +15,7 @@ type ILayoutProps = React.PropsWithChildren<{
15
15
  name: string;
16
16
  version: string;
17
17
  copy?: string;
18
+ error?: React.ComponentType<ErrorProps>;
18
19
  }>;
19
20
 
20
21
  const Layout = withLayoutProvider(function Layout(props: ILayoutProps) {
@@ -34,7 +35,7 @@ const Layout = withLayoutProvider(function Layout(props: ILayoutProps) {
34
35
  <NavMenu>
35
36
  <Navigation />
36
37
  </NavMenu>
37
- <LayoutContent>
38
+ <LayoutContent error={props.error}>
38
39
  {!isLoading && (
39
40
  //@ts-ignore
40
41
  <Breadcrumbs
@@ -8,3 +8,4 @@ export * from './Navigation';
8
8
  export * from './NavMenu';
9
9
  export * from './Provider';
10
10
  export * from './Wrapper';
11
+ export * from './Error'
@@ -6,7 +6,7 @@ import { useNavigate } from 'react-router';
6
6
 
7
7
  function useIsActive(id: string): boolean {
8
8
  const activeItem = useActiveItem();
9
- return isChild(id, activeItem ?? '');
9
+ return id === activeItem || isChild(id, activeItem ?? '');
10
10
  }
11
11
 
12
12
  function useChildren(id?: string): Array<IItem> {
@@ -7,7 +7,9 @@ function getId(data: Optional<Pick<IItem, 'id' | 'label'>, 'id'>): string {
7
7
  }
8
8
 
9
9
  function isChild(parentId: string, childId: string): boolean {
10
- return childId.match(new RegExp(`^${parentId}`)) !== null;
10
+ const regExpString = `^${parentId}.`.replaceAll('.', '\\.'),
11
+ regExp = new RegExp(regExpString, 'g');
12
+ return childId.match(regExp) !== null;
11
13
  }
12
14
 
13
15
  function getItemsIds(items: Array<IItem>): Array<string> {
@@ -1,7 +1,122 @@
1
- import { ListProps, List as RaList } from 'react-admin';
2
-
1
+ import { TitlePropType, ListProps } from 'react-admin';
3
2
  import MainCard from '../MainCard';
4
3
  import { styled } from '@mui/system';
4
+ import { ReactElement } from 'react';
5
+ import PropTypes from 'prop-types';
6
+ import { ListBase, RaRecord } from 'ra-core';
7
+ import { ListView } from './ListView';
8
+
9
+ /**
10
+ * List page component
11
+ *
12
+ * The <List> component renders the list layout (title, buttons, filters, pagination),
13
+ * and fetches the list of records from the REST API.
14
+ *
15
+ * It then delegates the rendering of the list of records to its child component.
16
+ * Usually, it's a <Datagrid>, responsible for displaying a table with one row for each post.
17
+ *
18
+ * The <List> component accepts the following props:
19
+ *
20
+ * - actions
21
+ * - aside: Side Component
22
+ * - children: List Layout
23
+ * - component
24
+ * - disableAuthentication
25
+ * - disableSyncWithLocation
26
+ * - empty: Empty Page Component
27
+ * - emptyWhileLoading
28
+ * - exporter
29
+ * - filters: Filter Inputs
30
+ * - filter: Permanent Filter
31
+ * - filterDefaultValues
32
+ * - pagination: Pagination Component
33
+ * - perPage: Pagination Size
34
+ * - queryOptions
35
+ * - sort: Default Sort Field & Order
36
+ * - title
37
+ * - sx: CSS API
38
+ *
39
+ * @example
40
+ * const postFilters = [
41
+ * <TextInput label="Search" source="q" alwaysOn />,
42
+ * <TextInput label="Title" source="title" />
43
+ * ];
44
+ * export const PostList = () => (
45
+ * <List
46
+ * title="List of posts"
47
+ * sort={{ field: 'published_at' }}
48
+ * filter={{ is_published: true }}
49
+ * filters={postFilters}
50
+ * >
51
+ * <Datagrid>
52
+ * <TextField source="id" />
53
+ * <TextField source="title" />
54
+ * <EditButton />
55
+ * </Datagrid>
56
+ * </List>
57
+ * );
58
+ */
59
+ const RaList = <RecordType extends RaRecord = any>({
60
+ debounce,
61
+ disableAuthentication,
62
+ disableSyncWithLocation,
63
+ exporter,
64
+ filter = defaultFilter,
65
+ filterDefaultValues,
66
+ perPage = 10,
67
+ queryOptions,
68
+ resource,
69
+ sort,
70
+ storeKey,
71
+ ...rest
72
+ }: ListProps<RecordType>): ReactElement => (
73
+ <ListBase<RecordType>
74
+ debounce={debounce}
75
+ disableAuthentication={disableAuthentication}
76
+ disableSyncWithLocation={disableSyncWithLocation}
77
+ exporter={exporter}
78
+ filter={filter}
79
+ filterDefaultValues={filterDefaultValues}
80
+ perPage={perPage}
81
+ queryOptions={queryOptions}
82
+ resource={resource}
83
+ sort={sort}
84
+ storeKey={storeKey}
85
+ >
86
+ <ListView<RecordType> {...rest} />
87
+ </ListBase>
88
+ );
89
+
90
+ // export interface ListProps<RecordType extends RaRecord = any> extends ListControllerProps<RecordType>, ListViewProps {}
91
+
92
+ RaList.propTypes = {
93
+ // the props you can change
94
+ // @ts-ignore-line
95
+ actions: PropTypes.oneOfType([PropTypes.bool, PropTypes.element]),
96
+ aside: PropTypes.element,
97
+ children: PropTypes.node.isRequired,
98
+ className: PropTypes.string,
99
+ emptyWhileLoading: PropTypes.bool,
100
+ filter: PropTypes.object,
101
+ filterDefaultValues: PropTypes.object,
102
+ filters: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
103
+ // @ts-ignore-line
104
+ pagination: PropTypes.oneOfType([PropTypes.element, PropTypes.bool]),
105
+ perPage: PropTypes.number,
106
+ //@ts-ignore-line
107
+ sort: PropTypes.shape({
108
+ field: PropTypes.string,
109
+ order: PropTypes.oneOf(['ASC', 'DESC'] as const)
110
+ }),
111
+ sx: PropTypes.any,
112
+ title: TitlePropType,
113
+ // the props managed by react-admin
114
+ disableSyncWithLocation: PropTypes.bool,
115
+ hasCreate: PropTypes.bool,
116
+ resource: PropTypes.string
117
+ };
118
+
119
+ const defaultFilter = {};
5
120
 
6
121
  const ApplicaStyledList = styled(RaList, {
7
122
  name: 'RaApplicaList',