From 2cd28856dfb576f99d0ed3d7e91ffc36d4d0135a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 15 Feb 2023 23:06:25 -0500 Subject: [PATCH] fix(striker-ui): generate fence parameter inputs --- striker-ui/components/AddFenceDeviceForm.tsx | 157 +++++++++++++++++-- striker-ui/types/APIFence.d.ts | 11 +- 2 files changed, 154 insertions(+), 14 deletions(-) diff --git a/striker-ui/components/AddFenceDeviceForm.tsx b/striker-ui/components/AddFenceDeviceForm.tsx index b30b7150..fd67584d 100644 --- a/striker-ui/components/AddFenceDeviceForm.tsx +++ b/striker-ui/components/AddFenceDeviceForm.tsx @@ -1,13 +1,19 @@ -import { Box } from '@mui/material'; -import { FC, useMemo, useState } from 'react'; -import useIsFirstRender from '../hooks/useIsFirstRender'; -import useProtectedState from '../hooks/useProtectedState'; +import { Box, Switch } from '@mui/material'; +import { FC, ReactElement, ReactNode, useMemo, useState } from 'react'; + import api from '../lib/api'; -import handleAPIError from '../lib/handleAPIError'; import Autocomplete from './Autocomplete'; +import ContainedButton from './ContainedButton'; import FlexBox from './FlexBox'; +import handleAPIError from '../lib/handleAPIError'; +import InputWithRef from './InputWithRef'; +import OutlinedInputWithLabel from './OutlinedInputWithLabel'; +import { ExpandablePanel } from './Panels'; +import SelectWithLabel from './SelectWithLabel'; import Spinner from './Spinner'; import { BodyText } from './Text'; +import useIsFirstRender from '../hooks/useIsFirstRender'; +import useProtectedState from '../hooks/useProtectedState'; type FenceDeviceAutocompleteOption = { fenceDeviceDescription: string; @@ -15,13 +21,54 @@ type FenceDeviceAutocompleteOption = { label: string; }; +type FenceParameterInputBuilder = (args: { + id: string; + isChecked?: boolean; + isRequired?: boolean; + label?: string; + selectOptions?: string[]; + value?: string; +}) => ReactElement; + +const MAP_TO_INPUT_BUILDER: Partial< + Record, FenceParameterInputBuilder> +> & { string: FenceParameterInputBuilder } = { + boolean: ({ id, isChecked = false, label }) => ( + + {label} + + + ), + select: ({ id, isRequired, label, selectOptions = [], value = '' }) => ( + + } + required={isRequired} + /> + ), + string: ({ id, isRequired, label = '', value }) => ( + } + required={isRequired} + /> + ), +}; + const AddFenceDeivceForm: FC = () => { const isFirstRender = useIsFirstRender(); const [fenceDeviceTemplate, setFenceDeviceTemplate] = useProtectedState< APIFenceTemplate | undefined >(undefined); - const [inputFenceDeviceTypeValue, setInputFenceDeviceTypeValue] = + const [fenceDeviceTypeValue, setInputFenceDeviceTypeValue] = useState(null); const [isLoadingTemplate, setIsLoadingTemplate] = useProtectedState(true); @@ -47,7 +94,7 @@ const AddFenceDeivceForm: FC = () => { [fenceDeviceTemplate], ); - const autocompleteFenceDeviceType = useMemo( + const fenceDeviceTypeElement = useMemo( () => ( { {fenceDeviceDescription} )} - value={inputFenceDeviceTypeValue} + value={fenceDeviceTypeValue} /> ), - [fenceDeviceTypeOptions, inputFenceDeviceTypeValue], + [fenceDeviceTypeOptions, fenceDeviceTypeValue], ); + const fenceParameterElements = useMemo(() => { + let result: ReactNode; + + if (fenceDeviceTemplate && fenceDeviceTypeValue) { + const { fenceDeviceId } = fenceDeviceTypeValue; + const { parameters: fenceDeviceParameters } = + fenceDeviceTemplate[fenceDeviceId]; + + const { optional: optionalInputs, required: requiredInputs } = + Object.entries(fenceDeviceParameters).reduce<{ + optional: ReactElement[]; + required: ReactElement[]; + }>( + ( + previous, + [ + parameterId, + { + content_type: contentType, + default: parameterDefault, + options: parameterSelectOptions, + required: isRequired, + }, + ], + ) => { + const { optional, required } = previous; + const buildInput = + MAP_TO_INPUT_BUILDER[contentType] ?? MAP_TO_INPUT_BUILDER.string; + + const fenceJoinParameterId = `${fenceDeviceId}-${parameterId}`; + const parameterIsRequired = isRequired === '1'; + const parameterInput = buildInput({ + id: fenceJoinParameterId, + isChecked: parameterDefault === '1', + isRequired: parameterIsRequired, + label: parameterId, + selectOptions: parameterSelectOptions, + value: parameterDefault, + }); + + if (parameterIsRequired) { + required.push(parameterInput); + } else { + optional.push(parameterInput); + } + + return previous; + }, + { + optional: [], + required: [ + MAP_TO_INPUT_BUILDER.string({ + id: `${fenceDeviceId}-name`, + isRequired: true, + label: 'Fence device name', + }), + ], + }, + ); + + result = ( + <> + + {requiredInputs} + + + {optionalInputs} + + + ); + } + + return result; + }, [fenceDeviceTemplate, fenceDeviceTypeValue]); const formContent = useMemo( () => isLoadingTemplate ? ( ) : ( - <>{autocompleteFenceDeviceType} + { + event.preventDefault(); + }} + sx={{ '& > div': { marginBottom: 0 } }} + > + {fenceDeviceTypeElement} + {fenceParameterElements} + + Add fence device + + ), - [autocompleteFenceDeviceType, isLoadingTemplate], + [fenceDeviceTypeElement, fenceParameterElements, isLoadingTemplate], ); if (isFirstRender) { @@ -119,7 +252,7 @@ const AddFenceDeivceForm: FC = () => { }); } - return {formContent}; + return <>{formContent}; }; export default AddFenceDeivceForm; diff --git a/striker-ui/types/APIFence.d.ts b/striker-ui/types/APIFence.d.ts index e44d522d..2ce11650 100644 --- a/striker-ui/types/APIFence.d.ts +++ b/striker-ui/types/APIFence.d.ts @@ -1,11 +1,18 @@ +type FenceParameterType = + | 'boolean' + | 'integer' + | 'second' + | 'select' + | 'string'; + type APIFenceTemplate = { [fenceId: string]: { actions: string[]; description: string; parameters: { [parameterId: string]: { - content_type: 'boolean' | 'integer' | 'second' | 'select' | 'string'; - default: string; + content_type: FenceParameterType; + default?: string; deprecated: number; description: string; obsoletes: number;