merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit 9ee8200672c49b91a6ee3ad3caf8c109d8c98d18
parent 789de7f8522ca03f8460df0431b4e1a7e792e25f
Author: Sebastian <sebasjm@gmail.com>
Date:   Wed, 10 Mar 2021 14:27:02 -0300

refactor navigation, part 2

Diffstat:
MCHANGELOG.md | 34++++++++++++++++++++++++++++++++--
Apackages/frontend/src/components/exception/loading.tsx | 6++++++
Rpackages/frontend/src/components/auth/index.tsx -> packages/frontend/src/components/exception/login.tsx | 0
Apackages/frontend/src/components/form/InputBoolean.tsx | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/frontend/src/components/menu/SideBar.tsx | 18+++++++++++++-----
Mpackages/frontend/src/components/menu/index.tsx | 150+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mpackages/frontend/src/hooks/backend.ts | 94++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mpackages/frontend/src/messages/en.po | 36++++++++++++++++++++++++++++++++----
Mpackages/frontend/src/routes/instance/orders/list/Table.tsx | 13+++++++------
Mpackages/frontend/src/routes/instance/orders/list/index.tsx | 33+++++++++++++++++++++++++++------
Mpackages/frontend/src/routes/instance/products/list/Table.tsx | 2+-
Mpackages/frontend/src/routes/instance/tips/list/Table.tsx | 2+-
Mpackages/frontend/src/routes/instance/tips/list/index.tsx | 4++--
Mpackages/frontend/src/routes/instance/transfers/list/Table.tsx | 2+-
Mpackages/frontend/src/routes/login/index.tsx | 2+-
Mpackages/frontend/src/scss/main.scss | 9+++++++++
16 files changed, 346 insertions(+), 139 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -5,10 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Future work] + - notifications should tale place between title and content, and not disapear (#6788) + - complete product list information (#6792) + - complete order list information (#6793) + - gettext templates should be generated from the source code (#6791) + - date format (error handling) - - connection errors - allow point separator for amounts - - prevent letters to be input in numbers - red color when input is invalid (onchange) - validate everything using onChange - feature: input as date format @@ -30,8 +33,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - create default instance if it is not already + - confirmation page when creating instances + +main action => refund +exchange errors +existing refund + +new refund, amount, < allow + +product +increase total +increase lost +changes taxes +changes prices +update descripcion + +deletiing product + + ## [Unreleased] + - change the admin title to "instances" if we are listing the instances and "settings: $ID" on updating instances (#6790) + - update title with: Taler Backoffice: $PAGE_TITLE (#6790) + - paths should be /orders instead of /o (same others) + - if there is enough space for tables in mobile, make the scrollables (#6789) + +## [0.0.4] - 2021-03-11 + - prevent letters to be input in numbers + - instance id in instance list should be clickable + - edit button to go to instance settings - add order section - add product section - add tips section diff --git a/packages/frontend/src/components/exception/loading.tsx b/packages/frontend/src/components/exception/loading.tsx @@ -0,0 +1,5 @@ +import { h, VNode } from "preact"; + +export function Loading():VNode { + return <div>loading...</div> +} +\ No newline at end of file diff --git a/packages/frontend/src/components/auth/index.tsx b/packages/frontend/src/components/exception/login.tsx diff --git a/packages/frontend/src/components/form/InputBoolean.tsx b/packages/frontend/src/components/form/InputBoolean.tsx @@ -0,0 +1,80 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { h, VNode } from "preact"; +import { Message, useMessage } from "preact-messages"; +import { useField } from "./Field"; + +interface Props<T> { + name: T; + readonly?: boolean; + expand?: boolean; + threeState?: boolean; + toBoolean?: (v?: any) => boolean | undefined; + fromBoolean?: (s: boolean | undefined) => any; +} + +const defaultToBoolean = (f?: any): boolean | undefined => f || '' +const defaultFromBoolean = (v: boolean | undefined): any => v as any + + + +export function InputBoolean<T>({ name, readonly, threeState, expand, fromBoolean = defaultFromBoolean, toBoolean = defaultToBoolean }: Props<keyof T>): VNode { + const { error, value, onChange } = useField<T>(name); + + const placeholder = useMessage(`fields.instance.${name}.placeholder`); + const tooltip = useMessage(`fields.instance.${name}.tooltip`); + + const onCheckboxClick = (): void => { + const c = toBoolean(value) + if (c === false && threeState) return onChange(undefined as any) + return onChange(fromBoolean(!c)) + } + + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <Message id={`fields.instance.${name}.label`} /> + {tooltip && <span class="icon has-tooltip-right" data-tooltip={tooltip}> + <i class="mdi mdi-information" /> + </span>} + </label> + </div> + <div class="field-body is-flex-grow-3"> + <div class="field"> + <p class={expand ? "control is-expanded" : "control"}> + <label class="b-checkbox checkbox"> + <input type="checkbox" class={toBoolean(value) === undefined ? "is-indeterminate" : ""} + checked={toBoolean(value)} + placeholder={placeholder} readonly={readonly} + name={String(name)} disabled={readonly} + onChange={onCheckboxClick} /> + + <span class="check" /> + </label> + <Message id={`fields.instance.${name}.help`}> </Message> + </p> + {error ? <p class="help is-danger"> + <Message id={`validation.${error.type}`} fields={error.params}>{error.message} </Message> + </p> : null} + </div> + </div> + </div>; +} diff --git a/packages/frontend/src/components/menu/SideBar.tsx b/packages/frontend/src/components/menu/SideBar.tsx @@ -70,25 +70,25 @@ export function Sidebar({ mobile, instance, onLogout }: Props): VNode { </Fragment>} <li> <a href="/o" class="has-icon"> - <span class="icon"><i class="mdi mdi-square-edit-outline" /></span> + <span class="icon"><i class="mdi mdi-cash-register" /></span> <span class="menu-item-label">Orders</span> </a> </li> <li> <a href="/p" class="has-icon"> - <span class="icon"><i class="mdi mdi-account-circle" /></span> + <span class="icon"><i class="mdi mdi-shopping" /></span> <span class="menu-item-label">Products</span> </a> </li> <li> <a href="/t" class="has-icon"> - <span class="icon"><i class="mdi mdi-account-circle" /></span> + <span class="icon"><i class="mdi mdi-bank" /></span> <span class="menu-item-label">Transfers</span> </a> </li> <li> <a href="/r" class="has-icon"> - <span class="icon"><i class="mdi mdi-account-circle" /></span> + <span class="icon"><i class="mdi mdi-cash" /></span> <span class="menu-item-label">Tips</span> </a> </li> @@ -105,7 +105,15 @@ export function Sidebar({ mobile, instance, onLogout }: Props): VNode { <div > <span style={{ width: '3rem' }} class="icon"><i class="mdi mdi-web" /></span> <span class="menu-item-label"> - {new URL(backend.url).hostname} / {!instance ? "default" : instance} + {new URL(backend.url).hostname} + </span> + </div> + </li> + <li> + <div > + <span style={{ width: '3rem' }} class="icon">ID</span> + <span class="menu-item-label"> + {!instance ? "default" : instance} </span> </div> </li> diff --git a/packages/frontend/src/components/menu/index.tsx b/packages/frontend/src/components/menu/index.tsx @@ -14,66 +14,88 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { h, VNode } from "preact"; -import { useEffect, useState } from "preact/hooks"; -import { AdminPaths } from "../../AdminRoutes"; -import { InstancePaths } from "../../InstanceRoutes"; -import { NavigationBar } from "./NavigationBar"; -import { Sidebar } from "./SideBar"; -import Match from 'preact-router/match'; - -interface Props { - title?: string; - instance?: string; - onLogout?: () => void; -} - -function getInstanceTitle(path: string, id: string): string { - - switch (path) { - case InstancePaths.details: return `${id}` - case InstancePaths.update: return `${id}: Settings` - case InstancePaths.order_list: return `${id}: Orders` - case InstancePaths.order_new: return `${id}: New order` - case InstancePaths.order_update: return `${id}: Update order` - case InstancePaths.product_list: return `${id}: Products` - case InstancePaths.product_new: return `${id}: New product` - case InstancePaths.product_update: return `${id}: Update product` - case InstancePaths.tips_list: return `${id}: Tips` - case InstancePaths.tips_new: return `${id}: New tip` - case InstancePaths.tips_update: return `${id}: Update tip` - case InstancePaths.transfers_list: return `${id}: Transfers` - case InstancePaths.transfers_new: return `${id}: New Transfer` - default: return ''; - } -} - -const INSTANCE_ID_LOOKUP = /^\/instance\/([^/]*)\// -function getAdminTitle(path: string) { - if (path === AdminPaths.new_instance) return `New instance` - if (path === AdminPaths.list_instances) return `Instances` - const match = INSTANCE_ID_LOOKUP.exec(path) - if (match && match[1]) return getInstanceTitle(path.replace(INSTANCE_ID_LOOKUP, '/'), match[1]); - return getInstanceTitle(path, 'default') -} - -export function Menu({ onLogout, title, instance }: Props): VNode { - const [mobileOpen, setMobileOpen] = useState(false) - - return <Match>{({ path }: any) => { - const titleWithSubtitle = title ? title : (instance ? getInstanceTitle(path, instance) : getAdminTitle(path)) - - useEffect(() => { - document.title = 'Taler Backoffice: ' + titleWithSubtitle - }, [titleWithSubtitle]) - - return ( - <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}> - <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={titleWithSubtitle} /> - {onLogout && <Sidebar onLogout={onLogout} instance={instance} mobile={mobileOpen} />} - </div> - ) - }}</Match> - - -} -\ No newline at end of file + import { h, VNode } from "preact"; + import { useEffect, useState } from "preact/hooks"; + import { AdminPaths } from "../../AdminRoutes"; + import { InstancePaths } from "../../InstanceRoutes"; + import { NavigationBar } from "./NavigationBar"; + import { Sidebar } from "./SideBar"; + import Match from 'preact-router/match'; + + + function getInstanceTitle(path: string, id: string): string { + + switch (path) { + case InstancePaths.details: return `${id}` + case InstancePaths.update: return `${id}: Settings` + case InstancePaths.order_list: return `${id}: Orders` + case InstancePaths.order_new: return `${id}: New order` + case InstancePaths.order_update: return `${id}: Update order` + case InstancePaths.product_list: return `${id}: Products` + case InstancePaths.product_new: return `${id}: New product` + case InstancePaths.product_update: return `${id}: Update product` + case InstancePaths.tips_list: return `${id}: Tips` + case InstancePaths.tips_new: return `${id}: New tip` + case InstancePaths.tips_update: return `${id}: Update tip` + case InstancePaths.transfers_list: return `${id}: Transfers` + case InstancePaths.transfers_new: return `${id}: New Transfer` + default: return ''; + } + } + + const INSTANCE_ID_LOOKUP = /^\/instance\/([^/]*)\// + function getAdminTitle(path: string) { + if (path === AdminPaths.new_instance) return `New instance` + if (path === AdminPaths.list_instances) return `Instances` + const match = INSTANCE_ID_LOOKUP.exec(path) + if (match && match[1]) return getInstanceTitle(path.replace(INSTANCE_ID_LOOKUP, '/'), match[1]); + return getInstanceTitle(path, 'default') + } + + interface MenuProps { + title?: string; + instance: string; + admin?: boolean; + onLogout?: () => void; + } + + + export function Menu({ onLogout, title, instance, admin }: MenuProps): VNode { + const [mobileOpen, setMobileOpen] = useState(false) + + return <Match>{({ path }: any) => { + const titleWithSubtitle = title ? title : (!admin ? getInstanceTitle(path, instance) : getAdminTitle(path)) + + useEffect(() => { + document.title = 'Taler Backoffice: ' + titleWithSubtitle + }, [titleWithSubtitle]) + + return ( + <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}> + <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={titleWithSubtitle} /> + {onLogout && <Sidebar onLogout={onLogout} admin={admin} instance={instance} mobile={mobileOpen} />} + </div> + ) + }}</Match> + + } + + interface NotYetReadyAppMenuProps { + title: string; + onLogout?: () => void; + } + + export function NotYetReadyAppMenu({ onLogout, title }: NotYetReadyAppMenuProps): VNode { + const [mobileOpen, setMobileOpen] = useState(false) + + useEffect(() => { + document.title = 'Taler Backoffice: ' + title + }, [title]) + + return <div class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)}> + <NavigationBar onMobileMenu={() => setMobileOpen(!mobileOpen)} title={title} /> + {onLogout && <Sidebar onLogout={onLogout} instance="" mobile={mobileOpen} />} + </div> + + } + +\ No newline at end of file diff --git a/packages/frontend/src/hooks/backend.ts b/packages/frontend/src/hooks/backend.ts @@ -19,12 +19,15 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import useSWR, { mutate } from 'swr'; +import useSWR, { mutate, cache } from 'swr'; import axios from 'axios' import { MerchantBackend } from '../declaration'; -import { useContext } from 'preact/hooks'; import { useBackendContext, useInstanceContext } from '../context/backend'; +function mutateAll(re: RegExp) { + cache.keys().filter(key => re.test(key)).forEach(key => mutate(key, null)) +} + type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError; interface HttpResponseOk<T> { @@ -90,6 +93,12 @@ function fetcher(url: string, token: string, backend: string) { return request(`${backend}${url}`, { token }) } +type YesOrNo = 'yes' | 'no'; + +function orderFetcher(url: string, token: string, backend: string, paid?: YesOrNo, refunded?: YesOrNo, wired?: YesOrNo) { + return request(`${backend}${url}`, { token, params: { paid, refunded, wired } }) +} + function transferFetcher(url: string, token: string, backend: string) { return request(`${backend}${url}`, { token, params: { payto_uri: '' } }) } @@ -108,7 +117,7 @@ export function useAdminMutateAPI(): AdminMutateAPI { data: instance }) - mutate(['/private/instances', token, url], null) + mutateAll(/@"\/private\/instances"@/) } const deleteInstance = async (id: string): Promise<void> => { @@ -117,7 +126,7 @@ export function useAdminMutateAPI(): AdminMutateAPI { token, }) - mutate(['/private/instances', token, url], null) + mutateAll(/@"\/private\/instances"@/) } return { createInstance, deleteInstance } @@ -138,8 +147,8 @@ export function useProductMutateAPI(): ProductMutateAPI { const { url, token } = !admin ? { url: baseUrl, token: adminToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } const createProduct = async (data: MerchantBackend.Products.ProductAddDetail): Promise<void> => { @@ -149,8 +158,7 @@ export function useProductMutateAPI(): ProductMutateAPI { data }) - if (adminToken) mutate(['/private/products', adminToken, baseUrl], null) - mutate([`/private/products`, token, url], null) + mutateAll(/@"\/private\/products"@/) } const updateProduct = async (productId: string, data: MerchantBackend.Products.ProductPatchDetail): Promise<void> => { @@ -160,8 +168,7 @@ export function useProductMutateAPI(): ProductMutateAPI { data }) - if (adminToken) mutate(['/private/products', adminToken, baseUrl], null) - mutate([`/private/products`, token, url], null) + mutateAll(/@"\/private\/products"@/) } const deleteProduct = async (productId: string): Promise<void> => { @@ -170,8 +177,7 @@ export function useProductMutateAPI(): ProductMutateAPI { token, }) - if (adminToken) mutate(['/private/products', adminToken, baseUrl], null) - mutate([`/private/products`, token, url], null) + mutateAll(/@"\/private\/products"@/) } const lockProduct = async (productId: string, data: MerchantBackend.Products.LockRequest): Promise<void> => { @@ -181,8 +187,7 @@ export function useProductMutateAPI(): ProductMutateAPI { data }) - if (adminToken) mutate(['/private/products', adminToken, baseUrl], null) - mutate([`/private/products`, token, url], null) + mutateAll(/@"\/private\/products"@/) } return { createProduct, updateProduct, deleteProduct, lockProduct } @@ -202,8 +207,8 @@ export function useOrderMutateAPI(): OrderMutateAPI { const { url, token } = !admin ? { url: baseUrl, token: adminToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } const createOrder = async (data: MerchantBackend.Orders.PostOrderRequest): Promise<MerchantBackend.Orders.PostOrderResponse> => { const res = await request(`${url}/private/orders`, { @@ -212,8 +217,7 @@ export function useOrderMutateAPI(): OrderMutateAPI { data }) - if (adminToken) mutate(['/private/orders', adminToken, baseUrl], null) - mutate([`/private/orders`, token, url], null) + mutateAll(/@"\/private\/orders"@/) return res } const forgetOrder = async (orderId: string, data: MerchantBackend.Orders.ForgetRequest): Promise<void> => { @@ -223,8 +227,7 @@ export function useOrderMutateAPI(): OrderMutateAPI { data }) - if (adminToken) mutate(['/private/orders', adminToken, baseUrl], null) - mutate([`/private/orders`, token, url], null) + mutateAll(/@"\/private\/orders"@/) } const deleteOrder = async (orderId: string): Promise<void> => { await request(`${url}/private/orders/${orderId}`, { @@ -232,8 +235,7 @@ export function useOrderMutateAPI(): OrderMutateAPI { token }) - if (adminToken) mutate(['/private/orders', adminToken, baseUrl], null) - mutate([`/private/orders`, token, url], null) + mutateAll(/@"\/private\/orders"@/) } return { createOrder, forgetOrder, deleteOrder } } @@ -249,8 +251,8 @@ export function useTransferMutateAPI(): TransferMutateAPI { const { url, token } = !admin ? { url: baseUrl, token: adminToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } const informTransfer = async (data: MerchantBackend.Transfers.TransferInformation): Promise<MerchantBackend.Transfers.MerchantTrackTransferResponse> => { const res = await request(`${url}/private/transfers`, { @@ -259,8 +261,7 @@ export function useTransferMutateAPI(): TransferMutateAPI { data }) - if (adminToken) mutate(['/private/transfers', adminToken, baseUrl], null) - mutate([`/private/transfers`, token, url], null) + mutateAll(/@"\/private\/transfers"@/) return res } @@ -281,8 +282,8 @@ export function useTipsMutateAPI(): TipsMutateAPI { const { url, token } = !admin ? { url: baseUrl, token: adminToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } //reserves const createReserve = async (data: MerchantBackend.Tips.ReserveCreateRequest): Promise<MerchantBackend.Tips.ReserveCreateConfirmation> => { @@ -292,8 +293,7 @@ export function useTipsMutateAPI(): TipsMutateAPI { data }) - if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null) - mutate([`/private/reserves`, token, url], null) + mutateAll(/@"\/private\/reserves"@/) return res } @@ -304,8 +304,7 @@ export function useTipsMutateAPI(): TipsMutateAPI { data }) - if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null) - mutate([`/private/reserves`, token, url], null) + mutateAll(/@"\/private\/reserves"@/) return res } @@ -316,8 +315,7 @@ export function useTipsMutateAPI(): TipsMutateAPI { data }) - if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null) - mutate([`/private/reserves`, token, url], null) + mutateAll(/@"\/private\/reserves"@/) return res } @@ -327,8 +325,7 @@ export function useTipsMutateAPI(): TipsMutateAPI { token, }) - if (adminToken) mutate(['/private/reserves', adminToken, baseUrl], null) - mutate([`/private/reserves`, token, url], null) + mutateAll(/@"\/private\/reserves"@/) } @@ -423,25 +420,30 @@ export function useInstanceProducts(): HttpResponse<MerchantBackend.Products.Inv const { url, token } = !admin ? { url: baseUrl, token: baseToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } const { data, error } = useSWR<MerchantBackend.Products.InventorySummaryResponse, SwrError>([`/private/products`, token, url], fetcher) return { data, unauthorized: error?.status === 401, notfound: error?.status === 404, error } } -export function useInstanceOrders(): HttpResponse<MerchantBackend.Orders.OrderHistory> { +export interface InstanceOrderFilter { + paid?: YesOrNo; + refunded?: YesOrNo; + wired?: YesOrNo; +} +export function useInstanceOrders(args?: InstanceOrderFilter): HttpResponse<MerchantBackend.Orders.OrderHistory> { const { url: baseUrl, token: baseToken } = useBackendContext(); const { token: instanceToken, id, admin } = useInstanceContext(); const { url, token } = !admin ? { url: baseUrl, token: baseToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } - const { data, error } = useSWR<MerchantBackend.Orders.OrderHistory, SwrError>([`/private/orders`, token, url], fetcher) + const { data, error } = useSWR<MerchantBackend.Orders.OrderHistory, SwrError>([`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired], orderFetcher) return { data, unauthorized: error?.status === 401, notfound: error?.status === 404, error } } @@ -453,8 +455,8 @@ export function useInstanceTips(): HttpResponse<MerchantBackend.Tips.TippingRese const { url, token } = !admin ? { url: baseUrl, token: baseToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } const { data, error } = useSWR<MerchantBackend.Tips.TippingReserveStatus, SwrError>([`/private/reserves`, token, url], fetcher) @@ -468,8 +470,8 @@ export function useInstanceTransfers(): HttpResponse<MerchantBackend.Transfers.T const { url, token } = !admin ? { url: baseUrl, token: baseToken } : { - url: `${baseUrl}/instances/${id}`, token: instanceToken - } + url: `${baseUrl}/instances/${id}`, token: instanceToken + } const { data, error } = useSWR<MerchantBackend.Transfers.TransferList, SwrError>([`/private/transfers`, token, url], transferFetcher) diff --git a/packages/frontend/src/messages/en.po b/packages/frontend/src/messages/en.po @@ -58,8 +58,8 @@ msgstr "Use this token to secure an instance with a password" msgid "fields.instance.payto_uris.label" msgstr "Account address" -msgid "fields.instance.payto_uris.help" -msgstr "x-taler-bank/bank.taler:5882/blogger" +# msgid "fields.instance.payto_uris.help" +# msgstr "x-taler-bank/bank.taler:5882/blogger" msgid "fields.instance.default_max_deposit_fee.label" msgstr "Max deposit fee label" @@ -104,7 +104,7 @@ msgid "fields.instance.address.building_number.label" msgstr "Building Number" msgid "fields.instance.address.address_lines.label" -msgstr "Adress Line" +msgstr "Address" msgid "fields.instance.jurisdiction.label" msgstr "Jurisdiction" @@ -137,7 +137,7 @@ msgid "fields.instance.jurisdiction.building_number.label" msgstr "Building Number" msgid "fields.instance.jurisdiction.address_lines.label" -msgstr "Adress Line" +msgstr "Address" msgid "fields.instance.default_pay_delay.label" msgstr "Pay delay" @@ -250,3 +250,30 @@ msgstr "Exchange Initial Amount" msgid "fields.tips.merchant_initial_amount.label" msgstr "Merchant Initial Amount" + +msgid "fields.instance.paid.placeholder" +msgstr "" + +msgid "fields.instance.paid.tooltip" +msgstr "three state boolean" + +msgid "fields.instance.paid.label" +msgstr "Paid" + +msgid "fields.instance.refunded.placeholder" +msgstr "" + +# msgid "fields.instance.refunded.tooltip" +# msgstr "" + +msgid "fields.instance.refunded.label" +msgstr "Refunded" + +msgid "fields.instance.wired.placeholder" +msgstr "" + +# msgid "fields.instance.wired.tooltip" +# msgstr "" + +msgid "fields.instance.wired.label" +msgstr "Wired" +\ No newline at end of file diff --git a/packages/frontend/src/routes/instance/orders/list/Table.tsx b/packages/frontend/src/routes/instance/orders/list/Table.tsx @@ -19,15 +19,14 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode } from "preact" +import { Fragment, h, VNode } from "preact" import { Message } from "preact-messages" -import { StateUpdater, useEffect, useState } from "preact/hooks" +import { StateUpdater, useCallback, useEffect, useRef, useState } from "preact/hooks" import { MerchantBackend, WidthId } from "../../../../declaration" import { Actions, buildActions } from "../../../../utils/table"; type Entity = MerchantBackend.Orders.OrderHistoryEntry & { id: string } - -interface Props { + interface Props { instances: Entity[]; onUpdate: (id: string) => void; onDelete: (id: Entity) => void; @@ -56,7 +55,7 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }: return <div class="card has-table"> <header class="card-header"> - <p class="card-header-title"><span class="icon"><i class="mdi mdi-account-multiple" /></span><Message id="Orders" /></p> + <p class="card-header-title"><span class="icon"><i class="mdi mdi-cash-register" /></span><Message id="Orders" /></p> <div class="card-header-icon" aria-label="more options"> @@ -85,6 +84,8 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }: </div> </div> </div> + + } interface TableProps { rowSelection: string[]; @@ -127,7 +128,7 @@ function Table({ rowSelection, rowSelectionHandler, instances, onUpdate, onDelet <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 'pointer' }} >{i.amount}</td> <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 'pointer' }} >{i.summary}</td> <td onClick={(): void => onUpdate(i.id)} style={{ cursor: 'pointer' }} >{i.paid}</td> - <td class="is-actions-cell"> + <td class="is-actions-cell right-sticky"> <div class="buttons is-right"> <button class="button is-small is-danger jb-modal" type="button" onClick={(): void => onDelete(i)}> <span class="icon"><i class="mdi mdi-trash-can" /></span> diff --git a/packages/frontend/src/routes/instance/orders/list/index.tsx b/packages/frontend/src/routes/instance/orders/list/index.tsx @@ -1,27 +1,48 @@ import { h, VNode } from 'preact'; +import { useState } from 'preact/hooks'; import { useConfigContext } from '../../../../context/backend'; import { MerchantBackend } from '../../../../declaration'; -import { SwrError, useInstanceOrders, useOrderMutateAPI, useProductMutateAPI } from '../../../../hooks/backend'; +import { InstanceOrderFilter, SwrError, useInstanceOrders, useOrderMutateAPI, useProductMutateAPI } from '../../../../hooks/backend'; import { CardTable } from './Table'; +import { FormProvider, FormErrors } from "../../../../components/form/Field" +import { InputBoolean } from "../../../../components/form/InputBoolean"; interface Props { onUnauthorized: () => VNode; onLoadError: (e: SwrError) => VNode; onCreate: () => void; } + +const fromBooleanToYesAndNo = { + fromBoolean: (b?: boolean) => b === true ? 'yes' : (b === false ? 'no' : undefined), + toBoolean: (b: string) => b === 'yes' ? true : (b === 'no' ? false : undefined) +} + export default function ({ onUnauthorized, onLoadError, onCreate }: Props): VNode { - const result = useInstanceOrders() + const [filter, setFilter] = useState<InstanceOrderFilter>({}) + const result = useInstanceOrders(filter) const { createOrder, deleteOrder } = useOrderMutateAPI() const { currency } = useConfigContext() + + let instances: (MerchantBackend.Orders.OrderHistoryEntry & {id: string})[]; + if (!result.data) { if (result.unauthorized) return onUnauthorized() if (result.error) return onLoadError(result.error) - return <div> - loading .... - </div> + //if loading asume empty list + instances = [] + } else { + instances = result.data.orders.map(o => ({ ...o, id: o.order_id })) } + return <section class="section is-main-section"> - <CardTable instances={result.data.orders.map(o => ({ ...o, id: o.order_id }))} + <FormProvider<InstanceOrderFilter> errors={{}} object={filter} valueHandler={(e) => setFilter(e as any)} > + <InputBoolean<InstanceOrderFilter> name="paid" threeState {...fromBooleanToYesAndNo} /> + <InputBoolean<InstanceOrderFilter> name="refunded" {...fromBooleanToYesAndNo} /> + <InputBoolean<InstanceOrderFilter> name="wired" {...fromBooleanToYesAndNo} /> + </FormProvider> + + <CardTable instances={instances} onCreate={() => createOrder({ order: { amount: `${currency}:${Math.floor(Math.random() * 20 + 1)}`, diff --git a/packages/frontend/src/routes/instance/products/list/Table.tsx b/packages/frontend/src/routes/instance/products/list/Table.tsx @@ -56,7 +56,7 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }: return <div class="card has-table"> <header class="card-header"> - <p class="card-header-title"><span class="icon"><i class="mdi mdi-account-multiple" /></span><Message id="Products" /></p> + <p class="card-header-title"><span class="icon"><i class="mdi mdi-shopping" /></span><Message id="Products" /></p> <div class="card-header-icon" aria-label="more options"> diff --git a/packages/frontend/src/routes/instance/tips/list/Table.tsx b/packages/frontend/src/routes/instance/tips/list/Table.tsx @@ -56,7 +56,7 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }: return <div class="card has-table"> <header class="card-header"> - <p class="card-header-title"><span class="icon"><i class="mdi mdi-account-multiple" /></span><Message id="Tips" /></p> + <p class="card-header-title"><span class="icon"><i class="mdi mdi-cash" /></span><Message id="Tips" /></p> <div class="card-header-icon" aria-label="more options"> diff --git a/packages/frontend/src/routes/instance/tips/list/index.tsx b/packages/frontend/src/routes/instance/tips/list/index.tsx @@ -23,12 +23,12 @@ export default function ({ onUnauthorized, onLoadError }: Props): VNode { <CardTable instances={result.data.reserves.filter(r => r.active).map(o => ({ ...o, id: o.reserve_pub }))} onCreate={() => createReserve({ // explode with basic - wire_method: 'x-taler-bank', + wire_method: 'x-taler-ban', initial_balance: `${currency}:${Math.floor(Math.random() * 20 + 1)}`, //explode with 1 // hangs with /asd/asd/ // http://localhost:8081/ - exchange_url: 'http://exchange.taler:8081', + exchange_url: 'https://exchange-demo.rigel.ar/', })} onDelete={(reserve: MerchantBackend.Tips.ReserveStatusEntry) => deleteReserve(reserve.reserve_pub)} onUpdate={() => null} diff --git a/packages/frontend/src/routes/instance/transfers/list/Table.tsx b/packages/frontend/src/routes/instance/transfers/list/Table.tsx @@ -56,7 +56,7 @@ export function CardTable({ instances, onCreate, onUpdate, onDelete, selected }: return <div class="card has-table"> <header class="card-header"> - <p class="card-header-title"><span class="icon"><i class="mdi mdi-account-multiple" /></span><Message id="Transfers" /></p> + <p class="card-header-title"><span class="icon"><i class="mdi mdi-bank" /></span><Message id="Transfers" /></p> <div class="card-header-icon" aria-label="more options"> diff --git a/packages/frontend/src/routes/login/index.tsx b/packages/frontend/src/routes/login/index.tsx @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ import { h, VNode } from "preact"; -import { LoginModal } from '../../components/auth'; +import { LoginModal } from '../../components/exception/login'; import { Notification } from "../../utils/types"; interface Props { diff --git a/packages/frontend/src/scss/main.scss b/packages/frontend/src/scss/main.scss @@ -81,4 +81,13 @@ div { border-width: 0.25em; } } +} + +input[type=checkbox]:indeterminate + .check { + background: red !important; +} + +.right-sticky { + position: sticky; + right: 0px; } \ No newline at end of file