commit aa3858a3e35d592377d3fbb23ec862044e99330a
parent 045cd4bf9f76846718fdd105829db4f2f3d3e9fe
Author: Sebastian <sebasjm@gmail.com>
Date: Thu, 11 Feb 2021 14:33:25 -0300
refactor table, taking out the modal
Diffstat:
8 files changed, 181 insertions(+), 201 deletions(-)
diff --git a/src/components/auth/LoginPage.tsx b/src/components/auth/LoginPage.tsx
@@ -23,7 +23,7 @@ export default function LoginPage({ onLogIn }: Props): VNode {
<div class="field-body">
<div class="field">
<p class="control is-expanded has-icons-left">
- <input class="input" type="text" placeholder="abcdef" name="id" value={token} onInput={e => update(e?.currentTarget.value)} />
+ <input class="input" type="text" placeholder="abcdef" name="id" value={token} onInput={(e): void => update(e?.currentTarget.value)} />
</p>
</div>
</div>
diff --git a/src/routes/instances/CardTable.tsx b/src/routes/instances/CardTable.tsx
@@ -0,0 +1,134 @@
+import { h, VNode } from "preact";
+import { useEffect, useState, StateUpdater } from "preact/hooks";
+import { MerchantBackend, WidthId as WithId } from "../../declaration";
+
+interface Props {
+ instances: MerchantBackend.Instances.Instance[];
+ onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+ onCreate: () => void;
+ selected: MerchantBackend.Instances.QueryInstancesResponse & WithId | undefined;
+}
+
+function toggleSelected<T>(id: T): (prev: T[]) => T[] {
+ return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] : prev.filter(e => e != id)
+}
+
+interface Actions {
+ element: MerchantBackend.Instances.Instance;
+ type: 'DELETE' | 'UPDATE';
+}
+
+function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
+ return value !== null && value !== undefined;
+}
+
+function buildActions(intances: MerchantBackend.Instances.Instance[], selected: string[], action: 'DELETE'): Actions[] {
+ return selected.map(id => intances.find(i => i.id === id))
+ .filter(notEmpty)
+ .map(id => ({ element: id, type: action }))
+}
+
+const EmptyTable = (): VNode => (
+ <div class="content has-text-grey has-text-centered">
+ <p>
+ <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px" /></span>
+ </p>
+ <p>No instance configured yet, setup one pressing the + button </p>
+ </div>
+)
+
+interface TableProps {
+ rowSelection: string[];
+ instances: MerchantBackend.Instances.Instance[];
+ onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+ rowSelectionHandler: StateUpdater<string[]>;
+}
+
+const Table = ({ rowSelection, rowSelectionHandler, instances, onSelect }: TableProps): VNode => (
+ <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
+ <thead>
+ <tr>
+ <th class="is-checkbox-cell">
+ <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>id</th>
+ <th>name</th>
+ <th>public key</th>
+ <th>payments</th>
+ <th />
+ </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 >{i.id}</td>
+ <td >{i.name}</td>
+ <td >{i.merchant_pub}</td>
+ <td >{i.payment_targets}</td>
+ <td class="is-actions-cell">
+ <div class="buttons is-right">
+ <button class="button is-small is-primary" type="button" onClick={(): void => onSelect(i.id, 'UPDATE')}>
+ <span class="icon"><i class="mdi mdi-eye" /></span>
+ </button>
+ <button class="button is-small is-danger jb-modal" type="button" onClick={(): void => onSelect(i.id, 'DELETE')}>
+ <span class="icon"><i class="mdi mdi-trash-can" /></span>
+ </button>
+ </div>
+ </td>
+ </tr>
+ })}
+
+ </tbody>
+ </table>)
+
+
+export default function CardTable({ instances, onCreate, onSelect, selected }: Props): VNode {
+ const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
+ const [rowSelection, rowSelectionHandler] = useState<string[]>([])
+
+ useEffect(() => {
+ if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'DELETE') {
+ onSelect(actionQueue[0].element.id, 'DELETE')
+ actionQueueHandler(actionQueue.slice(1))
+ }
+ }, [actionQueue, selected, onSelect])
+
+ useEffect(() => {
+ if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'UPDATE') {
+ onSelect(actionQueue[0].element.id, 'UPDATE')
+ actionQueueHandler(actionQueue.slice(1))
+ }
+ }, [actionQueue, selected, onSelect])
+
+
+ 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>Instances</p>
+ <button class={rowSelection.length > 0 ? "card-header-icon" : "card-header-icon is-hidden"} type="button" onClick={(): void => actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
+ <span class="icon"><i class="mdi mdi-trash-can" /></span>
+ </button>
+ <button class="card-header-icon" type="button" onClick={onCreate}>
+ <span class="icon"><i class="mdi mdi-plus" /></span>
+ </button>
+ </header>
+ <div class="card-content">
+ <div class="b-table has-pagination">
+ <div class="table-wrapper has-mobile-cards">
+ {instances.length > 0 ?
+ <Table instances={instances} onSelect={onSelect} rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} /> :
+ <EmptyTable />
+ }
+ </div>
+ </div>
+ </div>
+ </div>
+}
+\ No newline at end of file
diff --git a/src/routes/instances/CreateModal.tsx b/src/routes/instances/CreateModal.tsx
@@ -36,7 +36,6 @@ const schema = yup.object().shape({
});
interface Props {
- active: boolean;
onCancel: () => void;
onConfirm: (i: MerchantBackend.Instances.InstanceConfigurationMessage) => void;
}
@@ -45,8 +44,8 @@ interface KeyValue {
[key: string]: string;
}
-export default function CreateModal({ active, onCancel, onConfirm }: Props): VNode {
- const [value, valueHandler] = useState((active || {}) as any)
+export default function CreateModal({ onCancel, onConfirm }: Props): VNode {
+ const [value, valueHandler] = useState({} as any)
const [errors, setErrors] = useState<KeyValue>({})
const submit = (): void => {
@@ -60,7 +59,7 @@ export default function CreateModal({ active, onCancel, onConfirm }: Props): VNo
}
}
- return <ConfirmModal description="create instance" active={active} onConfirm={submit} onCancel={onCancel}>
+ return <ConfirmModal description="create instance" active onConfirm={submit} onCancel={onCancel}>
{Object.keys(schema.fields).map(f => {
const info = schema.fields[f].describe()
@@ -73,7 +72,7 @@ export default function CreateModal({ active, onCancel, onConfirm }: Props): VNo
<div class="field-body">
<div class="field">
<p class="control is-expanded has-icons-left">
- <input class="input" type="text" placeholder={info?.meta?.placeholder} readonly={info?.meta?.readonly} name={f} value={value[f]} onChange={e => valueHandler((prev: any) => ({ ...prev, [f]: e.currentTarget.value }))} />
+ <input class="input" type="text" placeholder={info?.meta?.placeholder} readonly={info?.meta?.readonly} name={f} value={value[f]} onChange={(e): void => valueHandler((prev: any) => ({ ...prev, [f]: e.currentTarget.value }))} />
{info?.meta?.help}
</p>
{errors[f] ? <p class="help is-danger">{errors[f]}</p> : null}
diff --git a/src/routes/instances/DeleteAllModal.tsx b/src/routes/instances/DeleteAllModal.tsx
@@ -1,29 +0,0 @@
-import { h, VNode } from "preact";
-import { MerchantBackend } from "../../declaration";
-
-interface Props {
- element: MerchantBackend.Instances.Instance | null;
- onCancel: () => void;
- onConfirm: (i: MerchantBackend.Instances.Instance) => void;
-}
-
-export default function DeleteAllModal({ element, onCancel, onConfirm }: Props): VNode {
- return <div class={element ? "modal is-active is-clipped" : "modal"}>
- <div class="modal-background jb-modal-close" onClick={e => onCancel()} />
- <div class="modal-card">
- <header class="modal-card-head">
- <p class="modal-card-title">Confirm action</p>
- <button class="delete jb-modal-close" aria-label="close" onClick={e => onCancel()} />
- </header>
- <section class="modal-card-body">
- <p>This will permanently delete instance "{element?.name}" with id <b>{element?.id}</b></p>
- <p>Please confirm this action</p>
- </section>
- <footer class="modal-card-foot">
- <button class="button jb-modal-close" onClick={e => onCancel()} >Cancel</button>
- <button class="button is-danger jb-modal-close" onClick={element ? e => onConfirm(element) : undefined} >Delete</button>
- </footer>
- </div>
- <button class="modal-close is-large jb-modal-close" aria-label="close" onClick={e => onCancel()} />
- </div>
-}
-\ No newline at end of file
diff --git a/src/routes/instances/DeleteModal.tsx b/src/routes/instances/DeleteModal.tsx
@@ -1,11 +1,11 @@
import { h, VNode } from "preact";
-import { MerchantBackend } from "../../declaration";
+import { MerchantBackend, WidthId } from "../../declaration";
import ConfirmModal from "./ConfirmModal";
interface Props {
- element: MerchantBackend.Instances.Instance;
+ element: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
onCancel: () => void;
- onConfirm: (i: MerchantBackend.Instances.Instance) => void;
+ onConfirm: (i: MerchantBackend.Instances.QueryInstancesResponse & WidthId) => void;
}
export default function DeleteModal({ element, onCancel, onConfirm }: Props): VNode {
diff --git a/src/routes/instances/Table.tsx b/src/routes/instances/Table.tsx
@@ -1,153 +0,0 @@
-import { h, VNode } from "preact";
-import { useEffect, useState, StateUpdater } from "preact/hooks";
-import { MerchantBackend, WidthId as WithId } from "../../declaration";
-import DeleteModal from './DeleteModal'
-import UpdateModal from './UpdateModal'
-import CreateModal from './CreateModal'
-
-interface Props {
- instances: MerchantBackend.Instances.Instance[];
- onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) => void;
- onUpdate: (id: string, d: MerchantBackend.Instances.InstanceReconfigurationMessage) => void;
- onDelete: (id: string) => void;
- onSelect: (id: string | null) => void;
- selected: MerchantBackend.Instances.QueryInstancesResponse & WithId | undefined;
-}
-
-function toggleSelected<T>(id: T): (prev: T[]) => T[] {
- return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] : prev.filter(e => e != id)
-}
-
-interface Actions {
- element: MerchantBackend.Instances.Instance;
- type: 'DELETE' | 'UPDATE';
-}
-
-function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
- return value !== null && value !== undefined;
-}
-
-function buildActions(intances: MerchantBackend.Instances.Instance[], selected: string[], action: 'DELETE'): Actions[] {
- return selected.map(id => intances.find(i => i.id === id))
- .filter(notEmpty)
- .map(id => ({ element: id, type: action }))
-}
-
-const EmptyTable = () => <div class="content has-text-grey has-text-centered">
- <p>
- <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px" /></span>
- </p>
- <p>No instance configured yet, setup one pressing the + button </p>
-</div>
-
-interface TableProps {
- rowSelection: string[];
- instances: MerchantBackend.Instances.Instance[];
- onSelect: (id: string | null) => void;
- rowSelectionHandler: StateUpdater<string[]>;
- toBeDeletedHandler: StateUpdater<MerchantBackend.Instances.Instance | null>;
-}
-
-const Table = ({ rowSelection, rowSelectionHandler, instances, onSelect, toBeDeletedHandler }: TableProps): VNode => (
- <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
- <thead>
- <tr>
- <th class="is-checkbox-cell">
- <label class="b-checkbox checkbox">
- <input type="checkbox" checked={rowSelection.length === instances.length} onClick={e => rowSelectionHandler(rowSelection.length === instances.length ? [] : instances.map(i => i.id))} />
- <span class="check" />
- </label>
- </th>
- <th>id</th>
- <th>name</th>
- <th>public key</th>
- <th>payments</th>
- <th />
- </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={e => rowSelectionHandler(toggleSelected(i.id))} />
- <span class="check" />
- </label>
- </td>
- <td >{i.id}</td>
- <td >{i.name}</td>
- <td >{i.merchant_pub}</td>
- <td >{i.payment_targets}</td>
- <td class="is-actions-cell">
- <div class="buttons is-right">
- <button class="button is-small is-primary" type="button" onClick={e => onSelect(i.id)}>
- <span class="icon"><i class="mdi mdi-eye" /></span>
- </button>
- <button class="button is-small is-danger jb-modal" type="button" onClick={e => toBeDeletedHandler(i)}>
- <span class="icon"><i class="mdi mdi-trash-can" /></span>
- </button>
- </div>
- </td>
- </tr>
- })}
-
- </tbody>
- </table>)
-
-
-export default function CardTable({ instances, onCreate, onDelete, onSelect, onUpdate, selected }: Props): VNode {
- const [toBeCreated, toBeCreatedHandler] = useState<boolean>(false)
- const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
- const [toBeDeleted, toBeDeletedHandler] = useState<MerchantBackend.Instances.Instance | null>(null)
-
- const [rowSelection, rowSelectionHandler] = useState<string[]>([])
-
- useEffect(() => {
- if (actionQueue.length > 0 && !toBeDeleted && actionQueue[0].type == 'DELETE') {
- toBeDeletedHandler(actionQueue[0].element)
- actionQueueHandler(actionQueue.slice(1))
- }
- }, [actionQueue, toBeDeleted])
-
- useEffect(() => {
- if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'UPDATE') {
- onSelect(actionQueue[0].element.id)
- actionQueueHandler(actionQueue.slice(1))
- }
- }, [actionQueue, 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>Instances</p>
- <button class={rowSelection.length > 0 ? "card-header-icon" : "card-header-icon is-hidden"} type="button" onClick={e => actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} >
- <span class="icon"><i class="mdi mdi-trash-can" /></span>
- </button>
- <button class="card-header-icon" type="button" onClick={e => toBeCreatedHandler(true)}>
- <span class="icon"><i class="mdi mdi-plus" /></span>
- </button>
- </header>
- <div class="card-content">
- <div class="b-table has-pagination">
- <div class="table-wrapper has-mobile-cards">
- {instances.length > 0 ?
- <Table instances={instances} onSelect={onSelect} rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} toBeDeletedHandler={toBeDeletedHandler} /> :
- <EmptyTable />
- }
- </div>
- {toBeDeleted ? <DeleteModal element={toBeDeleted} onCancel={() => toBeDeletedHandler(null)} onConfirm={(i) => {
- onDelete(i.id)
- toBeDeletedHandler(null);
- }} /> : null}
- {selected ? <UpdateModal element={selected} onCancel={() => onSelect(null)} onConfirm={(i) => {
- onUpdate(selected.id, i)
- onSelect(null);
- }} /> : null}
- {toBeCreated ? <CreateModal active={toBeCreated} onCancel={() => toBeCreatedHandler(false)} onConfirm={(i) => {
- onCreate(i)
- toBeCreatedHandler(false);
- }} /> : null}
- </div>
- </div>
- </div >
-}
-\ No newline at end of file
diff --git a/src/routes/instances/UpdateModal.tsx b/src/routes/instances/UpdateModal.tsx
@@ -66,7 +66,7 @@ export default function UpdateModal({ element, onCancel, onConfirm }: Props): VN
<input class="input" type="text"
placeholder={info?.meta?.placeholder} readonly={info?.meta?.readonly}
name={f} value={value[f]}
- onChange={e => valueHandler((prev: any) => ({ ...prev, [f]: e.currentTarget.value }))} />
+ onChange={(e): void => valueHandler((prev: any) => ({ ...prev, [f]: e.currentTarget.value }))} />
{info?.meta?.help}
</p>
{errors[f] ? <p class="help is-danger">{errors[f]}</p> : null}
diff --git a/src/routes/instances/View.tsx b/src/routes/instances/View.tsx
@@ -1,6 +1,10 @@
import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
import { MerchantBackend, WidthId } from "../../declaration";
-import Table from './Table';
+import Table from './CardTable';
+import DeleteModal from './DeleteModal'
+import UpdateModal from './UpdateModal'
+import CreateModal from './CreateModal'
interface Props {
instances: MerchantBackend.Instances.Instance[];
@@ -13,6 +17,14 @@ interface Props {
}
export default function View({ instances, isLoading, onCreate, onDelete, onSelect, onUpdate, selected }: Props): VNode {
+ const [create, setCreate] = useState<boolean>(false)
+ const [action, setAction] = useState<'UPDATE' | 'DELETE' | null>(null)
+
+ const onSelectAction = (id: string | null, action?: 'UPDATE' | 'DELETE'): void => {
+ onSelect(id)
+ setAction(action || null)
+ }
+
return <div id="app">
<section class="section is-title-bar">
@@ -27,7 +39,8 @@ export default function View({ instances, isLoading, onCreate, onDelete, onSelec
</div>
</div>
</section>
- <section class={ isLoading ? "hero is-hero-bar" : "hero is-hero-bar is-loading" }>
+
+ <section class={isLoading ? "hero is-hero-bar" : "hero is-hero-bar is-loading"}>
<div class="hero-body">
<div class="level">
<div class="level-left">
@@ -42,12 +55,29 @@ export default function View({ instances, isLoading, onCreate, onDelete, onSelec
</div>
</section>
<section class="section is-main-section">
- <Table instances={instances} onCreate={onCreate}
- onUpdate={onUpdate}
- onDelete={onDelete} onSelect={onSelect}
- selected={selected}
- />
+ <Table instances={instances} onSelect={onSelectAction} selected={selected} onCreate={(): void => setCreate(true)} />
</section>
+ {selected && action === 'DELETE' ?
+ <DeleteModal element={selected} onCancel={(): void => onSelectAction(null)} onConfirm={(i): void => {
+ onDelete(i.id)
+ onSelectAction(null);
+ }}
+ />
+ : null}
+
+ {selected && action === 'UPDATE' ?
+ <UpdateModal element={selected} onCancel={(): void => onSelectAction(null)} onConfirm={(i): void => {
+ onUpdate(selected.id, i);
+ onSelectAction(null);
+ }}
+ />
+ : null}
+
+ {create ? <CreateModal onCancel={(): void => setCreate(false)} onConfirm={(i): void => {
+ onCreate(i)
+ setCreate(false);
+ }} /> : null}
+
</div >
}
\ No newline at end of file