/* This file is part of GNU Taler (C) 2021-2023 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ /** * * @author Sebastian Javier Marchano (sebasjm) */ import { Amounts } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { format } from "date-fns"; import { h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; import { FormErrors, FormProvider, } from "../../../../components/form/FormProvider.js"; import { Input } from "../../../../components/form/Input.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputGroup } from "../../../../components/form/InputGroup.js"; import { InputSelector } from "../../../../components/form/InputSelector.js"; import { ConfirmModal } from "../../../../components/modal/index.js"; import { useConfigContext } from "../../../../context/config.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; import { mergeRefunds } from "../../../../utils/amount.js"; import { datetimeFormatForSettings, useSettings } from "../../../../hooks/useSettings.js"; type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId; interface Props { orders: Entity[]; onRefund: (value: Entity) => void; onCopyURL: (id: string) => void; onCreate: () => void; onSelect: (order: Entity) => void; onLoadMoreBefore?: () => void; hasMoreBefore?: boolean; hasMoreAfter?: boolean; onLoadMoreAfter?: () => void; } export function CardTable({ orders, onCreate, onRefund, onCopyURL, onSelect, onLoadMoreAfter, onLoadMoreBefore, hasMoreAfter, hasMoreBefore, }: Props): VNode { const [rowSelection, rowSelectionHandler] = useState([]); const { i18n } = useTranslationContext(); return (

Orders

{orders.length > 0 ? ( onCopyURL(o.id)} rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} onLoadMoreAfter={onLoadMoreAfter} onLoadMoreBefore={onLoadMoreBefore} hasMoreAfter={hasMoreAfter} hasMoreBefore={hasMoreBefore} /> ) : ( )} ); } interface TableProps { rowSelection: string[]; instances: Entity[]; onRefund: (id: Entity) => void; onCopyURL: (id: Entity) => void; onSelect: (id: Entity) => void; rowSelectionHandler: StateUpdater; onLoadMoreBefore?: () => void; hasMoreBefore?: boolean; hasMoreAfter?: boolean; onLoadMoreAfter?: () => void; } function Table({ instances, onSelect, onRefund, onCopyURL, onLoadMoreAfter, onLoadMoreBefore, hasMoreAfter, hasMoreBefore, }: TableProps): VNode { const { i18n } = useTranslationContext(); const [settings] = useSettings(); return (
{hasMoreBefore && ( )}
{instances.map((i) => { return ( ); })}
Date Amount Summary
onSelect(i)} style={{ cursor: "pointer" }} > {i.timestamp.t_s === "never" ? "never" : format( new Date(i.timestamp.t_s * 1000), datetimeFormatForSettings(settings), )} onSelect(i)} style={{ cursor: "pointer" }} > {i.amount} onSelect(i)} style={{ cursor: "pointer" }} > {i.summary}
{i.refundable && ( )} {!i.paid && ( )}
{hasMoreAfter && ( )}
); } function EmptyTable(): VNode { const { i18n } = useTranslationContext(); return (

No orders have been found matching your query!

); } interface RefundModalProps { onCancel: () => void; onConfirm: (value: MerchantBackend.Orders.RefundRequest) => void; order: MerchantBackend.Orders.MerchantOrderStatusResponse; } export function RefundModal({ order, onCancel, onConfirm, }: RefundModalProps): VNode { type State = { mainReason?: string; description?: string; refund?: string }; const [form, setValue] = useState({}); const [settings] = useSettings(); const { i18n } = useTranslationContext(); // const [errors, setErrors] = useState>({}); const refunds = ( order.order_status === "paid" ? order.refund_details : [] ).reduce(mergeRefunds, []); const config = useConfigContext(); const totalRefunded = refunds .map((r) => r.amount) .reduce( (p, c) => Amounts.add(p, Amounts.parseOrThrow(c)).amount, Amounts.zeroOfCurrency(config.currency), ); const orderPrice = order.order_status === "paid" ? Amounts.parseOrThrow(order.contract_terms.amount) : undefined; const totalRefundable = !orderPrice ? Amounts.zeroOfCurrency(totalRefunded.currency) : refunds.length ? Amounts.sub(orderPrice, totalRefunded).amount : orderPrice; const isRefundable = Amounts.isNonZero(totalRefundable); const duplicatedText = i18n.str`duplicated`; const errors: FormErrors = { mainReason: !form.mainReason ? i18n.str`required` : undefined, description: !form.description && form.mainReason !== duplicatedText ? i18n.str`required` : undefined, refund: !form.refund ? i18n.str`required` : !Amounts.parse(form.refund) ? i18n.str`invalid format` : Amounts.cmp(totalRefundable, Amounts.parse(form.refund)!) === -1 ? i18n.str`this value exceed the refundable amount` : undefined, }; const hasErrors = Object.keys(errors).some( (k) => (errors as any)[k] !== undefined, ); const validateAndConfirm = () => { try { if (!form.refund) return; onConfirm({ refund: Amounts.stringify( Amounts.add(Amounts.parse(form.refund)!, totalRefunded).amount, ), reason: form.description === undefined ? form.mainReason || "" : `${form.mainReason}: ${form.description}`, }); } catch (err) { console.log(err); } }; //FIXME: parameters in the translation return ( {refunds.length > 0 && (
{refunds.map((r) => { return ( ); })}
date amount reason
{r.timestamp.t_s === "never" ? "never" : format( new Date(r.timestamp.t_s * 1000), datetimeFormatForSettings(settings), )} {r.amount} {r.reason}
)} {isRefundable && ( errors={errors} object={form} valueHandler={(d) => setValue(d as any)} > name="refund" label={i18n.str`Refund`} tooltip={i18n.str`amount to be refunded`} > Max refundable:{" "} {Amounts.stringify(totalRefundable)} {form.mainReason && form.mainReason !== duplicatedText ? ( label={i18n.str`Description`} name="description" tooltip={i18n.str`more information to give context`} /> ) : undefined} )}
); }