taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit a53c8c3b87f87fe8178d006e0248d82306a443f9
parent cd4302880e99c6200822a6069863c99546b361ad
Author: Florian Dold <florian@dold.me>
Date:   Tue, 25 Mar 2025 00:17:10 +0100

form tweaks

Diffstat:
Mpackages/web-util/src/forms/fields/InputSelectMultiple.tsx | 26++++++++++++++++++++++----
Mpackages/web-util/src/forms/fields/InputToggle.tsx | 4++--
Mpackages/web-util/src/forms/forms-types.ts | 10+++++++---
Mpackages/web-util/src/forms/forms-utils.ts | 1-
Mpackages/web-util/src/forms/gana/VQF_902_4.ts | 231++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mpackages/web-util/src/forms/gana/taler_form_attributes.ts | 32++++----------------------------
6 files changed, 159 insertions(+), 145 deletions(-)

diff --git a/packages/web-util/src/forms/fields/InputSelectMultiple.tsx b/packages/web-util/src/forms/fields/InputSelectMultiple.tsx @@ -1,5 +1,5 @@ import { Fragment, VNode, h } from "preact"; -import { useState } from "preact/hooks"; +import { useRef, useState } from "preact/hooks"; import { useTranslationContext } from "../../index.browser.js"; import { UIFormProps } from "../FormProvider.js"; import { noHandlerPropsAndNoContextForField } from "./InputArray.js"; @@ -46,6 +46,8 @@ export function InputSelectMultiple<ChoiceVal>( {} as Record<string, string>, ); + const inputRef = useRef<HTMLInputElement>(null); + const list = (value ?? []) as string[]; const filteredChoices = filter === undefined @@ -67,13 +69,21 @@ export function InputSelectMultiple<ChoiceVal>( {!props.disabled && ( <div class="relative mt-2"> <input + ref={inputRef} id="combobox" type="text" value={filter ?? ""} + autoComplete="off" onChange={(e) => { setFilter(e.currentTarget.value); setDirty(true); }} + onBlur={(e) => { + setFilter(undefined); + }} + onFocus={(e) => { + setFilter(""); + }} placeholder={placeholder} class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" role="combobox" @@ -83,9 +93,14 @@ export function InputSelectMultiple<ChoiceVal>( <button type="button" disabled={props.disabled} - onClick={() => { + onMouseDown={(e) => { + // Input element should not lose focus + e.preventDefault(); + }} + onClick={(e) => { setFilter(filter === undefined ? "" : undefined); setDirty(true); + inputRef.current?.focus(); }} class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none" > @@ -111,7 +126,7 @@ export function InputSelectMultiple<ChoiceVal>( role="listbox" > <li class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600"> - <span class="block truncate"> + <span class="block truncate font-bold"> <i18n.Translate>No element found</i18n.Translate> </span> </li> @@ -129,6 +144,10 @@ export function InputSelectMultiple<ChoiceVal>( class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600" id="option-0" role="option" + onMouseDown={(e) => { + // Input element should not lose focus + e.preventDefault(); + }} onClick={() => { setFilter(undefined); if (unique && list.indexOf(v.value as string) !== -1) { @@ -139,7 +158,6 @@ export function InputSelectMultiple<ChoiceVal>( } const newValue = [...list]; newValue.push(v.value as string); - // newValue.splice(0, 0, v.value as string); onChange(newValue as any); }} > diff --git a/packages/web-util/src/forms/fields/InputToggle.tsx b/packages/web-util/src/forms/fields/InputToggle.tsx @@ -1,5 +1,5 @@ import { Fragment, VNode, h } from "preact"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { UIFormProps } from "../FormProvider.js"; import { noHandlerPropsAndNoContextForField } from "./InputArray.js"; import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; @@ -10,7 +10,7 @@ import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; * FIXME: Types would be clearer if two/tri state were different types. */ export function InputToggle( - props: { threeState: boolean } & UIFormProps<boolean>, + props: { threeState: boolean; defaultValue?: boolean } & UIFormProps<boolean>, ): VNode { const { label, tooltip, help, required, threeState } = props; const { value, onChange, error } = diff --git a/packages/web-util/src/forms/forms-types.ts b/packages/web-util/src/forms/forms-types.ts @@ -198,7 +198,7 @@ type UIFormFieldSecret = { export interface SelectUiChoice { label: string; description?: string; - value: string; + value: string | boolean; } type UIFormFieldSelectMultiple = { @@ -236,10 +236,14 @@ export type ComputableFieldConfig = { /* if the field should be initially hidden */ hidden?: boolean; - /* show a mark as required */ + /** + * show a mark as required + */ required?: boolean; - /* readonly and dim */ + /** + * readonly and dim + */ disabled?: boolean; /* diff --git a/packages/web-util/src/forms/forms-utils.ts b/packages/web-util/src/forms/forms-utils.ts @@ -354,7 +354,6 @@ export function convertFormConfigToUiField( config, getConverterByFieldType(config.type, config), ), - hidden, threeState: config.threeState, }, } as UIFormField; diff --git a/packages/web-util/src/forms/gana/VQF_902_4.ts b/packages/web-util/src/forms/gana/VQF_902_4.ts @@ -18,22 +18,52 @@ export function VQF_902_4( id: TalerFormAttributes.PEP_FOREIGN, label: i18n.str`Foreign PEP`, help: i18n.str`Is the customer, the beneficial owner or the controlling person or authorised representative a foreign PEP or closely related to such a person?`, - type: "toggle", + type: "choiceHorizontal", required: true, + choices: [ + { + value: true, + label: `Yes`, + }, + { + value: false, + label: `No`, + }, + ], }, { id: TalerFormAttributes.PEP_DOMESTIC, label: i18n.str`Domestic PEP`, help: i18n.str`Is the customer, the beneficial owner or the controlling person or authorised representative a domestic PEP or closely related to such a person?`, - type: "toggle", + type: "choiceHorizontal", required: true, + choices: [ + { + value: true, + label: `Yes`, + }, + { + value: false, + label: `No`, + }, + ], }, { id: TalerFormAttributes.PEP_INTERNATIONAL_ORGANIZATION, label: i18n.str`PEP of International Organisatons`, help: i18n.str`Is the customer, the beneficial owner or the controlling person or authorised representative a PEP in International Organizations or closely related to such a person?`, - type: "toggle", + type: "choiceHorizontal", required: true, + choices: [ + { + value: true, + label: `Yes`, + }, + { + value: false, + label: `No`, + }, + ], }, { id: TalerFormAttributes.PEP_ACCEPTANCE_DATE, @@ -61,8 +91,18 @@ export function VQF_902_4( id: TalerFormAttributes.HIGH_RISK_COUNTRY, label: i18n.str`High-risk or non-cooperative country`, help: i18n.str`Is the customer, the beneficial owner or the controlling person or authorised representative in a country considered by the FATF as high-risk or non-cooperative and for which FATF requires increased diligence?`, - type: "toggle", + type: "choiceHorizontal", required: true, + choices: [ + { + value: true, + label: `Yes`, + }, + { + value: false, + label: `No`, + }, + ], }, { id: TalerFormAttributes.HIGH_RISK_ACCEPTANCE_DATE, @@ -80,11 +120,21 @@ export function VQF_902_4( }, { title: i18n.str`Evaluation of business relationship risk`, - description: i18n.str`This evaluation has to be completed by all members who have in total more than 20 customers for every business relationship. At least two risk categories have to be chosen and assessed.`, + description: i18n.str``, + fields: [ + { + type: "caption", + label: + "This evaluation has to be completed by all members who have in total more than 20 customers for every business relationship. At least two risk categories have to be chosen and assessed.", + }, + ], + }, + { + title: i18n.str`Country risk`, fields: [ { id: TalerFormAttributes.COUNTRY_RISK_NATIONALITY_TYPE, - label: i18n.str`Country risk type (nationality)`, + label: i18n.str`Applicable country risk types`, choices: [ { label: i18n.str`Nationality of the customer`, @@ -107,100 +157,81 @@ export function VQF_902_4( value: "DOMICILE_CONTROLLING", }, ], - type: "choiceStacked", + type: "selectMultiple", required: false, }, { id: TalerFormAttributes.COUNTRY_RISK_NATIONALITY_LEVEL, label: i18n.str`Country risk level (nationality)`, + help: i18n.str`Risk category according to VQF country list (VQF doc. no. 902.4.1)`, choices: [ { - label: i18n.str`Low`, + label: i18n.str`Low (Risk 0)`, value: "LOW", description: i18n.str`Risk 0 acc. to VQF country list (VQF doc. no. 902.4.1)`, }, { - label: i18n.str`Medium`, + label: i18n.str`Medium (Risk 1)`, value: "MEDIUM", description: i18n.str`Risk 1 acc. to VQF country list (VQF doc. no. 902.4.1)`, }, { - label: i18n.str`High`, + label: i18n.str`High (Risk 2)`, value: "HIGH", description: i18n.str`Risk 2 acc. to VQF country list (VQF doc. no. 902.4.1)`, }, ], - type: "choiceStacked", + type: "choiceHorizontal", required: false, }, { id: TalerFormAttributes.COUNTRY_RISK_BUSINESS_TYPE, - label: i18n.str`Country risk type (business activity)`, + label: i18n.str`Country risk type (place of business activity)`, choices: [ { - label: i18n.str`Place of business activity of the customer`, + label: i18n.str`Customer`, value: "CUSTOMER", }, { - label: i18n.str`Place of business activity of the beneficial owner`, + label: i18n.str`Beneficial owner`, value: "OWNER", }, ], - type: "choiceStacked", + type: "selectMultiple", required: false, }, { id: TalerFormAttributes.COUNTRY_RISK_BUSINESS_LEVEL, label: i18n.str`Country risk level (business activity)`, + help: i18n.str`Risk category according to VQF country list (VQF doc. no. 902.4.1)`, choices: [ { - label: i18n.str`Low`, - value: "LOW", - description: i18n.str`Risk 0 acc. to VQF country list (VQF doc. no. 902.4.1)`, - }, - { - label: i18n.str`Medium`, - value: "MEDIUM", - description: i18n.str`Risk 1 acc. to VQF country list (VQF doc. no. 902.4.1)`, - }, - { - label: i18n.str`High`, - value: "HIGH", - description: i18n.str`Risk 2 acc. to VQF country list (VQF doc. no. 902.4.1)`, - }, - ], - type: "choiceStacked", - required: false, - }, - { - id: TalerFormAttributes.COUNTRY_RISK_PAYMENTS_LEVEL, - label: i18n.str`Country risk level (payments)`, - help: i18n.str`Contry of origin ad destination of the frequent payments (if known)`, - choices: [ - { - label: i18n.str`Low`, + label: i18n.str`Low (Risk 0)`, value: "LOW", description: i18n.str`Risk 0 acc. to VQF country list (VQF doc. no. 902.4.1)`, }, { - label: i18n.str`Medium`, + label: i18n.str`Medium (Risk 1)`, value: "MEDIUM", description: i18n.str`Risk 1 acc. to VQF country list (VQF doc. no. 902.4.1)`, }, { - label: i18n.str`High`, + label: i18n.str`High (Risk 2)`, value: "HIGH", description: i18n.str`Risk 2 acc. to VQF country list (VQF doc. no. 902.4.1)`, }, ], - type: "choiceStacked", - required: false, + type: "choiceHorizontal", }, + ], + }, + { + title: i18n.str`Industry risk`, + fields: [ { id: TalerFormAttributes.INDUSTRY_RISK_TYPE, - label: i18n.str`Industry risk type`, - help: i18n.str`Nature of customer's business activity`, - type: "choiceStacked", + label: i18n.str`Industry risk source`, + type: "selectMultiple", choices: [ { label: i18n.str`Customer`, value: "CUSTOMER" }, { @@ -216,112 +247,98 @@ export function VQF_902_4( type: "choiceStacked", choices: [ { - label: i18n.str`Clearly defined, transparent, easily comprehensible business activity well known to the member.`, + label: `Transparent (Risk Level 0)`, + description: i18n.str`Clearly defined, transparent, easily comprehensible business activity well known to the member.`, value: "TRANSPARENT", }, { - label: i18n.str`Business activity with a high level of cash transactions.`, + label: `High cash transactions (Risk Level 1)`, + description: i18n.str`Business activity with a high level of cash transactions.`, value: "HIGH_CASH_TRANSACTION", }, { - label: i18n.str`Business activity not well known to the member.`, + label: `Not well known (Risk Level 1)`, + description: i18n.str`Business activity not well known to the member.`, value: "NOT_WELL_KNOWN", }, { - label: i18n.str`Trade in munitions/arms, raw gem stones/diamonds, jewellery, international trade in exotic animals, casino and lottery business, trade in erotic wares.`, + label: `High-risk trade (Risk Level 2)`, + description: i18n.str`Trade in munitions/arms, raw gem stones/diamonds, jewellery, international trade in exotic animals, casino and lottery business, trade in erotic wares.`, value: "HIGH_RISK_TRADE", }, { - label: i18n.str`Member has no personal knowledge of the customer’s industry.`, + label: `Unknown industry (Risk Level 2)`, + description: i18n.str`Member has no personal knowledge of the customer’s industry.`, value: "UNKNOWN_INDUSTRY", }, ], required: false, }, + ], + }, + { + title: i18n.str`Contact risk`, + description: i18n.str`Type of contact to the customer/benefcial owner of the assets.`, + fields: [ { id: TalerFormAttributes.CONTACT_RISK_LEVEL, label: i18n.str`Contact risk level`, - help: i18n.str`Type of contact to the customer/benefcial owner of the assets.`, choices: [ { - label: i18n.str`Personal acquaintance between member and customer/beneficial owner of the assets over several years (at least 2) prior to entering into the business relationship`, + label: "Low contact risk", + description: i18n.str`Personal acquaintance between member and customer/beneficial owner of the assets over several years (at least 2) prior to entering into the business relationship`, value: "LOW", }, { - label: i18n.str`The customer/beneficial owner was not personally known to the member for several years (at least 2) prior to entering into the business relationship; however (a) no business was entered into in the absence of the customer/beneficial owner, or (b) the customer was at least introduced/brokered by a trusted third party.`, + label: "Medium contact risk", + description: i18n.str`The customer/beneficial owner was not personally known to the member for several years (at least 2) prior to entering into the business relationship; however (a) no business was entered into in the absence of the customer/beneficial owner, or (b) the customer was at least introduced/brokered by a trusted third party.`, value: "MEDIUM", }, { - label: i18n.str`The customer/beneficial owner was not personally known to the member and business was entered into in the absence of the former (relationship by correspondence) and the customer was not introduced/brokered by a trusted third party.`, + label: "High contact risk", + description: i18n.str`The customer/beneficial owner was not personally known to the member and business was entered into in the absence of the former (relationship by correspondence) and the customer was not introduced/brokered by a trusted third party.`, value: "HIGH", }, ], type: "choiceStacked", required: false, }, - + ], + }, + { + title: i18n.str`Summary evaluation`, + fields: [ { - id: TalerFormAttributes.PRODUCT_RISK_LEVEL, - label: i18n.str`Product risk level`, - help: i18n.str`Nature of services and products requested by the customer.`, - type: "choiceStacked", - choices: [ - { - label: i18n.str`Easy to understand, transparent services and products whose financial background is easy to comprehend and verify.`, - value: "EASY", - }, - { - label: i18n.str`More sophisticated services/products whose financial background is not readily easy to comprehend and verify.`, - value: "SOPHISTICATED", - }, - { - label: i18n.str`Main focus on offshore business (especially: relationships with domiciliary companies or other such offshore organisations).`, - value: "OFFSHORE", - }, - { - label: i18n.str`Complex structures in particular by using a domiciliary company with fiduciary shareholders in a non-transparent jurisdiction, without comprehensible reason or for the purpose of short-term asset placement.`, - value: "COMPLEX_STRUCTURE", - }, - { - label: i18n.str`The customer or beneficial owner of the assets has a large number of accounts with pass-through transactions (pass-through accounts).`, - value: "LARGE_NUMBER_OF_ACCOUNTS", - }, - { - label: i18n.str`Complex services/products whose financial background can’t be understood or verified with considerable effort.`, - value: "COMPLEX_SERVICE", - }, - { - label: i18n.str`Frequent transactions with increased risks.`, - value: "FREQ_TRANS_WITH_HIGH_RISK", - }, - ], + id: TalerFormAttributes.RISK_RATIONALY, + label: i18n.str`Justification for risk assessment`, + type: "textArea", required: false, }, { - id: TalerFormAttributes.RISK_JUSTIFICATION, - label: i18n.str`Justification for differing risk assessment`, - type: "text", + id: TalerFormAttributes.RISK_CLASSIFICATION_LEVEL, + label: i18n.str`Risk clasification`, + help: i18n.str`Conclusion whether the business relationship is with or without increased risk.`, + choices: [ + { label: i18n.str`No high risk`, value: "NO_HIGH_RISK" }, + { label: i18n.str`High risk`, value: "HIGH_RISK" }, + ], + type: "choiceHorizontal", required: false, }, { - id: TalerFormAttributes.RISK_CLASIFICATION_ACCEPTANCE_DATE, + id: TalerFormAttributes.RISK_ACCEPTANCE_DATE, label: i18n.str`Acceptance date`, help: i18n.str`When the decision of the Senior executive body on the acceptance of a business relationship with increased risk was obtained on.`, type: "isoDateText", placeholder: "dd/MM/yyyy", pattern: "dd/MM/yyyy", - required: false, - }, - { - id: TalerFormAttributes.RISK_CLASIFICATION_LEVEL, - label: i18n.str`Risk clasification`, - help: i18n.str`Conclusion whether the business relationship is with or without increased risk.`, - choices: [ - { label: i18n.str`WITH`, value: "WITH" }, - { label: i18n.str`WITHOUT`, value: "WITHOUT" }, - ], - type: "choiceHorizontal", - required: false, + required: true, + hide(value, root) { + return ( + root[TalerFormAttributes.RISK_CLASSIFICATION_LEVEL] != + "HIGH_RISK" + ); + }, }, ], }, diff --git a/packages/web-util/src/forms/gana/taler_form_attributes.ts b/packages/web-util/src/forms/gana/taler_form_attributes.ts @@ -400,47 +400,23 @@ export const TalerFormAttributes = { */ PRODUCT_RISK_LEVEL: "PRODUCT_RISK_LEVEL" as UIHandlerId, /** - * Description: Criteria description - * - * GANA Type: String - */ - EXTRA_CRITERA_1_RISK_DEFINITION: "EXTRA_CRITERA_1_RISK_DEFINITION" as UIHandlerId, - /** - * Description: Based on 902.4.1 country list - * - * GANA Type: 'LOW' | 'MEDIUM' | 'HIGH' - */ - EXTRA_CRITERA_1_RISK_LEVEL: "EXTRA_CRITERA_1_RISK_LEVEL" as UIHandlerId, - /** - * Description: Criteria description - * - * GANA Type: String - */ - EXTRA_CRITERA_2_RISK_DEFINITION: "EXTRA_CRITERA_2_RISK_DEFINITION" as UIHandlerId, - /** - * Description: Based on 902.4.1 country list - * - * GANA Type: 'LOW' | 'MEDIUM' | 'HIGH' - */ - EXTRA_CRITERA_2_RISK_LEVEL: "EXTRA_CRITERA_2_RISK_LEVEL" as UIHandlerId, - /** * Description: * - * GANA Type: 'WITH' | 'WITHOUT' + * GANA Type: 'HIGH_RISK' | 'NO_HIGH_RISK' */ - RISK_CLASIFICATION_LEVEL: "RISK_CLASIFICATION_LEVEL" as UIHandlerId, + RISK_CLASSIFICATION_LEVEL: "RISK_CLASSIFICATION_LEVEL" as UIHandlerId, /** * Description: Justification for differing risk assessment * * GANA Type: Paragraph */ - RISK_JUSTIFICATION: "RISK_JUSTIFICATION" as UIHandlerId, + RISK_RATIONALY: "RISK_RATIONALY" as UIHandlerId, /** * Description: The decision of the Senior executive body on the acceptance of a business relationsip was obtained on ___ * * GANA Type: AbsoluteDateTime */ - RISK_CLASIFICATION_ACCEPTANCE_DATE: "RISK_CLASIFICATION_ACCEPTANCE_DATE" as UIHandlerId, + RISK_ACCEPTANCE_DATE: "RISK_ACCEPTANCE_DATE" as UIHandlerId, /** * Description: Profession, business activities, etc. (former, current, potentially planned) *