@dashadmin/dash-auto-admin 1.3.24 → 1.3.26
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/DashAutoAdminComponentRegistry.js +64 -1
- package/dist/DashAutoAdminForm.js +137 -1
- package/dist/DashAutoAdminResizableDrawer.js +63 -1
- package/dist/DashAutoAdminSaveButton.js +57 -1
- package/dist/DashAutoCreate.js +102 -1
- package/dist/DashAutoDrawer.js +256 -1
- package/dist/DashAutoEdit.js +99 -1
- package/dist/DashAutoFiltersGenerator.js +193 -1
- package/dist/DashAutoFormGroups.js +118 -1
- package/dist/DashAutoFormLayout.js +68 -1
- package/dist/DashAutoFormMuiTabs.js +291 -1
- package/dist/DashAutoFormTabs.js +132 -1
- package/dist/DashAutoGroup.js +27 -1
- package/dist/DashAutoLayout.js +31 -1
- package/dist/DashAutoList.js +218 -1
- package/dist/DashAutoPostFilterForm.js +47 -1
- package/dist/DashAutoReferenceTab.js +10 -1
- package/dist/DashAutoResource.js +77 -1
- package/dist/DashAutoResourceLayout.js +16 -1
- package/dist/DashAutoShow.js +195 -1
- package/dist/DashAutoTabbedForm.js +244 -1
- package/dist/DashAutoTabbedForm.review.js +221 -1
- package/dist/DashAutoTabs.js +11 -1
- package/dist/DashFilterWrapper.js +62 -1
- package/dist/DashRedirect.js +56 -1
- package/dist/TabbedLayout.js +38 -1
- package/dist/common/DashAutoTitle.js +33 -1
- package/dist/common/components/FabButton.js +59 -1
- package/dist/common/components/TranslatedLabel.js +60 -1
- package/dist/common/settings/Settings.js +196 -1
- package/dist/common/settings/SettingsBarIcons.js +61 -1
- package/dist/context/DashAutoAdminFormContext.js +15 -1
- package/dist/hooks/useAutoAdminLoadingStateMediator.js +36 -1
- package/dist/hooks/useAutoAdminTranslate.js +61 -1
- package/dist/hooks/useHash.js +24 -1
- package/dist/hooks/useLogger.js +20 -1
- package/dist/hooks/useRoutePathPattern.js +20 -1
- package/dist/hooks/useVirtualHash.js +28 -1
- package/dist/index.js +77 -3406
- package/dist/list/DashAutoListActions.js +57 -1
- package/dist/list/DashAutoListDatagridWrapper.js +68 -1
- package/dist/list/DashAutoListDefaultActionsWrapper.js +14 -1
- package/dist/list/DashAutoListFilterFormWithButton.js +80 -1
- package/dist/list/DashAutoListTopToolbar.js +180 -1
- package/dist/mui/AttributeToField.js +576 -1
- package/dist/mui/AttributeToInput.js +630 -1
- package/dist/mui/AutoAdminResizableDrawer.js +63 -1
- package/dist/mui/AutoDataGrid.js +120 -1
- package/dist/mui/AutoReferenceFormTab.js +53 -1
- package/dist/mui/AutoReferenceTab.js +46 -1
- package/dist/mui/AutoTabs.js +19 -1
- package/dist/mui/components/ExtendedPagination.js +34 -1
- package/dist/mui/components/ListStringField.js +25 -1
- package/dist/providers/DashAuthProvider.js +215 -1
- package/dist/providers/DashDataProvider.js +310 -1
- package/dist/providers/index.js +10 -1
- package/dist/react-admin.d.js +0 -0
- package/dist/toolbar/buttons/BottomToolbarButtons.js +65 -1
- package/dist/toolbar/buttons/DashResourceButton.js +181 -1
- package/dist/toolbar/buttons/ListButtons.js +80 -1
- package/dist/toolbar/buttons/ToolbarButtons.js +135 -1
- package/dist/types/ActionCallback.d.js +0 -0
- package/dist/utils/SimpleLogger.js +52 -1
- package/dist/utils/enumToChoices.js +5 -1
- package/dist/utils/evalActionPermission.js +11 -1
- package/dist/utils/groupByTabs.js +21 -1
- package/dist/utils/hashedGroupByTabs.js +12 -1
- package/dist/utils/invertMap.js +12 -1
- package/dist/utils/isClassComponent.js +7 -1
- package/dist/utils/isComponent.js +29 -1
- package/dist/utils/isEnum.js +5 -1
- package/dist/utils/isFC.js +8 -1
- package/dist/utils/replaceParams.js +9 -1
- package/dist/utils/validate.js +19 -1
- package/dist/wrappers/UserAction.js +60 -1
- package/dist/wrappers/index.js +4 -1
- package/package.json +135 -120
- package/src/DashAutoAdminComponentRegistry.tsx +75 -0
- package/src/DashAutoAdminForm.tsx +155 -0
- package/src/DashAutoAdminResizableDrawer.tsx +69 -0
- package/src/DashAutoAdminSaveButton.tsx +116 -0
- package/src/DashAutoCreate.tsx +118 -0
- package/src/DashAutoDrawer.tsx +288 -0
- package/src/DashAutoEdit.tsx +109 -0
- package/src/DashAutoFiltersGenerator.tsx +208 -0
- package/src/DashAutoFormGroups.tsx +146 -0
- package/src/DashAutoFormLayout.tsx.review +113 -0
- package/src/DashAutoFormMuiTabs.tsx +368 -0
- package/src/DashAutoFormTabs.tsx +177 -0
- package/src/DashAutoGroup.tsx +42 -0
- package/src/DashAutoLayout.tsx +57 -0
- package/src/DashAutoList.tsx +237 -0
- package/src/DashAutoPostFilterForm.tsx +35 -0
- package/src/DashAutoReferenceTab.tsx +9 -0
- package/src/DashAutoResource.tsx +85 -0
- package/src/DashAutoResourceLayout.tsx +50 -0
- package/src/DashAutoShow.tsx +200 -0
- package/src/DashAutoTabbedForm.review.tsx +262 -0
- package/src/DashAutoTabbedForm.tsx +324 -0
- package/src/DashAutoTabs.tsx +19 -0
- package/src/DashFilterWrapper.tsx +43 -0
- package/src/DashRedirect.tsx +46 -0
- package/src/TabbedLayout.tsx +29 -0
- package/src/common/DashAutoTitle.tsx +24 -0
- package/src/common/components/FabButton.tsx +40 -0
- package/src/common/components/TranslatedLabel.tsx +81 -0
- package/src/common/settings/Settings.tsx +236 -0
- package/src/common/settings/SettingsBarIcons.tsx +72 -0
- package/src/context/DashAutoAdminFormContext.tsx +31 -0
- package/src/hooks/useAutoAdminLoadingStateMediator.tsx +42 -0
- package/src/hooks/useAutoAdminTranslate.tsx +106 -0
- package/src/hooks/useHash.tsx +27 -0
- package/src/hooks/useLogger.tsx +23 -0
- package/src/hooks/useRoutePathPattern.tsx +24 -0
- package/src/hooks/useVirtualHash.tsx +34 -0
- package/src/index.ts +85 -0
- package/src/interfaces/ICustomResourceSubComponent.ts +11 -0
- package/src/interfaces/IDashApplicationLayoutMainAction.ts +37 -0
- package/src/interfaces/IDashApplicationLayoutMenuItem.ts +24 -0
- package/src/interfaces/IDashAutoAdminAttribute.ts +104 -0
- package/src/interfaces/IDashAutoAdminCustomFieldComponent.ts +18 -0
- package/src/interfaces/IDashAutoAdminFormOptions.ts +22 -0
- package/src/interfaces/IDashAutoAdminListActions.ts +13 -0
- package/src/interfaces/IDashAutoAdminReference.ts +16 -0
- package/src/interfaces/IDashAutoAdminResourceConfig.ts +360 -0
- package/src/interfaces/IDashAutoForm.ts +24 -0
- package/src/interfaces/IDashAutoGridButton.ts +35 -0
- package/src/interfaces/IDashAutoShow.ts +12 -0
- package/src/interfaces/IGroupExtraData.ts +8 -0
- package/src/interfaces/IRecord.ts +7 -0
- package/src/interfaces/IReferenceFilter.ts +55 -0
- package/src/interfaces/IResourceComponent.ts +8 -0
- package/src/interfaces/IToolbarButton.ts +11 -0
- package/src/list/DashAutoListActions.tsx +88 -0
- package/src/list/DashAutoListDatagridWrapper.tsx +74 -0
- package/src/list/DashAutoListDefaultActionsWrapper.tsx +31 -0
- package/src/list/DashAutoListFilterFormWithButton.tsx +93 -0
- package/src/list/DashAutoListTopToolbar.tsx +240 -0
- package/src/mui/AttributeToField.tsx +657 -0
- package/src/mui/AttributeToInput.tsx +728 -0
- package/src/mui/AutoAdminResizableDrawer.tsx +69 -0
- package/src/mui/AutoDataGrid.tsx +127 -0
- package/src/mui/AutoReferenceFormTab.tsx +51 -0
- package/src/mui/AutoReferenceTab.tsx +45 -0
- package/src/mui/AutoTabs.tsx +32 -0
- package/src/mui/components/ExtendedPagination.tsx +23 -0
- package/src/mui/components/ListStringField.tsx +32 -0
- package/src/providers/DashAuthProvider.ts +341 -0
- package/src/providers/DashDataProvider.ts +466 -0
- package/src/providers/index.ts +17 -0
- package/src/toolbar/buttons/BottomToolbarButtons.tsx +59 -0
- package/src/toolbar/buttons/DashResourceButton.tsx +223 -0
- package/src/toolbar/buttons/ListButtons.tsx +137 -0
- package/src/toolbar/buttons/ToolbarButtons.tsx +186 -0
- package/src/utils/SimpleLogger.tsx +46 -0
- package/src/utils/enumToChoices.tsx +3 -0
- package/src/utils/evalActionPermission.tsx +14 -0
- package/src/utils/groupByTabs.tsx +23 -0
- package/src/utils/hashedGroupByTabs.tsx +16 -0
- package/src/utils/invertMap.tsx +10 -0
- package/src/utils/isClassComponent.tsx +9 -0
- package/src/utils/isComponent.tsx +45 -0
- package/src/utils/isEnum.tsx +3 -0
- package/src/utils/isFC.tsx +6 -0
- package/src/utils/replaceParams.tsx +6 -0
- package/src/utils/validate.tsx +24 -0
- package/src/wrappers/UserAction.tsx +67 -0
- package/src/wrappers/index.tsx +1 -0
- /package/{dist → src}/DashAutoAdminSaveButton.tsx.bkup +0 -0
- /package/{dist/DashAutoFormLayout.tsx.review → src/DashAutoFormLayout.tsx} +0 -0
- /package/{dist → src}/DashAutoFormTabs.review +0 -0
- /package/{dist → src}/DashAutoFormTabs.tsx.formcontext.wip +0 -0
- /package/{dist → src}/mui/AttributeToField.tsx.review +0 -0
- /package/{dist → src}/mui/AttributeToInput.tsx.review +0 -0
- /package/{dist → src}/react-admin.d.ts +0 -0
- /package/{dist → src}/types/ActionCallback.d.ts +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { TabbedShowLayout } from 'react-admin';
|
|
2
|
+
import IDashAutoAdminAttribute from './interfaces/IDashAutoAdminAttribute';
|
|
3
|
+
import { IDashAutoAdminReference } from './interfaces/IDashAutoAdminReference';
|
|
4
|
+
import AutoReferenceTab from './DashAutoReferenceTab';
|
|
5
|
+
import AutoTabs from './DashAutoTabs';
|
|
6
|
+
import IDashAutoAdminResourceConfig from './interfaces/IDashAutoAdminResourceConfig';
|
|
7
|
+
|
|
8
|
+
export interface ITabbedLayout {
|
|
9
|
+
resourceConfig: IDashAutoAdminResourceConfig;
|
|
10
|
+
schema: IDashAutoAdminAttribute[];
|
|
11
|
+
references?: IDashAutoAdminReference[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TabbedLayout: React.FC<ITabbedLayout> = ({
|
|
15
|
+
resourceConfig,
|
|
16
|
+
schema,
|
|
17
|
+
references,
|
|
18
|
+
..._props
|
|
19
|
+
}) => {
|
|
20
|
+
// console.log("references",references);
|
|
21
|
+
return (
|
|
22
|
+
<TabbedShowLayout >
|
|
23
|
+
{AutoTabs(resourceConfig)}
|
|
24
|
+
{references && references.map((reference) => AutoReferenceTab(reference))}
|
|
25
|
+
</TabbedShowLayout>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default TabbedLayout;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useTranslate } from 'react-admin';
|
|
2
|
+
import IDashAutoAdminResourceConfig from '../interfaces/IDashAutoAdminResourceConfig';
|
|
3
|
+
|
|
4
|
+
export interface IAutoTitle {
|
|
5
|
+
record?: any;
|
|
6
|
+
resourceConfig: IDashAutoAdminResourceConfig;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DashAutoTitle: React.FC<IAutoTitle> = ({
|
|
10
|
+
resourceConfig,
|
|
11
|
+
record,
|
|
12
|
+
..._props
|
|
13
|
+
}) => {
|
|
14
|
+
const translate = useTranslate();
|
|
15
|
+
const translatedLabel = translate(resourceConfig.label, { _: resourceConfig.label });
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<span>
|
|
19
|
+
{record ? `${translate('dash.action.edit')} "${record[resourceConfig.schema[0].attribute]}"` : translatedLabel}
|
|
20
|
+
</span>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default DashAutoTitle;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FC, JSX, cloneElement } from 'react';
|
|
2
|
+
import Settings from '@mui/icons-material/Settings';
|
|
3
|
+
import { Fab, FabProps, Tooltip } from '@mui/material';
|
|
4
|
+
|
|
5
|
+
export interface IFabButton extends FabProps {
|
|
6
|
+
onClick: (_e: any) => any;
|
|
7
|
+
tooltip: React.ReactNode;
|
|
8
|
+
icon?: JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const FabButton: FC<IFabButton> = (props) => {
|
|
12
|
+
const { icon, onClick, tooltip, children, ...rest } = props;
|
|
13
|
+
|
|
14
|
+
const _icon = icon ? (
|
|
15
|
+
cloneElement(props.icon, { sx: { fontSize: 16 }, fontSize: 'small' })
|
|
16
|
+
) : (
|
|
17
|
+
<Settings sx={{ fontSize: 16 }} fontSize={'small'} />
|
|
18
|
+
);
|
|
19
|
+
return (
|
|
20
|
+
<Tooltip title={tooltip}>
|
|
21
|
+
<Fab
|
|
22
|
+
size='small'
|
|
23
|
+
aria-label='settings'
|
|
24
|
+
color='primary'
|
|
25
|
+
onClick={onClick}
|
|
26
|
+
style={{
|
|
27
|
+
position: 'absolute',
|
|
28
|
+
}}
|
|
29
|
+
className={'toolbar-settings-button'}
|
|
30
|
+
|
|
31
|
+
{...rest}
|
|
32
|
+
>
|
|
33
|
+
|
|
34
|
+
{_icon}
|
|
35
|
+
</Fab>
|
|
36
|
+
</Tooltip>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default FabButton;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TranslatedLabel Component
|
|
3
|
+
*
|
|
4
|
+
* A component that automatically translates labels using react-admin's i18n system.
|
|
5
|
+
* Useful for rendering translated labels in legends, titles, and other places
|
|
6
|
+
* where raw text is displayed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { useTranslate } from 'react-admin';
|
|
11
|
+
import { isTranslationKey } from '../../hooks/useAutoAdminTranslate';
|
|
12
|
+
|
|
13
|
+
export interface TranslatedLabelProps {
|
|
14
|
+
/** The label to translate (can be a translation key or plain text) */
|
|
15
|
+
label: string | undefined;
|
|
16
|
+
/** Optional fallback if translation is not found */
|
|
17
|
+
fallback?: string;
|
|
18
|
+
/** Optional className for styling */
|
|
19
|
+
className?: string;
|
|
20
|
+
/** Optional component to wrap the text (default: span) */
|
|
21
|
+
component?: React.ElementType;
|
|
22
|
+
/** Optional props to pass to the wrapper component */
|
|
23
|
+
componentProps?: Record<string, any>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Component that renders a translated label
|
|
28
|
+
* If the label looks like a translation key (contains dots), it will be translated
|
|
29
|
+
* Otherwise, it renders the label as-is
|
|
30
|
+
*/
|
|
31
|
+
export const TranslatedLabel: React.FC<TranslatedLabelProps> = ({
|
|
32
|
+
label,
|
|
33
|
+
fallback = '',
|
|
34
|
+
className,
|
|
35
|
+
component: Component = 'span',
|
|
36
|
+
componentProps = {},
|
|
37
|
+
}) => {
|
|
38
|
+
const translate = useTranslate();
|
|
39
|
+
|
|
40
|
+
if (!label) {
|
|
41
|
+
return fallback ? <Component className={className} {...componentProps}>{fallback}</Component> : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let displayText = label;
|
|
45
|
+
|
|
46
|
+
if (isTranslationKey(label)) {
|
|
47
|
+
const translated = translate(label, { _: label });
|
|
48
|
+
// If translation returns the key itself and we have a fallback, use it
|
|
49
|
+
if (translated === label && fallback) {
|
|
50
|
+
displayText = fallback;
|
|
51
|
+
} else {
|
|
52
|
+
displayText = translated;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Component className={className} {...componentProps}>
|
|
58
|
+
{displayText}
|
|
59
|
+
</Component>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Higher-order component that provides translation to a component
|
|
65
|
+
*/
|
|
66
|
+
export const withTranslatedLabel = <P extends { label?: string }>(
|
|
67
|
+
WrappedComponent: React.ComponentType<P>
|
|
68
|
+
): React.FC<P> => {
|
|
69
|
+
return (props: P) => {
|
|
70
|
+
const translate = useTranslate();
|
|
71
|
+
|
|
72
|
+
let translatedLabel = props.label;
|
|
73
|
+
if (props.label && isTranslationKey(props.label)) {
|
|
74
|
+
translatedLabel = translate(props.label, { _: props.label });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return <WrappedComponent {...props} label={translatedLabel} />;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default TranslatedLabel;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dialog,
|
|
3
|
+
DialogContent,
|
|
4
|
+
Drawer,
|
|
5
|
+
Fade,
|
|
6
|
+
Popover,
|
|
7
|
+
Tooltip,
|
|
8
|
+
} from '@mui/material';
|
|
9
|
+
|
|
10
|
+
import React, { FC, memo } from 'react';
|
|
11
|
+
import { GearBadge, AlertBadge, ErrorBadge } from './SettingsBarIcons';
|
|
12
|
+
|
|
13
|
+
export interface ISettings {
|
|
14
|
+
/** Type. */
|
|
15
|
+
type?: 'drawer' | 'dialog' | 'tooltip';
|
|
16
|
+
/** Memoization data to compare. */
|
|
17
|
+
data?: any;
|
|
18
|
+
/** Warning. */
|
|
19
|
+
warning?: { active: boolean; message: string };
|
|
20
|
+
/** Error. */
|
|
21
|
+
error?: { active: boolean; message: string };
|
|
22
|
+
/** Optional ClassName. */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Optional GearIcon. */
|
|
25
|
+
gearIcon?: JSX.Element;
|
|
26
|
+
/** Children Required. */
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SettingsTooltip: FC<ISettings> = ({
|
|
31
|
+
type,
|
|
32
|
+
className,
|
|
33
|
+
children,
|
|
34
|
+
warning,
|
|
35
|
+
error,
|
|
36
|
+
gearIcon,
|
|
37
|
+
..._props
|
|
38
|
+
}) => {
|
|
39
|
+
if (!warning) warning = { active: false, message: '' };
|
|
40
|
+
if (!error) error = { active: false, message: '' };
|
|
41
|
+
|
|
42
|
+
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
|
43
|
+
const open = Boolean(anchorEl);
|
|
44
|
+
|
|
45
|
+
const openSettings = (event: React.MouseEvent<HTMLElement>) => {
|
|
46
|
+
setAnchorEl(event.currentTarget);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const closeSettings = () => {
|
|
50
|
+
setAnchorEl(null);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div {...(className && { className })}>
|
|
55
|
+
<div className='settings-widget-actions'>
|
|
56
|
+
{error.active && (
|
|
57
|
+
<div className='settings-widget-action'>
|
|
58
|
+
<ErrorBadge onClick={() => {}}>
|
|
59
|
+
<></>
|
|
60
|
+
</ErrorBadge>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
{warning.active && (
|
|
64
|
+
<Tooltip title={warning.message}>
|
|
65
|
+
<div className='settings-widget-action'>
|
|
66
|
+
<AlertBadge onClick={() => {}}>
|
|
67
|
+
<></>
|
|
68
|
+
</AlertBadge>
|
|
69
|
+
</div>
|
|
70
|
+
</Tooltip>
|
|
71
|
+
)}
|
|
72
|
+
<div className='settings-widget-action'>
|
|
73
|
+
<GearBadge icon={gearIcon} onClick={openSettings}>
|
|
74
|
+
<></>
|
|
75
|
+
</GearBadge>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<Popover
|
|
80
|
+
anchorOrigin={{
|
|
81
|
+
vertical: 'bottom',
|
|
82
|
+
horizontal: 'right',
|
|
83
|
+
}}
|
|
84
|
+
transformOrigin={{
|
|
85
|
+
vertical: 'top',
|
|
86
|
+
horizontal: 'left',
|
|
87
|
+
}}
|
|
88
|
+
id='fade-menu'
|
|
89
|
+
anchorEl={anchorEl}
|
|
90
|
+
open={open}
|
|
91
|
+
onClose={closeSettings}
|
|
92
|
+
TransitionComponent={Fade}
|
|
93
|
+
>
|
|
94
|
+
<div style={{ padding: 8 }}>{children}</div>
|
|
95
|
+
</Popover>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const SettingsDialog: FC<ISettings> = ({
|
|
101
|
+
type,
|
|
102
|
+
className,
|
|
103
|
+
children,
|
|
104
|
+
warning,
|
|
105
|
+
error,
|
|
106
|
+
..._props
|
|
107
|
+
}) => {
|
|
108
|
+
if (!warning) warning = { active: false, message: '' };
|
|
109
|
+
if (!error) error = { active: false, message: '' };
|
|
110
|
+
|
|
111
|
+
const [open, setOpen] = React.useState(false);
|
|
112
|
+
|
|
113
|
+
const openSettings = (_event: React.MouseEvent<HTMLElement>) => {
|
|
114
|
+
setOpen(true);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const closeSettings = () => {
|
|
118
|
+
setOpen(false);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div>
|
|
123
|
+
<div className='settings-widget-actions'>
|
|
124
|
+
{error.active && (
|
|
125
|
+
<div className='settings-widget-action'>
|
|
126
|
+
<ErrorBadge onClick={() => {}}>
|
|
127
|
+
<></>
|
|
128
|
+
</ErrorBadge>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
131
|
+
{warning.active && (
|
|
132
|
+
<Tooltip title={warning.message}>
|
|
133
|
+
<div className='settings-widget-action'>
|
|
134
|
+
<AlertBadge onClick={() => {}}>
|
|
135
|
+
<></>
|
|
136
|
+
</AlertBadge>
|
|
137
|
+
</div>
|
|
138
|
+
</Tooltip>
|
|
139
|
+
)}
|
|
140
|
+
<div className='settings-widget-action'>
|
|
141
|
+
<GearBadge onClick={openSettings}>
|
|
142
|
+
<></>
|
|
143
|
+
</GearBadge>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<Dialog open={open} onClose={closeSettings}>
|
|
148
|
+
{/* @ts-ignore Expected mismatch ReactNode. */}
|
|
149
|
+
<DialogContent>{children}</DialogContent>
|
|
150
|
+
</Dialog>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const SettingsDrawer: FC<ISettings> = ({
|
|
156
|
+
type,
|
|
157
|
+
className,
|
|
158
|
+
children,
|
|
159
|
+
warning,
|
|
160
|
+
error,
|
|
161
|
+
..._props
|
|
162
|
+
}) => {
|
|
163
|
+
if (!warning) warning = { active: false, message: '' };
|
|
164
|
+
if (!error) error = { active: false, message: '' };
|
|
165
|
+
|
|
166
|
+
const [open, setOpen] = React.useState(false);
|
|
167
|
+
|
|
168
|
+
const openSettings = (_event: React.MouseEvent<HTMLElement>) => {
|
|
169
|
+
setOpen(true);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const closeSettings = () => {
|
|
173
|
+
setOpen(false);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div>
|
|
178
|
+
<div className='settings-widget-actions'>
|
|
179
|
+
{error.active && (
|
|
180
|
+
<div className='settings-widget-action'>
|
|
181
|
+
<ErrorBadge onClick={() => {}}>
|
|
182
|
+
<></>
|
|
183
|
+
</ErrorBadge>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
{warning.active && (
|
|
187
|
+
<Tooltip title={warning.message}>
|
|
188
|
+
<div className='settings-widget-action'>
|
|
189
|
+
<AlertBadge onClick={() => {}}>
|
|
190
|
+
<></>
|
|
191
|
+
</AlertBadge>
|
|
192
|
+
</div>
|
|
193
|
+
</Tooltip>
|
|
194
|
+
)}
|
|
195
|
+
<div className='settings-widget-action'>
|
|
196
|
+
<GearBadge onClick={openSettings}>
|
|
197
|
+
<></>
|
|
198
|
+
</GearBadge>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<Drawer
|
|
203
|
+
open={open}
|
|
204
|
+
onClose={closeSettings}
|
|
205
|
+
anchor={'right'}
|
|
206
|
+
PaperProps={{
|
|
207
|
+
sx: { width: '20%' },
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
{/* @ts-ignore Expected mismatch ReactNode. */}
|
|
211
|
+
{children}
|
|
212
|
+
</Drawer>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const propsAreEqual = (oldProps, newProps) => {
|
|
218
|
+
return oldProps.data === newProps.data;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
const Settings: FC<ISettings> = ({ type = 'dialog', ...props }) => {
|
|
223
|
+
switch (type) {
|
|
224
|
+
case 'tooltip':
|
|
225
|
+
return <SettingsTooltip type={type} {...props} />;
|
|
226
|
+
case 'drawer':
|
|
227
|
+
return <SettingsDrawer type={type} {...props} />;
|
|
228
|
+
case 'dialog':
|
|
229
|
+
default:
|
|
230
|
+
return <SettingsDialog type={type} {...props} />;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const MemoizedSettings = memo(Settings, propsAreEqual);
|
|
235
|
+
|
|
236
|
+
export default MemoizedSettings;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import Badge, { BadgeProps } from '@mui/material/Badge';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import {Settings, Error, Warning }from '@mui/icons-material';
|
|
4
|
+
|
|
5
|
+
import { FC, JSX, cloneElement } from 'react';
|
|
6
|
+
import { Fab } from '@mui/material';
|
|
7
|
+
|
|
8
|
+
const StyledBadge = styled(Badge)<BadgeProps>(({ theme: _theme }) => ({
|
|
9
|
+
'& .MuiBadge-badge': {
|
|
10
|
+
padding: 0,
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
export interface ISettingsBadge {
|
|
15
|
+
onClick: (e: any) => any;
|
|
16
|
+
icon?: JSX.Element;
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const GearBadge: FC<ISettingsBadge> = (props) => {
|
|
21
|
+
const icon = props.icon ? (
|
|
22
|
+
cloneElement(props.icon, { sx: { fontSize: 16 }, fontSize: 'small' })
|
|
23
|
+
) : (
|
|
24
|
+
<Settings sx={{ fontSize: 16 }} fontSize={'small'} />
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Fab
|
|
29
|
+
size='small'
|
|
30
|
+
aria-label='settings'
|
|
31
|
+
color='primary'
|
|
32
|
+
onClick={props.onClick}
|
|
33
|
+
style={{
|
|
34
|
+
position: 'absolute',
|
|
35
|
+
}}
|
|
36
|
+
className={'toolbar-settings-button'}
|
|
37
|
+
>
|
|
38
|
+
{icon}
|
|
39
|
+
</Fab>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const AlertBadge: FC<ISettingsBadge> = (props) => {
|
|
44
|
+
return (
|
|
45
|
+
<StyledBadge
|
|
46
|
+
onClick={props.onClick}
|
|
47
|
+
badgeContent={
|
|
48
|
+
<Warning
|
|
49
|
+
sx={{ fontSize: 16, color: 'orange' }}
|
|
50
|
+
fontSize={'small'}
|
|
51
|
+
/>
|
|
52
|
+
}
|
|
53
|
+
color='default'
|
|
54
|
+
>
|
|
55
|
+
{props.children}
|
|
56
|
+
</StyledBadge>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const ErrorBadge: FC<ISettingsBadge> = (props) => {
|
|
61
|
+
return (
|
|
62
|
+
<StyledBadge
|
|
63
|
+
onClick={props.onClick}
|
|
64
|
+
badgeContent={
|
|
65
|
+
<Error sx={{ fontSize: 16, color: 'red' }} fontSize={'small'} />
|
|
66
|
+
}
|
|
67
|
+
color='default'
|
|
68
|
+
>
|
|
69
|
+
{props.children}
|
|
70
|
+
</StyledBadge>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface IDashAutoAdminFormContext {
|
|
4
|
+
/**
|
|
5
|
+
* The save handler that wraps form submission with validation.
|
|
6
|
+
* This is exposed so child components (like SaveButton) can trigger it manually.
|
|
7
|
+
*/
|
|
8
|
+
onSave?: (values: any) => Promise<void> | void;
|
|
9
|
+
/**
|
|
10
|
+
* The current form mode
|
|
11
|
+
*/
|
|
12
|
+
mode: 'create' | 'edit';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DashAutoAdminFormContext = createContext<IDashAutoAdminFormContext>({
|
|
16
|
+
mode: 'create',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const DashAutoAdminFormProvider = DashAutoAdminFormContext.Provider;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook to access the DashAutoAdminForm context.
|
|
23
|
+
* This is useful for child components that need to trigger form submission manually,
|
|
24
|
+
* such as when using SaveButton with type="button".
|
|
25
|
+
*/
|
|
26
|
+
export const useDashAutoAdminForm = () => {
|
|
27
|
+
const context = useContext(DashAutoAdminFormContext);
|
|
28
|
+
return context;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default DashAutoAdminFormContext;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const useAutoAdminLoadingStateMediator = (origin?: string) => {
|
|
4
|
+
const useAutoAdminLoadingState = useState<boolean>(false);
|
|
5
|
+
|
|
6
|
+
const localState = useState<MessageEvent>(null);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!localState[0]) return;
|
|
10
|
+
|
|
11
|
+
if (origin && localState[0]?.origin) {
|
|
12
|
+
|
|
13
|
+
if ( /* @ts-ignore : An expression of type 'void' cannot be tested for truthiness.ts(1345). */
|
|
14
|
+
localState[0].origin === origin &&
|
|
15
|
+
useAutoAdminLoadingState[1](localState[0].data)
|
|
16
|
+
) {
|
|
17
|
+
// eslint-disable-line no-debugger
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
useAutoAdminLoadingState[1](localState[0]?.data);
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
}, [localState[0]]);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
window.addEventListener('auto-admin-loading-state', (e: any) =>
|
|
29
|
+
localState[1](e),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
window.removeEventListener('auto-admin-loading-state', (e: any) =>
|
|
34
|
+
localState[1](e),
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
return useAutoAdminLoadingState;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default useAutoAdminLoadingStateMediator;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAutoAdminTranslate Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides translation functionality for dash-auto-admin components.
|
|
5
|
+
* Uses react-admin's useTranslate hook internally.
|
|
6
|
+
*
|
|
7
|
+
* This hook handles:
|
|
8
|
+
* - Translating labels that are translation keys
|
|
9
|
+
* - Fallback to original label if no translation found
|
|
10
|
+
* - Checking if a string looks like a translation key (contains dots)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useTranslate } from 'react-admin';
|
|
14
|
+
import { useCallback, useMemo } from 'react';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if a string looks like a translation key (contains dots like 'tab.resource.tabs')
|
|
18
|
+
*/
|
|
19
|
+
export const isTranslationKey = (value: string): boolean => {
|
|
20
|
+
if (!value || typeof value !== 'string') return false;
|
|
21
|
+
// Translation keys typically contain dots and are lowercase/snake_case
|
|
22
|
+
// Examples: 'tab.resource.tabs', 'common.save', 'resources.users.name'
|
|
23
|
+
return /^[a-z][a-z0-9_.]*[a-z0-9]$/i.test(value) && value.includes('.');
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Hook that provides translation utilities for dash-auto-admin
|
|
28
|
+
*/
|
|
29
|
+
export const useAutoAdminTranslate = () => {
|
|
30
|
+
const translate = useTranslate();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Translate a label if it's a translation key, otherwise return as-is
|
|
34
|
+
*/
|
|
35
|
+
const translateLabel = useCallback((label: string | undefined, fallback?: string): string => {
|
|
36
|
+
if (!label) return fallback || '';
|
|
37
|
+
|
|
38
|
+
if (isTranslationKey(label)) {
|
|
39
|
+
const translated = translate(label, { _: label });
|
|
40
|
+
// If translation returns the same key, it means no translation was found
|
|
41
|
+
// In that case, check if we should return the label or a fallback
|
|
42
|
+
if (translated === label && fallback) {
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
return translated;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return label;
|
|
49
|
+
}, [translate]);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Translate a value only if it looks like a translation key
|
|
53
|
+
*/
|
|
54
|
+
const translateIfKey = useCallback((value: string | undefined): string => {
|
|
55
|
+
if (!value) return '';
|
|
56
|
+
if (isTranslationKey(value)) {
|
|
57
|
+
return translate(value, { _: value });
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}, [translate]);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Translate with parameters
|
|
64
|
+
*/
|
|
65
|
+
const translateWithParams = useCallback((key: string, params?: Record<string, any>): string => {
|
|
66
|
+
return translate(key, params);
|
|
67
|
+
}, [translate]);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a translation exists for a key
|
|
71
|
+
*/
|
|
72
|
+
const hasTranslation = useCallback((key: string): boolean => {
|
|
73
|
+
if (!isTranslationKey(key)) return false;
|
|
74
|
+
const translated = translate(key, { _: key });
|
|
75
|
+
return translated !== key;
|
|
76
|
+
}, [translate]);
|
|
77
|
+
|
|
78
|
+
return useMemo(() => ({
|
|
79
|
+
translate,
|
|
80
|
+
translateLabel,
|
|
81
|
+
translateIfKey,
|
|
82
|
+
translateWithParams,
|
|
83
|
+
hasTranslation,
|
|
84
|
+
isTranslationKey,
|
|
85
|
+
}), [translate, translateLabel, translateIfKey, translateWithParams, hasTranslation]);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Standalone translation function for use outside of React components
|
|
90
|
+
* Uses the translate function from React Admin context if available
|
|
91
|
+
*/
|
|
92
|
+
export const translateLabelSync = (label: string | undefined, translate: (key: string, options?: any) => string, fallback?: string): string => {
|
|
93
|
+
if (!label) return fallback || '';
|
|
94
|
+
|
|
95
|
+
if (isTranslationKey(label)) {
|
|
96
|
+
const translated = translate(label, { _: label });
|
|
97
|
+
if (translated === label && fallback) {
|
|
98
|
+
return fallback;
|
|
99
|
+
}
|
|
100
|
+
return translated;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return label;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default useAutoAdminTranslate;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const useHash = () => {
|
|
4
|
+
const [hash, setHash] = React.useState(() => window.location.hash);
|
|
5
|
+
|
|
6
|
+
const hashChangeHandler = React.useCallback(() => {
|
|
7
|
+
setHash(window.location.hash);
|
|
8
|
+
}, []);
|
|
9
|
+
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
window.addEventListener('hashchange', hashChangeHandler);
|
|
12
|
+
return () => {
|
|
13
|
+
window.removeEventListener('hashchange', hashChangeHandler);
|
|
14
|
+
};
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const updateHash = React.useCallback(
|
|
18
|
+
(newHash) => {
|
|
19
|
+
if (newHash !== hash) window.location.hash = newHash;
|
|
20
|
+
},
|
|
21
|
+
[hash],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return [hash, updateHash];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default useHash;
|