taler-typescript-core

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

commit 5bfb673fe313ed99b0abff24fe5a3daba002a6bb
parent 41b06846b48bdf540896e636ff8f69859ec22109
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue, 13 May 2025 19:08:33 -0300

fix #9905

Diffstat:
Mpackages/aml-backoffice-ui/src/pages/NewMeasure.tsx | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mpackages/aml-backoffice-ui/src/pages/decision/Measures.tsx | 8+++++---
2 files changed, 112 insertions(+), 16 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx b/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx @@ -10,19 +10,22 @@ import { FormDesign, FormUI, InternationalizationAPI, - undefinedIfEmpty, useForm, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; -import { useServerMeasures } from "../hooks/server-info.js"; import { useCurrentDecisionRequest } from "../hooks/decision-request.js"; +import { useServerMeasures } from "../hooks/server-info.js"; export type MeasureDefinition = { name: string; program: string; check: string; - context: { key: string; value: string }[]; + context: { + key: string; + type: "string" | "number" | "boolean" | "json"; + value: string; + }[]; }; /** @@ -129,7 +132,7 @@ export function MeasureForm({ const context = !form.status.result || !form.status.result.context ? [] - : (form.status.result.context as { key: string; value: string }[]); + : (form.status.result.context as MeasureDefinition["context"]); return ( <div> @@ -159,13 +162,13 @@ export function MeasureForm({ prog_name: newMeasure.program, context: (newMeasure.context ?? []).reduce( (prev, cur) => { - prev[cur.key] = cur.value; + prev[cur.key] = getContextValueByType(cur.type, cur.value); return prev; }, - {} as Record<string, string>, + {} as Record<string, any>, ), }; - updateRequest("add new measure",{ + updateRequest("add new measure", { custom_measures: currentMeasures, }); onCancel(); @@ -180,20 +183,20 @@ export function MeasureForm({ disabled={form.status.status === "fail"} onClick={() => { const newMeasure = form.status.result as MeasureDefinition; - + const CURRENT_MEASURES = { ...request.custom_measures }; CURRENT_MEASURES[name!] = { check_name: newMeasure.check, prog_name: newMeasure.program, context: (newMeasure.context ?? []).reduce( (prev, cur) => { - prev[cur.key] = cur.value; + prev[cur.key] = getContextValueByType(cur.type, cur.value); return prev; }, - {} as Record<string, string>, + {} as Record<string, any>, ), }; - updateRequest("update measure",{ + updateRequest("update measure", { custom_measures: CURRENT_MEASURES, }); onCancel(); @@ -206,7 +209,7 @@ export function MeasureForm({ onClick={() => { const currentMeasures = { ...request.custom_measures }; delete currentMeasures[name!]; - updateRequest("remove measure",{ + updateRequest("remove measure", { custom_measures: currentMeasures, }); onCancel(); @@ -410,12 +413,41 @@ const formDesign = ( { type: "text", id: "key", + required: true, label: i18n.str`Field name`, }, { - type: "text", + type: "choiceHorizontal", + id: "type", + label: i18n.str`Type`, + required: true, + choices: [ + { + label: i18n.str`string`, + value: "string", + }, + { + label: i18n.str`number`, + value: "number", + }, + { + label: i18n.str`boolean`, + value: "boolean", + }, + { + label: i18n.str`json`, + value: "json", + }, + ], + }, + { + type: "textArea", id: "value", + required: true, label: i18n.str`Value`, + validator(value, form) { + return validateContextValueByType(i18n, form["type"], value); + }, }, ], }, @@ -483,3 +515,65 @@ function programAndContextMatch( } return; } + +function getJsonError(str: string) { + try { + JSON.parse(str); + return undefined; + } catch (e) { + if (e instanceof SyntaxError) { + return e.message; + } + return String(e); + } +} + +// convert the string value of the form into the corresponding type +// based on the user choice +// check the function validateContextValueByType +function getContextValueByType(type: string, value: string) { + if (type === "number") { + return Number.parseInt(value, 10); + } + if (type === "boolean") { + return value === "true" ? true : value === "false" ? false : undefined; + } + if (type === "json") { + return JSON.parse(value); + } + return value; +} + +const REGEX_NUMER = /$[0-9]*^/; + +function validateContextValueByType( + i18n: InternationalizationAPI, + type: string, + value: string, +) { + if (!value) return i18n.str`Can't be empty`; + if (type === "number") { + const num = Number.parseInt(value, 10); + return !REGEX_NUMER.test(value) + ? i18n.str`It should be a number` + : Number.isNaN(num) + ? i18n.str`Not a number` + : !Number.isFinite(num) + ? i18n.str`It should be finite` + : !Number.isSafeInteger(num) + ? i18n.str`It should be a safe integer` + : undefined; + } + if (type === "boolean") { + if (value === "true" || value === "false") return undefined; + return i18n.str`It should be either "true" or "false"`; + } + if (type === "json") { + const error = getJsonError(value); + if (error) { + return i18n.str`Couldn't parse as json string: ${error}`; + } + return undefined; + } + return undefined; +} diff --git a/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx b/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx @@ -148,7 +148,8 @@ function ShowAllMeasures({ ? [] : Object.entries(m.context).map(([key, value]) => ({ key, - value, + type: "json", + value: JSON.stringify(value), })), name: m.name, program: m.type !== "info" ? m.programName : undefined, @@ -174,8 +175,9 @@ function ShowAllMeasures({ ? [] : Object.entries(m.context).map(([key, value]) => ({ key, - value, - })), + type: "json", + value: JSON.stringify(value), + })), name: m.name, program: m.type !== "info" ? m.programName : undefined, });