diff options
Diffstat (limited to 'packages/aml-backoffice-ui/src/pages/Cases.tsx')
-rw-r--r-- | packages/aml-backoffice-ui/src/pages/Cases.tsx | 392 |
1 files changed, 171 insertions, 221 deletions
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx index 64cacf68c..32e162e5b 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx @@ -1,4 +1,4 @@ -import { TalerError, TranslatedString, assertUnreachable } from "@gnu-taler/taler-util"; +import { TalerError, TalerExchangeApi, TranslatedString, assertUnreachable } from "@gnu-taler/taler-util"; import { ErrorLoading, Loading, useTranslationContext } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { useState } from "preact/hooks"; @@ -10,13 +10,152 @@ import { AmlExchangeBackend } from "../types.js"; import { Officer } from "./Officer.js"; import { amlStateConverter } from "./ShowConsolidated.js"; -export function Cases() { +export function CasesUI({ records, filter, onChangeFilter, onFirstPage, onNext }: { onFirstPage?: () => void, onNext?: () => void, filter: AmlExchangeBackend.AmlState, onChangeFilter: (f: AmlExchangeBackend.AmlState) => void, records: TalerExchangeApi.AmlRecord[] }): VNode { const { i18n } = useTranslationContext(); const form = createNewForm<{ state: AmlExchangeBackend.AmlState }>(); - const initial = AmlExchangeBackend.AmlState.pending; - const [stateFilter, setStateFilter] = useState(initial); + return <div> + <div class="sm:flex sm:items-center"> + <div class="px-2 sm:flex-auto"> + <h1 class="text-base font-semibold leading-6 text-gray-900"> + <i18n.Translate> + Cases + </i18n.Translate> + </h1> + <p class="mt-2 text-sm text-gray-700"> + <i18n.Translate> + A list of all the account with the status + </i18n.Translate> + </p> + </div> + <div class="px-2"> + <form.Provider + initialValue={{ state: filter }} + onUpdate={(v) => { + onChangeFilter(v.state ?? filter); + }} + onSubmit={(v) => { }} + > + <form.InputChoiceHorizontal + name="state" + label={i18n.str`Filter`} + converter={amlStateConverter} + choices={[ + { + label: "Pending" as TranslatedString, + value: AmlExchangeBackend.AmlState.pending, + }, + { + label: "Frozen" as TranslatedString, + value: AmlExchangeBackend.AmlState.frozen, + }, + { + label: "Normal" as TranslatedString, + value: AmlExchangeBackend.AmlState.normal, + }, + ]} + /> + + </form.Provider> + </div> + </div> + <div class="mt-8 flow-root"> + <div class="overflow-x-auto"> + {!records.length ? ( + <div>empty result </div> + ) : ( + <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> + <table class="min-w-full divide-y divide-gray-300"> + <thead> + <tr> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" + > + <i18n.Translate> + Account Id + </i18n.Translate> + </th> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" + > + <i18n.Translate> + Status + </i18n.Translate> + </th> + <th + scope="col" + class="sm:hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900" + > + <i18n.Translate> + Threshold + </i18n.Translate> + </th> + </tr> + </thead> + <tbody class="divide-y divide-gray-200 bg-white"> + {records.map((r) => { + return ( + <tr class="hover:bg-gray-100 "> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 "> + <div class="text-gray-900"> + <a + href={Pages.account.url({ account: r.h_payto })} + class="text-indigo-600 hover:text-indigo-900" + > + {r.h_payto.substring(0, 16)}... + </a> + </div> + </td> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500"> + {((state: AmlExchangeBackend.AmlState): VNode => { + switch (state) { + case AmlExchangeBackend.AmlState.normal: { + return ( + <span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20"> + Normal + </span> + ); + } + case AmlExchangeBackend.AmlState.pending: { + return ( + <span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20"> + Pending + </span> + ); + } + case AmlExchangeBackend.AmlState.frozen: { + return ( + <span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20"> + Frozen + </span> + ); + } + } + })(r.current_state)} + </td> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900"> + {r.threshold} + </td> + </tr> + ); + })} + </tbody> + </table> + <Pagination onFirstPage={onFirstPage} onNext={onNext} /> + </div> + )} + </div> + </div> + </div> + +} + + +export function Cases() { + const [stateFilter, setStateFilter] = useState(AmlExchangeBackend.AmlState.pending); const list = useCases(stateFilter); @@ -38,150 +177,26 @@ export function Cases() { const { records } = list.data.body - return ( - <div> - <div class="px-4 sm:px-6 lg:px-8"> - <div class="sm:flex sm:items-center"> - <div class="sm:flex-auto"> - <h1 class="text-base font-semibold leading-6 text-gray-900"> - <i18n.Translate> - Cases - </i18n.Translate> - </h1> - <p class="mt-2 text-sm text-gray-700"> - <i18n.Translate> - A list of all the account with the status - </i18n.Translate> - </p> - </div> - <form.Provider - initialValue={{ state: stateFilter }} - onUpdate={(v) => { - setStateFilter(v.state ?? initial); - }} - onSubmit={(v) => { }} - > - <form.InputChoiceHorizontal - name="state" - label={"Filter" as TranslatedString} - converter={amlStateConverter} - choices={[ - { - label: "Pending" as TranslatedString, - value: AmlExchangeBackend.AmlState.pending, - }, - { - label: "Frozen" as TranslatedString, - value: AmlExchangeBackend.AmlState.frozen, - }, - { - label: "Normal" as TranslatedString, - value: AmlExchangeBackend.AmlState.normal, - }, - ]} - /> - </form.Provider> - </div> - <div class="mt-8 flow-root"> - <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> - {!records.length ? ( - <div>empty result </div> - ) : ( - <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> - <Pagination /> - <table class="min-w-full divide-y divide-gray-300"> - <thead> - <tr> - <th - scope="col" - class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" - > - Account Id - </th> - <th - scope="col" - class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" - > - Status - </th> - <th - scope="col" - class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" - > - Threshold - </th> - </tr> - </thead> - <tbody class="divide-y divide-gray-200 bg-white"> - {records.map((r) => { - return ( - <tr class="hover:bg-gray-100 "> - <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 "> - <div class="text-gray-900"> - <a - href={Pages.account.url({ account: r.h_payto })} - class="text-indigo-600 hover:text-indigo-900" - > - {r.h_payto} - </a> - </div> - </td> - <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500"> - {((state: AmlExchangeBackend.AmlState): VNode => { - switch (state) { - case AmlExchangeBackend.AmlState.normal: { - return ( - <span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20"> - Normal - </span> - ); - } - case AmlExchangeBackend.AmlState.pending: { - return ( - <span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20"> - Pending - </span> - ); - } - case AmlExchangeBackend.AmlState.frozen: { - return ( - <span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20"> - Frozen - </span> - ); - } - } - })(r.current_state)} - </td> - <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900"> - {r.threshold} - </td> - </tr> - ); - })} - </tbody> - </table> - <Pagination /> - </div> - )} - </div> - </div> - </div> - </div> - ); + return <CasesUI + records={records} + onFirstPage={list.pagination && !list.pagination.isFirstPage ? list.pagination.reset : undefined} + onNext={list.pagination && !list.pagination.isLastPage ? list.pagination.loadMore : undefined} + filter={stateFilter} + onChangeFilter={setStateFilter} + /> } export const PeopleIcon = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> -<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" /> + <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" /> </svg> export const HomeIcon = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> -<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /> + <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /> </svg> export const ChevronRightIcon = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> -<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> + <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /> </svg> @@ -190,92 +205,27 @@ export const ArrowRightIcon = () => <svg xmlns="http://www.w3.org/2000/svg" fill </svg> -function Pagination() { +function Pagination({ onFirstPage, onNext }: { onFirstPage?: () => void, onNext?: () => void, }) { + const { i18n } = useTranslationContext() return ( - <nav class="flex items-center justify-between px-4 sm:px-0"> - <div class="-mt-px flex w-0 flex-1"> - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent pr-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" - > - <svg - class="mr-3 h-5 w-5 text-gray-400" - viewBox="0 0 20 20" - fill="currentColor" - aria-hidden="true" - > - <path - fill-rule="evenodd" - d="M18 10a.75.75 0 01-.75.75H4.66l2.1 1.95a.75.75 0 11-1.02 1.1l-3.5-3.25a.75.75 0 010-1.1l3.5-3.25a.75.75 0 111.02 1.1l-2.1 1.95h12.59A.75.75 0 0118 10z" - clip-rule="evenodd" - /> - </svg> - Previous - </a> - </div> - <div class="hidden md:-mt-px md:flex"> - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent px-4 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" + <nav class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination"> + <div class="flex flex-1 justify-between sm:justify-end"> + <button + class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0" + disabled={!onFirstPage} + onClick={onFirstPage} > - 1 - </a> - {/* <!-- Current: "border-indigo-500 text-indigo-600", Default: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" --> */} - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent px-4 pt-4 text-sm font-medium text-gray-500" - aria-current="page" + <i18n.Translate>First page</i18n.Translate> + </button> + <button + class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0" + disabled={!onNext} + onClick={onNext} > - 2 - </a> - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent px-4 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" - > - 3 - </a> - <span class="inline-flex items-center border-t-2 border-transparent px-4 pt-4 text-sm font-medium text-gray-500"> - ... - </span> - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent px-4 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" - > - 8 - </a> - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent px-4 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" - > - 9 - </a> - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent px-4 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" - > - 10 - </a> - </div> - <div class="-mt-px flex w-0 flex-1 justify-end"> - <a - href="#" - class="inline-flex items-center border-t-2 border-transparent pl-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700" - > - Next - <svg - class="ml-3 h-5 w-5 text-gray-400" - viewBox="0 0 20 20" - fill="currentColor" - aria-hidden="true" - > - <path - fill-rule="evenodd" - d="M2 10a.75.75 0 01.75-.75h12.59l-2.1-1.95a.75.75 0 111.02-1.1l3.5 3.25a.75.75 0 010 1.1l-3.5 3.25a.75.75 0 11-1.02-1.1l2.1-1.95H2.75A.75.75 0 012 10z" - clip-rule="evenodd" - /> - </svg> - </a> + <i18n.Translate>Next</i18n.Translate> + </button> </div> </nav> - ); + + ) } |