taler-typescript-core

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

commit 56bd2c3cd40ebe67ce8235ccb74f028703fc0ef0
parent 6069e2deaa06e7debabc17816bd97e4584dfc7a9
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu,  1 Aug 2024 15:50:35 -0300

suggestion for categories

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/form/InputArray.tsx | 29++++++++++++++++++-----------
Mpackages/merchant-backoffice-ui/src/components/product/ProductForm.tsx | 64++++++++++++++++++++++++++++++++++++++++++++++------------------
2 files changed, 64 insertions(+), 29 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx b/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx @@ -19,17 +19,18 @@ * @author Sebastian Javier Marchano (sebasjm) */ import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { InputProps, useField } from "./useField.js"; import { DropdownList } from "./InputSearchOnList.js"; export interface Props<T> extends InputProps<T> { isValid?: (e: any) => boolean; - getSuggestion?: (e: any) => { id: string; description: string }[]; + getSuggestion?: (e: any) => Promise<{ id: string; description: string }[]>; addonBefore?: string; toStr?: (v?: any) => string; fromStr?: (s: string) => any; + unique?: boolean; } const defaultToString = (f?: any): string => (f ? String(f) : ""); @@ -41,15 +42,14 @@ export function InputArray<T>({ placeholder, tooltip, label, + unique, help, addonBefore, getSuggestion, fromStr = defaultFromString, toStr = defaultToString, }: Props<keyof T>): VNode { - const { error: formError, value, onChange, required } = useField<T>(name); - - const error = formError; + const { error, value, onChange, required } = useField<T>(name); const array: T[keyof T][] = value ? value! : []; const [currentValue, setCurrentValue] = useState(""); @@ -87,10 +87,13 @@ export function InputArray<T>({ disabled={readonly} name={String(name)} value={currentValue} - onChange={(e): void => { - setCurrentValue(e.currentTarget.value); + onChange={async (e): Promise<void> => { + const v = e.currentTarget.value; + setCurrentValue(v); if (getSuggestion) { - setSuggestions(getSuggestion(e.currentTarget.value)); + getSuggestion(v).then((ss) => { + setSuggestions(ss); + }); } }} /> @@ -107,7 +110,9 @@ export function InputArray<T>({ disabled={!currentValue} onClick={(): void => { const v = fromStr(currentValue); - onChange([v, ...array] as T[keyof T]); + if (!unique || array.indexOf(v) === -1) { + onChange([v, ...array] as T[keyof T]); + } setCurrentValue(""); }} data-tooltip={i18n.str`Add element to the list`} @@ -125,7 +130,7 @@ export function InputArray<T>({ class="tag is-medium is-info mb-0" style={{ maxWidth: "90%" }} > - {toStr(v)} + {getSuggestion ? (v as any).description : toStr(v)} </span> <a class="tag is-medium is-danger is-delete mb-0" @@ -142,8 +147,10 @@ export function InputArray<T>({ name={currentValue} list={suggestions} onSelect={(p): void => { + if (!unique || array.indexOf(p as any) === -1) { + onChange([p, ...array] as T[keyof T]); + } setCurrentValue(""); - onChange([p, ...array] as T[keyof T]); setSuggestions([]); }} withImage={false} diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx @@ -35,7 +35,9 @@ import { InputTaxes } from "../form/InputTaxes.js"; import { InputWithAddon } from "../form/InputWithAddon.js"; import { InputArray } from "../form/InputArray.js"; -type Entity = TalerMerchantApi.ProductDetail & { product_id: string }; +type Entity = TalerMerchantApi.ProductDetail & { + product_id: string; +}; interface Props { onSubscribe: (c?: () => Entity | undefined) => void; @@ -45,9 +47,16 @@ interface Props { export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { const { i18n } = useTranslationContext(); - const { state } = useSessionContext(); - - const [value, valueHandler] = useState<Partial<Entity & { stock: Stock }>>({ + const { state, lib } = useSessionContext(); + + const [value, valueHandler] = useState< + Partial< + Entity & { + stock: Stock; + categories_map: { id: string; description: string }[]; + } + > + >({ address: {}, description_i18n: {}, taxes: [], @@ -67,6 +76,28 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { }, }); + function notEmpty<TValue>(value: TValue | null | undefined): value is TValue { + return value !== null && value !== undefined; + } + + 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, description: !value.description ? i18n.str`Required` : undefined, @@ -106,6 +137,8 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { value.address = stock.address; } delete value.stock; + value.categories = value.categories_map?.map((d) => parseInt(d.id, 10))!; + delete value.categories_map; if (typeof value.minimum_age !== "undefined" && value.minimum_age < 1) { delete value.minimum_age; @@ -175,23 +208,18 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { label={i18n.str`Taxes`} tooltip={i18n.str`Taxes included in the product price, exposed to customers.`} /> - <InputArray<Entity> - name="categories" + <InputArray + name="categories_map" label={i18n.str`Categories`} - getSuggestion={() => { - return [ - { - description: "brown beer", - id: "bb", - }, - { - description: "yellow beer", - id: "yb", - }, - ]; + getSuggestion={async () => { + const resp = await lib.instance.listCategories(state.token); + if (resp.type === "fail") return []; + return resp.body.categories.map((cat) => { + return { description: cat.name, id: String(cat.category_id) }; + }); }} - toStr={(v) => v.description} tooltip={i18n.str`Categories where this product will be listed on.`} + unique /> </FormProvider> </div>