aboutsummaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui/src/pages/Cases.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/aml-backoffice-ui/src/pages/Cases.tsx')
-rw-r--r--packages/aml-backoffice-ui/src/pages/Cases.tsx334
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,
}: {