commit 7032a2da0a6120a81b010097ef66b6beeef6aa5d
parent fab4ba2e17ac6c7758d3e90eed47091f2ac2590c
Author: Sebastian <sebasjm@taler-systems.com>
Date: Sun, 14 Dec 2025 09:55:17 -0300
fix #10774
Diffstat:
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 && (