aboutsummaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-08-27 13:45:57 -0300
committerSebastian <sebasjm@gmail.com>2024-08-27 13:45:57 -0300
commit014ad600d79df7e31d70c33e7820238cc8018f56 (patch)
tree2f339c8b135c848ab13f5d05c7e4026481d08291 /packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
parent874124d2e301ac4b5bf70937ac144bf3d371ecca (diff)
downloadwallet-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.tsx173
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;