From caa3f50c240695db39a97fddee24d8d7488526aa Mon Sep 17 00:00:00 2001 From: Deaponn Date: Mon, 15 Dec 2025 08:59:34 +0100 Subject: [PATCH 1/3] fix: converting arrays to objects on save fix: properly highlighting modified toggle buttons --- .../utils/convertFormValuesToConfigObject.ts | 35 +++++++++++++++---- .../utils/getDefaultValuesFromConfigObject.ts | 6 ++++ .../webapp/app/hooks/useConfigurationForm.tsx | 17 +++++++-- .../webapp/app/routes/configuration.tsx | 1 + 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts b/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts index e177ce6a1..c1b5e4ee9 100644 --- a/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts +++ b/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts @@ -14,18 +14,29 @@ import { KEY_SEPARATOR } from '../constants'; import type { InputsType } from '~/routes/configuration'; -import type { FormObjectValue, FormValue } from '../types'; +import type { Restrictions, FormObjectValue, FormValue, ArrayRestrictions } from '../types'; +import { isArrayRestrictions, isObjectRestrictions } from '../types/helpers'; /** * Convert flat form values back to nested configuration object format. * @param {InputsType} formValues - The flat form values with prefixed keys. + * @param {Restrictions} restrictions - The object with restrictions. + * Used to determine if the current value is an object or an array * @param {string} prefix - The prefix to remove from keys (e.g., '/configuration'). * @returns {FormValue} The nested configuration object. */ export const convertFormValuesToConfigObject = ( formValues: InputsType, + restrictions: Restrictions | undefined, prefix: string, ): FormValue => { + if (restrictions === undefined) { + throw new Error('Missing restrictions parameter'); + } + + // eslint-disable-next-line no-console + console.log({ formValues, restrictions, prefix }); + const result: FormValue = {}; for (const [key, value] of Object.entries(formValues)) { @@ -48,17 +59,29 @@ export const convertFormValuesToConfigObject = ( continue; } - let current = result; + let currentValue = result; // pointer for currentValue place in the configuration + let currentRestrictions = restrictions; // pointer at currentRestrictions for (let i = 0; i < keys.length - 1; i++) { const currentKey = keys[i]; - if (!(currentKey in current) || typeof current[currentKey] !== 'object') { - current[currentKey] = {}; + currentRestrictions = isObjectRestrictions(currentRestrictions) + ? currentRestrictions[currentKey] + : (currentRestrictions as ArrayRestrictions)[0][Number(currentKey)]; + + if (!(currentKey in currentValue)) { + // we need to create next level of nesting + if (isObjectRestrictions(currentRestrictions)) { + currentValue[currentKey] = {}; + } else if (isArrayRestrictions(currentRestrictions)) { + // eslint-disable-next-line no-console + console.log({ currentRestrictions }); + currentValue[currentKey] = Array(currentRestrictions[0].length); + } } - current = current[currentKey] as FormObjectValue; + currentValue = currentValue[currentKey] as FormObjectValue; } const finalKey = keys[keys.length - 1]; - current[finalKey] = value.toString(); + currentValue[finalKey] = value; } return result; diff --git a/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts b/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts index ba4c60884..ef50fdd6d 100644 --- a/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts +++ b/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts @@ -31,6 +31,12 @@ export const getDefaultValuesFromConfigObject = ( } if (isPrimitiveValue(val)) { + if (val === 'true') { + return { [prefix]: true }; + } + if (val === 'false') { + return { [prefix]: false }; + } return { [prefix]: val }; } diff --git a/Configuration/webapp/app/hooks/useConfigurationForm.tsx b/Configuration/webapp/app/hooks/useConfigurationForm.tsx index a898d24ee..d45ef8de6 100644 --- a/Configuration/webapp/app/hooks/useConfigurationForm.tsx +++ b/Configuration/webapp/app/hooks/useConfigurationForm.tsx @@ -18,7 +18,7 @@ import { useForm, type KeepStateOptions, type SubmitHandler } from 'react-hook-f import { getDefaultValuesFromConfigObject } from '~/components/form/utils/getDefaultValuesFromConfigObject'; import { convertFormValuesToConfigObject } from '~/components/form/utils/convertFormValuesToConfigObject'; import { useLocation } from 'react-router'; -import type { FormValue } from '~/components/form/types'; +import type { FormValue, Restrictions } from '~/components/form/types'; import { useConfigurationMutation } from '~/api/mutations/useConfigurationMutation'; const RESET_PROPS: KeepStateOptions = { keepDirty: false }; @@ -34,9 +34,11 @@ const RESET_PROPS: KeepStateOptions = { keepDirty: false }; export const useConfigurationForm = ({ configuration, configurationName, + configurationRestrictions, }: { configuration: FormValue | undefined; configurationName: string; + configurationRestrictions: Restrictions | undefined; }) => { const { pathname } = useLocation(); const mutation = useConfigurationMutation(configurationName); @@ -46,13 +48,22 @@ export const useConfigurationForm = ({ [configuration, pathname], ); + // eslint-disable-next-line no-console + console.log({ defaultValues }); + const { control, handleSubmit, getValues, formState, reset } = useForm({ defaultValues, }); const onSubmit: SubmitHandler = (data) => { - const configurationData = convertFormValuesToConfigObject(data, pathname); - mutation.mutate(configurationData); + const configurationData = convertFormValuesToConfigObject( + data, + configurationRestrictions, + pathname, + ); + // eslint-disable-next-line no-console + console.log({ configurationData }); + // mutation.mutate(configurationData); }; useEffect(() => reset(defaultValues, RESET_PROPS), [defaultValues, reset]); diff --git a/Configuration/webapp/app/routes/configuration.tsx b/Configuration/webapp/app/routes/configuration.tsx index 2ed155cc6..4a625622f 100644 --- a/Configuration/webapp/app/routes/configuration.tsx +++ b/Configuration/webapp/app/routes/configuration.tsx @@ -44,6 +44,7 @@ const ConfigurationPage = () => { } = useConfigurationForm({ configuration, configurationName, + configurationRestrictions, }); const { showModal, handleProceed, handleSaveAndProceed, handleCancel } = useUnsavedChangesBlocker( From 6e2f396494b92ca1fff148db7d1252afaf692240 Mon Sep 17 00:00:00 2001 From: Deaponn Date: Mon, 15 Dec 2025 09:46:50 +0100 Subject: [PATCH 2/3] fix: handle empty objects and arrays properly --- .../form/utils/getDefaultValuesFromConfigObject.ts | 9 +++++++++ Configuration/webapp/app/hooks/useConfigurationForm.tsx | 7 +------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts b/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts index ef50fdd6d..437cb0e38 100644 --- a/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts +++ b/Configuration/webapp/app/components/form/utils/getDefaultValuesFromConfigObject.ts @@ -47,5 +47,14 @@ export const getDefaultValuesFromConfigObject = ( result = { ...result, ...getDefaultValuesFromConfigObject(value, newPrefix) }; } + // this is an exception where the empty object / empty array is the leaf + // of the Configuration Form tree, because it is empty + // however we still need to render that in the UI, so this bit of info needs to be present + if (entries.length === 0) { + const emptyPrefix = `${prefix}${KEY_SEPARATOR}`; + // bypass typescript since this is an exception from the usual logic of the application + result = { [emptyPrefix]: val as unknown as string }; + } + return result; }; diff --git a/Configuration/webapp/app/hooks/useConfigurationForm.tsx b/Configuration/webapp/app/hooks/useConfigurationForm.tsx index d45ef8de6..aa46e693b 100644 --- a/Configuration/webapp/app/hooks/useConfigurationForm.tsx +++ b/Configuration/webapp/app/hooks/useConfigurationForm.tsx @@ -48,9 +48,6 @@ export const useConfigurationForm = ({ [configuration, pathname], ); - // eslint-disable-next-line no-console - console.log({ defaultValues }); - const { control, handleSubmit, getValues, formState, reset } = useForm({ defaultValues, }); @@ -61,9 +58,7 @@ export const useConfigurationForm = ({ configurationRestrictions, pathname, ); - // eslint-disable-next-line no-console - console.log({ configurationData }); - // mutation.mutate(configurationData); + mutation.mutate(configurationData); }; useEffect(() => reset(defaultValues, RESET_PROPS), [defaultValues, reset]); From 11ac692589e75f6ec24c6e8e64320275f2583237 Mon Sep 17 00:00:00 2001 From: Deaponn Date: Mon, 15 Dec 2025 10:20:25 +0100 Subject: [PATCH 3/3] chore: remove console logs --- .../form/utils/convertFormValuesToConfigObject.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts b/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts index c1b5e4ee9..bbb844ffb 100644 --- a/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts +++ b/Configuration/webapp/app/components/form/utils/convertFormValuesToConfigObject.ts @@ -34,9 +34,6 @@ export const convertFormValuesToConfigObject = ( throw new Error('Missing restrictions parameter'); } - // eslint-disable-next-line no-console - console.log({ formValues, restrictions, prefix }); - const result: FormValue = {}; for (const [key, value] of Object.entries(formValues)) { @@ -60,7 +57,7 @@ export const convertFormValuesToConfigObject = ( } let currentValue = result; // pointer for currentValue place in the configuration - let currentRestrictions = restrictions; // pointer at currentRestrictions + let currentRestrictions = restrictions; // pointer for currentRestrictions for (let i = 0; i < keys.length - 1; i++) { const currentKey = keys[i]; currentRestrictions = isObjectRestrictions(currentRestrictions) @@ -72,8 +69,6 @@ export const convertFormValuesToConfigObject = ( if (isObjectRestrictions(currentRestrictions)) { currentValue[currentKey] = {}; } else if (isArrayRestrictions(currentRestrictions)) { - // eslint-disable-next-line no-console - console.log({ currentRestrictions }); currentValue[currentKey] = Array(currentRestrictions[0].length); } }