taler-typescript-core

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

commit c2a0525c627ff01f166afeb4e6c0f04dc6955e32
parent 56bd2c3cd40ebe67ce8235ccb74f028703fc0ef0
Author: Sebastian <sebasjm@gmail.com>
Date:   Fri,  2 Aug 2024 06:44:43 -0300

wip #8839

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/product/ProductForm.tsx | 9+++++----
Mpackages/merchant-backoffice-ui/src/hooks/product.ts | 28++++++++++++++++++++++------
Mpackages/merchant-backoffice-ui/src/paths/instance/categories/update/UpdatePage.tsx | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mpackages/merchant-backoffice-ui/src/paths/instance/categories/update/index.tsx | 23++++++++++++++++++++++-
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx | 4+++-
5 files changed, 116 insertions(+), 18 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx @@ -76,10 +76,6 @@ 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; @@ -218,6 +214,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { return { description: cat.name, id: String(cat.category_id) }; }); }} + help={i18n.str`Search by category description or id`} tooltip={i18n.str`Categories where this product will be listed on.`} unique /> @@ -225,3 +222,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { </div> ); } + +function notEmpty<TValue>(value: TValue | null | undefined): value is TValue { + return value !== null && value !== undefined; +} diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts b/packages/merchant-backoffice-ui/src/hooks/product.ts @@ -15,7 +15,15 @@ */ // FIX default import https://github.com/microsoft/TypeScript/issues/49189 -import { AccessToken, OperationOk, TalerHttpError, TalerMerchantApi, TalerMerchantManagementErrorsByMethod, TalerMerchantManagementResultByMethod, opFixedSuccess } from "@gnu-taler/taler-util"; +import { + AccessToken, + OperationOk, + TalerHttpError, + TalerMerchantApi, + TalerMerchantManagementErrorsByMethod, + TalerMerchantManagementResultByMethod, + opFixedSuccess, +} from "@gnu-taler/taler-util"; import { useState } from "preact/hooks"; import _useSWR, { SWRHook, mutate } from "swr"; import { useSessionContext } from "../context/session.js"; @@ -23,7 +31,10 @@ import { PAGINATED_LIST_REQUEST } from "../utils/constants.js"; import { buildPaginatedResult } from "./webhooks.js"; const useSWR = _useSWR as unknown as SWRHook; -type ProductWithId = TalerMerchantApi.ProductDetail & { id: string, serial: number }; +export type ProductWithId = TalerMerchantApi.ProductDetail & { + id: string; + serial: number; +}; function notUndefined(c: ProductWithId | undefined): c is ProductWithId { return c !== undefined; } @@ -43,7 +54,7 @@ export function useInstanceProducts() { async function fetcher([token, bid]: [AccessToken, number]) { const list = await lib.instance.listProducts(token, { limit: PAGINATED_LIST_REQUEST, - offset: bid === undefined ? undefined: String(bid), + offset: bid === undefined ? undefined : String(bid), order: "dec", }); if (list.type !== "ok") { @@ -64,8 +75,8 @@ export function useInstanceProducts() { } const { data, error } = useSWR< - OperationOk<{ products: ProductWithId[] }> | - TalerMerchantManagementErrorsByMethod<"listProducts">, + | OperationOk<{ products: ProductWithId[] }> + | TalerMerchantManagementErrorsByMethod<"listProducts">, TalerHttpError >([state.token, offset, "listProductsWithId"], fetcher); @@ -73,7 +84,12 @@ export function useInstanceProducts() { if (data === undefined) return undefined; if (data.type !== "ok") return data; - return buildPaginatedResult(data.body.products, offset, setOffset, (d) => d.serial) + return buildPaginatedResult( + data.body.products, + offset, + setOffset, + (d) => d.serial, + ); } export function revalidateProductDetails() { diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/UpdatePage.tsx @@ -19,16 +19,17 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { - TalerMerchantApi -} from "@gnu-taler/taler-util"; +import { TalerMerchantApi } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { FormProvider } from "../../../../components/form/FormProvider.js"; import { Input } from "../../../../components/form/Input.js"; import { WithId } from "../../../../declaration.js"; +import { InputArray } from "../../../../components/form/InputArray.js"; +import { useSessionContext } from "../../../../context/session.js"; +import { ProductWithId } from "../../../../hooks/product.js"; type Entity = TalerMerchantApi.CategoryProductList & WithId; @@ -36,13 +37,52 @@ interface Props { onUpdate: (d: Entity) => Promise<void>; onBack?: () => void; category: Entity; + instanceInventory: ProductWithId[]; } -export function UpdatePage({category, onUpdate, onBack }: Props): VNode { +export function UpdatePage({ + category, + instanceInventory, + onUpdate, + onBack, +}: Props): VNode { const { i18n } = useTranslationContext(); + const { + state: { token }, + lib, + } = useSessionContext(); - const [state, setState] = useState<Partial<Entity>>(category); + const [state, setState] = useState< + Partial<Entity & { product_map: { id: string; description: string }[] }> + >({ + ...category, + product_map: [], + }); + + useEffect(() => { + if (!category || !category?.products) return; + console.log(category.products); + const ps = category.products.map((prod) => { + return lib.instance + .getProductDetails(token, String(prod.product_id)) + .then((res) => { + return res.type === "fail" + ? undefined + : { id: String(prod), description: res.body.description }; + }); + }); + Promise.all(ps).then((all) => { + const product_map = all.filter(notEmpty); + console.log(product_map); + setState({ ...state, product_map }); + }); + }, []); const submitForm = () => { + const pids = state.product_map?.map((p) => { + return { product_id: p.id }; + }); + state.products = pids; + delete state.product_map; return onUpdate(state as Entity); }; @@ -75,6 +115,21 @@ export function UpdatePage({category, onUpdate, onBack }: Props): VNode { label={i18n.str`Name`} tooltip={i18n.str`Name of the category`} /> + <InputArray + name="product_map" + label={i18n.str`Products`} + getSuggestion={async () => { + return instanceInventory.map((prod) => { + return { + description: prod.description, + id: prod.id, + }; + }); + }} + help={i18n.str`Search by product description or id`} + tooltip={i18n.str`Products that this category will list.`} + unique + /> </FormProvider> <div class="buttons is-right mt-5"> @@ -98,3 +153,6 @@ export function UpdatePage({category, onUpdate, onBack }: Props): VNode { </div> ); } +function notEmpty<TValue>(value: TValue | null | undefined): value is TValue { + return value !== null && value !== undefined; +} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/categories/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/categories/update/index.tsx @@ -22,7 +22,7 @@ import { HttpStatusCode, TalerError, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; @@ -36,6 +36,7 @@ import { Notification } from "../../../../utils/types.js"; import { LoginPage } from "../../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js"; import { UpdatePage } from "./UpdatePage.js"; +import { useInstanceProducts } from "../../../../hooks/product.js"; interface Props { onBack?: () => void; @@ -48,6 +49,8 @@ export default function UpdateCategory({ onBack, }: Props): VNode { const result = useCategoryDetails(cid); + // FIXME: if the product list is big the will bring a lot of info + const inventoryResult = useInstanceProducts(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { state, lib } = useSessionContext(); @@ -70,6 +73,23 @@ export default function UpdateCategory({ } } } + if (!inventoryResult) return <Loading />; + if (inventoryResult instanceof TalerError) { + return <ErrorLoadingMerchant error={inventoryResult} />; + } + if (inventoryResult.type === "fail") { + switch (inventoryResult.case) { + case HttpStatusCode.NotFound: { + return <NotFoundPageOrAdminCreate />; + } + case HttpStatusCode.Unauthorized: { + return <LoginPage />; + } + default: { + assertUnreachable(inventoryResult); + } + } + } return ( <Fragment> @@ -79,6 +99,7 @@ export default function UpdateCategory({ ...result.body, id: cid, }} + instanceInventory={inventoryResult.body} onBack={onBack} onUpdate={async (newInfo) => { return lib.instance diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx @@ -51,6 +51,7 @@ export default function OrderCreate({ onConfirm, onBack }: Props): VNode { const { state, lib } = useSessionContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const detailsResult = useInstanceDetails(); + // FIXME: if the product list is big the will bring a lot of info const inventoryResult = useInstanceProducts(); const { i18n } = useTranslationContext(); @@ -116,7 +117,8 @@ export default function OrderCreate({ onConfirm, onBack }: Props): VNode { setNotif({ message: i18n.str`Could not create order`, type: "ERROR", - description: error instanceof Error ? error.message : String(error), + description: + error instanceof Error ? error.message : String(error), }); }); }}