aboutsummaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'packages/aml-backoffice-ui/src/pages')
-rw-r--r--packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx46
-rw-r--r--packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx152
-rw-r--r--packages/aml-backoffice-ui/src/pages/CaseDetails.tsx288
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.stories.tsx42
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx392
-rw-r--r--packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx68
-rw-r--r--packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx69
-rw-r--r--packages/aml-backoffice-ui/src/pages/index.stories.ts1
8 files changed, 633 insertions, 425 deletions
diff --git a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx
index a14966cc0..0b055f682 100644
--- a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx
+++ b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.stories.tsx
@@ -30,65 +30,75 @@ export default {
export const SimpleComment = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 0,
+ formId: "simple_comment",
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
+
export const Identification = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 1,
+ formId: "902.1e",
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
+
export const OperationalLegalEntity = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 2,
+ formId: "902.11e",
+
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
export const Foundations = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 3,
+ formId: "902.12e",
+
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
export const DelcarationOfTrusts = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 4,
+ formId: "902.13e",
+
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
+
export const InformationOnLifeInsurance = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 5,
+ formId: "902.15e",
+
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
export const DeclarationOfBeneficialOwner = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 6,
+ formId: "902.9e",
+
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
export const CustomerProfile = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 7,
+ formId: "902.5e",
+
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
export const RiskProfile = tests.createExample(TestedComponent, {
account: "the_account",
- selectedForm: 8,
+ formId: "902.4e",
+
onSubmit: async (justification, newState, newThreshold) => {
- alert(JSON.stringify({justification, newState, newThreshold}, undefined, 2))
+ alert(JSON.stringify({ justification, newState, newThreshold }, undefined, 2))
}
});
diff --git a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx
index faf9671bb..d1fb3b895 100644
--- a/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx
+++ b/packages/aml-backoffice-ui/src/pages/AntiMoneyLaunderingForm.tsx
@@ -1,5 +1,5 @@
-import { AbsoluteTime, AmountJson, Amounts } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { AbsoluteTime, AmountJson, Amounts, Codec, OperationResult, buildCodecForObject, codecForNumber, codecForString, codecOptional } from "@gnu-taler/taler-util";
+import { FlexibleForm, useTranslationContext } from "@gnu-taler/web-util/browser";
import { h } from "preact";
import { NiceForm } from "../NiceForm.js";
import { v1 as form_902_11e_v1 } from "../forms/902_11e.js";
@@ -10,29 +10,21 @@ import { v1 as form_902_1e_v1 } from "../forms/902_1e.js";
import { v1 as form_902_4e_v1 } from "../forms/902_4e.js";
import { v1 as form_902_5e_v1 } from "../forms/902_5e.js";
import { v1 as form_902_9e_v1 } from "../forms/902_9e.js";
-import { v1 as simplest } from "../forms/simplest.js";
+import { Simplest, v1 as simplest } from "../forms/simplest.js";
import { Pages } from "../pages.js";
import { AmlExchangeBackend } from "../types.js";
import { useExchangeApiContext } from "../context/config.js";
-export type Justification = {
- // form index in the list of forms
- index: number;
- // form name
- name: string;
- // form values
- value: any;
-}
-
-export function AntiMoneyLaunderingForm({ account, selectedForm, onSubmit }: { account: string, selectedForm: number, onSubmit: (justification: Justification, state: AmlExchangeBackend.AmlState, threshold: AmountJson) => Promise<void>; }) {
+export function AntiMoneyLaunderingForm({ account, formId, onSubmit }: { account: string, formId: string, onSubmit: (justification: Justification, state: AmlExchangeBackend.AmlState, threshold: AmountJson) => Promise<void>; }) {
const { i18n } = useTranslationContext()
- const showingFrom = allForms[selectedForm].impl;
- const formName = allForms[selectedForm].name
+ const theForm = allForms.find((v) => v.id === formId)
+ if (!theForm) {
+ return <div>form with id {formId} not found</div>
+ }
const { config } = useExchangeApiContext()
const initial = {
- fullName: "loggedIn_user_fullname",
when: AbsoluteTime.now(),
state: AmlExchangeBackend.AmlState.pending,
threshold: Amounts.zeroOfCurrency(config.currency),
@@ -40,16 +32,17 @@ export function AntiMoneyLaunderingForm({ account, selectedForm, onSubmit }: { a
return (
<NiceForm
initial={initial}
- form={showingFrom(initial)}
+ form={theForm.impl(initial)}
onUpdate={() => { }}
onSubmit={(formValue) => {
if (formValue.state === undefined || formValue.threshold === undefined) return;
const st = formValue.state;
const amount = formValue.threshold;
- const justification = {
- index: selectedForm,
- name: formName,
+ const justification: Justification = {
+ id: theForm.id,
+ label: theForm.label,
+ version: theForm.version,
value: formValue
}
@@ -75,7 +68,7 @@ export function AntiMoneyLaunderingForm({ account, selectedForm, onSubmit }: { a
);
}
-export interface State {
+export interface BaseForm {
state: AmlExchangeBackend.AmlState;
threshold: AmountJson;
}
@@ -85,49 +78,146 @@ const DocumentDuplicateIcon = <svg xmlns="http://www.w3.org/2000/svg" fill="none
</svg>
-export const allForms = [
+export type FormMetadata = {
+ label: string,
+ id: string,
+ version: number,
+ icon: h.JSX.Element,
+ impl: (current: BaseForm) => FlexibleForm<BaseForm>
+}
+
+export type Justification<T = any> = {
+ // form values
+ value: T;
+} & Omit<Omit<FormMetadata, "icon">, "impl">
+
+export function stringifyJustification(j: Justification): string {
+ return JSON.stringify(j)
+}
+
+
+type SimpleFormMetadata = {
+ version?: number,
+ id?: string,
+}
+
+export const codecForSimpleFormMetadata = (): Codec<SimpleFormMetadata> =>
+ buildCodecForObject<SimpleFormMetadata>()
+ .property("id", codecOptional(codecForString()))
+ .property("version", codecOptional(codecForNumber()))
+ .build("SimpleFormMetadata");
+
+type ParseJustificationFail =
+ "not-json" |
+ "id-not-found" |
+ "form-not-found" |
+ "version-not-found";
+
+export function parseJustification(s: string, listOfAllKnownForms: FormMetadata[]): OperationResult<{ justification: Justification, metadata: FormMetadata }, ParseJustificationFail> {
+ try {
+ const justification = JSON.parse(s)
+ const info = codecForSimpleFormMetadata().decode(justification)
+ if (!info.id) {
+ return {
+ type: "fail",
+ case: "id-not-found",
+ detail: {} as any
+ }
+ }
+ if (!info.version) {
+ return {
+ type: "fail",
+ case: "version-not-found",
+ detail: {} as any
+ }
+ }
+ const found = listOfAllKnownForms.find((f) => {
+ return f.id === info.id && f.version === info.version
+ })
+ if (!found) {
+ return {
+ type: "fail",
+ case: "form-not-found",
+ detail: {} as any
+ }
+ }
+ return {
+ type: "ok",
+ body: {
+ justification, metadata: found
+ }
+ }
+ } catch (e) {
+ return {
+ type: "fail",
+ case: "not-json",
+ detail: {} as any
+ }
+ }
+
+}
+
+export const allForms: Array<FormMetadata> = [
{
- name: "Simple comment",
+ label: "Simple comment",
+ id: "simple_comment",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: simplest,
},
{
- name: "Identification form (902.1e)",
+ label: "Identification form",
+ id: "902.1e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_1e_v1,
},
{
- name: "Operational legal entity or partnership (902.11e)",
+ label: "Operational legal entity or partnership",
+ id: "902.11e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_11e_v1,
},
{
- name: "Foundations (902.12e)",
+ label: "Foundations",
+ id: "902.12e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_12e_v1,
},
{
- name: "Declaration for trusts (902.13e)",
+ label: "Declaration for trusts",
+ id: "902.13e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_13e_v1,
},
{
- name: "Information on life insurance policies (902.15e)",
+ label: "Information on life insurance policies",
+ id: "902.15e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_15e_v1,
},
{
- name: "Declaration of beneficial owner (902.9e)",
+ label: "Declaration of beneficial owner",
+ id: "902.9e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_9e_v1,
},
{
- name: "Customer profile (902.5e)",
+ label: "Customer profile",
+ id: "902.5e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_5e_v1,
},
{
- name: "Risk profile (902.4e)",
+ label: "Risk profile",
+ id: "902.4e",
+ version: 1,
icon: DocumentDuplicateIcon,
impl: form_902_4e_v1,
},
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
index 1f8d6ac5e..1cfa65926 100644
--- a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
+++ b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -2,6 +2,7 @@ import {
AbsoluteTime,
AmountJson,
Amounts,
+ OperationResult,
TalerError,
TranslatedString,
assertUnreachable
@@ -14,12 +15,25 @@ import { useCaseDetails } from "../hooks/useCaseDetails.js";
import { Pages } from "../pages.js";
import { AmlExchangeBackend } from "../types.js";
import { ShowConsolidated } from "./ShowConsolidated.js";
+import { FormMetadata, Justification, allForms, parseJustification } from "./AntiMoneyLaunderingForm.js";
+import { NiceForm } from "../NiceForm.js";
-export type AmlEvent = AmlFormEvent | KycCollectionEvent | KycExpirationEvent;
+export type AmlEvent = AmlFormEvent | AmlFormEventError | KycCollectionEvent | KycExpirationEvent;
type AmlFormEvent = {
type: "aml-form";
when: AbsoluteTime;
title: TranslatedString;
+ justification: Justification;
+ metadata: FormMetadata;
+ state: AmlExchangeBackend.AmlState;
+ threshold: AmountJson;
+};
+type AmlFormEventError = {
+ type: "aml-form-error";
+ when: AbsoluteTime;
+ title: TranslatedString;
+ justification: undefined,
+ metadata: undefined,
state: AmlExchangeBackend.AmlState;
threshold: AmountJson;
};
@@ -43,16 +57,32 @@ function selectSooner(a: WithTime, b: WithTime) {
return AbsoluteTime.cmp(a.when, b.when);
}
+function titleForJustification(op: ReturnType<typeof parseJustification>): TranslatedString {
+ if (op.type === "ok") {
+ return op.body.justification.label as TranslatedString;
+ }
+ switch (op.case) {
+ case "not-json": return "error: the justification is not a form" as TranslatedString
+ case "id-not-found": return "error: justification form's id not found" as TranslatedString
+ case "version-not-found": return "error: justification form's version not found" as TranslatedString
+ case "form-not-found": return `error: justification form not found` as TranslatedString
+ }
+ assertUnreachable(op.case)
+}
+
export function getEventsFromAmlHistory(
aml: AmlExchangeBackend.AmlDecisionDetail[],
kyc: AmlExchangeBackend.KycDetail[],
): AmlEvent[] {
const ae: AmlEvent[] = aml.map((a) => {
+ const just = parseJustification(a.justification, allForms)
return {
- type: "aml-form",
+ type: just.type === "ok" ? "aml-form" : "aml-form-error",
state: a.new_state,
threshold: Amounts.parseOrThrow(a.new_threshold),
- title: a.justification as TranslatedString,
+ title: titleForJustification(just),
+ metadata: just.type === "ok" ? just.body.metadata : undefined,
+ justification: just.type === "ok" ? just.body.justification : undefined,
when: {
t_ms:
a.decision_time.t_s === "never"
@@ -81,7 +111,8 @@ export function getEventsFromAmlHistory(
}
export function CaseDetails({ account }: { account: string }) {
- const [selected, setSelected] = useState<AmlEvent | undefined>(undefined);
+ const [selected, setSelected] = useState<AbsoluteTime>(AbsoluteTime.now());
+ const [showForm, setShowForm] = useState<{ justification: Justification, metadata: FormMetadata }>()
const { i18n } = useTranslationContext();
const details = useCaseDetails(account)
@@ -103,6 +134,25 @@ export function CaseDetails({ account }: { account: string }) {
const events = getEventsFromAmlHistory(aml_history, kyc_attributes);
+ if (showForm !== undefined) {
+ return <NiceForm
+ readOnly={true}
+ initial={showForm.justification.value}
+ form={showForm.metadata.impl(showForm.justification.value)}
+ >
+ <div class="mt-6 flex items-center justify-end gap-x-6">
+ <button
+ class="text-sm font-semibold leading-6 text-gray-900"
+ onClick={() => {
+ setShowForm(undefined)
+ }}
+ >
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ </div>
+
+ </NiceForm>
+ }
return (
<div>
<a
@@ -117,116 +167,138 @@ export function CaseDetails({ account }: { account: string }) {
<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">
<h1 class="text-base font-semibold leading-7 text-black">
<i18n.Translate>
- Case history
+ Case history for account <span title={account}>{account.substring(0, 16)}...</span>
</i18n.Translate>
</h1>
</header>
- <div class="flow-root">
- <ul role="list">
- {events.map((e, idx) => {
- const isLast = events.length - 1 === idx;
- return (
- <li
- class="hover:bg-gray-200 p-2 rounded cursor-pointer"
- onClick={() => {
- setSelected(e);
- }}
- >
- <div class="relative pb-6">
- {!isLast ? (
- <span
- class="absolute left-4 top-4 -ml-px h-full w-1 bg-gray-200"
- aria-hidden="true"
- ></span>
- ) : undefined}
- <div class="relative flex space-x-3">
- {(() => {
- switch (e.type) {
- case "aml-form": {
- switch (e.state) {
- case AmlExchangeBackend.AmlState.normal: {
- return (
- <div>
- <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>
- <span class="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 ">
- {e.threshold.currency}{" "}
- {Amounts.stringifyValue(e.threshold)}
- </span>
- </div>
- );
- }
- case AmlExchangeBackend.AmlState.pending: {
- return (
- <div>
- <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>
- <span class="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 ">
- {e.threshold.currency}{" "}
- {Amounts.stringifyValue(e.threshold)}
- </span>
- </div>
- );
- }
- case AmlExchangeBackend.AmlState.frozen: {
- return (
- <div>
- <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>
- <span class="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 ">
- {e.threshold.currency}{" "}
- {Amounts.stringifyValue(e.threshold)}
- </span>
- </div>
- );
- }
- }
- }
- case "kyc-collection": {
- return (
- // <ArrowDownCircleIcon class="h-8 w-8 text-green-700" />
- <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="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
- </svg>
- );
- }
- case "kyc-expiration": {
- // return <ClockIcon class="h-8 w-8 text-gray-700" />;
- return <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="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
- </svg>
-
- }
- }
- })()}
- <div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
- <div>
- <p class="text-sm text-gray-900">{e.title}</p>
- </div>
- <div class="whitespace-nowrap text-right text-sm text-gray-500">
- {e.when.t_ms === "never" ? (
- "never"
- ) : (
- <time dateTime={format(e.when.t_ms, "dd MMM yyyy")}>
- {format(e.when.t_ms, "dd MMM yyyy")}
- </time>
- )}
+ <ShowTimeline history={events} onSelect={(e) => {
+ switch (e.type) {
+ case "aml-form": {
+ const { justification, metadata } = e
+ setShowForm({ justification, metadata })
+ break;
+ }
+ case "kyc-collection":
+ case "kyc-expiration": {
+ setSelected(e.when);
+ break;
+ }
+ case "aml-form-error":
+ }
+ }} />
+ {/* {selected && <ShowEventDetails event={selected} />} */}
+ {selected && <ShowConsolidated history={events} until={selected} />}
+ </div>
+ );
+}
+
+function AmlStateBadge({ state }: { 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>
+ );
+ }
+ }
+ assertUnreachable(state)
+}
+
+function ShowTimeline({ history, onSelect }: { onSelect: (e: AmlEvent) => void, history: AmlEvent[] }): VNode {
+ return <div class="flow-root">
+ <ul role="list">
+ {history.map((e, idx) => {
+ const isLast = history.length - 1 === idx;
+ return (
+ <li
+ data-ok={e.type !== "aml-form-error"}
+ class="hover:bg-gray-200 p-2 rounded data-[ok=true]:cursor-pointer"
+ onClick={() => {
+ onSelect(e);
+ }}
+ >
+ <div class="relative pb-6">
+ {!isLast ? (
+ <span
+ class="absolute left-4 top-4 -ml-px h-full w-1 bg-gray-200"
+ aria-hidden="true"
+ ></span>
+ ) : undefined}
+ <div class="relative flex space-x-3">
+ {(() => {
+ switch (e.type) {
+ case "aml-form-error":
+ case "aml-form": {
+ return <div>
+ <AmlStateBadge state={e.state} />
+ <span class="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 ">
+ {e.threshold.currency}{" "}
+ {Amounts.stringifyValue(e.threshold)}
+ </span>
</div>
- </div>
+ }
+ case "kyc-collection": {
+ return (
+ // <ArrowDownCircleIcon class="h-8 w-8 text-green-700" />
+ <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="M9 12.75l3 3m0 0l3-3m-3 3v-7.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
+ </svg>
+ );
+ }
+ case "kyc-expiration": {
+ // return <ClockIcon class="h-8 w-8 text-gray-700" />;
+ return <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="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
+ </svg>
+
+ }
+ }
+ assertUnreachable(e)
+ })()}
+ <div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
+ {e.type === "aml-form" ?
+ <span
+ // href={Pages.newFormEntry.url({ account })}
+ class="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"
+ >
+ {e.title}
+ </span>
+ :
+ <p class="text-sm text-gray-900">{e.title}</p>
+ }
+ <div class="whitespace-nowrap text-right text-sm text-gray-500">
+ {e.when.t_ms === "never" ? (
+ "never"
+ ) : (
+ <time dateTime={format(e.when.t_ms, "dd MMM yyyy")}>
+ {format(e.when.t_ms, "dd MMM yyyy")}
+ </time>
+ )}
</div>
</div>
- </li>
- );
- })}
- </ul>
- </div>
- {selected && <ShowEventDetails event={selected} />}
- {selected && <ShowConsolidated history={events} until={selected.when} />}
- </div>
- );
+ </div>
+ </div>
+ </li>
+ );
+ })}
+ </ul>
+ </div>
+
}
function ShowEventDetails({ event }: { event: AmlEvent }): VNode {
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx
new file mode 100644
index 000000000..0355d5a31
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/pages/Cases.stories.tsx
@@ -0,0 +1,42 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import * as tests from "@gnu-taler/web-util/testing";
+import { AmlExchangeBackend } from "../types.js";
+import {
+ CasesUI as TestedComponent,
+} from "./Cases.js";
+import { AmountString } from "@gnu-taler/taler-util";
+
+export default {
+ title: "cases",
+};
+
+export const OneRow = tests.createExample(TestedComponent, {
+ filter: AmlExchangeBackend.AmlState.normal,
+ onChangeFilter: () => null,
+ records: [{
+ current_state: AmlExchangeBackend.AmlState.normal,
+ h_payto: "QWEQWEQWEQWE",
+ rowid: 1,
+ threshold: "USD:1" as AmountString
+ }]
+});
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>
- );
+
+ )
}
diff --git a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
index 95b1f35c4..214c17648 100644
--- a/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
+++ b/packages/aml-backoffice-ui/src/pages/NewFormEntry.tsx
@@ -1,13 +1,11 @@
+import { Amounts, TalerExchangeApi, TalerProtocolTimestamp, TranslatedString } from "@gnu-taler/taler-util";
+import { LocalNotificationBanner, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
-import { AntiMoneyLaunderingForm, allForms } from "./AntiMoneyLaunderingForm.js";
-import { Pages } from "../pages.js";
-import { NiceForm } from "../NiceForm.js";
-import { AbsoluteTime, Amounts, TalerExchangeApi, TalerProtocolTimestamp, TranslatedString } from "@gnu-taler/taler-util";
-import { AmlExchangeBackend } from "../types.js";
+import { useExchangeApiContext } from "../context/config.js";
import { useOfficer } from "../hooks/useOfficer.js";
+import { Pages } from "../pages.js";
+import { AntiMoneyLaunderingForm, allForms } from "./AntiMoneyLaunderingForm.js";
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
-import { useExchangeApiContext } from "../context/config.js";
-import { LocalNotificationBanner, useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser";
export function NewFormEntry({
account,
@@ -31,19 +29,13 @@ export function NewFormEntry({
return <HandleAccountNotReady officer={officer} />;
}
- const selectedForm = Number.parseInt(type ?? "0", 10);
- if (Number.isNaN(selectedForm)) {
- return <div>WHAT! {type}</div>;
- }
-
-
return (
<Fragment>
<LocalNotificationBanner notification={notification} />
<AntiMoneyLaunderingForm
account={account}
- selectedForm={selectedForm}
+ formId={type}
onSubmit={async (justification, new_state, new_threshold) => {
const decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig"> = {
@@ -56,27 +48,29 @@ export function NewFormEntry({
}
await handleError(async () => {
const resp = await api.addDecisionDetails(officer.account, decision);
- if (resp.type === "fail") {
- switch (resp.case) {
- case "unauthorized": return notify({
- type: "error",
- title: i18n.str`Wrong credentials for "${officer.account}"`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "officer-or-account-not-found": return notify({
- type: "error",
- title: i18n.str`Officer or account not found`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- case "officer-disabled-or-recent-decision": return notify({
- type: "error",
- title: i18n.str`Officer disabled or more recent decision was already submitted.`,
- description: resp.detail.hint as TranslatedString,
- debug: resp.detail,
- })
- }
+ if (resp.type === "ok") {
+ window.location.href = Pages.cases.url;
+ return;
+ }
+ switch (resp.case) {
+ case "unauthorized": return notify({
+ type: "error",
+ title: i18n.str`Wrong credentials for "${officer.account}"`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "officer-or-account-not-found": return notify({
+ type: "error",
+ title: i18n.str`Officer or account not found`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
+ case "officer-disabled-or-recent-decision": return notify({
+ type: "error",
+ title: i18n.str`Officer disabled or more recent decision was already submitted.`,
+ description: resp.detail.hint as TranslatedString,
+ debug: resp.detail,
+ })
}
})
}}
@@ -92,10 +86,10 @@ function SelectForm({ account }: { account: string }) {
{allForms.map((form, idx) => {
return (
<a
- href={Pages.newFormEntry.url({ account, type: String(idx) })}
+ href={Pages.newFormEntry.url({ account, type: form.id })}
class="m-4 block rounded-md w-fit border-0 p-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-600"
>
- {form.name}
+ {form.label}
</a>
);
})}
diff --git a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx
index 1a86e8e98..dc073a5f5 100644
--- a/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx
+++ b/packages/aml-backoffice-ui/src/pages/ShowConsolidated.stories.tsx
@@ -32,25 +32,74 @@ export default {
};
export const WithEmptyHistory = tests.createExample(TestedComponent, {
- history: getEventsFromAmlHistory([],[]),
+ history: getEventsFromAmlHistory([], []),
until: AbsoluteTime.now()
});
export const WithSomeEvents = tests.createExample(TestedComponent, {
- history: getEventsFromAmlHistory([{
- decider_pub: "123",
- decision_time: { t_s: 1 },
- justification: "yes",
- new_state: 1,
- new_threshold: "USD:10",
- }],[{
+ history: getEventsFromAmlHistory([
+ {
+ "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
+ "justification": "{\"index\":0,\"name\":\"Simple comment\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700207199558},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"test\"}}",
+ "new_threshold": "STATER:0",
+ "new_state": 1,
+ "decision_time": {
+ "t_s": 1700208199
+ }
+ },
+ {
+ "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
+ "justification": "{\"index\":0,\"name\":\"Simple comment\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700207199558},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"test\"}}",
+ "new_threshold": "STATER:0",
+ "new_state": 1,
+ "decision_time": {
+ "t_s": 1700208211
+ }
+ },
+ {
+ "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
+ "justification": "{\"index\":0,\"name\":\"Simple comment\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700207199558},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"test\"}}",
+ "new_threshold": "STATER:0",
+ "new_state": 1,
+ "decision_time": {
+ "t_s": 1700208220
+ }
+ },
+ {
+ "decider_pub": "JD70N2XZ8FZKB7C146ZWR6XBDCS4Z84PJKJMPB73PMJ2B1X35ZFG",
+ "justification": "{\"index\":4,\"name\":\"Declaration for trusts (902.13e)\",\"value\":{\"fullName\":\"loggedIn_user_fullname\",\"when\":{\"t_ms\":1700208362854},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"contractingPartner\":\"f\",\"knownAs\":\"a\",\"trust\":{\"name\":\"b\",\"type\":\"discretionary\",\"revocability\":\"irrevocable\"}}}",
+ "new_threshold": "STATER:0",
+ "new_state": 1,
+ "decision_time": {
+ "t_s": 1700208385
+ }
+ },
+ {
+ "decider_pub": "6CD3J8XSKWQPFFDJY4SP4RK2D7T7WW7JRJDTXHNZY7YKGXDCE2QG",
+ "justification": "{\"id\":\"simple_comment\",\"label\":\"Simple comment\",\"version\":1,\"value\":{\"when\":{\"t_ms\":1700488420810},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"qwe\"}}",
+ "new_threshold": "STATER:0",
+ "new_state": 1,
+ "decision_time": {
+ "t_s": 1700488423
+ }
+ },
+ {
+ "decider_pub": "6CD3J8XSKWQPFFDJY4SP4RK2D7T7WW7JRJDTXHNZY7YKGXDCE2QG",
+ "justification": "{\"id\":\"simple_comment\",\"label\":\"Simple comment\",\"version\":1,\"value\":{\"when\":{\"t_ms\":1700488671251},\"state\":1,\"threshold\":{\"currency\":\"STATER\",\"fraction\":0,\"value\":0},\"comment\":\"asd asd asd \"}}",
+ "new_threshold": "STATER:0",
+ "new_state": 1,
+ "decision_time": {
+ "t_s": 1700488677
+ }
+ }
+ ], [{
collection_time: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.subtractDuraction(AbsoluteTime.now(), Duration.fromPrettyString("1d"))
),
- expiration_time: { t_s: "never"},
+ expiration_time: { t_s: "never" },
provider_section: "asd",
attributes: {
- email: "sebasjm@qwe.com"
+ email: "sebasjm@qwdde.com"
}
}]),
until: AbsoluteTime.now()
diff --git a/packages/aml-backoffice-ui/src/pages/index.stories.ts b/packages/aml-backoffice-ui/src/pages/index.stories.ts
index e31e13a28..afe73227a 100644
--- a/packages/aml-backoffice-ui/src/pages/index.stories.ts
+++ b/packages/aml-backoffice-ui/src/pages/index.stories.ts
@@ -1,2 +1,3 @@
export * as a1 from "./ShowConsolidated.stories.js";
export * as a2 from "./AntiMoneyLaunderingForm.stories.js";
+export * as a3 from "./Cases.stories.js";