diff options
Diffstat (limited to 'packages/aml-backoffice-ui/src/pages/Cases.tsx')
-rw-r--r-- | packages/aml-backoffice-ui/src/pages/Cases.tsx | 334 |
1 files changed, 204 insertions, 130 deletions
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx index f66eca33f..278d4bac2 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx @@ -21,111 +21,98 @@ import { } from "@gnu-taler/taler-util"; import { Attention, - ErrorLoading, - InputChoiceHorizontal, Loading, - UIHandlerId, - amlStateConverter, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { useEffect, useState } from "preact/hooks"; -import { useCases } from "../hooks/useCases.js"; +import { + useCurrentDecisions, + useCurrentDecisionsUnderInvestigation, +} from "../hooks/decisions.js"; import { privatePages } from "../Routing.js"; -import { FormErrors, RecursivePartial, useFormState } from "../hooks/form.js"; -import { undefinedIfEmpty } from "./CreateAccount.js"; +import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { Officer } from "./Officer.js"; type FormType = { - state: TalerExchangeApi.AmlState; + // state: TalerExchangeApi.AmlState; }; export function CasesUI({ records, - filter, - onChangeFilter, onFirstPage, onNext, + filtered, }: { + filtered: boolean; onFirstPage?: () => void; onNext?: () => void; - filter: TalerExchangeApi.AmlState; - onChangeFilter: (f: TalerExchangeApi.AmlState) => void; - records: TalerExchangeApi.AmlRecord[]; + records: TalerExchangeApi.AmlDecision[]; }): VNode { const { i18n } = useTranslationContext(); - const [form, status] = useFormState<FormType>( - [".state"] as Array<UIHandlerId>, - { - state: filter, - }, - (state) => { - const errors = undefinedIfEmpty<FormErrors<FormType>>({ - state: state.state === undefined ? i18n.str`required` : undefined, - }); - if (errors === undefined) { - const result: FormType = { - state: state.state!, - }; - return { - status: "ok", - result, - errors, - }; - } - const result: RecursivePartial<FormType> = { - state: state.state, - }; - return { - status: "fail", - result, - errors, - }; - }, - ); - useEffect(() => { - if (status.status === "ok" && filter !== status.result.state) { - onChangeFilter(status.result.state); - } - }, [form?.state?.value]); + // const [form, status] = useFormState<FormType>( + // [".state"] as Array<UIHandlerId>, + // { + // // state: filter, + // }, + // (state) => { + // const errors = undefinedIfEmpty<FormErrors<FormType>>({ + // state: state.state === undefined ? i18n.str`required` : undefined, + // }); + // if (errors === undefined) { + // const result: FormType = { + // state: state.state!, + // }; + // return { + // status: "ok", + // result, + // errors, + // }; + // } + // const result: RecursivePartial<FormType> = { + // state: state.state, + // }; + // return { + // status: "fail", + // result, + // errors, + // }; + // }, + // ); + // useEffect(() => { + // if (status.status === "ok" && filter !== status.result.state) { + // onChangeFilter(status.result.state); + // } + // }, [form?.state?.value]); 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 w-80"> - <i18n.Translate> - A list of all the account with the status - </i18n.Translate> - </p> - </div> - <div class="px-2"> - <InputChoiceHorizontal<FormType, "state"> - name="state" - label={i18n.str`Filter`} - handler={form.state} - converter={amlStateConverter} - choices={[ - { - label: i18n.str`Pending`, - value: "pending", - }, - { - label: i18n.str`Frozen`, - value: "frozen", - }, - { - label: i18n.str`Normal`, - value: "normal", - }, - ]} - /> - </div> + {filtered ? ( + <div class="px-2 sm:flex-auto"> + <h1 class="text-base font-semibold leading-6 text-gray-900"> + <i18n.Translate>Cases under investigation</i18n.Translate> + </h1> + <p class="mt-2 text-sm text-gray-700 w-80"> + <i18n.Translate> + A list of all the accounts which are waiting for a deicison to + be made. + </i18n.Translate> + </p> + </div> + ) : ( + <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 w-80"> + <i18n.Translate> + A list of all the known account by the exchange. + </i18n.Translate> + </p> + </div> + )} </div> <div class="mt-8 flow-root"> <div class="overflow-x-auto"> @@ -148,12 +135,6 @@ export function CasesUI({ > <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 w-40" - > - <i18n.Translate>Threshold</i18n.Translate> - </th> </tr> </thead> <tbody class="divide-y divide-gray-200 bg-white"> @@ -172,35 +153,12 @@ export function CasesUI({ </a> </div> </td> - <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500"> - {((state: TalerExchangeApi.AmlState): VNode => { - switch (state) { - case TalerExchangeApi.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 TalerExchangeApi.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 TalerExchangeApi.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} + {r.to_investigate ? ( + <span title="require investigation"> + <ToInvestigateIcon /> + </span> + ) : undefined} </td> </tr> ); @@ -217,18 +175,14 @@ export function CasesUI({ } export function Cases() { - const [stateFilter, setStateFilter] = useState( - TalerExchangeApi.AmlState.pending, - ); - - const list = useCases(stateFilter); + const list = useCurrentDecisions(); const { i18n } = useTranslationContext(); if (!list) { return <Loading />; } if (list instanceof TalerError) { - return <ErrorLoading error={list} />; + return <ErrorLoadingWithDebug error={list} />; } if (list.type === "fail") { @@ -238,28 +192,107 @@ export function Cases() { <Fragment> <Attention type="danger" title={i18n.str`Operation denied`}> <i18n.Translate> - This account doesn't have access. Request account activation - sending your public key. + This account signature is wrong, contact administrator or create + a new one. </i18n.Translate> </Attention> <Officer /> </Fragment> ); } - case HttpStatusCode.Unauthorized: { + case HttpStatusCode.NotFound: { + return ( + <Fragment> + <Attention type="danger" title={i18n.str`Operation denied`}> + <i18n.Translate>This account is not known.</i18n.Translate> + </Attention> + <Officer /> + </Fragment> + ); + } + case HttpStatusCode.Conflict: + { + return ( + <Fragment> + <Attention type="danger" title={i18n.str`Operation denied`}> + <i18n.Translate> + This account doesn't have access. Request account activation + sending your public key. + </i18n.Translate> + </Attention> + <Officer /> + </Fragment> + ); + } + return <Officer />; + default: + assertUnreachable(list); + } + } + + return ( + <CasesUI + filtered={false} + records={list.body} + onFirstPage={list.isFirstPage ? undefined : list.loadFirst} + onNext={list.isLastPage ? undefined : list.loadNext} + // filter={stateFilter} + // onChangeFilter={(d) => { + // setStateFilter(d); + // }} + /> + ); +} +export function CasesUnderInvestigation() { + const list = useCurrentDecisionsUnderInvestigation(); + const { i18n } = useTranslationContext(); + + if (!list) { + return <Loading />; + } + if (list instanceof TalerError) { + return <ErrorLoadingWithDebug error={list} />; + } + + if (list.type === "fail") { + switch (list.case) { + case HttpStatusCode.Forbidden: { return ( <Fragment> <Attention type="danger" title={i18n.str`Operation denied`}> <i18n.Translate> - This account is not allowed to perform list the cases. + This account signature is wrong, contact administrator or create + a new one. </i18n.Translate> </Attention> <Officer /> </Fragment> ); } - case HttpStatusCode.NotFound: + case HttpStatusCode.NotFound: { + return ( + <Fragment> + <Attention type="danger" title={i18n.str`Operation denied`}> + <i18n.Translate>This account is not known.</i18n.Translate> + </Attention> + <Officer /> + </Fragment> + ); + } case HttpStatusCode.Conflict: + { + return ( + <Fragment> + <Attention type="danger" title={i18n.str`Operation denied`}> + <i18n.Translate> + This account doesn't have access. Request account activation + sending your public key. + </i18n.Translate> + </Attention> + <Officer /> + </Fragment> + ); + } return <Officer />; default: assertUnreachable(list); @@ -268,17 +301,41 @@ export function Cases() { return ( <CasesUI + filtered={true} records={list.body} onFirstPage={list.isFirstPage ? undefined : list.loadFirst} onNext={list.isLastPage ? undefined : list.loadNext} - filter={stateFilter} - onChangeFilter={(d) => { - setStateFilter(d); - }} + // filter={stateFilter} + // onChangeFilter={(d) => { + // setStateFilter(d); + // }} /> ); } +// function ToInvestigateIcon(): VNode { +// return <svg title="requires investigation" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 w-6"> +// <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" /> +// </svg> +// } +export const ToInvestigateIcon = () => ( + <svg + title="requires investigation" + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="1.5" + stroke="currentColor" + class="size-6 w-6" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" + /> + </svg> +); + export const PeopleIcon = () => ( <svg xmlns="http://www.w3.org/2000/svg" @@ -313,7 +370,24 @@ export const HomeIcon = () => ( </svg> ); -function Pagination({ +export const SearchIcon = () => ( + <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="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" + /> + </svg> +); + +export function Pagination({ onFirstPage, onNext, }: { |