diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/paths/instance/orders/list')
4 files changed, 321 insertions, 205 deletions
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/List.stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/List.stories.tsx index 8cddd7fd6..156c577f4 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/List.stories.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/List.stories.tsx @@ -43,7 +43,7 @@ export default { function createExample<Props>( Component: FunctionalComponent<Props>, - props: Partial<Props> + props: Partial<Props>, ) { const r = (args: any) => <Component {...args} />; r.args = props; 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 index 60be23c21..bca90e352 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx @@ -15,33 +15,33 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { format } from 'date-fns'; -import { h, VNode } from 'preact'; -import { useState } from 'preact/hooks'; +import { format } from "date-fns"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; import { DatePicker } from "../../../../components/picker/DatePicker.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; -import { Translate, useTranslator } from '../../../../i18n/index.js'; +import { Translate, useTranslator } from "../../../../i18n/index.js"; import { CardTable } from "./Table.js"; export interface ListPageProps { - errorOrderId: string | undefined, + errorOrderId: string | undefined; - onShowAll: () => void, - onShowPaid: () => void, - onShowRefunded: () => void, - onShowNotWired: () => void, + onShowAll: () => void; + onShowPaid: () => void; + onShowRefunded: () => void; + onShowNotWired: () => void; onCopyURL: (id: string) => void; - isAllActive: string, - isPaidActive: string, - isRefundedActive: string, - isNotWiredActive: string, + isAllActive: string; + isPaidActive: string; + isRefundedActive: string; + isNotWiredActive: string; - jumpToDate?: Date, - onSelectDate: (date?: Date) => void, + jumpToDate?: Date; + onSelectDate: (date?: Date) => void; orders: (MerchantBackend.Orders.OrderHistoryEntry & WithId)[]; onLoadMoreBefore?: () => void; @@ -55,92 +55,168 @@ export interface ListPageProps { onCreate: () => void; } -export function ListPage({ orders, errorOrderId, isAllActive, onSelectOrder, onRefundOrder, onSearchOrderById, jumpToDate, onCopyURL, onShowAll, onShowPaid, onShowRefunded, onShowNotWired, onSelectDate, isPaidActive, isRefundedActive, isNotWiredActive, onCreate }: ListPageProps): VNode { +export function ListPage({ + orders, + errorOrderId, + isAllActive, + onSelectOrder, + onRefundOrder, + onSearchOrderById, + jumpToDate, + onCopyURL, + onShowAll, + onShowPaid, + onShowRefunded, + onShowNotWired, + onSelectDate, + isPaidActive, + isRefundedActive, + isNotWiredActive, + onCreate, +}: ListPageProps): VNode { const i18n = useTranslator(); const dateTooltip = i18n`select date to show nearby orders`; const [pickDate, setPickDate] = useState(false); - const [orderId, setOrderId] = useState<string>(''); - - return <section class="section is-main-section"> + const [orderId, setOrderId] = useState<string>(""); - <div class="level"> - <div class="level-left"> - <div class="level-item"> - <div class="field has-addons"> - <div class="control"> - <input class={errorOrderId ? "input is-danger" : "input"} type="text" value={orderId} onChange={e => setOrderId(e.currentTarget.value)} placeholder={i18n`order id`} /> - {errorOrderId && <p class="help is-danger">{errorOrderId}</p>} + return ( + <section class="section is-main-section"> + <div class="level"> + <div class="level-left"> + <div class="level-item"> + <div class="field has-addons"> + <div class="control"> + <input + class={errorOrderId ? "input is-danger" : "input"} + type="text" + value={orderId} + onChange={(e) => setOrderId(e.currentTarget.value)} + placeholder={i18n`order id`} + /> + {errorOrderId && <p class="help is-danger">{errorOrderId}</p>} + </div> + <span + class="has-tooltip-bottom" + data-tooltip={i18n`jump to order with the given order ID`} + > + <button + class="button" + onClick={(e) => onSearchOrderById(orderId)} + > + <span class="icon"> + <i class="mdi mdi-arrow-right" /> + </span> + </button> + </span> </div> - <span class="has-tooltip-bottom" data-tooltip={i18n`jump to order with the given order ID`}> - <button class="button" onClick={(e) => onSearchOrderById(orderId)}> - <span class="icon"><i class="mdi mdi-arrow-right" /></span> - </button> - </span> </div> </div> </div> - </div> - <div class="columns"> - <div class="column is-two-thirds"> - <div class="tabs" style={{overflow:'inherit'}}> - <ul> - <li class={isAllActive}> - <div class="has-tooltip-right" data-tooltip={i18n`remove all filters`}> - <a onClick={onShowAll}><Translate>All</Translate></a> - </div> - </li> - <li class={isPaidActive}> - <div class="has-tooltip-right" data-tooltip={i18n`only show paid orders`}> - <a onClick={onShowPaid}><Translate>Paid</Translate></a> - </div> - </li> - <li class={isRefundedActive}> - <div class="has-tooltip-right" data-tooltip={i18n`only show orders with refunds`}> - <a onClick={onShowRefunded}><Translate>Refunded</Translate></a> + <div class="columns"> + <div class="column is-two-thirds"> + <div class="tabs" style={{ overflow: "inherit" }}> + <ul> + <li class={isAllActive}> + <div + class="has-tooltip-right" + data-tooltip={i18n`remove all filters`} + > + <a onClick={onShowAll}> + <Translate>All</Translate> + </a> + </div> + </li> + <li class={isPaidActive}> + <div + class="has-tooltip-right" + data-tooltip={i18n`only show paid orders`} + > + <a onClick={onShowPaid}> + <Translate>Paid</Translate> + </a> + </div> + </li> + <li class={isRefundedActive}> + <div + class="has-tooltip-right" + data-tooltip={i18n`only show orders with refunds`} + > + <a onClick={onShowRefunded}> + <Translate>Refunded</Translate> + </a> + </div> + </li> + <li class={isNotWiredActive}> + <div + class="has-tooltip-left" + data-tooltip={i18n`only show orders where customers paid, but wire payments from payment provider are still pending`} + > + <a onClick={onShowNotWired}> + <Translate>Not wired</Translate> + </a> + </div> + </li> + </ul> + </div> + </div> + <div class="column "> + <div class="buttons is-right"> + <div class="field has-addons"> + {jumpToDate && ( + <div class="control"> + <a class="button" onClick={() => onSelectDate(undefined)}> + <span class="icon" data-tooltip={i18n`clear date filter`}> + <i class="mdi mdi-close" /> + </span> + </a> + </div> + )} + <div class="control"> + <span class="has-tooltip-top" data-tooltip={dateTooltip}> + <input + class="input" + type="text" + readonly + value={!jumpToDate ? "" : format(jumpToDate, "yyyy/MM/dd")} + placeholder={i18n`date (YYYY/MM/DD)`} + onClick={() => { + setPickDate(true); + }} + /> + </span> </div> - </li> - <li class={isNotWiredActive}> - <div class="has-tooltip-left" data-tooltip={i18n`only show orders where customers paid, but wire payments from payment provider are still pending`}> - <a onClick={onShowNotWired}><Translate>Not wired</Translate></a> + <div class="control"> + <span class="has-tooltip-left" data-tooltip={dateTooltip}> + <a + class="button" + onClick={() => { + setPickDate(true); + }} + > + <span class="icon"> + <i class="mdi mdi-calendar" /> + </span> + </a> + </span> </div> - </li> - </ul> - </div> - </div> - <div class="column "> - <div class="buttons is-right"> - <div class="field has-addons"> - {jumpToDate && <div class="control"> - <a class="button" onClick={() => onSelectDate(undefined)}> - <span class="icon" data-tooltip={i18n`clear date filter`}><i class="mdi mdi-close" /></span> - </a> - </div>} - <div class="control"> - <span class="has-tooltip-top" data-tooltip={dateTooltip}> - <input class="input" type="text" readonly value={!jumpToDate ? '' : format(jumpToDate, 'yyyy/MM/dd')} placeholder={i18n`date (YYYY/MM/DD)`} onClick={() => { setPickDate(true); }} /> - </span> - </div> - <div class="control"> - <span class="has-tooltip-left" data-tooltip={dateTooltip}> - <a class="button" onClick={() => { setPickDate(true); }}> - <span class="icon"><i class="mdi mdi-calendar" /></span> - </a> - </span> </div> </div> </div> </div> - </div> - <DatePicker - opened={pickDate} - closeFunction={() => setPickDate(false)} - dateReceiver={onSelectDate} /> + <DatePicker + opened={pickDate} + closeFunction={() => setPickDate(false)} + dateReceiver={onSelectDate} + /> - <CardTable orders={orders} - onCreate={onCreate} - onCopyURL={onCopyURL} - onSelect={onSelectOrder} - onRefund={onRefundOrder} /> - </section>; + <CardTable + orders={orders} + onCreate={onCreate} + onCopyURL={onCopyURL} + onSelect={onSelectOrder} + onRefund={onRefundOrder} + /> + </section> + ); } 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 index 924d09682..a1ec8d291 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx @@ -173,7 +173,7 @@ function Table({ ? "never" : format( new Date(i.timestamp.t_s * 1000), - "yyyy/MM/dd HH:mm:ss" + "yyyy/MM/dd HH:mm:ss", )} </td> <td @@ -268,7 +268,7 @@ export function RefundModal({ .map((r) => r.amount) .reduce( (p, c) => Amounts.add(p, Amounts.parseOrThrow(c)).amount, - Amounts.zeroOfCurrency(config.currency) + Amounts.zeroOfCurrency(config.currency), ); const orderPrice = order.order_status === "paid" @@ -298,7 +298,7 @@ export function RefundModal({ : undefined, }; const hasErrors = Object.keys(errors).some( - (k) => (errors as any)[k] !== undefined + (k) => (errors as any)[k] !== undefined, ); const validateAndConfirm = () => { @@ -306,7 +306,7 @@ export function RefundModal({ if (!form.refund) return; onConfirm({ refund: Amounts.stringify( - Amounts.add(Amounts.parse(form.refund)!, totalRefunded).amount + Amounts.add(Amounts.parse(form.refund)!, totalRefunded).amount, ), reason: form.description === undefined @@ -358,7 +358,7 @@ export function RefundModal({ ? "never" : format( new Date(r.timestamp.t_s * 1000), - "yyyy-MM-dd HH:mm:ss" + "yyyy-MM-dd HH:mm:ss", )} </td> <td>{r.amount}</td> 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 index a033e7b3a..b5fe7611c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx @@ -15,18 +15,23 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { h, VNode, Fragment } from 'preact'; -import { useState } from 'preact/hooks'; +import { h, VNode, Fragment } from "preact"; +import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; import { NotificationCard } from "../../../../components/menu/index.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; import { HttpError } from "../../../../hooks/backend.js"; -import { InstanceOrderFilter, useInstanceOrders, useOrderAPI, useOrderDetails } from "../../../../hooks/order.js"; -import { useTranslator } from '../../../../i18n/index.js'; +import { + InstanceOrderFilter, + useInstanceOrders, + useOrderAPI, + useOrderDetails, +} from "../../../../hooks/order.js"; +import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { RefundModal } from "./Table.js"; import { ListPage } from "./ListPage.js"; @@ -39,107 +44,133 @@ interface Props { onCreate: () => void; } -export default function ({ onUnauthorized, onLoadError, onCreate, onSelect, onNotFound }: Props): VNode { - const [filter, setFilter] = useState<InstanceOrderFilter>({}) - const [orderToBeRefunded, setOrderToBeRefunded] = useState<MerchantBackend.Orders.OrderHistoryEntry | undefined>(undefined) - - const setNewDate = (date?: Date) => setFilter(prev => ({ ...prev, date })) - - const result = useInstanceOrders(filter, setNewDate) - const { refundOrder, getPaymentURL } = useOrderAPI() - - const [notif, setNotif] = useState<Notification | undefined>(undefined) - - if (result.clientError && result.isUnauthorized) return onUnauthorized() - if (result.clientError && result.isNotfound) return onNotFound() - if (result.loading) return <Loading /> - if (!result.ok) return onLoadError(result) - - const isPaidActive = filter.paid === 'yes' ? "is-active" : '' - const isRefundedActive = filter.refunded === 'yes' ? "is-active" : '' - const isNotWiredActive = filter.wired === 'no' ? "is-active" : '' - const isAllActive = filter.paid === undefined && filter.refunded === undefined && filter.wired === undefined ? 'is-active' : '' - - const i18n = useTranslator() - const [errorOrderId, setErrorOrderId] = useState<string | undefined>(undefined) +export default function ({ + onUnauthorized, + onLoadError, + onCreate, + onSelect, + onNotFound, +}: Props): VNode { + const [filter, setFilter] = useState<InstanceOrderFilter>({}); + const [orderToBeRefunded, setOrderToBeRefunded] = useState< + MerchantBackend.Orders.OrderHistoryEntry | undefined + >(undefined); + + const setNewDate = (date?: Date) => setFilter((prev) => ({ ...prev, date })); + + const result = useInstanceOrders(filter, setNewDate); + const { refundOrder, getPaymentURL } = useOrderAPI(); + + const [notif, setNotif] = useState<Notification | undefined>(undefined); + + if (result.clientError && result.isUnauthorized) return onUnauthorized(); + if (result.clientError && result.isNotfound) return onNotFound(); + if (result.loading) return <Loading />; + if (!result.ok) return onLoadError(result); + + const isPaidActive = filter.paid === "yes" ? "is-active" : ""; + const isRefundedActive = filter.refunded === "yes" ? "is-active" : ""; + const isNotWiredActive = filter.wired === "no" ? "is-active" : ""; + const isAllActive = + filter.paid === undefined && + filter.refunded === undefined && + filter.wired === undefined + ? "is-active" + : ""; + + const i18n = useTranslator(); + const [errorOrderId, setErrorOrderId] = useState<string | undefined>( + undefined, + ); async function testIfOrderExistAndSelect(orderId: string) { if (!orderId) { - setErrorOrderId(i18n`Enter an order id`) + setErrorOrderId(i18n`Enter an order id`); return; } try { - await getPaymentURL(orderId) - onSelect(orderId) - setErrorOrderId(undefined) + await getPaymentURL(orderId); + onSelect(orderId); + setErrorOrderId(undefined); } catch { - setErrorOrderId(i18n`order not found`) + setErrorOrderId(i18n`order not found`); } } - return <Fragment> - <NotificationCard notification={notif} /> - - <ListPage - orders={result.data.orders.map(o => ({ ...o, id: o.order_id }))} - onLoadMoreBefore={result.loadMorePrev} hasMoreBefore={!result.isReachingStart} - onLoadMoreAfter={result.loadMore} hasMoreAfter={!result.isReachingEnd} - - onSelectOrder={(order) => onSelect(order.id)} - onRefundOrder={(value) => setOrderToBeRefunded(value)} - - errorOrderId={errorOrderId} - isAllActive={isAllActive} - isNotWiredActive={isNotWiredActive} - isPaidActive={isPaidActive} - isRefundedActive={isRefundedActive} - jumpToDate={filter.date} - onCopyURL={(id) => getPaymentURL(id).then((resp) => copyToClipboard(resp.data))} - - onCreate={onCreate} - onSearchOrderById={testIfOrderExistAndSelect} - onSelectDate={setNewDate} - onShowAll={() => setFilter({})} - onShowPaid={() => setFilter({ paid: 'yes' })} - onShowRefunded={() => setFilter({ refunded: 'yes' })} - onShowNotWired={() => setFilter({ wired: 'no' })} - - /> - - {orderToBeRefunded && <RefundModalForTable - id={orderToBeRefunded.order_id} - onCancel={() => setOrderToBeRefunded(undefined)} - onConfirm={(value) => refundOrder(orderToBeRefunded.order_id, value) - .then(() => setNotif({ - message: i18n`refund created successfully`, - type: "SUCCESS" - })) - .catch((error) => setNotif({ - message: i18n`could not create the refund`, - type: "ERROR", - description: error.message - })) - .then(() => setOrderToBeRefunded(undefined))} - onLoadError={(error) => { - setNotif({ - message: i18n`could not create the refund`, - type: "ERROR", - description: error.message - }); - setOrderToBeRefunded(undefined); - return <div />; - }} - onUnauthorized={onUnauthorized} - onNotFound={() => { - setNotif({ - message: i18n`could not get the order to refund`, - type: "ERROR", - // description: error.message - }); - setOrderToBeRefunded(undefined); - return <div />; - }} />} - </Fragment> + return ( + <Fragment> + <NotificationCard notification={notif} /> + + <ListPage + orders={result.data.orders.map((o) => ({ ...o, id: o.order_id }))} + onLoadMoreBefore={result.loadMorePrev} + hasMoreBefore={!result.isReachingStart} + onLoadMoreAfter={result.loadMore} + hasMoreAfter={!result.isReachingEnd} + onSelectOrder={(order) => onSelect(order.id)} + onRefundOrder={(value) => setOrderToBeRefunded(value)} + errorOrderId={errorOrderId} + isAllActive={isAllActive} + isNotWiredActive={isNotWiredActive} + isPaidActive={isPaidActive} + isRefundedActive={isRefundedActive} + jumpToDate={filter.date} + onCopyURL={(id) => + getPaymentURL(id).then((resp) => copyToClipboard(resp.data)) + } + onCreate={onCreate} + onSearchOrderById={testIfOrderExistAndSelect} + onSelectDate={setNewDate} + onShowAll={() => setFilter({})} + onShowPaid={() => setFilter({ paid: "yes" })} + onShowRefunded={() => setFilter({ refunded: "yes" })} + onShowNotWired={() => setFilter({ wired: "no" })} + /> + + {orderToBeRefunded && ( + <RefundModalForTable + id={orderToBeRefunded.order_id} + onCancel={() => setOrderToBeRefunded(undefined)} + onConfirm={(value) => + refundOrder(orderToBeRefunded.order_id, value) + .then(() => + setNotif({ + message: i18n`refund created successfully`, + type: "SUCCESS", + }), + ) + .catch((error) => + setNotif({ + message: i18n`could not create the refund`, + type: "ERROR", + description: error.message, + }), + ) + .then(() => setOrderToBeRefunded(undefined)) + } + onLoadError={(error) => { + setNotif({ + message: i18n`could not create the refund`, + type: "ERROR", + description: error.message, + }); + setOrderToBeRefunded(undefined); + return <div />; + }} + onUnauthorized={onUnauthorized} + onNotFound={() => { + setNotif({ + message: i18n`could not get the order to refund`, + type: "ERROR", + // description: error.message + }); + setOrderToBeRefunded(undefined); + return <div />; + }} + /> + )} + </Fragment> + ); } interface RefundProps { @@ -151,21 +182,30 @@ interface RefundProps { onConfirm: (m: MerchantBackend.Orders.RefundRequest) => void; } -function RefundModalForTable({ id, onUnauthorized, onLoadError, onNotFound, onConfirm, onCancel }: RefundProps) { +function RefundModalForTable({ + id, + onUnauthorized, + onLoadError, + onNotFound, + onConfirm, + onCancel, +}: RefundProps) { const result = useOrderDetails(id); - if (result.clientError && result.isUnauthorized) return onUnauthorized() - if (result.clientError && result.isNotfound) return onNotFound() - if (result.loading) return <Loading /> - if (!result.ok) return onLoadError(result) + if (result.clientError && result.isUnauthorized) return onUnauthorized(); + if (result.clientError && result.isNotfound) return onNotFound(); + if (result.loading) return <Loading />; + if (!result.ok) return onLoadError(result); - return <RefundModal - order={result.data} - onCancel={onCancel} - onConfirm={onConfirm} - /> + return ( + <RefundModal + order={result.data} + onCancel={onCancel} + onConfirm={onConfirm} + /> + ); } async function copyToClipboard(text: string) { - return navigator.clipboard.writeText(text) + return navigator.clipboard.writeText(text); } |