@applica-software-guru/react-admin 1.5.248 → 1.5.250
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/AuthWrapper.d.ts +2 -1
- package/dist/components/AuthWrapper.d.ts.map +1 -1
- package/dist/components/Layout/Footer.d.ts.map +1 -1
- package/dist/components/Layout/Layout.d.ts.map +1 -1
- package/dist/components/Layout/ThemeColor.d.ts +10 -0
- package/dist/components/Layout/ThemeColor.d.ts.map +1 -0
- package/dist/components/Layout/ThemeToggler.d.ts.map +1 -1
- package/dist/components/Layout/index.d.ts +1 -0
- package/dist/components/Layout/index.d.ts.map +1 -1
- package/dist/components/Logo.d.ts.map +1 -1
- package/dist/components/ra-fields/CoverField.d.ts +0 -1
- package/dist/components/ra-fields/CoverField.d.ts.map +1 -1
- package/dist/components/ra-inputs/FileInput.d.ts +0 -1
- package/dist/components/ra-inputs/FileInput.d.ts.map +1 -1
- package/dist/components/ra-inputs/LabeledInput.d.ts +1 -1
- package/dist/components/ra-inputs/LabeledInput.d.ts.map +1 -1
- package/dist/components/ra-inputs/LocalizedTextInput.d.ts +12 -2
- package/dist/components/ra-inputs/LocalizedTextInput.d.ts.map +1 -1
- package/dist/react-admin.cjs.js +65 -65
- package/dist/react-admin.cjs.js.map +1 -1
- package/dist/react-admin.es.js +12536 -12817
- package/dist/react-admin.es.js.map +1 -1
- package/dist/react-admin.umd.js +64 -64
- package/dist/react-admin.umd.js.map +1 -1
- package/dist/utils/localizedValue.d.ts +4 -1
- package/dist/utils/localizedValue.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/AuthWrapper.tsx +19 -13
- package/src/components/Layout/Footer.tsx +8 -1
- package/src/components/Layout/Layout.tsx +2 -0
- package/src/components/Layout/ThemeColor.tsx +24 -0
- package/src/components/Layout/ThemeToggler.tsx +0 -1
- package/src/components/Layout/index.ts +1 -0
- package/src/components/Logo.tsx +1 -5
- package/src/components/ra-fields/CoverField.tsx +0 -1
- package/src/components/ra-inputs/FileInput.tsx +0 -1
- package/src/components/ra-inputs/LabeledInput.tsx +1 -1
- package/src/components/ra-inputs/LocalizedTextInput.tsx +59 -9
- package/src/playground/App.jsx +1 -0
- package/src/playground/components/ra-forms/CategoryForm.jsx +10 -2
- package/src/playground/components/ra-forms/DeviceForm.jsx +4 -1
- package/src/playground/components/ra-lists/CategoryList.jsx +2 -1
- package/src/playground/config.jsx +4 -3
- package/src/playground/menu.jsx +0 -7
- package/src/utils/localizedValue.ts +10 -1
|
@@ -8,11 +8,14 @@ type ILocalizedValue<T> = {
|
|
|
8
8
|
declare function localizedValueHasAllLocales<T>(value?: ILocalizedValue<T>, locales?: Array<ILocale>, options?: {
|
|
9
9
|
isEmpty?: (v: T) => boolean;
|
|
10
10
|
}): boolean;
|
|
11
|
+
declare function localizedValueHasAtLeastOneLocale<T>(value?: ILocalizedValue<T>, locales?: Array<ILocale>, options?: {
|
|
12
|
+
isEmpty?: (v: T) => boolean;
|
|
13
|
+
}): boolean;
|
|
11
14
|
type IGetLocalizedValueOptions = {
|
|
12
15
|
fallback?: boolean;
|
|
13
16
|
fallbackLocales?: Array<string>;
|
|
14
17
|
};
|
|
15
18
|
declare function getLocalizedValue<T>(value: ILocalizedValue<T>, locale: string, options?: IGetLocalizedValueOptions): T | undefined;
|
|
16
19
|
export type { IGetLocalizedValueOptions, ILocalizedValue };
|
|
17
|
-
export { getLocalizedValue, localizedValueHasAllLocales };
|
|
20
|
+
export { getLocalizedValue, localizedValueHasAllLocales, localizedValueHasAtLeastOneLocale };
|
|
18
21
|
//# sourceMappingURL=localizedValue.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localizedValue.d.ts","sourceRoot":"","sources":["../../../src/utils/localizedValue.ts"],"names":[],"mappings":"AAEA,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AACF,KAAK,eAAe,CAAC,CAAC,IAAI;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAA;CAAE,CAAC;AAE/C,iBAAS,2BAA2B,CAAC,CAAC,EACpC,KAAK,GAAE,eAAe,CAAC,CAAC,CAAM,EAC9B,OAAO,GAAE,KAAK,CAAC,OAAO,CAAM,EAC5B,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;CAAO,GAC5C,OAAO,CAIT;AAED,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AACF,iBAAS,iBAAiB,CAAC,CAAC,EAC1B,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,yBAAyB,GAClC,CAAC,GAAG,SAAS,CAyBf;AAED,YAAY,EAAE,yBAAyB,EAAE,eAAe,EAAE,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"localizedValue.d.ts","sourceRoot":"","sources":["../../../src/utils/localizedValue.ts"],"names":[],"mappings":"AAEA,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AACF,KAAK,eAAe,CAAC,CAAC,IAAI;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAA;CAAE,CAAC;AAE/C,iBAAS,2BAA2B,CAAC,CAAC,EACpC,KAAK,GAAE,eAAe,CAAC,CAAC,CAAM,EAC9B,OAAO,GAAE,KAAK,CAAC,OAAO,CAAM,EAC5B,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;CAAO,GAC5C,OAAO,CAIT;AAED,iBAAS,iCAAiC,CAAC,CAAC,EAC1C,KAAK,GAAE,eAAe,CAAC,CAAC,CAAM,EAC9B,OAAO,GAAE,KAAK,CAAC,OAAO,CAAM,EAC5B,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;CAAO,GAC5C,OAAO,CAGT;AAED,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AACF,iBAAS,iBAAiB,CAAC,CAAC,EAC1B,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,EACzB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,yBAAyB,GAClC,CAAC,GAAG,SAAS,CAyBf;AAED,YAAY,EAAE,yBAAyB,EAAE,eAAe,EAAE,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,iCAAiC,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -108,12 +108,12 @@
|
|
|
108
108
|
"format": "prettier --write .",
|
|
109
109
|
"prepare": "husky",
|
|
110
110
|
"preview": "vite preview",
|
|
111
|
-
"start": "vite",
|
|
111
|
+
"start": "vite --host",
|
|
112
112
|
"test": "vitest run",
|
|
113
113
|
"watch": "vite build --watch"
|
|
114
114
|
},
|
|
115
115
|
"type": "module",
|
|
116
116
|
"types": "dist/index.d.ts",
|
|
117
117
|
"typings": "dist/index.d.ts",
|
|
118
|
-
"version": "1.5.
|
|
118
|
+
"version": "1.5.250"
|
|
119
119
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthBackground } from '@/components/AuthBackground';
|
|
2
2
|
import { AuthCard } from '@/components/AuthCard';
|
|
3
|
+
import { ThemeColor } from '@/components/Layout';
|
|
3
4
|
import { Footer } from '@/components/Layout/Footer';
|
|
4
5
|
import { MainIcon } from '@/components/MainIcon';
|
|
5
6
|
import { Box, Grid } from '@mui/material';
|
|
@@ -11,20 +12,23 @@ type AuthWrapperProps = PropsWithChildren<{
|
|
|
11
12
|
copy?: string;
|
|
12
13
|
background?: React.ReactNode | React.ComponentType;
|
|
13
14
|
logo?: React.ReactNode;
|
|
15
|
+
footer?: boolean;
|
|
14
16
|
}>;
|
|
15
17
|
|
|
16
|
-
function AuthWrapper({
|
|
18
|
+
function AuthWrapper({
|
|
19
|
+
version,
|
|
20
|
+
name,
|
|
21
|
+
copy,
|
|
22
|
+
children,
|
|
23
|
+
logo,
|
|
24
|
+
background = AuthBackground,
|
|
25
|
+
footer = true
|
|
26
|
+
}: AuthWrapperProps) {
|
|
17
27
|
return (
|
|
18
|
-
<Box
|
|
28
|
+
<Box>
|
|
29
|
+
<ThemeColor source="default" />
|
|
19
30
|
{React.isValidElement(background) ? background : React.createElement(background as any)}
|
|
20
|
-
<Grid
|
|
21
|
-
container
|
|
22
|
-
direction="column"
|
|
23
|
-
justifyContent="flex-end"
|
|
24
|
-
sx={{
|
|
25
|
-
minHeight: '100vh'
|
|
26
|
-
}}
|
|
27
|
-
>
|
|
31
|
+
<Grid container direction="column" justifyContent="flex-end">
|
|
28
32
|
<Grid item xs={12} sx={{ ml: 3 }}>
|
|
29
33
|
{logo ? logo : <MainIcon title={name} />}
|
|
30
34
|
</Grid>
|
|
@@ -41,9 +45,11 @@ function AuthWrapper({ version, name, copy, children, logo, background = AuthBac
|
|
|
41
45
|
<AuthCard>{children}</AuthCard>
|
|
42
46
|
</Grid>
|
|
43
47
|
</Grid>
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
48
|
+
{footer ? (
|
|
49
|
+
<Grid item sx={{ pb: 1, position: 'absolute', bottom: 0, ml: 2 }}>
|
|
50
|
+
<Footer name={name} copy={copy} version={version} />
|
|
51
|
+
</Grid>
|
|
52
|
+
) : null}
|
|
47
53
|
</Grid>
|
|
48
54
|
</Grid>
|
|
49
55
|
</Box>
|
|
@@ -9,7 +9,14 @@ type IFooterProps = {
|
|
|
9
9
|
function Footer(props: IFooterProps) {
|
|
10
10
|
const { name, version, copy = '© Applica Software Guru for' } = props;
|
|
11
11
|
return (
|
|
12
|
-
<Stack
|
|
12
|
+
<Stack
|
|
13
|
+
direction="row"
|
|
14
|
+
justifyContent="space-between"
|
|
15
|
+
alignItems="flex-end"
|
|
16
|
+
flexGrow={1}
|
|
17
|
+
height="auto"
|
|
18
|
+
sx={{ pt: 1 }}
|
|
19
|
+
>
|
|
13
20
|
<Typography variant="caption">
|
|
14
21
|
<span dangerouslySetInnerHTML={{ __html: copy || '' }} /> {name} - {version}
|
|
15
22
|
</Typography>
|
|
@@ -8,6 +8,7 @@ import { ThemeToggler } from './ThemeToggler';
|
|
|
8
8
|
import { LayoutWrapper } from './Wrapper';
|
|
9
9
|
import { Breadcrumbs } from '@/components/@extended';
|
|
10
10
|
import { useBreadcrumbs } from '@/components/Layout/MenuProvider';
|
|
11
|
+
import { ThemeColor } from '@/components/Layout/ThemeColor';
|
|
11
12
|
import { ErrorProps, LoadingIndicator } from 'ra-ui-materialui';
|
|
12
13
|
import { Outlet } from 'react-router-dom';
|
|
13
14
|
|
|
@@ -29,6 +30,7 @@ const Layout = withLayoutProvider(function Layout(props: ILayoutProps) {
|
|
|
29
30
|
<HeaderSpacer />
|
|
30
31
|
<LocaleButton />
|
|
31
32
|
{enableThemeToggler ? <ThemeToggler /> : null}
|
|
33
|
+
<ThemeColor />
|
|
32
34
|
<LoadingIndicator />
|
|
33
35
|
<HeaderNotification />
|
|
34
36
|
<ResponsiveSection>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useThemeConfig } from '@/components/Layout/ThemeProvider';
|
|
2
|
+
import { useTheme } from '@mui/material';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
type ThemeColorProps = {
|
|
7
|
+
source?: 'paper' | 'default';
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* ThemeColor component is responsible for setting the theme color in the browser.
|
|
11
|
+
* It uses the current theme mode and the theme object to set the theme color.
|
|
12
|
+
*/
|
|
13
|
+
function ThemeColor({ source = 'paper' }: ThemeColorProps) {
|
|
14
|
+
const { mode } = useThemeConfig();
|
|
15
|
+
const theme = useTheme();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const backgroundColor = _.get(theme.palette.background, source);
|
|
18
|
+
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', backgroundColor as string);
|
|
19
|
+
}, [mode, theme, source]);
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { ThemeColor };
|
package/src/components/Logo.tsx
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import logoIcon from '@/assets/logo-icon.png';
|
|
2
|
-
import logoMain from '@/assets/logo-main.png';
|
|
3
1
|
import { useThemeConfig } from '@/components/Layout/ThemeProvider';
|
|
4
2
|
import { ButtonBase, SxProps } from '@mui/material';
|
|
5
3
|
import { Link } from 'react-router-dom';
|
|
6
4
|
|
|
7
|
-
const defaultIcon = <img src={logoIcon} alt="Applica" width="20px" />;
|
|
8
|
-
const defaultMain = <img src={logoMain} alt="Applica" width="100px" />;
|
|
9
5
|
type LogoProps = {
|
|
10
6
|
isIcon: boolean;
|
|
11
7
|
sx: SxProps;
|
|
@@ -13,7 +9,7 @@ type LogoProps = {
|
|
|
13
9
|
logoIcon: React.ReactNode;
|
|
14
10
|
logoMain: React.ReactNode;
|
|
15
11
|
};
|
|
16
|
-
function Logo({ isIcon, sx, to, logoIcon
|
|
12
|
+
function Logo({ isIcon, sx, to, logoIcon, logoMain }: LogoProps) {
|
|
17
13
|
const { appDefaultPath } = useThemeConfig();
|
|
18
14
|
return (
|
|
19
15
|
<ButtonBase disableRipple component={Link} to={!to ? appDefaultPath : to} sx={sx}>
|
|
@@ -15,7 +15,6 @@ type CoverFieldProps = {
|
|
|
15
15
|
* @example
|
|
16
16
|
* // Suppose you have a property, in your record, called 'image' and it contains a base64 string related field called '_image'.
|
|
17
17
|
* // You can use this component as follows:
|
|
18
|
-
* import { CoverField } from 'ra-ui-applica';
|
|
19
18
|
* ...
|
|
20
19
|
* <CoverField source="image" />
|
|
21
20
|
*
|
|
@@ -33,7 +33,6 @@ const StyledFileInput = styled(RaFileInput, { slot: 'root' })(({ theme }) => ({
|
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* // If you hav an object class with @File annotation, you can map it to a FileInput:
|
|
36
|
-
* import { FileInput } from 'ra-ui-applica';
|
|
37
36
|
* ...
|
|
38
37
|
* <FileInput source="file_property" />
|
|
39
38
|
*
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { TextInput } from './TextInput';
|
|
2
2
|
import { LabeledInput, LabeledInputProps } from '@/components/ra-inputs/LabeledInput';
|
|
3
|
-
import { localizedValueHasAllLocales } from '@/utils';
|
|
3
|
+
import { localizedValueHasAllLocales, localizedValueHasAtLeastOneLocale } from '@/utils';
|
|
4
4
|
import { Box, Chip, ListItemText, Menu, MenuItem, PopoverOrigin, Typography } from '@mui/material';
|
|
5
5
|
import _ from 'lodash';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ValidationError,
|
|
8
|
+
ValidationErrorMessage,
|
|
9
|
+
composeValidators,
|
|
10
|
+
useInput,
|
|
11
|
+
useLocaleState,
|
|
12
|
+
useLocales
|
|
13
|
+
} from 'ra-core';
|
|
7
14
|
import { TextInputProps } from 'ra-ui-materialui';
|
|
8
15
|
import { ReactElement, useCallback, useMemo, useState } from 'react';
|
|
9
16
|
import { useWatch } from 'react-hook-form';
|
|
@@ -11,14 +18,49 @@ import { useWatch } from 'react-hook-form';
|
|
|
11
18
|
const ANCHOR_ORIGIN: PopoverOrigin = { vertical: 'bottom', horizontal: 'right' };
|
|
12
19
|
const TRANSFORM_ORIGIN: PopoverOrigin = { vertical: 'top', horizontal: 'right' };
|
|
13
20
|
|
|
14
|
-
type ILocalizedTextInputProps = TextInputProps &
|
|
21
|
+
type ILocalizedTextInputProps = TextInputProps &
|
|
22
|
+
LabeledInputProps & {
|
|
23
|
+
/**
|
|
24
|
+
* Indicates if at least one locale is required.
|
|
25
|
+
*/
|
|
26
|
+
requiredAtLeastOneLocale?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Indicates the locale that is required.
|
|
29
|
+
* If not provided, one of the locales will be required.
|
|
30
|
+
*/
|
|
31
|
+
requiredLocale?: string;
|
|
32
|
+
};
|
|
15
33
|
|
|
16
|
-
function LocalizedTextInput(
|
|
34
|
+
function LocalizedTextInput({
|
|
35
|
+
requiredAtLeastOneLocale,
|
|
36
|
+
requiredLocale,
|
|
37
|
+
validate: _validate,
|
|
38
|
+
required: _required,
|
|
39
|
+
...props
|
|
40
|
+
}: ILocalizedTextInputProps) {
|
|
17
41
|
const { source } = props;
|
|
18
|
-
const { fieldState } = useInput(props);
|
|
19
|
-
const value = useWatch({ name: source });
|
|
20
|
-
const { error } = fieldState;
|
|
21
42
|
const locales = useLocales();
|
|
43
|
+
const required = useCallback(
|
|
44
|
+
(requiredMessage = 'ra.validation.required', specificRequiredMessage = 'ra.validation.required_locale') => {
|
|
45
|
+
return (value: any) => {
|
|
46
|
+
if (requiredAtLeastOneLocale && !requiredLocale && !localizedValueHasAtLeastOneLocale(value, locales)) {
|
|
47
|
+
return requiredMessage;
|
|
48
|
+
} else if (requiredLocale && (!_.has(value, requiredLocale) || _.isEmpty(_.get(value, requiredLocale)))) {
|
|
49
|
+
return { message: specificRequiredMessage, args: { locale: requiredLocale } };
|
|
50
|
+
} else if (!requiredAtLeastOneLocale && _required && !localizedValueHasAllLocales(value, locales)) {
|
|
51
|
+
return requiredMessage;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
[requiredAtLeastOneLocale, requiredLocale, locales, _required]
|
|
57
|
+
);
|
|
58
|
+
const validate = useMemo(() => {
|
|
59
|
+
return composeValidators(_validate, required());
|
|
60
|
+
}, [_validate, required]);
|
|
61
|
+
const { fieldState } = useInput({ ...props, validate });
|
|
62
|
+
const { error } = fieldState;
|
|
63
|
+
const value = useWatch({ name: source });
|
|
22
64
|
const isMissingLocalizations = !localizedValueHasAllLocales(value, locales);
|
|
23
65
|
const [currentLocale] = useLocaleState();
|
|
24
66
|
const [locale, setLocale] = useState(currentLocale);
|
|
@@ -29,6 +71,7 @@ function LocalizedTextInput(props: ILocalizedTextInputProps) {
|
|
|
29
71
|
[open, setAnchorEl]
|
|
30
72
|
);
|
|
31
73
|
const handleClose = useCallback(() => setAnchorEl(null), [setAnchorEl]);
|
|
74
|
+
|
|
32
75
|
const MenuItems: Array<ReactElement> = useMemo(() => {
|
|
33
76
|
return _.map(locales, (l, index) => {
|
|
34
77
|
return (
|
|
@@ -44,9 +87,14 @@ function LocalizedTextInput(props: ILocalizedTextInputProps) {
|
|
|
44
87
|
);
|
|
45
88
|
});
|
|
46
89
|
}, [locales, setLocale, handleClose, value]);
|
|
47
|
-
|
|
48
90
|
return (
|
|
49
|
-
<LabeledInput
|
|
91
|
+
<LabeledInput
|
|
92
|
+
{...props}
|
|
93
|
+
validate={validate}
|
|
94
|
+
helperText={
|
|
95
|
+
error?.message ? <ValidationError error={error?.message as ValidationErrorMessage} /> : props?.helperText
|
|
96
|
+
}
|
|
97
|
+
>
|
|
50
98
|
<>
|
|
51
99
|
<Box flex={1}>
|
|
52
100
|
{_.map(locales, (l, index) => {
|
|
@@ -57,6 +105,8 @@ function LocalizedTextInput(props: ILocalizedTextInputProps) {
|
|
|
57
105
|
source={`${source}.${l.locale}`}
|
|
58
106
|
sx={{ width: '100%', display: l.locale === locale ? 'inline-flex' : 'none' }}
|
|
59
107
|
label={false}
|
|
108
|
+
helperText={false}
|
|
109
|
+
required={false}
|
|
60
110
|
InputProps={{
|
|
61
111
|
endAdornment: (
|
|
62
112
|
<Chip
|
package/src/playground/App.jsx
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import { BooleanInput, SimpleForm,
|
|
1
|
+
import { BooleanInput, LocalizedTextInput, SimpleForm, required } from '@/';
|
|
2
2
|
|
|
3
|
+
function requiredAtLeastOneTranslation(message = 'ra.validation.at_least_one_translation') {
|
|
4
|
+
return (value, values) => {
|
|
5
|
+
if (!value || !Object.keys(value).length) {
|
|
6
|
+
return message;
|
|
7
|
+
}
|
|
8
|
+
return undefined;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
3
11
|
function CategoryForm(props) {
|
|
4
12
|
return (
|
|
5
13
|
<SimpleForm {...props}>
|
|
6
|
-
<
|
|
14
|
+
<LocalizedTextInput source="description" fullWidth validate={requiredAtLeastOneTranslation()} />
|
|
7
15
|
<BooleanInput source="active" />
|
|
8
16
|
</SimpleForm>
|
|
9
17
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DateField,
|
|
2
3
|
ReadonlyField,
|
|
3
4
|
SimpleForm,
|
|
4
5
|
TextInput,
|
|
@@ -16,7 +17,9 @@ function DeviceForm() {
|
|
|
16
17
|
<Grid container spacing={spacing}>
|
|
17
18
|
{record?.id ? (
|
|
18
19
|
<Grid item xs={12} sm={12}>
|
|
19
|
-
<ReadonlyField source="registrationDate" fullWidth
|
|
20
|
+
<ReadonlyField source="registrationDate" fullWidth>
|
|
21
|
+
<DateField showTime />
|
|
22
|
+
</ReadonlyField>
|
|
20
23
|
</Grid>
|
|
21
24
|
) : null}
|
|
22
25
|
<Grid item xs={12} sm={12}>
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Empty,
|
|
8
8
|
FilterButton,
|
|
9
9
|
List,
|
|
10
|
+
LocalizedTextField,
|
|
10
11
|
SearchInput,
|
|
11
12
|
SimpleForm,
|
|
12
13
|
TextField,
|
|
@@ -51,7 +52,7 @@ function CategoryList() {
|
|
|
51
52
|
empty={<Empty actions={<CategoryAddButton />} />}
|
|
52
53
|
>
|
|
53
54
|
<Datagrid>
|
|
54
|
-
<
|
|
55
|
+
<LocalizedTextField source="description" />
|
|
55
56
|
<EditInDialogButton>
|
|
56
57
|
<CategoryForm />
|
|
57
58
|
</EditInDialogButton>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
let environment = 'PRODUCTION';
|
|
2
2
|
let appUrl = `//${document.location.host}/`;
|
|
3
|
-
if (appUrl.endsWith(':3000/')
|
|
4
|
-
|
|
3
|
+
if (appUrl.endsWith(':3000/')) {
|
|
4
|
+
const callingHostOrIp = document.location.host.split(':')[0];
|
|
5
|
+
appUrl = `//${callingHostOrIp}:8080/`;
|
|
5
6
|
environment = 'DEVELOPER';
|
|
6
7
|
}
|
|
7
|
-
const APP_NAME = '
|
|
8
|
+
const APP_NAME = 'Applica';
|
|
8
9
|
const APP_URL = appUrl;
|
|
9
10
|
const API_URL = `${APP_URL}api`;
|
|
10
11
|
const ENVIRONMENT = environment;
|
package/src/playground/menu.jsx
CHANGED
|
@@ -20,13 +20,6 @@ export const menu = [
|
|
|
20
20
|
type: 'item',
|
|
21
21
|
url: '/entities/notification',
|
|
22
22
|
icon: NotificationOutlined
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: 'entities/order',
|
|
26
|
-
title: 'ra.menu.item.order',
|
|
27
|
-
type: 'item',
|
|
28
|
-
url: '/entities/order',
|
|
29
|
-
icon: TableOutlined
|
|
30
23
|
}
|
|
31
24
|
]
|
|
32
25
|
},
|
|
@@ -16,6 +16,15 @@ function localizedValueHasAllLocales<T>(
|
|
|
16
16
|
return !_.some(locales, (l) => isEmpty((value ?? {})[l.locale]));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function localizedValueHasAtLeastOneLocale<T>(
|
|
20
|
+
value: ILocalizedValue<T> = {},
|
|
21
|
+
locales: Array<ILocale> = [],
|
|
22
|
+
options: { isEmpty?: (v: T) => boolean } = {}
|
|
23
|
+
): boolean {
|
|
24
|
+
const { isEmpty = _.isEmpty } = options;
|
|
25
|
+
return _.some(locales, (l) => !isEmpty((value ?? {})[l.locale]));
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
type IGetLocalizedValueOptions = {
|
|
20
29
|
fallback?: boolean;
|
|
21
30
|
fallbackLocales?: Array<string>;
|
|
@@ -52,4 +61,4 @@ function getLocalizedValue<T>(
|
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
export type { IGetLocalizedValueOptions, ILocalizedValue };
|
|
55
|
-
export { getLocalizedValue, localizedValueHasAllLocales };
|
|
64
|
+
export { getLocalizedValue, localizedValueHasAllLocales, localizedValueHasAtLeastOneLocale };
|