Merge pull request #216 from stephb9959/main

[WIFI-13515] Supporting deviceTypes in lowercase
This commit is contained in:
Charles Bourque
2024-03-15 17:53:25 +01:00
committed by GitHub
7 changed files with 160 additions and 104 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "3.0.2(8)", "version": "3.0.2(9)",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ucentral-client", "name": "ucentral-client",
"version": "3.0.2(8)", "version": "3.0.2(9)",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@chakra-ui/anatomy": "^2.1.1", "@chakra-ui/anatomy": "^2.1.1",

View File

@@ -1,6 +1,6 @@
{ {
"name": "ucentral-client", "name": "ucentral-client",
"version": "3.0.2(8)", "version": "3.0.2(9)",
"description": "", "description": "",
"private": true, "private": true,
"main": "index.tsx", "main": "index.tsx",

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { FormControl, FormErrorMessage, FormLabel } from '@chakra-ui/react'; import { FormControl, FormErrorMessage, FormLabel } from '@chakra-ui/react';
import { Select } from 'chakra-react-select'; import { CreatableSelect, Select } from 'chakra-react-select';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import isEqual from 'react-fast-compare'; import isEqual from 'react-fast-compare';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -25,6 +25,7 @@ const propTypes = {
isHidden: PropTypes.bool, isHidden: PropTypes.bool,
isPortal: PropTypes.bool.isRequired, isPortal: PropTypes.bool.isRequired,
definitionKey: PropTypes.string, definitionKey: PropTypes.string,
isCreatable: PropTypes.bool,
}; };
const defaultProps = { const defaultProps = {
@@ -36,6 +37,7 @@ const defaultProps = {
isDisabled: false, isDisabled: false,
isHidden: false, isHidden: false,
definitionKey: null, definitionKey: null,
isCreatable: false,
}; };
const FastMultiSelectInput = ({ const FastMultiSelectInput = ({
@@ -50,6 +52,7 @@ const FastMultiSelectInput = ({
isRequired, isRequired,
isDisabled, isDisabled,
isHidden, isHidden,
isCreatable,
isPortal, isPortal,
definitionKey, definitionKey,
}) => { }) => {
@@ -61,35 +64,62 @@ const FastMultiSelectInput = ({
{label} {label}
<ConfigurationFieldExplanation definitionKey={definitionKey} /> <ConfigurationFieldExplanation definitionKey={definitionKey} />
</FormLabel> </FormLabel>
<Select {isCreatable ? (
chakraStyles={{ <CreatableSelect
control: (provided, { isDisabled: isControlDisabled }) => ({ chakraStyles={{
...provided, control: (provided, { isDisabled: isControlDisabled }) => ({
borderRadius: '15px', ...provided,
opacity: isControlDisabled ? '0.8 !important' : '1', borderRadius: '15px',
border: '2px solid', opacity: isControlDisabled ? '0.8 !important' : '1',
}), border: '2px solid',
dropdownIndicator: (provided) => ({ }),
...provided, dropdownIndicator: (provided) => ({
backgroundColor: 'unset', ...provided,
border: 'unset', backgroundColor: 'unset',
}), border: 'unset',
}} }),
classNamePrefix={isPortal ? 'chakra-react-select' : ''} }}
menuPortalTarget={isPortal ? document.body : undefined} classNamePrefix={isPortal ? 'chakra-react-select' : ''}
isMulti menuPortalTarget={isPortal ? document.body : undefined}
closeMenuOnSelect={false} isMulti
options={canSelectAll ? [{ value: '*', label: t('common.all') }, ...options] : options} closeMenuOnSelect={false}
value={ options={options}
value?.map((val) => { value={value}
if (val === '*') return { value: val, label: t('common.all') }; onChange={onChange}
return options.find((opt) => opt.value === val); onBlur={onBlur}
}) ?? [] isDisabled={isDisabled}
} />
onChange={onChange} ) : (
onBlur={onBlur} <Select
isDisabled={isDisabled} chakraStyles={{
/> control: (provided, { isDisabled: isControlDisabled }) => ({
...provided,
borderRadius: '15px',
opacity: isControlDisabled ? '0.8 !important' : '1',
border: '2px solid',
}),
dropdownIndicator: (provided) => ({
...provided,
backgroundColor: 'unset',
border: 'unset',
}),
}}
classNamePrefix={isPortal ? 'chakra-react-select' : ''}
menuPortalTarget={isPortal ? document.body : undefined}
isMulti
closeMenuOnSelect={false}
options={canSelectAll ? [{ value: '*', label: t('common.all') }, ...options] : options}
value={
value?.map((val) => {
if (val === '*') return { value: val, label: t('common.all') };
return options.find((opt) => opt.value === val);
}) ?? []
}
onChange={onChange}
onBlur={onBlur}
isDisabled={isDisabled}
/>
)}
<FormErrorMessage>{error}</FormErrorMessage> <FormErrorMessage>{error}</FormErrorMessage>
</FormControl> </FormControl>
); );

View File

@@ -20,6 +20,7 @@ const propTypes = {
canSelectAll: PropTypes.bool, canSelectAll: PropTypes.bool,
isPortal: PropTypes.bool, isPortal: PropTypes.bool,
definitionKey: PropTypes.string, definitionKey: PropTypes.string,
isCreatable: PropTypes.bool,
}; };
const defaultProps = { const defaultProps = {
@@ -31,6 +32,7 @@ const defaultProps = {
canSelectAll: false, canSelectAll: false,
isPortal: false, isPortal: false,
definitionKey: null, definitionKey: null,
isCreatable: false,
}; };
const MultiSelectField = ({ const MultiSelectField = ({
@@ -43,25 +45,39 @@ const MultiSelectField = ({
emptyIsUndefined, emptyIsUndefined,
canSelectAll, canSelectAll,
hasVirtualAll, hasVirtualAll,
isCreatable,
isPortal, isPortal,
definitionKey, definitionKey,
}) => { }) => {
const [{ value }, { touched, error }, { setValue, setTouched }] = useField(name); const [{ value }, { touched, error }, { setValue, setTouched }] = useField(name);
const onChange = useCallback((option) => { const onChange = useCallback(
const allIndex = option.findIndex((opt) => opt.value === '*'); (option) => {
if (option.length === 0 && emptyIsUndefined) { if (isCreatable) {
setValue(undefined); if (typeof option === 'string') {
} else if (allIndex === 0 && option.length > 1) { setValue([...value, option]);
const newValues = option.slice(1); } else {
setValue(newValues.map((val) => val.value)); setValue(option);
} else if (allIndex >= 0) { }
if (!hasVirtualAll) setValue(['*']);
else setValue(options.map(({ value: v }) => v)); // setValue([...value, option]);
} else if (option.length > 0) setValue(option.map((val) => val.value)); } else {
else setValue([]); const allIndex = option.findIndex((opt) => opt.value === '*');
setTouched(true); if (option.length === 0 && emptyIsUndefined) {
}, []); setValue(undefined);
} else if (allIndex === 0 && option.length > 1) {
const newValues = option.slice(1);
setValue(newValues.map((val) => val.value));
} else if (allIndex >= 0) {
if (!hasVirtualAll) setValue(['*']);
else setValue(options.map(({ value: v }) => v));
} else if (option.length > 0) setValue(option.map((val) => val.value));
else setValue([]);
setTouched(true);
}
},
[value],
);
const onFieldBlur = useCallback(() => { const onFieldBlur = useCallback(() => {
setTouched(true); setTouched(true);
@@ -82,6 +98,7 @@ const MultiSelectField = ({
isHidden={isHidden} isHidden={isHidden}
isPortal={isPortal} isPortal={isPortal}
definitionKey={definitionKey} definitionKey={definitionKey}
isCreatable={isCreatable}
/> />
); );
}; };

View File

@@ -69,35 +69,38 @@ const CreateDefaultConfigurationModal = () => {
key={formKey} key={formKey}
validationSchema={DefaultConfigurationSchema(t)} validationSchema={DefaultConfigurationSchema(t)}
onSubmit={(data, { setSubmitting, resetForm }) => { onSubmit={(data, { setSubmitting, resetForm }) => {
createConfig.mutateAsync(data, { createConfig.mutateAsync(
onSuccess: () => { { ...data, modelIds: data.modelIds.map((v) => v.value) },
toast({ {
id: `config-create-success`, onSuccess: () => {
title: t('common.success'), toast({
description: t('controller.configurations.create_success'), id: `config-create-success`,
status: 'success', title: t('common.success'),
duration: 5000, description: t('controller.configurations.create_success'),
isClosable: true, status: 'success',
position: 'top-right', duration: 5000,
}); isClosable: true,
setSubmitting(false); position: 'top-right',
resetForm(); });
modalProps.onClose(); setSubmitting(false);
resetForm();
modalProps.onClose();
},
onError: (error) => {
const e = error as AxiosError;
toast({
id: `config-create-error`,
title: t('common.error'),
description: e?.response?.data?.ErrorDescription,
status: 'error',
duration: 5000,
isClosable: true,
position: 'top-right',
});
setSubmitting(false);
},
}, },
onError: (error) => { );
const e = error as AxiosError;
toast({
id: `config-create-error`,
title: t('common.error'),
description: e?.response?.data?.ErrorDescription,
status: 'error',
duration: 5000,
isClosable: true,
position: 'top-right',
});
setSubmitting(false);
},
});
}} }}
> >
<Box> <Box>
@@ -133,6 +136,7 @@ const CreateDefaultConfigurationModal = () => {
value: devType, value: devType,
})) ?? [] })) ?? []
} }
isCreatable
isRequired isRequired
/> />
<StringField name="configuration" label={t('configurations.one')} isArea isDisabled={isDisabled} mt={4} /> <StringField name="configuration" label={t('configurations.one')} isArea isDisabled={isDisabled} mt={4} />

View File

@@ -70,40 +70,44 @@ const EditDefaultConfiguration = ({ modalProps, config }: Props) => {
innerRef={formRef as React.Ref<FormikProps<DefaultConfigurationResponse>>} innerRef={formRef as React.Ref<FormikProps<DefaultConfigurationResponse>>}
initialValues={{ initialValues={{
...config, ...config,
modelIds: config.modelIds.map((v) => ({ label: v, value: v })),
configuration: JSON.stringify(config.configuration, null, 2), configuration: JSON.stringify(config.configuration, null, 2),
}} }}
key={formKey} key={formKey}
validationSchema={DefaultConfigurationSchema(t)} validationSchema={DefaultConfigurationSchema(t)}
onSubmit={(data, { setSubmitting, resetForm }) => { onSubmit={(data, { setSubmitting, resetForm }) => {
updateConfig.mutateAsync(data, { updateConfig.mutateAsync(
onSuccess: () => { { ...data, modelIds: data.modelIds.map((v) => v.value) },
toast({ {
id: `config-edit-success`, onSuccess: () => {
title: t('common.success'), toast({
description: t('controller.configurations.update_success'), id: `config-edit-success`,
status: 'success', title: t('common.success'),
duration: 5000, description: t('controller.configurations.update_success'),
isClosable: true, status: 'success',
position: 'top-right', duration: 5000,
}); isClosable: true,
setSubmitting(false); position: 'top-right',
resetForm(); });
modalProps.onClose(); setSubmitting(false);
resetForm();
modalProps.onClose();
},
onError: (error) => {
const e = error as AxiosError;
toast({
id: `config-edit-error`,
title: t('common.error'),
description: e?.response?.data?.ErrorDescription,
status: 'error',
duration: 5000,
isClosable: true,
position: 'top-right',
});
setSubmitting(false);
},
}, },
onError: (error) => { );
const e = error as AxiosError;
toast({
id: `config-edit-error`,
title: t('common.error'),
description: e?.response?.data?.ErrorDescription,
status: 'error',
duration: 5000,
isClosable: true,
position: 'top-right',
});
setSubmitting(false);
},
});
}} }}
> >
<Box> <Box>
@@ -139,6 +143,7 @@ const EditDefaultConfiguration = ({ modalProps, config }: Props) => {
value: devType, value: devType,
})) ?? [] })) ?? []
} }
isCreatable
isRequired isRequired
/> />
<StringField <StringField

View File

@@ -6,7 +6,7 @@ export const DefaultConfigurationSchema = (t: (str: string) => string) =>
.shape({ .shape({
name: Yup.string().required(t('form.required')), name: Yup.string().required(t('form.required')),
description: Yup.string(), description: Yup.string(),
modelIds: Yup.array().of(Yup.string()).required(t('form.required')).min(1, t('form.required')), modelIds: Yup.array().of(Yup.object()).required(t('form.required')).min(1, t('form.required')),
platform: Yup.string().oneOf(['ap', 'switch']).required(t('form.required')), platform: Yup.string().oneOf(['ap', 'switch']).required(t('form.required')),
configuration: Yup.string() configuration: Yup.string()
.required(t('form.required')) .required(t('form.required'))