diff options
Diffstat (limited to 'packages/aml-backoffice-ui/src/hooks/form.ts')
-rw-r--r-- | packages/aml-backoffice-ui/src/hooks/form.ts | 153 |
1 files changed, 121 insertions, 32 deletions
diff --git a/packages/aml-backoffice-ui/src/hooks/form.ts b/packages/aml-backoffice-ui/src/hooks/form.ts index e14e29819..e9194d86d 100644 --- a/packages/aml-backoffice-ui/src/hooks/form.ts +++ b/packages/aml-backoffice-ui/src/hooks/form.ts @@ -15,12 +15,18 @@ */ import { + AbsoluteTime, AmountJson, TalerExchangeApi, TranslatedString, } from "@gnu-taler/taler-util"; +import { + UIFieldHandler, + UIFormFieldConfig, + UIHandlerId, +} from "@gnu-taler/web-util/browser"; import { useState } from "preact/hooks"; -import { UIField } from "@gnu-taler/web-util/browser"; +import { undefinedIfEmpty } from "../pages/CreateAccount.js"; // export type UIField = { // value: string | undefined; @@ -28,13 +34,13 @@ import { UIField } from "@gnu-taler/web-util/browser"; // error: TranslatedString | undefined; // }; -type FormHandler<T> = { +export type FormHandler<T> = { [k in keyof T]?: T[k] extends string - ? UIField + ? UIFieldHandler : T[k] extends AmountJson - ? UIField + ? UIFieldHandler : T[k] extends TalerExchangeApi.AmlState - ? UIField + ? UIFieldHandler : FormHandler<T[k]>; }; @@ -57,9 +63,11 @@ export type FormErrors<T> = { ? TranslatedString : T[k] extends AmountJson ? TranslatedString - : T[k] extends TalerExchangeApi.AmlState + : T[k] extends AbsoluteTime ? TranslatedString - : FormErrors<T[k]>; + : T[k] extends TalerExchangeApi.AmlState + ? TranslatedString + : FormErrors<T[k]>; }; export type FormStatus<T> = @@ -75,42 +83,32 @@ export type FormStatus<T> = }; function constructFormHandler<T>( + shape: Array<UIHandlerId>, form: RecursivePartial<FormValues<T>>, updateForm: (d: RecursivePartial<FormValues<T>>) => void, errors: FormErrors<T> | undefined, ): FormHandler<T> { - const keys = Object.keys(form) as Array<keyof T>; + const handler = shape.reduce((handleForm, fieldId) => { + const path = fieldId.split("."); - const handler = keys.reduce((prev, fieldName) => { - const currentValue: unknown = form[fieldName]; - const currentError: unknown = - errors !== undefined ? errors[fieldName] : undefined; function updater(newValue: unknown) { - updateForm({ ...form, [fieldName]: newValue }); - } - /** - * There is no clear way to know if this object is a custom field - * or a group of fields - */ - if (typeof currentValue === "object") { - // @ts-expect-error FIXME better typing - const group = constructFormHandler(currentValue, updater, currentError); - // @ts-expect-error FIXME better typing - prev[fieldName] = group; - return prev; + updateForm(setValueDeeper(form, path, newValue)); } - const field: UIField = { - // @ts-expect-error FIXME better typing + const currentValue = getValueDeeper<string>(form as any, path, undefined); + const currentError = getValueDeeper<TranslatedString>( + errors as any, + path, + undefined, + ); + const field: UIFieldHandler = { error: currentError, - // @ts-expect-error FIXME better typing value: currentValue, onChange: updater, - state: {}, + state: {}, //FIXME: add the state of the field (hidden, ) }; - // @ts-expect-error FIXME better typing - prev[fieldName] = field; - return prev; + + return setValueDeeper(handleForm, path, field); }, {} as FormHandler<T>); return handler; @@ -125,6 +123,7 @@ function constructFormHandler<T>( * @returns */ export function useFormState<T>( + shape: Array<UIHandlerId>, defaultValue: RecursivePartial<FormValues<T>>, check: (f: RecursivePartial<FormValues<T>>) => FormStatus<T>, ): [FormHandler<T>, FormStatus<T>] { @@ -132,7 +131,97 @@ export function useFormState<T>( useState<RecursivePartial<FormValues<T>>>(defaultValue); const status = check(form); - const handler = constructFormHandler(form, updateForm, status.errors); + const handler = constructFormHandler(shape, form, updateForm, status.errors); return [handler, status]; } + +interface Tree<T> extends Record<string, Tree<T> | T> {} + +export function getValueDeeper<T>( + object: Tree<T> | undefined, + names: string[], + notFoundValue?: T, +): T | undefined { + if (names.length === 0) return object as T; + const [head, ...rest] = names; + if (!head) { + return getValueDeeper(object, rest, notFoundValue); + } + if (object === undefined) { + return notFoundValue; + } + return getValueDeeper(object[head] as Tree<T>, rest, notFoundValue); +} + +export function setValueDeeper(object: any, names: string[], value: any): any { + if (names.length === 0) return value; + const [head, ...rest] = names; + if (!head) { + return setValueDeeper(object, rest, value); + } + if (object === undefined) { + return undefinedIfEmpty({ [head]: setValueDeeper({}, rest, value) }); + } + return undefinedIfEmpty({ ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) }); +} + +export function getShapeFromFields( + fields: UIFormFieldConfig[], +): Array<UIHandlerId> { + const shape: Array<UIHandlerId> = []; + fields.forEach((field) => { + if ("id" in field.properties) { + // FIXME: this should be a validation when loading the form + // consistency check + if (shape.indexOf(field.properties.id) !== -1) { + throw Error(`already present: ${field.properties.id}`); + } + shape.push(field.properties.id); + } else if (field.type === "group") { + Array.prototype.push.apply( + shape, + getShapeFromFields(field.properties.fields), + ); + } + }); + return shape; +} + +export function getRequiredFields( + fields: UIFormFieldConfig[], +): Array<UIHandlerId> { + const shape: Array<UIHandlerId> = []; + fields.forEach((field) => { + if ("id" in field.properties) { + // FIXME: this should be a validation when loading the form + // consistency check + if (shape.indexOf(field.properties.id) !== -1) { + throw Error(`already present: ${field.properties.id}`); + } + if (!field.properties.required) { + return; + } + shape.push(field.properties.id); + } else if (field.type === "group") { + Array.prototype.push.apply( + shape, + getRequiredFields(field.properties.fields), + ); + } + }); + return shape; +} +export function validateRequiredFields<FormType>( + errors: FormErrors<FormType> | undefined, + form: object, + fields: Array<UIHandlerId>, +): FormErrors<FormType> | undefined { + let result: FormErrors<FormType> | undefined = errors; + fields.forEach((f) => { + const path = f.split("."); + const v = getValueDeeper(form as any, path); + result = setValueDeeper(result, path, !v ? "required" : undefined); + }); + return result; +} |