[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] branch master updated: pagination into the o
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] branch master updated: pagination into the orders |
Date: |
Tue, 23 Mar 2021 18:09:23 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
The following commit(s) were added to refs/heads/master by this push:
new da73a14 pagination into the orders
da73a14 is described below
commit da73a14c6909150b5779574dfc0036b27a3752e8
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Mar 23 14:09:10 2021 -0300
pagination into the orders
---
CHANGELOG.md | 22 +---
packages/frontend/src/ApplicationReadyRoutes.tsx | 6 +-
packages/frontend/src/InstanceRoutes.tsx | 69 ++++++-----
packages/frontend/src/components/form/Field.tsx | 4 +-
packages/frontend/src/components/menu/index.tsx | 12 +-
packages/frontend/src/hooks/backend.ts | 84 +++++++++++--
packages/frontend/src/index.tsx | 7 --
packages/frontend/src/messages/en.po | 4 +-
.../frontend/src/paths/admin/create/CreatePage.tsx | 42 +++++--
packages/frontend/src/paths/admin/create/index.tsx | 133 +++++++++++----------
.../src/paths/instance/orders/list/Table.tsx | 59 +++++----
.../src/paths/instance/orders/list/index.tsx | 47 ++++++--
packages/frontend/src/utils/constants.ts | 3 +
13 files changed, 307 insertions(+), 185 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03f65f1..d5e2e4a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,6 @@ 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)
@@ -27,30 +26,17 @@ and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0
- edit button to go to instance settings
- check if there is a way to remove auto async for /routes
/components/{async,routes} so it can be turned on when building
non-single-bundle
-
- - product: main action => refund
- * exchange errors
- * existing refund
-
-new refund, amount, < allow
-
-product
-increase total
-increase lost
-changes taxes
-changes prices
-update descripcion
-
-deletiing product
-
+ - product detail: we could have some button that brings us to thedetailed
screen for the product
+ -
## [Unreleased]
+## [0.0.5] - 2021-03-18
- 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)
- show create default instance if it is not already
- - notifications should tale place between title and content, and not disapear
+ - notifications should tale place between title and content, and not disapear
(#6788)
- create a loading page to be use when the data is not ready (test at
#/loading)
- confirmation page when creating instances
diff --git a/packages/frontend/src/ApplicationReadyRoutes.tsx
b/packages/frontend/src/ApplicationReadyRoutes.tsx
index e36a2b5..c16caf2 100644
--- a/packages/frontend/src/ApplicationReadyRoutes.tsx
+++ b/packages/frontend/src/ApplicationReadyRoutes.tsx
@@ -32,7 +32,7 @@ interface Props {
}
export function ApplicationReadyRoutes({ }: Props): VNode {
const i18n = useMessageTemplate();
- const { url: currentBaseUrl, changeBackend, updateToken, clearAllTokens } =
useBackendContext();
+ const { url: backendURL, changeBackend, updateToken, clearAllTokens } =
useBackendContext();
const updateLoginStatus = (url: string, token?: string) => {
changeBackend(url);
@@ -61,7 +61,7 @@ export function ApplicationReadyRoutes({ }: Props): VNode {
</Fragment>
}
if (list.notfound) {
- const path = new URL(currentBaseUrl).pathname
+ const path = new URL(backendURL).pathname
const match = INSTANCE_ID_LOOKUP.exec(path)
if (!match || !match[1]) {
// this should be rare becuase
@@ -71,7 +71,7 @@ export function ApplicationReadyRoutes({ }: Props): VNode {
<NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} />
<NotificationCard notification={{
message: i18n`Couldnt access the server`,
- description: i18n`Could not infer instance id from url
${currentBaseUrl}`,
+ description: i18n`Could not infer instance id from url
${backendURL}`,
type: 'ERROR',
}}
/>
diff --git a/packages/frontend/src/InstanceRoutes.tsx
b/packages/frontend/src/InstanceRoutes.tsx
index 1804ca7..01f6204 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -55,7 +55,7 @@ import { NotificationCard } from './components/menu';
import { Loading } from './components/exception/loading';
export enum InstancePaths {
- details = '/',
+ // details = '/',
update = '/update',
product_list = '/products',
@@ -63,15 +63,15 @@ export enum InstancePaths {
product_new = '/product/new',
order_list = '/orders',
- order_update = '/p/:oid/update',
- order_new = '/o/new',
+ order_update = '/order/:oid/update',
+ // order_new = '/oreder/new',
- tips_list = '/tips',
- tips_update = '/tip/:rid/update',
- tips_new = '/tip/new',
+ // tips_list = '/tips',
+ // tips_update = '/tip/:rid/update',
+ // tips_new = '/tip/new',
transfers_list = '/transfers',
- transfers_new = '/transfer/new',
+ // transfers_new = '/transfer/new',
}
export enum AdminPaths {
@@ -197,7 +197,8 @@ export function InstanceRoutes({ id, admin }: Props): VNode
{
/>
}
- <Route path={InstancePaths.details}
+ <Route path="/" component={Redirect} to={InstancePaths.order_list} />
+ {/*
component={DetailPage}
onUnauthorized={() => {
@@ -212,14 +213,14 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
return <Fragment>
<NotificationCard notification={{
message: 'No default instance',
- description: 'in order to use merchant backend, you should
create the default instance',
+ description: 'in order to use merchant backoffice, you should
create the default instance',
type: 'INFO'
}} />
<InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
</Fragment>
}
return <NotFoundPage />
- }}
+ }}
onLoadError={(error: SwrError) => {
return <Fragment>
@@ -227,7 +228,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode
{
<LoginPage onConfirm={updateLoginStatus} />
</Fragment>
}}
- />
+ /> */}
<Route path={InstancePaths.update}
component={InstanceUpdatePage}
@@ -251,10 +252,12 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
return <Fragment>
<NotificationCard notification={{
message: 'No default instance',
- description: 'in order to use merchant backend, you should
create the default instance',
+ description: 'in order to use merchant backoffice, you should
create the default instance',
type: 'INFO'
}} />
- <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ <InstanceCreatePage onError={() => null} forceId="default"
onConfirm={() => {
+ route(AdminPaths.list_instances)
+ }} />
</Fragment>
}
return <NotFoundPage />
@@ -289,10 +292,12 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
return <Fragment>
<NotificationCard notification={{
message: 'No default instance',
- description: 'in order to use merchant backend, you should
create the default instance',
+ description: 'in order to use merchant backoffice, you should
create the default instance',
type: 'INFO'
}} />
- <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ <InstanceCreatePage onError={() => null} forceId="default"
onConfirm={() => {
+ route(AdminPaths.list_instances)
+ }} />
</Fragment>
}
return <NotFoundPage />
@@ -327,10 +332,12 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
return <Fragment>
<NotificationCard notification={{
message: 'No default instance',
- description: 'in order to use merchant backend, you should
create the default instance',
+ description: 'in order to use merchant backoffice, you should
create the default instance',
type: 'INFO'
}} />
- <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ <InstanceCreatePage onError={() => null} forceId="default"
onConfirm={() => {
+ route(AdminPaths.list_instances)
+ }} />
</Fragment>
}
return <NotFoundPage />
@@ -346,10 +353,7 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
<Route path={InstancePaths.order_update}
component={OrderUpdatePage}
/>
- <Route path={InstancePaths.order_new}
- component={OrderCreatePage}
- />
-
+ {/*
<Route path={InstancePaths.tips_list}
component={TipListPage}
@@ -365,7 +369,7 @@ export function InstanceRoutes({ id, admin }: Props): VNode
{
return <Fragment>
<NotificationCard notification={{
message: 'No default instance',
- description: 'in order to use merchant backend, you should
create the default instance',
+ description: 'in order to use merchant backoffice, you should
create the default instance',
type: 'INFO'
}} />
<InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
@@ -386,7 +390,8 @@ export function InstanceRoutes({ id, admin }: Props): VNode
{
/>
<Route path={InstancePaths.tips_new}
component={TipCreatePage}
- />
+ />
+ */}
<Route path={InstancePaths.transfers_list}
component={TransferListPage}
@@ -403,10 +408,12 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
return <Fragment>
<NotificationCard notification={{
message: 'No default instance',
- description: 'in order to use merchant backend, you should
create the default instance',
+ description: 'in order to use merchant backoffice, you should
create the default instance',
type: 'INFO'
}} />
- <InstanceCreatePage onError={() => null} onConfirm={() => null}
/>
+ <InstanceCreatePage onError={() => null} forceId="default"
onConfirm={() => {
+ route(AdminPaths.list_instances)
+ }} />
</Fragment>
}
return <NotFoundPage />
@@ -419,9 +426,9 @@ export function InstanceRoutes({ id, admin }: Props): VNode
{
</Fragment>
}}
/>
- <Route path={InstancePaths.transfers_new}
+ {/* <Route path={InstancePaths.transfers_new}
component={TransferCreatePage}
- />
+ /> */}
{/* example of loading page*/}
<Route path="/loading" component={Loading} />
@@ -430,3 +437,11 @@ export function InstanceRoutes({ id, admin }: Props):
VNode {
</InstanceContextProvider>;
}
+
+export function Redirect({ to }: { to: string }): null {
+ useEffect(() => {
+ route(to, true)
+ })
+ return null
+}
+
diff --git a/packages/frontend/src/components/form/Field.tsx
b/packages/frontend/src/components/form/Field.tsx
index 43c04f7..2bb2712 100644
--- a/packages/frontend/src/components/form/Field.tsx
+++ b/packages/frontend/src/components/form/Field.tsx
@@ -82,13 +82,13 @@ export function FormProvider<T>({ object = {}, errors = {},
valueHandler, childr
* @returns
*/
const readField = (object: any, name: string) => {
- return name.split('.').reduce((prev, current) => prev[current], object)
+ return name.split('.').reduce((prev, current) => prev && prev[current],
object)
}
const setValueDeeper = (object: any, names: string[], value: any): any => {
if (names.length === 0) return value
const [head, ...rest] = names
- return {...object, [head]: setValueDeeper(object[head], rest, value) }
+ return {...object, [head]: setValueDeeper(object[head] || {}, rest, value) }
}
export function useField<T>(name: keyof T) {
diff --git a/packages/frontend/src/components/menu/index.tsx
b/packages/frontend/src/components/menu/index.tsx
index 5fa1494..ce80248 100644
--- a/packages/frontend/src/components/menu/index.tsx
+++ b/packages/frontend/src/components/menu/index.tsx
@@ -27,19 +27,19 @@ import { Notification } from "../../utils/types";
function getInstanceTitle(path: string, id: string): string {
switch (path) {
- case InstancePaths.details: return `${id}`
+ // 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_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.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`
+ // case InstancePaths.transfers_new: return `${id}: New Transfer`
default: return '';
}
}
diff --git a/packages/frontend/src/hooks/backend.ts
b/packages/frontend/src/hooks/backend.ts
index a9160c9..9721f9f 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -19,11 +19,13 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import useSWR, { mutate, cache } from 'swr';
+import useSWR, { mutate, cache, useSWRInfinite } from 'swr';
import axios from 'axios'
import { MerchantBackend } from '../declaration';
import { useBackendContext, useInstanceContext } from '../context/backend';
-import { useEffect, useState } from 'preact/hooks';
+import { useEffect, useMemo, useState } from 'preact/hooks';
+import { MAX_RESULT_SIZE, PAGE_SIZE } from '../utils/constants';
+import { format, max } from 'date-fns';
function mutateAll(re: RegExp) {
cache.keys().filter(key => re.test(key)).forEach(key => mutate(key, null))
@@ -35,6 +37,11 @@ interface HttpResponseOk<T> {
data: T;
unauthorized: boolean;
notfound: boolean;
+ isLoadingMore?: boolean;
+ loadMore?: () => void;
+ loadMorePrev?: () => void;
+ isReachingEnd?: boolean;
+ isReachingStart?: boolean;
}
export interface SwrError {
@@ -49,6 +56,11 @@ interface HttpResponseError {
unauthorized: boolean;
notfound: boolean;
error?: SwrError;
+ isLoadingMore?: boolean;
+ loadMore?: () => void;
+ loadMorePrev?: () => void;
+ isReachingEnd?: boolean;
+ isReachingStart?: boolean;
}
@@ -98,8 +110,9 @@ function fetcher(url: string, token: string, backend:
string) {
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 orderFetcher(url: string, token: string, backend: string, paid?:
YesOrNo, refunded?: YesOrNo, wired?: YesOrNo, date?: Date, start?: number,
delta?: number) {
+ //%Y-%m-%d %H:%M:%S
+ return request(`${backend}${url}`, { token, params: { paid, refunded, wired,
start, delta, date: date? format(date, 'yyyy-MM-dd HH:mm:ss'): undefined } })
}
function transferFetcher(url: string, token: string, backend: string) {
@@ -405,13 +418,13 @@ export function useBackendInstancesTestForAdmin():
HttpResponse<MerchantBackend.
data?: MerchantBackend.Instances.InstancesResponse;
error?: SwrError;
}
- const [result, setResult] = useState<Result|undefined>(undefined)
+ const [result, setResult] = useState<Result | undefined>(undefined)
useEffect(() => {
request(`${url}/private/instances`, { token })
- .then(data => setResult({data}))
- .catch(error => setResult({error}))
- },[url, token])
+ .then(data => setResult({ data }))
+ .catch(error => setResult({ error }))
+ }, [url, token])
const data = result?.data
const error = result?.error
@@ -456,7 +469,9 @@ export interface InstanceOrderFilter {
paid?: YesOrNo;
refunded?: YesOrNo;
wired?: YesOrNo;
+ date?: Date;
}
+
export function useInstanceOrders(args?: InstanceOrderFilter):
HttpResponse<MerchantBackend.Orders.OrderHistory> {
const { url: baseUrl, token: baseToken } = useBackendContext();
const { token: instanceToken, id, admin } = useInstanceContext();
@@ -467,9 +482,58 @@ export function useInstanceOrders(args?:
InstanceOrderFilter): HttpResponse<Merc
url: `${baseUrl}/instances/${id}`, token: instanceToken
}
- const { data, error } = useSWR<MerchantBackend.Orders.OrderHistory,
SwrError>([`/private/orders`, token, url, args?.paid, args?.refunded,
args?.wired], orderFetcher)
+ const [pageBefore, setPageBefore] = useState(0)
+ const [pageAfter, setPageAfter] = useState(1)
+ const [start, setStart] = useState<number | undefined>(17);
+ const totalAfter = pageAfter * PAGE_SIZE;
+ const totalBefore = pageBefore * PAGE_SIZE;
+
+ const { data:beforeData, error:beforeError } =
useSWR<MerchantBackend.Orders.OrderHistory, SwrError>(
+ [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired,
args?.date, start, totalBefore],
+ orderFetcher,
+ )
+ const { data:afterData, error:afterError } =
useSWR<MerchantBackend.Orders.OrderHistory, SwrError>(
+ [`/private/orders`, token, url, args?.paid, args?.refunded, args?.wired,
args?.date, start ? start+1 : undefined, -totalAfter],
+ orderFetcher,
+ )
+
+ //this will save last result
+ const [lastBefore, setLastBefore] =
useState<MerchantBackend.Orders.OrderHistory | undefined>(undefined)
+ const [lastAfter, setLastAfter] =
useState<MerchantBackend.Orders.OrderHistory | undefined>(undefined)
+ useEffect(() => {
+ if (afterData) setLastAfter(afterData)
+ if (beforeData) setLastBefore(beforeData)
+ }, [afterData, beforeData])
+
+ // this has problems when there are some ids missing
+ const isReachingEnd = afterData && afterData.orders.length < totalAfter;
+ const isReachingStart = beforeData && beforeData.orders.length < totalBefore;
+
+ const loadMore = () => {
+ if (totalAfter < MAX_RESULT_SIZE) {
+ setPageAfter(pageAfter + 1)
+ } else {
+ const from = afterData?.orders?.[PAGE_SIZE]?.row_id
+ console.log('after',start,from)
+ if (from) setStart(from)
+ }
+ }
- return { data, unauthorized: error?.status === 401, notfound: error?.status
=== 404, error }
+ const loadMorePrev = () => {
+ if (totalBefore < MAX_RESULT_SIZE) {
+ setPageBefore(pageBefore + 1)
+ } else {
+ const from = beforeData?.orders?.[PAGE_SIZE-1]?.row_id
+ console.log('prev',start,from)
+ if (from) setStart(from)
+ }
+ }
+
+ const orders = (beforeData || lastBefore ||
{orders:[]}).orders.slice().reverse().concat((afterData || lastAfter ||
{orders:[]}).orders)
+ const unauthorized = beforeError?.status === 401 || afterError?.status ===
401
+ const notfound = beforeError?.status === 404 || afterError?.status === 404
+
+ return { data: {orders}, loadMorePrev, loadMore, isReachingEnd,
isReachingStart, unauthorized, notfound }
}
export function useInstanceTips():
HttpResponse<MerchantBackend.Tips.TippingReserveStatus> {
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index 57e2cc2..a2145c8 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -36,13 +36,6 @@ import LoginPage from './paths/login';
import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes";
import { NotificationCard, NotYetReadyAppMenu } from "./components/menu";
-export function Redirect({ to }: { to: string }): null {
- useEffect(() => {
- route(to, true)
- })
- return null
-}
-
export default function Application(): VNode {
const state = useBackendContextState()
diff --git a/packages/frontend/src/messages/en.po
b/packages/frontend/src/messages/en.po
index 49ecd68..8f19d50 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -227,8 +227,8 @@ msgstr "Amount"
msgid "fields.order.summary.label"
msgstr "Summary"
-msgid "fields.order.paid.label"
-msgstr "Paid"
+msgid "fields.order.date.label"
+msgstr "Date"
msgid "Products"
msgstr "Products"
diff --git a/packages/frontend/src/paths/admin/create/CreatePage.tsx
b/packages/frontend/src/paths/admin/create/CreatePage.tsx
index a286ba4..4c26caa 100644
--- a/packages/frontend/src/paths/admin/create/CreatePage.tsx
+++ b/packages/frontend/src/paths/admin/create/CreatePage.tsx
@@ -35,28 +35,30 @@ import { InputDuration } from
"../../../components/form/InputDuration";
import { InputCurrency } from "../../../components/form/InputCurrency";
import { InputPayto } from "../../../components/form/InputPayto";
-type Entity = MerchantBackend.Instances.InstanceConfigurationMessage &
{auth_token?: string}
+type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & {
auth_token?: string }
interface Props {
onCreate: (d: Entity) => void;
isLoading: boolean;
onBack?: () => void;
+ forceId?: string;
}
interface KeyValue {
[key: string]: string;
}
-function with_defaults(): Partial<Entity> {
+function with_defaults(id?:string): Partial<Entity> {
return {
+ id,
default_pay_delay: { d_ms: 1000 },
default_wire_fee_amortization: 10,
default_wire_transfer_delay: { d_ms: 2000 },
};
}
-export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode {
- const [value, valueHandler] = useState(with_defaults())
+export function CreatePage({ onCreate, isLoading, onBack, forceId }: Props):
VNode {
+ const [value, valueHandler] = useState(with_defaults(forceId))
const [errors, setErrors] = useState<FormErrors<Entity>>({})
const submit = (): void => {
@@ -85,7 +87,7 @@ export function CreatePage({ onCreate, isLoading, onBack }:
Props): VNode {
<div class="column is-two-thirds">
<FormProvider<Entity> errors={errors} object={value}
valueHandler={valueHandler} >
- <InputWithAddon<Entity> name="id"
addonBefore={`${backend.url}/private/instances/`} />
+ <InputWithAddon<Entity> name="id"
addonBefore={`${backend.url}/private/instances/`} readonly={!!forceId}/>
<Input<Entity> name="name" />
@@ -100,11 +102,35 @@ export function CreatePage({ onCreate, isLoading, onBack
}: Props): VNode {
<Input<Entity> name="default_wire_fee_amortization" />
<InputGroup name="address">
- <Input<Entity> name="name" />
+ <Input name="address.country" />
+ <Input name="address.address_lines" inputType="multiline"
+ toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
+ fromStr={(v: string) => v.split('\n')}
+ />
+ <Input name="address.building_number" />
+ <Input name="address.building_name" />
+ <Input name="address.street" />
+ <Input name="address.post_code" />
+ <Input name="address.town_location" />
+ <Input name="address.town" />
+ <Input name="address.district" />
+ <Input name="address.country_subdivision" />
</InputGroup>
<InputGroup name="jurisdiction">
- <Input<Entity> name="name" />
+ <Input name="jurisdiction.country" />
+ <Input name="jurisdiction.address_lines" inputType="multiline"
+ toStr={(v: string[] | undefined) => !v ? '' : v.join('\n')}
+ fromStr={(v: string) => v.split('\n')}
+ />
+ <Input name="jurisdiction.building_number" />
+ <Input name="jurisdiction.building_name" />
+ <Input name="jurisdiction.street" />
+ <Input name="jurisdiction.post_code" />
+ <Input name="jurisdiction.town_location" />
+ <Input name="jurisdiction.town" />
+ <Input name="jurisdiction.district" />
+ <Input name="jurisdiction.country_subdivision" />
</InputGroup>
<InputDuration<Entity> name="default_pay_delay" />
@@ -114,7 +140,7 @@ export function CreatePage({ onCreate, isLoading, onBack }:
Props): VNode {
</FormProvider>
<div class="buttons is-right mt-5">
- { onBack && <button class="button" onClick={onBack} ><Message
id="Cancel" /></button> }
+ {onBack && <button class="button" onClick={onBack} ><Message
id="Cancel" /></button>}
<button class="button is-success" onClick={submit} ><Message
id="Confirm" /></button>
</div>
diff --git a/packages/frontend/src/paths/admin/create/index.tsx
b/packages/frontend/src/paths/admin/create/index.tsx
index 7aeba5f..023f0b2 100644
--- a/packages/frontend/src/paths/admin/create/index.tsx
+++ b/packages/frontend/src/paths/admin/create/index.tsx
@@ -13,7 +13,7 @@
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/>
*/
-import { Fragment, h, VNode } from "preact";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { Loading } from "../../../components/exception/loading";
import { FormProvider } from "../../../components/form/Field";
@@ -28,79 +28,54 @@ interface Props {
onBack?: () => void;
onConfirm: () => void;
onError: (error: any) => void;
+ forceId?: string;
}
type Entity = MerchantBackend.Instances.InstanceConfigurationMessage;
-export default function Create({ onBack, onConfirm, onError }: Props): VNode {
+export default function Create({ onBack, onConfirm, onError, forceId }:
Props): VNode {
const { createInstance } = useAdminMutateAPI();
const [notif, setNotif] = useState<Notification | undefined>(undefined)
const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined);
if (createdOk) {
- return <div class="columns is-fullwidth is-vcentered content-full-size">
- <div class="column" />
- <div class="column is-half">
- <div class="card">
- <header class="card-header has-background-success">
- <p class="card-header-title has-text-white-ter">
- Instance created successfully
+ return <CreatedSuccessfully onConfirm={onConfirm}>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">ID</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input class="input" readonly value={createdOk.id} readOnly />
</p>
- </header>
- <div class="card-content">
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">ID</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input class="input" readonly value={createdOk.id}
disabled />
- </p>
- </div>
- </div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Business Name</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input class="input" readonly value={createdOk.name}
disabled />
- </p>
- </div>
- </div>
- </div>
- <div class="field is-horizontal">
- <div class="field-label is-normal">
- <label class="label">Token</label>
- </div>
- <div class="field-body is-flex-grow-3">
- <div class="field">
- <p class="control">
- <input class="input" readonly value={createdOk.auth.token}
disabled />
- </p>
- </div>
- </div>
- </div>
</div>
- <footer class="card-footer">
- <p class="card-footer-item" style={{ border: 'none' }}>
- <span>
- </span>
- </p>
- <p class="card-footer-item" style={{ border: 'none' }}>
- <span>
- </span>
+ </div>
+ </div>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">Business Name</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input class="input" readonly value={createdOk.name} disabled />
</p>
- <p class="card-footer-item">
- <button class="button is-info"
onClick={onConfirm}>Continue</button>
+ </div>
+ </div>
+ </div>
+ <div class="field is-horizontal">
+ <div class="field-label is-normal">
+ <label class="label">Token</label>
+ </div>
+ <div class="field-body is-flex-grow-3">
+ <div class="field">
+ <p class="control">
+ <input class="input" readonly value={createdOk.auth.token}
disabled />
</p>
- </footer>
+ </div>
</div>
</div>
- <div class="column" />
- </div>
+ </CreatedSuccessfully>
}
return <Fragment>
@@ -108,6 +83,7 @@ export default function Create({ onBack, onConfirm, onError
}: Props): VNode {
<CreatePage
onBack={onBack}
+ forceId={forceId}
isLoading={false}
onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage) =>
{
createInstance(d).then((r) => {
@@ -121,4 +97,41 @@ export default function Create({ onBack, onConfirm, onError
}: Props): VNode {
})
}} />
</Fragment>
+}
+
+interface CreatedSuccessfullyProps {
+ onConfirm: () => void;
+ children: ComponentChildren;
+}
+
+function CreatedSuccessfully({ children, onConfirm }:
CreatedSuccessfullyProps) {
+ return <div class="columns is-fullwidth is-vcentered content-full-size">
+ <div class="column" />
+ <div class="column is-half">
+ <div class="card">
+ <header class="card-header has-background-success">
+ <p class="card-header-title has-text-white-ter">
+ Instance created successfully
+ </p>
+ </header>
+ <div class="card-content">
+ {children}
+ </div>
+ <footer class="card-footer">
+ <p class="card-footer-item" style={{ border: 'none' }}>
+ <span>
+ </span>
+ </p>
+ <p class="card-footer-item" style={{ border: 'none' }}>
+ <span>
+ </span>
+ </p>
+ <p class="card-footer-item">
+ <button class="button is-info"
onClick={onConfirm}>Continue</button>
+ </p>
+ </footer>
+ </div>
+ </div>
+ <div class="column" />
+ </div>
}
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/orders/list/Table.tsx
b/packages/frontend/src/paths/instance/orders/list/Table.tsx
index 26c3912..4079faf 100644
--- a/packages/frontend/src/paths/instance/orders/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/Table.tsx
@@ -19,6 +19,7 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { format } from "date-fns";
import { Fragment, h, VNode } from "preact"
import { Message } from "preact-messages"
import { StateUpdater, useCallback, useEffect, useRef, useState } from
"preact/hooks"
@@ -32,9 +33,16 @@ interface Props {
onDelete: (id: Entity) => void;
onCreate: () => void;
selected?: boolean;
+ onLoadMoreBefore?: () => void;
+ hasMoreBefore?: boolean;
+ hasMoreAfter?: boolean;
+ onLoadMoreAfter?: () => void;
}
+// onLoadMoreBefore={result.loadMorePrev}
hasMoreBefore={!result.isReachingStart}
+// onLoadMoreAfter={result.loadMore} hasMoreAfter={!result.isReachingEnd}
-export function CardTable({ instances, onCreate, onUpdate, onDelete, selected
}: Props): VNode {
+
+export function CardTable({ instances, onCreate, onUpdate, onDelete, selected,
onLoadMoreAfter, onLoadMoreBefore, hasMoreAfter, hasMoreBefore }: Props): VNode
{
const [actionQueue, actionQueueHandler] = useState<Actions<Entity>[]>([]);
const [rowSelection, rowSelectionHandler] = useState<string[]>([])
@@ -59,12 +67,6 @@ export function CardTable({ instances, onCreate, onUpdate,
onDelete, selected }:
<div class="card-header-icon" aria-label="more options">
- <button class={rowSelection.length > 0 ? "button is-danger" :
"is-hidden"}
- type="button"
- onClick={(): void => actionQueueHandler(buildActions(instances,
rowSelection, 'DELETE'))}
- >
- Delete
- </button>
</div>
<div class="card-header-icon" aria-label="more options">
<button class="button is-info" type="button" onClick={onCreate}>
@@ -77,7 +79,11 @@ export function CardTable({ instances, onCreate, onUpdate,
onDelete, selected }:
<div class="b-table has-pagination">
<div class="table-wrapper has-mobile-cards">
{instances.length > 0 ?
- <Table instances={instances} onUpdate={onUpdate}
onDelete={onDelete} rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler} /> :
+ <Table instances={instances} onUpdate={onUpdate}
onDelete={onDelete}
+ rowSelection={rowSelection}
rowSelectionHandler={rowSelectionHandler}
+ onLoadMoreAfter={onLoadMoreAfter}
onLoadMoreBefore={onLoadMoreBefore}
+ hasMoreAfter={hasMoreAfter} hasMoreBefore={hasMoreBefore}
+ /> :
<EmptyTable />
}
</div>
@@ -93,53 +99,46 @@ interface TableProps {
onUpdate: (id: string) => void;
onDelete: (id: Entity) => void;
rowSelectionHandler: StateUpdater<string[]>;
+ onLoadMoreBefore?: () => void;
+ hasMoreBefore?: boolean;
+ hasMoreAfter?: boolean;
+ onLoadMoreAfter?: () => void;
}
function toggleSelected<T>(id: T): (prev: T[]) => T[] {
return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] :
prev.filter(e => e != id)
}
-function Table({ rowSelection, rowSelectionHandler, instances, onUpdate,
onDelete }: TableProps): VNode {
+function Table({ rowSelection, rowSelectionHandler, instances, onUpdate,
onDelete, onLoadMoreAfter, onLoadMoreBefore, hasMoreAfter, hasMoreBefore }:
TableProps): VNode {
return <div class="table-container">
+ {onLoadMoreBefore && <button class="button is-fullwidth"
disabled={!hasMoreBefore} onClick={onLoadMoreBefore}> load more before
</button>}
<table class="table is-striped is-hoverable is-fullwidth">
<thead>
<tr>
- <th class="is-checkbox-cell" style={{minWidth: 50}}>
- <label class="b-checkbox checkbox">
- <input type="checkbox" checked={rowSelection.length ===
instances.length} onClick={(): void => rowSelectionHandler(rowSelection.length
=== instances.length ? [] : instances.map(i => i.id))} />
- <span class="check" />
- </label>
- </th>
- <th style={{minWidth: 100}}><Message id="fields.order.amount.label"
/></th>
- <th style={{minWidth: 500}}><Message id="fields.order.summary.label"
/></th>
- <th style={{minWidth: 100}}><Message id="fields.order.paid.label"
/></th>
- <th style={{minWidth: 50}}/>
+ <th style={{ minWidth: 100 }}><Message id="fields.order.date.label"
/></th>
+ <th style={{ minWidth: 100 }}><Message
id="fields.order.amount.label" /></th>
+ <th style={{ minWidth: 500 }}><Message
id="fields.order.summary.label" /></th>
+ <th style={{ minWidth: 50 }} />
</tr>
</thead>
<tbody>
{instances.map(i => {
return <tr>
- <td class="is-checkbox-cell">
- <label class="b-checkbox checkbox">
- <input type="checkbox" checked={rowSelection.indexOf(i.id) !=
-1} onClick={(): void => rowSelectionHandler(toggleSelected(i.id))} />
- <span class="check" />
- </label>
- </td>
+ <td onClick={(): void => onUpdate(i.id)} style={{ cursor:
'pointer' }} >{format(new Date(i.timestamp.t_ms), 'dd/MM/yyyy HH:mm:ss')}</td>
<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 onClick={(): void => onUpdate(i.id)} style={{ cursor:
'pointer' }} >{i.row_id}</td>
<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)}>
- Delete
+ Refund
</button>
</div>
</td>
</tr>
})}
-
</tbody>
</table>
+ {onLoadMoreAfter && <button class="button is-fullwidth"
disabled={!hasMoreAfter} onClick={onLoadMoreAfter}> load more after </button>}
</div>
}
@@ -148,7 +147,7 @@ function EmptyTable(): VNode {
<p>
<span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px"
/></span>
</p>
- <p><Message id="There is no instances yet, add more pressing the + sign"
/></p>
+ <p>No orders has been found</p>
</div>
}
diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx
b/packages/frontend/src/paths/instance/orders/list/index.tsx
index c0dde74..3e488ab 100644
--- a/packages/frontend/src/paths/instance/orders/list/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/index.tsx
@@ -6,6 +6,8 @@ import { InstanceOrderFilter, SwrError, useInstanceOrders,
useOrderMutateAPI, us
import { CardTable } from './Table';
import { FormProvider, FormErrors } from "../../../../components/form/Field"
import { InputBoolean } from "../../../../components/form/InputBoolean";
+import { InputArray } from '../../../../components/form/InputArray';
+import { format, parse } from 'date-fns';
interface Props {
onUnauthorized: () => VNode;
@@ -14,35 +16,54 @@ interface Props {
onCreate: () => void;
}
-const fromBooleanToYesAndNo = {
- fromBoolean: (b?: boolean) => b === true ? 'yes' : (b === false ? 'no' :
undefined),
- toBoolean: (b: string) => b === 'yes' ? true : (b === 'no' ? false :
undefined)
-}
+// 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, onNotFound
}: Props): VNode {
+ // const [filter, setFilter] = useState<InstanceOrderFilter>({paid:'yes'})
+ const date = parse('22/03/2021 11:13:25','dd/MM/yyyyy HH:mm:ss', new Date())
const [filter, setFilter] = useState<InstanceOrderFilter>({})
+
+
const result = useInstanceOrders(filter)
const { createOrder, deleteOrder } = useOrderMutateAPI()
const { currency } = useConfigContext()
- let instances: (MerchantBackend.Orders.OrderHistoryEntry & {id: string})[];
-
+ let instances: (MerchantBackend.Orders.OrderHistoryEntry & { id: string })[];
+
if (result.unauthorized) return onUnauthorized()
if (result.notfound) return onNotFound()
if (!result.data) {
if (result.error) return onLoadError(result.error)
- //if loading asume empty list
+ //if loading assume empty list
instances = []
} else {
instances = result.data.orders.map(o => ({ ...o, id: o.order_id }))
}
+ interface Values {
+ filter: []
+ }
+
+ const VALUE_REGEX = /d/
+
+ const fields = ['wired','refunded','paid']
+
+ 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' : ''
return <section class="section is-main-section">
- <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>
+ <div class="tabs">
+ <ul>
+ <li class={isPaidActive}><a onClick={() => setFilter({paid:
'yes'})}>Paid</a></li>
+ <li class={isRefundedActive}><a onClick={() => setFilter({refunded:
'yes'})}>Refunded</a></li>
+ <li class={isNotWiredActive}><a onClick={() => setFilter({wired:
'no'})}>Not wired</a></li>
+ <li class={isAllActive}><a onClick={() => setFilter({})}>All</a></li>
+ </ul>
+ </div>
<CardTable instances={instances}
onCreate={() => createOrder({
@@ -53,6 +74,8 @@ export default function ({ onUnauthorized, onLoadError,
onCreate, onNotFound }:
})}
onDelete={(order: MerchantBackend.Orders.OrderHistoryEntry) =>
deleteOrder(order.order_id)}
onUpdate={() => null}
+ onLoadMoreBefore={result.loadMorePrev}
hasMoreBefore={!result.isReachingStart}
+ onLoadMoreAfter={result.loadMore} hasMoreAfter={!result.isReachingEnd}
/>
</section>
}
\ No newline at end of file
diff --git a/packages/frontend/src/utils/constants.ts
b/packages/frontend/src/utils/constants.ts
index b8c9cdd..42ea934 100644
--- a/packages/frontend/src/utils/constants.ts
+++ b/packages/frontend/src/utils/constants.ts
@@ -25,3 +25,6 @@ export const
PAYTO_REGEX=/^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]+(\/[a-zA-Z0-9\-\.\~\(
export const AMOUNT_REGEX=/^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/
export const INSTANCE_ID_LOOKUP = /^\/instances\/([^/]*)\/?$/
+
+export const PAGE_SIZE = 3
+export const MAX_RESULT_SIZE = 4;
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-merchant-backoffice] branch master updated: pagination into the orders,
gnunet <=