taler-typescript-core

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

commit 7032a2da0a6120a81b010097ef66b6beeef6aa5d
parent fab4ba2e17ac6c7758d3e90eed47091f2ac2590c
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Sun, 14 Dec 2025 09:55:17 -0300

fix #10774

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/form/InputArray.tsx | 15++++++++-------
Mpackages/merchant-backoffice-ui/src/components/product/ProductForm.tsx | 77++++++++++++++++++++++++++++++++---------------------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/products/create/CreatePage.tsx | 19++++++++++++++++---
Mpackages/merchant-backoffice-ui/src/paths/instance/products/update/UpdatePage.tsx | 18++++++++++++++++--
4 files changed, 72 insertions(+), 57 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx b/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx @@ -28,7 +28,9 @@ const TALER_SCREEN_ID = 6; export interface Props<T> extends InputProps<T> { isValid?: (e: any) => boolean; - getSuggestion?: (e: any) => Promise<{ id: string; description: string }[]>; + getSuggestion?: ( + e: any, + ) => Promise<Array<undefined | { id: string; description: string }>>; addonBefore?: string; toStr?: (v?: any) => string; fromStr?: (s: string) => any; @@ -66,7 +68,7 @@ export function InputArray<T>({ <label class="label"> {label} {required && ( - <span class="has-text-danger" style={{marginLeft:5}}> + <span class="has-text-danger" style={{ marginLeft: 5 }}> * </span> )} @@ -99,7 +101,7 @@ export function InputArray<T>({ setCurrentValue(v); if (getSuggestion) { getSuggestion(v).then((ss) => { - setSuggestions(ss); + setSuggestions(ss.filter((d) => !!d) as any); }); } }} @@ -107,7 +109,9 @@ export function InputArray<T>({ </p> {getSuggestion ? undefined : ( <p class="control"> - <button type="button" class="button is-info has-tooltip-left" + <button + type="button" + class="button is-info has-tooltip-left" disabled={!currentValue} onClick={(): void => { const v = fromStr(currentValue); @@ -159,9 +163,6 @@ export function InputArray<T>({ class="tag is-medium is-danger is-delete mb-0" onClick={() => { onChange(array.filter((f) => f !== v) as T[keyof T]); - setCurrentValue( - getSuggestion ? (v as any).description : toStr(v), - ); }} /> </div> diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx @@ -54,22 +54,25 @@ interface Props { onSubscribe: (c?: () => Entity | undefined) => void; initial?: Partial<Entity>; alreadyExist?: boolean; + categories: TalerMerchantApi.CategoryListEntry[]; } -export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { +export function ProductForm({ + onSubscribe, + initial, + alreadyExist, + categories, +}: Props) { const { i18n } = useTranslationContext(); - const { state, lib } = useSessionContext(); - // const [preference] = usePreference(); + const { state } = useSessionContext(); + + const initial_categories_map = + !initial || !initial.categories + ? [] + : categories + .filter((c) => initial.categories?.indexOf(c.category_id) !== -1) + .map((c) => ({ id: String(c.category_id), description: c.name })); - // FIXME: if the category list is big the will bring a lot of info - // we could find a lazy way to add up on searches - const categoriesResult = useInstanceCategories(); - if (!categoriesResult) return <Loading />; - if (categoriesResult instanceof TalerError) { - return <ErrorLoadingMerchant error={categoriesResult} />; - } - const categories = - categoriesResult.type === "fail" ? [] : categoriesResult.body.categories; const [value, valueHandler] = useState< Partial< Entity & { @@ -82,6 +85,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { description_i18n: {}, taxes: [], categories: [], + categories_map: initial_categories_map, next_restock: { t_s: "never" }, price: ":0" as AmountString, ...initial, @@ -98,24 +102,6 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { }, }); - useEffect(() => { - if (!initial || !initial?.categories) return; - - const ps = initial.categories.map((catId) => { - return lib.instance - .getCategoryDetails(state.token, String(catId)) - .then((res) => { - return res.type === "fail" - ? undefined - : { id: String(catId), description: res.body.name }; - }); - }); - Promise.all(ps).then((all) => { - const categories_map = all.filter(notEmpty); - valueHandler({ ...value, categories_map }); - }); - }, []); - const errors = undefinedIfEmpty({ product_id: !value.product_id ? i18n.str`Required` : undefined, product_name: !value.product_name ? i18n.str`Required` : undefined, @@ -142,37 +128,38 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { const hasErrors = errors !== undefined; const submit = useCallback((): Entity | undefined => { + const result = { ...value }; const stock = value.stock; if (!stock) { // the the input was untouched, then set the same // initial value - value.total_stock = initial?.total_stock; - value.total_lost = initial?.total_lost; - value.next_restock = initial?.next_restock; - value.address = initial?.address; + result.total_stock = initial?.total_stock; + result.total_lost = initial?.total_lost; + result.next_restock = initial?.next_restock; + result.address = initial?.address; if (!value.total_stock) { - value.total_stock = -1; + result.total_stock = -1; } } else { - value.total_stock = stock.current; - value.total_lost = stock.lost; - value.next_restock = + result.total_stock = stock.current; + result.total_lost = stock.lost; + result.next_restock = stock.nextRestock instanceof Date ? { t_s: stock.nextRestock.getTime() / 1000 } : stock.nextRestock; - value.address = stock.address; + result.address = stock.address; } - delete value.stock; - value.categories = value.categories_map?.map((d) => parseInt(d.id, 10)); - delete value.categories_map; + delete result.stock; + result.categories = value.categories_map?.map((d) => parseInt(d.id, 10)); + delete result.categories_map; if (typeof value.minimum_age !== "undefined" && value.minimum_age < 1) { - delete value.minimum_age; + delete result.minimum_age; } - return value as TalerMerchantApi.ProductDetail & { + return result as TalerMerchantApi.ProductDetail & { product_id: string; }; }, [value]); @@ -258,7 +245,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { <InputArray name="categories_map" label={i18n.str`Categories`} - getSuggestion={async () => { + getSuggestion={async (v: string) => { return categories.map((cat) => { return { description: cat.name, id: String(cat.category_id) }; }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatePage.tsx @@ -19,17 +19,20 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { HttpStatusCode, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { HttpStatusCode, TalerError, TalerMerchantApi } from "@gnu-taler/taler-util"; import { ButtonBetterBulma, LocalNotificationBannerBulma, useLocalNotificationBetter, - useTranslationContext + useTranslationContext, } from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { ProductForm } from "../../../../components/product/ProductForm.js"; import { useSessionContext } from "../../../../context/session.js"; +import { useInstanceCategories } from "../../../../hooks/category.js"; +import { Loading } from "../../../../components/exception/loading.js"; +import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; export interface Props { onCreate: () => void; @@ -59,6 +62,16 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { const { i18n } = useTranslationContext(); + // FIXME: if the category list is big the will bring a lot of info + // we could find a lazy way to add up on searches + const categoriesResult = useInstanceCategories(); + if (!categoriesResult) return <Loading />; + if (categoriesResult instanceof TalerError) { + return <ErrorLoadingMerchant error={categoriesResult} />; + } + const categories = + categoriesResult.type === "fail" ? [] : categoriesResult.body.categories; + return ( <div> <LocalNotificationBannerBulma notification={notification} /> @@ -66,7 +79,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { <div class="columns"> <div class="column" /> <div class="column is-four-fifths"> - <ProductForm onSubscribe={setForm} /> + <ProductForm onSubscribe={setForm} categories={categories} /> <div class="buttons is-right mt-5"> {onBack && ( diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/update/UpdatePage.tsx @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { HttpStatusCode, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { HttpStatusCode, TalerError, TalerMerchantApi } from "@gnu-taler/taler-util"; import { ButtonBetterBulma, LocalNotificationBannerBulma, @@ -30,6 +30,9 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { ProductForm } from "../../../../components/product/ProductForm.js"; import { useSessionContext } from "../../../../context/session.js"; +import { useInstanceCategories } from "../../../../hooks/category.js"; +import { Loading } from "../../../../components/exception/loading.js"; +import { ErrorLoadingMerchant } from "../../../../components/ErrorLoadingMerchant.js"; const TALER_SCREEN_ID = 57; @@ -66,6 +69,17 @@ export function UpdatePage({ product, onBack, onConfirm }: Props): VNode { const { i18n } = useTranslationContext(); + // FIXME: if the category list is big the will bring a lot of info + // we could find a lazy way to add up on searches + const categoriesResult = useInstanceCategories(); + if (!categoriesResult) return <Loading />; + if (categoriesResult instanceof TalerError) { + return <ErrorLoadingMerchant error={categoriesResult} />; + } + const categories = + categoriesResult.type === "fail" ? [] : categoriesResult.body.categories; + + return ( <div> <LocalNotificationBannerBulma notification={notification} /> @@ -89,7 +103,7 @@ export function UpdatePage({ product, onBack, onConfirm }: Props): VNode { <div class="columns"> <div class="column" /> <div class="column is-four-fifths"> - <ProductForm initial={product} onSubscribe={setForm} alreadyExist /> + <ProductForm initial={product} onSubscribe={setForm} alreadyExist categories={categories}/> <div class="buttons is-right mt-5"> {onBack && (