commit e5a22cf09987829fed2b84309417f94c7f0d2169
parent 0ee19f2c03e0366cb73077e325f97f5b823fa2ae
Author: Sebastian <sebasjm@taler-systems.com>
Date: Thu, 15 Jan 2026 15:52:13 -0300
fix #10673
Diffstat:
2 files changed, 68 insertions(+), 9 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/assets/pdf-icon.png b/packages/aml-backoffice-ui/src/assets/pdf-icon.png
Binary files differ.
diff --git a/packages/aml-backoffice-ui/src/pages/AccountDetails.tsx b/packages/aml-backoffice-ui/src/pages/AccountDetails.tsx
@@ -24,10 +24,14 @@ import {
} from "@gnu-taler/taler-util";
import {
Attention,
+ ButtonBetter,
CopyButton,
ErrorLoading,
Loading,
+ LocalNotificationBanner,
RouteDefinition,
+ useExchangeApiContext,
+ useLocalNotificationBetter,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
@@ -42,6 +46,11 @@ import { useAccountDecisions } from "../hooks/decisions.js";
import { useCurrentLegitimizations } from "../hooks/legitimizations.js";
import { useServerMeasures } from "../hooks/server-info.js";
import { BANK_RULES, WALLET_RULES } from "./decision/Rules.js";
+import { useState } from "preact/hooks";
+import pdfIcon from "../assets/pdf-icon.png";
+import { useOfficer } from "../hooks/officer.js";
+
+const utfDecoder = new TextDecoder("Latin1");
export function ShowProperties(props: {
properties?: AccountProperties;
@@ -82,6 +91,11 @@ export function AccountDetails({
const details = useAccountInformation(account);
const history = useAccountDecisions(account);
const legistimizations = useCurrentLegitimizations(account);
+ const officer = useOfficer();
+ const session = officer.state === "ready" ? officer.account : undefined;
+ const { lib } = useExchangeApiContext();
+ const [exported, setExported] = useState<{ content: string; file: string }>();
+ const [notification, safeFunctionHandler] = useLocalNotificationBetter();
const measures = useServerMeasures();
@@ -176,13 +190,28 @@ export function AccountDetails({
const filteredRulesByType = !activeDecision
? defaultRules
: defaultRules.filter((r) => {
- return activeDecision.is_wallet
- ? WALLET_RULES.includes(r.operation_type)
- : BANK_RULES.includes(r.operation_type);
- });
+ return activeDecision.is_wallet
+ ? WALLET_RULES.includes(r.operation_type)
+ : BANK_RULES.includes(r.operation_type);
+ });
+
+ const time = format(new Date(), "yyyyMMdd_HHmmss");
+
+ const downloadPdf = safeFunctionHandler(lib.exchange.getAmlAttributesForAccountAsPdf.bind(lib.exchange), session ? [session, account] : undefined);
+ downloadPdf.onSuccess = (result) => {
+ setExported({
+ content: new Uint8Array(result).reduce(
+ (data, byte) => data + String.fromCharCode(byte),
+ "",
+ ),
+ file: `account_${time}_${account}.pdf`,
+ });
+ };
+
return (
<div class="min-w-60">
+ <LocalNotificationBanner notification={notification} />
<header class="flex flex-col justify-between border-b border-white/5 px-4 py-4 sm:px-6 sm:py-6 lg:px-8 gap-2">
<h1 class="text-base font-semibold leading-7 text-black">
<i18n.Translate>Case history for selected account</i18n.Translate>
@@ -231,11 +260,41 @@ export function AccountDetails({
{collectionEvents.length === 0 ? (
<Attention title={i18n.str`The event list is empty`} type="warning" />
) : (
- <ShowTimeline
- account={account}
- history={collectionEvents}
- routeToShowCollectedInfo={routeToShowCollectedInfo}
- />
+ <Fragment>
+ <div class="flex space-x-2 mb-4">
+ <i18n.Translate>Export as PDF</i18n.Translate>
+ <ButtonBetter onClick={downloadPdf}>
+ <img class="size-6 w-6" src={pdfIcon} />
+ </ButtonBetter>
+ </div>
+ {!exported ? (
+ <div />
+ ) : (
+ <a
+ href={
+ "data:application/pdf;base64," +
+ window.btoa(exported.content)
+ }
+ name="save file"
+ download={exported.file}
+ >
+ <Attention
+ title={i18n.str`Export completed`}
+ onClose={() => setExported(undefined)}
+ >
+ <i18n.Translate>
+ Click here to save the file in your computer.
+ </i18n.Translate>
+ </Attention>
+ </a>
+ )}
+
+ <ShowTimeline
+ account={account}
+ history={collectionEvents}
+ routeToShowCollectedInfo={routeToShowCollectedInfo}
+ />
+ </Fragment>
)}
<h1 class="mb-4 text-base font-semibold leading-6 text-black">