commit 56bd2c3cd40ebe67ce8235ccb74f028703fc0ef0
parent 6069e2deaa06e7debabc17816bd97e4584dfc7a9
Author: Sebastian <sebasjm@gmail.com>
Date: Thu, 1 Aug 2024 15:50:35 -0300
suggestion for categories
Diffstat:
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>