diff options
author | Sebastian <sebasjm@gmail.com> | 2024-08-27 13:45:57 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-08-27 13:45:57 -0300 |
commit | 014ad600d79df7e31d70c33e7820238cc8018f56 (patch) | |
tree | 2f339c8b135c848ab13f5d05c7e4026481d08291 /packages/aml-backoffice-ui/src/pages/CaseDetails.tsx | |
parent | 874124d2e301ac4b5bf70937ac144bf3d371ecca (diff) | |
download | wallet-core-014ad600d79df7e31d70c33e7820238cc8018f56.tar.xz |
more aml info
Diffstat (limited to 'packages/aml-backoffice-ui/src/pages/CaseDetails.tsx')
-rw-r--r-- | packages/aml-backoffice-ui/src/pages/CaseDetails.tsx | 173 |
1 files changed, 162 insertions, 11 deletions
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx index d42e1f2c6..b26e6f430 100644 --- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx +++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx @@ -32,22 +32,25 @@ import { codecOptional, } from "@gnu-taler/taler-util"; import { + Attention, DefaultForm, - ErrorLoading, FormMetadata, InternationalizationAPI, Loading, - useTranslationContext, + ShowInputErrorLabel, + Time, + useExchangeApiContext, + useTranslationContext } from "@gnu-taler/web-util/browser"; -import { format } from "date-fns"; -import { VNode, h } from "preact"; +import { format, formatDuration, intervalToDuration } from "date-fns"; +import { Fragment, Ref, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { privatePages } from "../Routing.js"; +import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; import { useUiFormsContext } from "../context/ui-forms.js"; import { preloadedForms } from "../forms/index.js"; import { useAccountInformation } from "../hooks/account.js"; +import { useAccountDecisions } from "../hooks/decisions.js"; import { ShowConsolidated } from "./ShowConsolidated.js"; -import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js"; export type AmlEvent = | AmlFormEvent @@ -175,10 +178,12 @@ export function CaseDetails({ account }: { account: string }) { const { i18n } = useTranslationContext(); const details = useAccountInformation(account); + const history = useAccountDecisions(account); + const { forms } = useUiFormsContext(); const allForms = [...forms, ...preloadedForms(i18n)]; - if (!details) { + if (!details || !history) { return <Loading />; } if (details instanceof TalerError) { @@ -195,8 +200,22 @@ export function CaseDetails({ account }: { account: string }) { assertUnreachable(details); } } + if (history instanceof TalerError) { + return <ErrorLoadingWithDebug error={history} />; + } + if (history.type === "fail") { + switch (history.case) { + // case HttpStatusCode.Unauthorized: + case HttpStatusCode.Forbidden: + case HttpStatusCode.NotFound: + case HttpStatusCode.Conflict: + return <div />; + default: + assertUnreachable(history); + } + } const { details: accountDetails } = details.body; - + const activeDesicion = history.body.find(d => d.is_active) const events = getEventsFromAmlHistory( accountDetails, @@ -227,10 +246,25 @@ export function CaseDetails({ account }: { account: string }) { return ( <div> <a - href={privatePages.caseNew.url({ cid: account })} + // href={privatePages.caseNew.url({ cid: account })} + href="#" + class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700" + > + <i18n.Translate>Freeze account</i18n.Translate> + </a> + <a + // href={privatePages.caseNew.url({ cid: account })} + href="#" + class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700" + > + <i18n.Translate>New threshold</i18n.Translate> + </a> + <a + // href={privatePages.caseNew.url({ cid: account })} + href="#" class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700" > - <i18n.Translate>New AML form</i18n.Translate> + <i18n.Translate>Ask more information</i18n.Translate> </a> <header class="flex items-center justify-between border-b border-white/5 px-4 py-4 sm:px-6 sm:py-6 lg:px-8"> @@ -241,6 +275,10 @@ export function CaseDetails({ account }: { account: string }) { </i18n.Translate> </h1> </header> + {!activeDesicion ? <Attention title={i18n.str`No active decision found`} type="warning" /> : <Fragment> + {!activeDesicion.to_investigate ? undefined : <Attention title={i18n.str`Requires investigation`} type="warning" />} + <ShowActiveDecision decision={activeDesicion} /> + </Fragment>} <ShowTimeline history={events} onSelect={(e) => { @@ -265,6 +303,59 @@ export function CaseDetails({ account }: { account: string }) { ); } +function ShowActiveDecision({ decision }: { decision: TalerExchangeApi.AmlDecision }): VNode { + const { i18n } = useTranslationContext(); + const { config } = useExchangeApiContext() + + return <Fragment> + <h1 class="mt-4 text-base font-semibold leading-6 text-black"> + <i18n.Translate>Current active rules</i18n.Translate> + </h1> + + <div class="sm:col-span-5"> + <label + for="amount" + class="block text-sm font-medium leading-6 text-gray-900" + > + <b>Expiration time</b> + <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600"> + + <div + class="p-4 disabled:bg-gray-200 rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6" + > + + <Time format="dd/MM/yyyy HH:mm:ss" timestamp={AbsoluteTime.fromProtocolTimestamp(decision.limits.expiration_time)} /> + </div> + </div> + + + </label> + </div> + {decision.limits.rules.map(r => { + const bySpec = Amounts.stringifyValueWithSpec(Amounts.parseOrThrow(r.threshold), config.currency_specification) + return <div class="sm:col-span-5"> + <label + for="amount" + class="block text-sm font-medium leading-6 text-gray-900" + > + {r.operation_type} + <InputAmount + name="minCashout" + left + currency={bySpec.currency} + value={bySpec.normal} + onChange={undefined} + /> + </label> + <p class="mt-2 text-sm text-gray-500"> + over {r.timeframe.d_us === "forever" ? "" : formatDuration(intervalToDuration({ start: 0, end: r.timeframe.d_us / 1000 }))} + </p> + </div> + })} + + </Fragment> +} + function AmlStateBadge({ state }: { state: TalerExchangeApi.AmlState }): VNode { switch (state) { case TalerExchangeApi.AmlState.normal: { @@ -392,7 +483,7 @@ function ShowTimeline({ "never" ) : ( <time dateTime={format(e.when.t_ms, "dd MMM yyyy")}> - {format(e.when.t_ms, "dd MMM yyyy")} + {format(e.when.t_ms, "dd MMM yyyy HH:mm:ss")} </time> )} </div> @@ -407,6 +498,66 @@ function ShowTimeline({ ); } +function InputAmount( + { + currency, + name, + value, + left, + onChange, + }: { + currency: string; + name: string; + left?: boolean | undefined; + value: string | undefined; + onChange?: (s: string) => void; + }, + ref: Ref<HTMLInputElement>, +): VNode { + const FRAC_SEPARATOR = "," + const { config } = useExchangeApiContext(); + return ( + <div class="mt-2"> + <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600"> + <div class="pointer-events-none inset-y-0 flex items-center px-3"> + <span class="text-gray-500 sm:text-sm">{currency}</span> + </div> + <input + type="number" + data-left={left} + class="disabled:bg-gray-200 text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900 placeholder:text-gray-400 sm:text-sm sm:leading-6" + placeholder="0.00" + aria-describedby="price-currency" + ref={ref} + name={name} + id={name} + autocomplete="off" + value={value ?? ""} + disabled={!onChange} + onInput={(e) => { + if (!onChange) return; + const l = e.currentTarget.value.length; + const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR); + if ( + sep_pos !== -1 && + l - sep_pos - 1 > + config.currency_specification.num_fractional_input_digits + ) { + e.currentTarget.value = e.currentTarget.value.substring( + 0, + sep_pos + + config.currency_specification.num_fractional_input_digits + + 1, + ); + } + onChange(e.currentTarget.value); + }} + /> + </div> + </div> + ); +} + export type Justification<T = Record<string, unknown>> = { // form values value: T; |