taler-typescript-core

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

commit ed9cefef1457f5fb9f5646bdc014c4efc1f23514
parent fd1ad66496a34b158ddb648934e8d7e1d0ab16b7
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Tue,  9 Dec 2025 14:59:30 -0300

fix #10658


Diffstat:
Mpackages/merchant-backoffice-ui/src/Routing.tsx | 33++++++++++++++++++++++++++++++++-
Mpackages/merchant-backoffice-ui/src/components/menu/index.tsx | 2++
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx | 104+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx | 15++++++++-------
Mpackages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx | 83++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
5 files changed, 157 insertions(+), 80 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/Routing.tsx b/packages/merchant-backoffice-ui/src/Routing.tsx @@ -53,7 +53,9 @@ import UpdateCategory from "./paths/instance/categories/update/index.js"; import ListKYCPage from "./paths/instance/kyc/list/index.js"; import OrderCreatePage from "./paths/instance/orders/create/index.js"; import OrderDetailsPage from "./paths/instance/orders/details/index.js"; -import OrderListPage from "./paths/instance/orders/list/index.js"; +import OrderListPage, { + OrderListSection, +} from "./paths/instance/orders/list/index.js"; import ValidatorCreatePage from "./paths/instance/otp_devices/create/index.js"; import ValidatorListPage from "./paths/instance/otp_devices/list/index.js"; import ValidatorUpdatePage from "./paths/instance/otp_devices/update/index.js"; @@ -110,6 +112,7 @@ export enum InstancePaths { inventory_new = "/inventory/new", order_list = "/orders", + order_list_section = "/orders/:section", order_new = "/order/new", order_details = "/order/:oid/details", @@ -509,12 +512,40 @@ export function Routing(_p: Props): VNode { <Route path={InstancePaths.order_list} component={OrderListPage} + section={OrderListSection.NEW} onCreate={() => { route(InstancePaths.order_new); }} onSelect={(id: string) => { route(InstancePaths.order_details.replace(":oid", id)); }} + onChangeSection={(s?: OrderListSection) => { + if (!s) { + route(InstancePaths.order_list); + } else { + route(InstancePaths.order_list_section.replace(":section", s)); + } + }} + /> + {/** + * Order pages + */} + <Route + path={InstancePaths.order_list_section} + component={OrderListPage} + onCreate={() => { + route(InstancePaths.order_new); + }} + onSelect={(id: string) => { + route(InstancePaths.order_details.replace(":oid", id)); + }} + onChangeSection={(s?: OrderListSection) => { + if (!s) { + route(InstancePaths.order_list); + } else { + route(InstancePaths.order_list_section.replace(":section", s)); + } + }} /> <Route path={InstancePaths.order_details} diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx @@ -37,6 +37,8 @@ function getInstanceTitle(path: string, id: string): string { return `${id}: Update bank Account`; case InstancePaths.order_list: return `${id}: Orders`; + case InstancePaths.order_list_section: + return `${id}: Orders`; case InstancePaths.order_new: return `${id}: New order`; case InstancePaths.inventory_list: diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx @@ -25,25 +25,19 @@ import { format } from "date-fns"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { DatePicker } from "../../../../components/picker/DatePicker.js"; -import { dateFormatForSettings, usePreference } from "../../../../hooks/preference.js"; +import { + dateFormatForSettings, + usePreference, +} from "../../../../hooks/preference.js"; import { CardTable } from "./Table.js"; import { WithId } from "../../../../declaration.js"; +import { OrderListSection } from "./index.js"; const TALER_SCREEN_ID = 47; export interface ListPageProps { - onShowAll: () => void; - onShowNotPaid: () => void; - onShowPaid: () => void; - onShowRefunded: () => void; - onShowNotWired: () => void; - onShowWired: () => void; - isAllActive: string; - isPaidActive: string; - isNotPaidActive: string; - isRefundedActive: string; - isNotWiredActive: string; - isWiredActive: string; + onChangeSection: (s: OrderListSection) => void; + section: OrderListSection; jumpToDate?: AbsoluteTime; onSelectDate: (date?: AbsoluteTime) => void; @@ -61,23 +55,13 @@ export function ListPage({ onLoadMoreAfter, onLoadMoreBefore, orders, - isAllActive, onSelectOrder, onRefundOrder, jumpToDate, - onShowAll, - onShowPaid, - onShowNotPaid, - onShowRefunded, - onShowNotWired, - onShowWired, + onChangeSection, onSelectDate, - isPaidActive, - isRefundedActive, - isNotWiredActive, + section, onCreate, - isNotPaidActive, - isWiredActive, }: ListPageProps): VNode { const { i18n } = useTranslationContext(); const dateTooltip = i18n.str`Select date to show nearby orders`; @@ -90,62 +74,86 @@ export function ListPage({ <div class="column is-two-thirds"> <div class="tabs" style={{ overflow: "inherit" }}> <ul> - <li class={isNotPaidActive}> + <li + class={ + section === OrderListSection.NEW ? "is-active" : undefined + } + > <div class="has-tooltip-right" data-tooltip={i18n.str`Only show paid orders`} > - <a onClick={onShowNotPaid}> + <a onClick={() => onChangeSection(OrderListSection.NEW)}> <i18n.Translate>New</i18n.Translate> </a> </div> </li> - <li class={isPaidActive}> + <li + class={ + section === OrderListSection.PAID ? "is-active" : undefined + } + > <div class="has-tooltip-right" data-tooltip={i18n.str`Only show paid orders`} > - <a onClick={onShowPaid}> + <a onClick={() => onChangeSection(OrderListSection.PAID)}> <i18n.Translate>Paid</i18n.Translate> </a> </div> </li> - <li class={isRefundedActive}> + <li + class={ + section === OrderListSection.REFUNDED + ? "is-active" + : undefined + } + > <div class="has-tooltip-right" data-tooltip={i18n.str`Only show orders with refunds`} > - <a onClick={onShowRefunded}> + <a onClick={() => onChangeSection(OrderListSection.REFUNDED)}> <i18n.Translate>Refunded</i18n.Translate> </a> </div> </li> - <li class={isNotWiredActive}> + <li + class={ + section === OrderListSection.PENDING ? "is-active" : undefined + } + > <div class="has-tooltip-left" data-tooltip={i18n.str`Show only paid orders for which the wire transfer is still pending.`} > - <a onClick={onShowNotWired}> + <a onClick={() => onChangeSection(OrderListSection.PENDING)}> <i18n.Translate>Not wired</i18n.Translate> </a> </div> </li> - <li class={isWiredActive}> + <li + class={ + section === OrderListSection.INCOMING + ? "is-active" + : undefined + } + > <div class="has-tooltip-left" data-tooltip={i18n.str`Only display orders that have already been transferred by the payment service provider`} > - <a onClick={onShowWired}> + <a onClick={() => onChangeSection(OrderListSection.INCOMING)}> <i18n.Translate>Completed</i18n.Translate> </a> </div> </li> - <li class={isAllActive}> + <li class={section === undefined ? "is-active" : undefined}> <div class="has-tooltip-right" data-tooltip={i18n.str`Remove all filters`} > - <a onClick={onShowAll}> + <a onClick={() => onChangeSection(OrderListSection.ALL)}> <i18n.Translate>All</i18n.Translate> </a> </div> @@ -158,7 +166,10 @@ export function ListPage({ <div class="field has-addons"> {jumpToDate && ( <div class="control"> - <a class="button is-fullwidth" onClick={() => onSelectDate(undefined)}> + <a + class="button is-fullwidth" + onClick={() => onSelectDate(undefined)} + > <span class="icon" data-tooltip={i18n.str`Clear date filter`} @@ -174,8 +185,17 @@ export function ListPage({ class="input" type="text" readonly - value={!jumpToDate || jumpToDate.t_ms === "never" ? "" : format(jumpToDate.t_ms, dateFormatForSettings(settings))} - placeholder={i18n.str`Jump to date (${dateFormatForSettings(settings)})`} + value={ + !jumpToDate || jumpToDate.t_ms === "never" + ? "" + : format( + jumpToDate.t_ms, + dateFormatForSettings(settings), + ) + } + placeholder={i18n.str`Jump to date (${dateFormatForSettings( + settings, + )})`} onClick={() => { setPickDate(true); }} @@ -205,14 +225,14 @@ export function ListPage({ opened={pickDate} closeFunction={() => setPickDate(false)} dateReceiver={(d) => { - onSelectDate(AbsoluteTime.fromMilliseconds(d.getTime())) + onSelectDate(AbsoluteTime.fromMilliseconds(d.getTime())); }} /> <CardTable orders={orders} onCreate={onCreate} - showRefund={!!isRefundedActive} + section={section} onSelect={onSelectOrder} onRefund={onRefundOrder} onLoadMoreAfter={onLoadMoreAfter} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx @@ -56,6 +56,7 @@ import { } from "../../../../hooks/preference.js"; import { mergeRefunds } from "../../../../utils/amount.js"; import { getOrderAmountAndWirefee } from "../details/DetailPage.js"; +import { OrderListSection } from "./index.js"; const TALER_SCREEN_ID = 48; @@ -64,15 +65,15 @@ interface Props { orders: Entity[]; onRefund: (value: Entity) => void; onCreate: () => void; + section?: OrderListSection; onSelect: (order: Entity) => void; onLoadMoreBefore?: () => void; onLoadMoreAfter?: () => void; - showRefund?: boolean; } export function CardTable({ orders, - showRefund = false, + section, onCreate, onRefund, onSelect, @@ -123,7 +124,7 @@ export function CardTable({ instances={orders} onSelect={onSelect} onRefund={onRefund} - showRefund={showRefund} + section={section} rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} onLoadMoreAfter={onLoadMoreAfter} @@ -146,12 +147,12 @@ interface TableProps { rowSelectionHandler: StateUpdater<string[]>; onLoadMoreBefore?: () => void; onLoadMoreAfter?: () => void; - showRefund: boolean; + section?: OrderListSection; } function Table({ instances, - showRefund, + section, onSelect, onRefund, onLoadMoreAfter, @@ -196,7 +197,7 @@ function Table({ <th style={{ minWidth: 100 }}> <i18n.Translate>Amount</i18n.Translate> </th> - {showRefund ? ( + {section === OrderListSection.REFUNDED ? ( <th style={{ minWidth: 100 }}> <i18n.Translate>Refunded</i18n.Translate> </th> @@ -229,7 +230,7 @@ function Table({ > {i.amount} </td> - {showRefund ? ( + {section === OrderListSection.REFUNDED ? ( <td onClick={(): void => onSelect(i)} style={{ cursor: "pointer" }} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx @@ -41,7 +41,7 @@ import { useSessionContext } from "../../../../context/session.js"; import { InstanceOrderFilter, useInstanceOrders, - useOrderDetails + useOrderDetails, } from "../../../../hooks/order.js"; import { LoginPage } from "../../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js"; @@ -52,11 +52,57 @@ const TALER_SCREEN_ID = 46; interface Props { onSelect: (id: string) => void; + onChangeSection: (s: OrderListSection) => void; onCreate: () => void; + section: OrderListSection; } -export default function OrderList({ onCreate, onSelect }: Props): VNode { - const [filter, setFilter] = useState<InstanceOrderFilter>({ paid: false }); +export enum OrderListSection { + NEW = "new", // paid = false + PAID = "paid", // paid = true + REFUNDED = "refunded", // refunded = true + PENDING = "pending", // paid = true, wired = false + INCOMING = "incoming", // wired = true, + SETTLED = "settled", // wired = true, + ALL = "all", // +} + +function sectionToFilter(s?: OrderListSection): { + paid?: boolean; + wired?: boolean; + refunded?: boolean; +} { + switch (s) { + case OrderListSection.NEW: + return { paid: false }; + case OrderListSection.PAID: + return { paid: true }; + case OrderListSection.REFUNDED: + return { refunded: true }; + case OrderListSection.PENDING: + return { paid: true, wired: false }; + case OrderListSection.INCOMING: + return { wired: true }; + case OrderListSection.SETTLED: + case OrderListSection.ALL: + case undefined: + return {}; + default: + assertUnreachable(s); + } +} + +export default function OrderList({ + onCreate, + onSelect, + onChangeSection, + section, +}: Props): VNode { + const [filter, setFilter] = useState<{ + date?: AbsoluteTime; + position?: string; + }>({}); + const [orderToBeRefunded, setOrderToBeRefunded] = useState< TalerMerchantApi.OrderHistoryEntry | undefined >(undefined); @@ -64,7 +110,8 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode { const setNewDate = (date?: AbsoluteTime): void => setFilter((prev) => ({ ...prev, date })); - const result = useInstanceOrders(filter, (d) => + + const result = useInstanceOrders({ ...sectionToFilter(section), ...filter}, (d) => setFilter({ ...filter, position: d }), ); const [notification, safeFunctionHandler] = useLocalNotificationBetter(); @@ -90,20 +137,6 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode { } } - const isNotPaidActive = filter.paid === false ? "is-active" : ""; - const isPaidActive = - filter.paid === true && filter.wired === undefined ? "is-active" : ""; - const isRefundedActive = filter.refunded === true ? "is-active" : ""; - const isNotWiredActive = - filter.wired === false && filter.paid === true ? "is-active" : ""; - const isWiredActive = filter.wired === true ? "is-active" : ""; - const isAllActive = - filter.paid === undefined && - filter.refunded === undefined && - filter.wired === undefined - ? "is-active" - : ""; - const data = {} as TalerMerchantApi.RefundRequest; const refund = safeFunctionHandler( lib.instance.addRefund.bind(lib.instance), @@ -146,21 +179,11 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode { onLoadMoreAfter={result.loadNext} onSelectOrder={(order) => onSelect(order.id)} onRefundOrder={(value) => setOrderToBeRefunded(value)} - isAllActive={isAllActive} - isNotWiredActive={isNotWiredActive} - isWiredActive={isWiredActive} - isPaidActive={isPaidActive} - isNotPaidActive={isNotPaidActive} - isRefundedActive={isRefundedActive} + section={section} jumpToDate={filter.date} onSelectDate={setNewDate} onCreate={onCreate} - onShowAll={() => setFilter({})} - onShowNotPaid={() => setFilter({ paid: false })} - onShowPaid={() => setFilter({ paid: true })} - onShowRefunded={() => setFilter({ refunded: true })} - onShowNotWired={() => setFilter({ wired: false, paid: true })} - onShowWired={() => setFilter({ wired: true })} + onChangeSection={onChangeSection} /> {orderToBeRefunded && (