@applica-software-guru/react-admin 1.3.142 → 1.3.144
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/Layout/Provider.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/Form.d.ts +1 -0
- package/dist/components/ra-forms/LongForm/Form.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/NavMenu.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/Provider.d.ts +3 -1
- package/dist/components/ra-forms/LongForm/Provider.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/Tab.d.ts +3 -1
- package/dist/components/ra-forms/LongForm/Tab.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/index.d.ts +2 -1
- package/dist/components/ra-forms/LongForm/index.d.ts.map +1 -1
- package/dist/components/ra-forms/LongForm/types.d.ts +1 -0
- package/dist/components/ra-forms/LongForm/types.d.ts.map +1 -1
- package/dist/react-admin.cjs.js +56 -56
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +7984 -7951
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +56 -56
- package/dist/react-admin.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Layout/Provider.tsx +6 -3
- package/src/components/ra-forms/LongForm/Form.tsx +2 -1
- package/src/components/ra-forms/LongForm/NavMenu.tsx +25 -3
- package/src/components/ra-forms/LongForm/Provider.tsx +35 -2
- package/src/components/ra-forms/LongForm/Tab.tsx +39 -7
- package/src/components/ra-forms/LongForm/index.ts +3 -0
- package/src/components/ra-forms/LongForm/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Dialog, useMediaQuery, useTheme } from '@mui/material';
|
|
2
|
-
import { UseGetIdentityResult, useGetIdentity } from 'ra-core';
|
|
2
|
+
import { UseGetIdentityResult, useGetIdentity, useAuthProvider } from 'ra-core';
|
|
3
3
|
import { createContext, forwardRef, useCallback, useContext, useEffect, useMemo, useReducer, useState } from 'react';
|
|
4
4
|
import { useMenuConfig, useThemeConfig } from '../../hooks';
|
|
5
5
|
|
|
@@ -130,6 +130,7 @@ const DefaultState: ILayoutState = {
|
|
|
130
130
|
LayoutContext = createContext<ILayoutContext | undefined>(undefined);
|
|
131
131
|
|
|
132
132
|
function LayoutProvider(props: ILayoutProviderProps) {
|
|
133
|
+
const authProvider = useAuthProvider();
|
|
133
134
|
const identity = useGetIdentity() as UseGetIdentityResult,
|
|
134
135
|
theme = useTheme(),
|
|
135
136
|
downMd = useMediaQuery(theme.breakpoints.down('md')),
|
|
@@ -215,8 +216,10 @@ function LayoutProvider(props: ILayoutProviderProps) {
|
|
|
215
216
|
}, [theme.palette.mode]);
|
|
216
217
|
|
|
217
218
|
useEffect(() => {
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
authProvider.isImpersonating().then((isImpersonating: boolean) => {
|
|
220
|
+
setNeedToChangePassword(!isImpersonating && identity?.data?.needToChangePassword === true);
|
|
221
|
+
});
|
|
222
|
+
}, [identity?.data?.needToChangePassword, authProvider]);
|
|
220
223
|
|
|
221
224
|
return (
|
|
222
225
|
<LayoutContext.Provider value={value}>
|
|
@@ -12,11 +12,12 @@ type IFormProps = IProviderProps &
|
|
|
12
12
|
spacing?: number;
|
|
13
13
|
tabsDisposition: Disposition;
|
|
14
14
|
contentDisposition: Disposition;
|
|
15
|
+
errorCount: boolean;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
function Form(props: IFormProps) {
|
|
18
19
|
const { tabsDisposition, contentDisposition, spacing = 2 } = props,
|
|
19
|
-
providerProps = _.pick(props, ['syncWithLocation']),
|
|
20
|
+
providerProps = _.pick(props, ['syncWithLocation', 'errorCount']),
|
|
20
21
|
baseFormProps = _.omit(props, ['tabsDisposition', 'contentDisposition', 'syncWithLocation']),
|
|
21
22
|
{ top: sidebarTop, bottom: sidebarBottom } = useSidebarChildren(props),
|
|
22
23
|
contentChildren = useBaseItemChildren(props);
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import { Avatar } from '../../@extended';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
Collapse,
|
|
6
|
+
List,
|
|
7
|
+
ListItem,
|
|
8
|
+
ListItemProps,
|
|
9
|
+
ListItemIcon,
|
|
10
|
+
ListItemText,
|
|
11
|
+
ListItemButton,
|
|
12
|
+
ListItemAvatar,
|
|
13
|
+
Badge
|
|
14
|
+
} from '@mui/material';
|
|
4
15
|
import { useSetActiveItem, useSyncWithLocation } from './Provider';
|
|
5
16
|
import { IItem } from './types';
|
|
6
17
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
@@ -44,7 +55,7 @@ function NavMenu() {
|
|
|
44
55
|
function NavMenuItem(props: INavMenuItemProps) {
|
|
45
56
|
const translate = useTranslate() ?? _.identity,
|
|
46
57
|
navMenuItemProps = useNavMenuItem(props),
|
|
47
|
-
{ selected, label, icon, onClick: _onClick, badge, items, isGroup, level } = navMenuItemProps,
|
|
58
|
+
{ selected, label, icon, onClick: _onClick, badge, items, isGroup, level, errors = 0 } = navMenuItemProps,
|
|
48
59
|
{ spacing } = useThemeConfig(),
|
|
49
60
|
[open, setOpen] = useState(selected),
|
|
50
61
|
onClick = useCallback(
|
|
@@ -68,7 +79,18 @@ function NavMenuItem(props: INavMenuItemProps) {
|
|
|
68
79
|
<ListItem {...listItemProps} disablePadding>
|
|
69
80
|
<ListItemButton selected={selected && !isGroup} onClick={onClick} sx={{ pl: spacing * level }}>
|
|
70
81
|
{hasIcon && <ListItemIcon>{icon}</ListItemIcon>}
|
|
71
|
-
<ListItemText
|
|
82
|
+
<ListItemText
|
|
83
|
+
inset={!hasIcon}
|
|
84
|
+
primary={
|
|
85
|
+
errors === 0 ? (
|
|
86
|
+
translate(label)
|
|
87
|
+
) : (
|
|
88
|
+
<Badge variant="dot" color="error" sx={{ '& > .MuiBadge-badge': { transform: 'scale(1) translate(125%, 0)' } }}>
|
|
89
|
+
{translate(label)}
|
|
90
|
+
</Badge>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
/>
|
|
72
94
|
{badge && (
|
|
73
95
|
<ListItemAvatar>
|
|
74
96
|
<Avatar type="filled" size="xs" color={badge.color ?? 'default'}>
|
|
@@ -10,16 +10,19 @@ enum ActionType {
|
|
|
10
10
|
SET_SYNC_WITH_LOCATION = 'setSyncWithLocation',
|
|
11
11
|
SET_ACTIVE_ITEM = 'setActiveItem',
|
|
12
12
|
ADD_ITEM = 'addItem',
|
|
13
|
-
REMOVE_ITEM = 'removeItem'
|
|
13
|
+
REMOVE_ITEM = 'removeItem',
|
|
14
|
+
SET_ERROR_COUNT = 'setErrorCount'
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
type IProviderProps = React.PropsWithChildren<{
|
|
17
18
|
syncWithLocation?: boolean;
|
|
18
19
|
rootMatchString?: string;
|
|
20
|
+
errorCount?: boolean;
|
|
19
21
|
}>;
|
|
20
22
|
type IState = {
|
|
21
23
|
formRootPath?: string;
|
|
22
24
|
syncWithLocation: boolean;
|
|
25
|
+
errorCount: boolean;
|
|
23
26
|
items: Array<IItem>;
|
|
24
27
|
activeItem?: string;
|
|
25
28
|
};
|
|
@@ -32,6 +35,10 @@ type IAction =
|
|
|
32
35
|
type: ActionType.SET_SYNC_WITH_LOCATION;
|
|
33
36
|
payload: boolean;
|
|
34
37
|
}
|
|
38
|
+
| {
|
|
39
|
+
type: ActionType.SET_ERROR_COUNT;
|
|
40
|
+
payload: boolean;
|
|
41
|
+
}
|
|
35
42
|
| {
|
|
36
43
|
type: ActionType.SET_ACTIVE_ITEM;
|
|
37
44
|
payload?: string;
|
|
@@ -58,6 +65,8 @@ function reducer(state: IState, action: IAction): IState {
|
|
|
58
65
|
return _.extend(newState, { formRootPath: payload });
|
|
59
66
|
case ActionType.SET_SYNC_WITH_LOCATION:
|
|
60
67
|
return _.extend(newState, { syncWithLocation: payload });
|
|
68
|
+
case ActionType.SET_ERROR_COUNT:
|
|
69
|
+
return _.extend(newState, { errorCount: payload });
|
|
61
70
|
case ActionType.SET_ACTIVE_ITEM:
|
|
62
71
|
return _.includes(getItemsIds(state.items), payload) ? _.extend(newState, { activeItem: payload }) : newState;
|
|
63
72
|
case ActionType.ADD_ITEM: {
|
|
@@ -91,6 +100,7 @@ function reducer(state: IState, action: IAction): IState {
|
|
|
91
100
|
|
|
92
101
|
const DefaultState: IState = {
|
|
93
102
|
syncWithLocation: true,
|
|
103
|
+
errorCount: true,
|
|
94
104
|
items: [],
|
|
95
105
|
activeItem: undefined
|
|
96
106
|
};
|
|
@@ -99,6 +109,7 @@ const Context = createContext<IContext | undefined>(undefined);
|
|
|
99
109
|
|
|
100
110
|
function Provider(props: IProviderProps) {
|
|
101
111
|
const syncWithLocation = Boolean(props.syncWithLocation ?? true),
|
|
112
|
+
errorCount = Boolean(props.errorCount ?? true),
|
|
102
113
|
{ rootMatchString } = props,
|
|
103
114
|
{ pathname } = useLocation(),
|
|
104
115
|
[state, dispatch] = useReducer(reducer, _.cloneDeep(DefaultState)),
|
|
@@ -140,6 +151,13 @@ function Provider(props: IProviderProps) {
|
|
|
140
151
|
});
|
|
141
152
|
}, [syncWithLocation, dispatch]);
|
|
142
153
|
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
dispatch({
|
|
156
|
+
type: ActionType.SET_ERROR_COUNT,
|
|
157
|
+
payload: Boolean(errorCount)
|
|
158
|
+
});
|
|
159
|
+
}, [errorCount, dispatch]);
|
|
160
|
+
|
|
143
161
|
useEffect(() => {
|
|
144
162
|
if (syncWithLocation && formRootPath !== undefined) {
|
|
145
163
|
let locationItem = pathname.replace(formRootPath, '').replace(new RegExp(/^\/?/), '');
|
|
@@ -201,6 +219,11 @@ function useSyncWithLocation(): boolean {
|
|
|
201
219
|
return state.syncWithLocation;
|
|
202
220
|
}
|
|
203
221
|
|
|
222
|
+
function useErrorCount(): boolean {
|
|
223
|
+
const state = useFormState();
|
|
224
|
+
return state.errorCount;
|
|
225
|
+
}
|
|
226
|
+
|
|
204
227
|
function useSetActiveItem(): (activeItem: string) => void {
|
|
205
228
|
const dispatch = useFormDispatch(),
|
|
206
229
|
setActiveItem = useCallback(
|
|
@@ -246,5 +269,15 @@ function useRemoveItem(): (item: string | IItem) => void {
|
|
|
246
269
|
return removeItem;
|
|
247
270
|
}
|
|
248
271
|
|
|
249
|
-
export {
|
|
272
|
+
export {
|
|
273
|
+
Provider,
|
|
274
|
+
useItems,
|
|
275
|
+
useSyncWithLocation,
|
|
276
|
+
useFormRootPath,
|
|
277
|
+
useActiveItem,
|
|
278
|
+
useSetActiveItem,
|
|
279
|
+
useAddItem,
|
|
280
|
+
useRemoveItem,
|
|
281
|
+
useErrorCount
|
|
282
|
+
};
|
|
250
283
|
export type { IProviderProps };
|
|
@@ -1,22 +1,53 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import { Box } from '@mui/material';
|
|
3
|
-
import React, { Children, cloneElement, isValidElement, useEffect, useMemo } from 'react';
|
|
4
|
-
import { useAddItem, useRemoveItem } from './Provider';
|
|
3
|
+
import React, { Children, ReactElement, ReactNode, cloneElement, isValidElement, useEffect, useMemo } from 'react';
|
|
4
|
+
import { useAddItem, useRemoveItem, useErrorCount } from './Provider';
|
|
5
5
|
import { Optional } from 'src/types';
|
|
6
6
|
import { IItem } from './types';
|
|
7
7
|
import { useIsActive } from './hooks';
|
|
8
8
|
import { getId } from './utils';
|
|
9
|
+
import { useFormState } from 'react-hook-form';
|
|
9
10
|
|
|
10
|
-
type IBaseItemProps = React.PropsWithChildren<Optional<IItem, 'id' | 'index'
|
|
11
|
+
type IBaseItemProps = React.PropsWithChildren<Optional<IItem, 'id' | 'index'> & { sources: Array<string> }>;
|
|
11
12
|
type ITabProps = IBaseItemProps;
|
|
12
13
|
type IGroupProps = IBaseItemProps;
|
|
13
14
|
|
|
15
|
+
function walkChildren(children: ReactNode = [], callback: (el: ReactElement) => void) {
|
|
16
|
+
const _children = _.isArray(children) ? children : [children],
|
|
17
|
+
validChildren = _.filter(_children, (child) => isValidElement(child));
|
|
18
|
+
_.each(validChildren, (child) => {
|
|
19
|
+
callback(child);
|
|
20
|
+
walkChildren(child?.props?.children ?? [], callback);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
function BaseItem(props: IBaseItemProps) {
|
|
15
|
-
const {
|
|
25
|
+
const { errors } = useFormState(),
|
|
26
|
+
countErrors = useErrorCount(),
|
|
27
|
+
{ label, icon, badge, index = 0, children } = props,
|
|
16
28
|
id = getId(props),
|
|
17
29
|
addItem = useAddItem(),
|
|
18
30
|
removeItem = useRemoveItem(),
|
|
19
|
-
visible = useIsActive(id)
|
|
31
|
+
visible = useIsActive(id),
|
|
32
|
+
sources: Array<string> = [];
|
|
33
|
+
|
|
34
|
+
if (countErrors) {
|
|
35
|
+
if (props.sources !== undefined) {
|
|
36
|
+
sources.push(...props.sources);
|
|
37
|
+
} else {
|
|
38
|
+
walkChildren(children, (el) => {
|
|
39
|
+
if (el?.props?.source !== undefined) {
|
|
40
|
+
sources.push(el.props.source);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const errorsCount = _.chain(sources)
|
|
47
|
+
.uniq()
|
|
48
|
+
.map((s) => _.get(errors, s))
|
|
49
|
+
.reject((s) => s === undefined)
|
|
50
|
+
.value().length;
|
|
20
51
|
|
|
21
52
|
useEffect(() => {
|
|
22
53
|
addItem({
|
|
@@ -24,12 +55,13 @@ function BaseItem(props: IBaseItemProps) {
|
|
|
24
55
|
index: index,
|
|
25
56
|
label: label,
|
|
26
57
|
icon: icon,
|
|
27
|
-
badge: badge
|
|
58
|
+
badge: badge,
|
|
59
|
+
errors: errorsCount
|
|
28
60
|
});
|
|
29
61
|
return () => {
|
|
30
62
|
removeItem(id);
|
|
31
63
|
};
|
|
32
|
-
}, [addItem, removeItem, label, icon, id, badge, index]);
|
|
64
|
+
}, [addItem, removeItem, label, icon, id, badge, index, errorsCount]);
|
|
33
65
|
|
|
34
66
|
/* All tabs are rendered (not only the one in focus), to allow validation
|
|
35
67
|
on tabs not in focus. The tabs receive a `hidden` property, which they'll
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useActiveItem,
|
|
7
7
|
useItems,
|
|
8
8
|
useSyncWithLocation,
|
|
9
|
+
useErrorCount,
|
|
9
10
|
useSetActiveItem,
|
|
10
11
|
useAddItem,
|
|
11
12
|
useRemoveItem,
|
|
@@ -21,6 +22,7 @@ type IForm = typeof Form & {
|
|
|
21
22
|
Provider: typeof Provider;
|
|
22
23
|
useItems: typeof useItems;
|
|
23
24
|
useSyncWithLocation: typeof useSyncWithLocation;
|
|
25
|
+
useErrorCount: typeof useErrorCount;
|
|
24
26
|
useFormRootPath: typeof useFormRootPath;
|
|
25
27
|
useActiveItem: typeof useActiveItem;
|
|
26
28
|
useSetActiveItem: typeof useSetActiveItem;
|
|
@@ -44,6 +46,7 @@ DefaultForm.Content = Content;
|
|
|
44
46
|
DefaultForm.Provider = Provider;
|
|
45
47
|
DefaultForm.useItems = useItems;
|
|
46
48
|
DefaultForm.useSyncWithLocation = useSyncWithLocation;
|
|
49
|
+
DefaultForm.useErrorCount = useErrorCount;
|
|
47
50
|
DefaultForm.useFormRootPath = useFormRootPath;
|
|
48
51
|
DefaultForm.useActiveItem = useActiveItem;
|
|
49
52
|
DefaultForm.useSetActiveItem = useSetActiveItem;
|