diff options
Diffstat (limited to 'packages/aml-backoffice-ui/src/pages')
6 files changed, 176 insertions, 92 deletions
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx index 62c54d222..11b6d053e 100644 --- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx @@ -45,6 +45,7 @@ import { privatePages } from "../Routing.js"; import { FormMetadata, useUiFormsContext } from "../context/ui-forms.js"; import { useCaseDetails } from "../hooks/useCaseDetails.js"; import { ShowConsolidated } from "./ShowConsolidated.js"; +import { preloadedForms } from "../forms/index.js"; export type AmlEvent = | AmlFormEvent @@ -164,6 +165,7 @@ export function CaseDetails({ account }: { account: string }) { const details = useCaseDetails(account); const {forms} = useUiFormsContext() + const allForms = [...forms, ...preloadedForms(i18n)] if (!details) { return <Loading />; } @@ -183,7 +185,7 @@ export function CaseDetails({ account }: { account: string }) { } const { aml_history, kyc_attributes } = details.body; - const events = getEventsFromAmlHistory(aml_history, kyc_attributes, i18n, forms); + const events = getEventsFromAmlHistory(aml_history, kyc_attributes, i18n, allForms); if (showForm !== undefined) { return ( diff --git a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx index f04d404d0..bbfa65ca7 100644 --- a/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseUpdate.tsx @@ -20,13 +20,15 @@ import { HttpStatusCode, TalerExchangeApi, TalerProtocolTimestamp, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { + Addon, Button, InternationalizationAPI, LocalNotificationBanner, RenderAllFieldsByUiConfig, + StringConverter, UIFieldHandler, UIFormField, useExchangeApiContext, @@ -37,14 +39,19 @@ import { Fragment, VNode, h } from "preact"; import { privatePages } from "../Routing.js"; import { FormMetadata, + UIFieldBaseDescription, + UIFormFieldBaseConfig, UIFormFieldConfig, + UIHandlerId, useUiFormsContext, } from "../context/ui-forms.js"; import { preloadedForms } from "../forms/index.js"; -import { FormHandler, useFormState } from "../hooks/form.js"; +import { FormErrors, FormHandler, useFormState } from "../hooks/form.js"; import { useOfficer } from "../hooks/officer.js"; import { Justification } from "./CaseDetails.js"; import { HandleAccountNotReady } from "./HandleAccountNotReady.js"; +import { undefinedIfEmpty } from "./CreateAccount.js"; +import { getConverterById } from "../utils/converter.js"; function searchForm( i18n: InternationalizationAPI, @@ -67,6 +74,7 @@ type FormType = { when: AbsoluteTime; state: TalerExchangeApi.AmlState; threshold: AmountJson; + comment: string; }; export function CaseUpdate({ @@ -89,6 +97,7 @@ export function CaseUpdate({ when: AbsoluteTime.now(), state: TalerExchangeApi.AmlState.pending, threshold: Amounts.zeroOfCurrency(config.currency), + comment: "", }; if (officer.state !== "ready") { @@ -99,27 +108,41 @@ export function CaseUpdate({ return <div>form with id {formId} not found</div>; } - let defaultValue = initial + const shape: Array<UIHandlerId> = []; theForm.config.design.forEach((section) => { section.fields.forEach((field) => { if ("id" in field.properties) { - const path = field.properties.id.split("."); - defaultValue = setValueDeeper(defaultValue, path, undefined); + shape.push(field.properties.id); + // const path = field.properties.id.split("."); + // defaultValue = setValueDeeper(defaultValue, path, undefined); } }); }); - - const [form, state] = useFormState<FormType>(defaultValue, (st) => { + const [form, state] = useFormState<FormType>(shape, initial, (st) => { + const errors = undefinedIfEmpty<FormErrors<FormType>>({ + state: !st.state ? i18n.str`required` : undefined, + threshold: !st.threshold ? i18n.str`required` : undefined, + when: !st.when ? i18n.str`required` : undefined, + comment: !st.comment ? i18n.str`required` : undefined, + }); + if (errors === undefined) { + return { + status: "ok", + result: st as any, + errors: undefined, + }; + } return { - status: "ok", + status: "fail", result: st as any, - errors: undefined, + errors, }; }); - - console.log("FORM", form) - const validatedForm = state.status === "fail" ? undefined : state.result; + + console.log("NOW FORM", form); + + const validatedForm = state.status !== "ok" ? undefined : state.result; const submitHandler = validatedForm === undefined @@ -166,75 +189,6 @@ export function CaseUpdate({ }, ); - function convertUiField( - fieldConfig: UIFormFieldConfig[], - form: FormHandler<unknown>, - ): UIFormField[] { - return fieldConfig.map((config) => { - switch (config.type) { - case "absoluteTime": { - return undefined!; - } - case "amount": { - return { - type: "amount", - properties: { - ...(config.properties as any), - handler: getValueDeeper(form, config.properties.id.split(".")), - }, - } as UIFormField; - } - case "array": { - return undefined!; - } - case "caption": { - return undefined!; - } - case "choiceHorizontal": { - return { - type: "choiceHorizontal", - properties: { - handler: getValueDeeper(form, config.properties.id.split(".")), - choices: config.properties.choices, - }, - } as UIFormField; - } - case "choiceStacked": - case "file": - case "group": - case "integer": - case "selectMultiple": - case "selectOne": { - return undefined!; - } - case "text": { - return { - type: "text", - properties: { - ...(config.properties as any), - handler: getValueDeeper(form, config.properties.id.split(".")), - }, - } as UIFormField; - } - case "textArea": { - return { - type: "text", - properties: { - ...(config.properties as any), - handler: getValueDeeper(form, config.properties.id.split(".")), - }, - } as UIFormField; - } - case "toggle": { - return undefined!; - } - default: { - assertUnreachable(config); - } - } - }); - } - return ( <Fragment> <LocalNotificationBanner notification={notification} /> @@ -261,7 +215,7 @@ export function CaseUpdate({ <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> <RenderAllFieldsByUiConfig key={i} - fields={convertUiField(section.fields, form)} + fields={convertUiField(i18n, section.fields, form)} /> </div> </div> @@ -281,7 +235,8 @@ export function CaseUpdate({ <Button type="submit" handler={submitHandler} - class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" + disabled={!submitHandler} + class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" > <i18n.Translate>Confirm</i18n.Translate> </Button> @@ -350,3 +305,121 @@ function setValueDeeper(object: any, names: string[], value: any): any { } return { ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) }; } + +function getAddonById(_id: string | undefined): Addon { + return undefined!; +} + + +function converInputFieldsProps( + form: FormHandler<unknown>, + p: UIFormFieldBaseConfig, +) { + return { + converter: getConverterById(p.converterId), + handler: getValueDeeper(form, p.id.split(".")), + }; +} + +function converBaseFieldsProps( + i18n_: InternationalizationAPI, + p: UIFieldBaseDescription, +) { + return { + after: getAddonById(p.addonAfterId), + before: getAddonById(p.addonBeforeId), + hidden: p.hidden, + name: p.name, + help: i18n_.str`${p.help}`, + label: i18n_.str`${p.label}`, + tooltip: i18n_.str`${p.tooltip}`, + }; +} + +function convertUiField( + i18n_: InternationalizationAPI, + fieldConfig: UIFormFieldConfig[], + form: FormHandler<unknown>, +): UIFormField[] { + return fieldConfig.map((config) => { + // NON input fields + switch (config.type) { + case "caption": { + const resp: UIFormField = { + type: config.type, + properties: converBaseFieldsProps(i18n_, config.properties), + }; + return resp; + } + case "group": { + const resp: UIFormField = { + type: config.type, + properties: { + ...converBaseFieldsProps(i18n_, config.properties), + fields: convertUiField(i18n_, config.properties.fields, form), + }, + }; + return resp; + } + } + // Input Fields + switch (config.type) { + case "absoluteTime": { + return undefined!; + } + case "amount": { + return { + type: "amount", + properties: { + ...converBaseFieldsProps(i18n_, config.properties), + ...converInputFieldsProps(form, config.properties), + }, + } as UIFormField; + } + case "array": { + return undefined!; + } + case "choiceHorizontal": { + return { + type: "choiceHorizontal", + properties: { + ...converBaseFieldsProps(i18n_, config.properties), + ...converInputFieldsProps(form, config.properties), + choices: config.properties.choices, + }, + } as UIFormField; + } + case "choiceStacked": + case "file": + case "integer": + case "selectMultiple": + case "selectOne": { + return undefined!; + } + case "text": { + return { + type: "text", + properties: { + ...converBaseFieldsProps(i18n_, config.properties), + ...converInputFieldsProps(form, config.properties), + }, + } as UIFormField; + } + case "textArea": { + return { + type: "text", + properties: { + ...converBaseFieldsProps(i18n_, config.properties), + ...converInputFieldsProps(form, config.properties), + }, + } as UIFormField; + } + case "toggle": { + return undefined!; + } + default: { + assertUnreachable(config); + } + } + }); +} diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx index 2e92c111e..3860bcd98 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx @@ -24,7 +24,7 @@ import { ErrorLoading, InputChoiceHorizontal, Loading, - useTranslationContext + useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; @@ -34,6 +34,8 @@ import { privatePages } from "../Routing.js"; import { FormErrors, RecursivePartial, useFormState } from "../hooks/form.js"; import { undefinedIfEmpty } from "./CreateAccount.js"; import { Officer } from "./Officer.js"; +import { UIHandlerId } from "../context/ui-forms.js"; +import { amlStateConverter } from "../utils/converter.js"; type FormType = { state: TalerExchangeApi.AmlState; @@ -55,6 +57,7 @@ export function CasesUI({ const { i18n } = useTranslationContext(); const [form, status] = useFormState<FormType>( + [".state"] as Array<UIHandlerId>, { state: filter, }, @@ -106,18 +109,19 @@ export function CasesUI({ name="state" label={i18n.str`Filter`} handler={form.state} + converter={amlStateConverter} choices={[ { label: i18n.str`Pending`, - value: TalerExchangeApi.AmlState.pending, + value: "pending", }, { label: i18n.str`Frozen`, - value: TalerExchangeApi.AmlState.frozen, + value: "frozen", }, { label: i18n.str`Normal`, - value: TalerExchangeApi.AmlState.normal, + value: "normal", }, ]} /> @@ -269,7 +273,7 @@ export function Cases() { onNext={list.isLastPage ? undefined : list.loadNext} filter={stateFilter} onChangeFilter={(d) => { - setStateFilter(d) + setStateFilter(d); }} /> ); diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx index abcaaa2a6..98160fb3a 100644 --- a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx +++ b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx @@ -31,6 +31,7 @@ import { } from "../hooks/form.js"; import { useOfficer } from "../hooks/officer.js"; import { usePreferences } from "../hooks/preferences.js"; +import { UIHandlerId } from "../context/ui-forms.js"; type FormType = { password: string; @@ -104,6 +105,7 @@ export function CreateAccount(): VNode { const [notification, withErrorHandler] = useLocalNotificationHandler(); const [form, status] = useFormState<FormType>( + [".password", ".repeat"] as Array<UIHandlerId>, { password: undefined, repeat: undefined, diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx index 0169572bf..0978d8bcc 100644 --- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx +++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.tsx @@ -72,18 +72,19 @@ export function ShowConsolidated({ properties: { label: i18n.str`State`, name: "aml.state", + choices: [ { label: i18n.str`Frozen`, - value: TalerExchangeApi.AmlState.frozen, + value: "frozen", }, { label: i18n.str`Pending`, - value: TalerExchangeApi.AmlState.pending, + value: "pending", }, { label: i18n.str`Normal`, - value: TalerExchangeApi.AmlState.normal, + value: "normal", }, ], }, diff --git a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx index 9552f2b0c..b81e66468 100644 --- a/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx +++ b/packages/aml-backoffice-ui/src/pages/UnlockAccount.tsx @@ -24,6 +24,7 @@ import { VNode, h } from "preact"; import { FormErrors, useFormState } from "../hooks/form.js"; import { useOfficer } from "../hooks/officer.js"; import { undefinedIfEmpty } from "./CreateAccount.js"; +import { UIHandlerId } from "../context/ui-forms.js"; type FormType = { password: string; @@ -36,6 +37,7 @@ export function UnlockAccount(): VNode { const [notification, withErrorHandler] = useLocalNotificationHandler(); const [form, status] = useFormState<FormType>( + [".password"] as Array<UIHandlerId>, { password: undefined, }, |