gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]