commit e5c92834ed27a359f3da5c79d5b511661962fce0
parent 7a7029a5e883e36315ab1da9028b49e48bfc5e2b
Author: Sebastian <sebasjm@gmail.com>
Date: Tue, 11 Nov 2025 09:07:27 -0300
fix #10413 add more information on the spa table
Diffstat:
2 files changed, 146 insertions(+), 31 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/pages/AccountList.tsx b/packages/aml-backoffice-ui/src/pages/AccountList.tsx
@@ -16,7 +16,9 @@
import {
AbsoluteTime,
CustomerAccountSummary,
+ Duration,
HttpStatusCode,
+ Paytos,
TalerError,
TalerProtocolTimestamp,
assertUnreachable,
@@ -245,7 +247,9 @@ export function AccountList({
<div class="mt-8 flow-root">
<div class="overflow-x-auto">
{!records.length ? (
- <div>empty result </div>
+ <div>
+ <i18n.Translate>No results</i18n.Translate>{" "}
+ </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">
@@ -253,43 +257,109 @@ export function AccountList({
<tr>
<th
scope="col"
+ class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900"
+ >
+ <i18n.Translate>#</i18n.Translate>
+ </th>
+ <th
+ scope="col"
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-80"
>
<i18n.Translate>Account Identification</i18n.Translate>
</th>
<th
scope="col"
- class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-40"
+ class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 "
+ >
+ <i18n.Translate>Open time</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>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
- {records.map((r) => {
+ {records.map((r,i) => {
+ const uri = Paytos.asString(r.full_payto);
+ if (i === 1) {
+ r.open_time = AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now())
+ r.close_time = AbsoluteTime.toProtocolTimestamp(AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({minutes:5})))
+ } else if (i === 2) {
+ r.open_time = AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now())
+ }
+ const openTime = r.open_time.t_s !== "never" ? format(r.open_time.t_s*1000, "yyyy/MM/dd HH:mm") : undefined;
+ const closeTime = r.close_time.t_s !== "never" ? format(r.close_time.t_s*1000, "yyyy/MM/dd HH:mm") : undefined;
+ const openDescription = openTime
+ ? closeTime
+ ? <span><i18n.Translate>From {openTime}<br/>To {closeTime}</i18n.Translate></span>
+ : i18n.str`Since ${openTime}`
+ : i18n.str`Not opened`;
+
+ const paramsDesc = Object.entries(uri.params).map(([name,value]) => {
+ return <div>{name}: {value}</div>
+ })
return (
- <tr key={r.h_payto} 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={caseByIdRoute.url({
- cid: r.h_payto,
- })}
- class="text-indigo-600 hover:text-indigo-900 font-mono"
- >
- {r.h_payto}
- </a>
- <p class="text-gray-500 text-xs">{r.full_payto}</p>
- </div>
- </td>
- <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900">
- {r.to_investigate ? (
- <span title="require investigation">
- <ToInvestigateIcon />
+ <Fragment key={r.h_payto}>
+ <tr class="hover:bg-gray-100 ">
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 text-right">
+ {r.rowid}
+ </td>
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 ">
+ <div class="text-gray-900">
+ <a
+ href={caseByIdRoute.url({
+ cid: r.h_payto,
+ })}
+ class="text-indigo-600 hover:text-indigo-900 font-mono"
+ >
+ {uri.displayName}
+ </a>
+ <p class="text-gray-500 text-xs">
+ {paramsDesc}
+ </p>
+ </div>
+ </td>
+ <td class="whitespace-nowrap px-3 text-sm text-gray-900">
+ {openDescription}
+ </td>
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900 flex">
+ {r.to_investigate ? (
+ <span title={i18n.str`Under investigation`}>
+ <ToInvestigateIcon
+ stroke="#be2206ff"
+ stroke-width="2"
+ />
+ </span>
+ ) : undefined}
+ {r.high_risk ? (
+ <span title={i18n.str`High risk`}>
+ <HighRiskIcon
+ stroke="#b2be06ff"
+ stroke-width="2"
+ />
+ </span>
+ ) : undefined}
+ {/* {r.open_time.t_s !== "never" ? ( */}
+ <span title={i18n.str`With custom rules`}>
+ <OpenIcon stroke="#1806beff" stroke-width="2" />
</span>
- ) : undefined}
- </td>
- </tr>
+ {/* ) : undefined} */}
+ </td>
+ </tr>
+ {r.comments ? (
+ <tr>
+ <td
+ class="whitespace-nowrap px-3 py-1 text-sm text-gray-500 "
+ colSpan={3}
+ >
+ {r.comments}
+ </td>
+ </tr>
+ ) : undefined}
+ </Fragment>
);
})}
</tbody>
@@ -303,15 +373,34 @@ export function AccountList({
);
}
-export const ToInvestigateIcon = () => (
+export const ToInvestigateIcon = (
+ props?: h.JSX.SVGAttributes<SVGSVGElement>,
+) => (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6"
+ {...props}
+ >
+ <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 const HighRiskIcon = (props?: h.JSX.SVGAttributes<SVGSVGElement>) => (
<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"
+ {...props}
>
<path
stroke-linecap="round"
@@ -320,8 +409,25 @@ export const ToInvestigateIcon = () => (
/>
</svg>
);
+export const OpenIcon = (props?: h.JSX.SVGAttributes<SVGSVGElement>) => (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke-width="1.5"
+ stroke="currentColor"
+ class="size-6"
+ {...props}
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 0 0-1.883 2.542l.857 6a2.25 2.25 0 0 0 2.227 1.932H19.05a2.25 2.25 0 0 0 2.227-1.932l.857-6a2.25 2.25 0 0 0-1.883-2.542m-16.5 0V6A2.25 2.25 0 0 1 6 3.75h3.879a1.5 1.5 0 0 1 1.06.44l2.122 2.12a1.5 1.5 0 0 0 1.06.44H18A2.25 2.25 0 0 1 20.25 9v.776"
+ />
+ </svg>
+);
-export const TransfersIcon = () => (
+export const TransfersIcon = (props?: h.JSX.SVGAttributes<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -329,6 +435,7 @@ export const TransfersIcon = () => (
stroke-width="1.5"
stroke="currentColor"
class="size-6"
+ {...props}
>
<path
stroke-linecap="round"
@@ -338,7 +445,7 @@ export const TransfersIcon = () => (
</svg>
);
-export const PeopleIcon = () => (
+export const PeopleIcon = (props?: h.JSX.SVGAttributes<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -346,6 +453,7 @@ export const PeopleIcon = () => (
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
+ {...props}
>
<path
stroke-linecap="round"
@@ -355,7 +463,7 @@ export const PeopleIcon = () => (
</svg>
);
-export const HomeIcon = () => (
+export const HomeIcon = (props?: h.JSX.SVGAttributes<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -363,6 +471,7 @@ export const HomeIcon = () => (
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
+ {...props}
>
<path
stroke-linecap="round"
@@ -371,12 +480,13 @@ export const HomeIcon = () => (
/>
</svg>
);
-export const FormIcon = () => (
+export const FormIcon = (props?: h.JSX.SVGAttributes<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-6 h-6"
+ {...props}
>
<path
fillRule="evenodd"
@@ -386,7 +496,7 @@ export const FormIcon = () => (
</svg>
);
-export const SearchIcon = () => (
+export const SearchIcon = (props?: h.JSX.SVGAttributes<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -394,6 +504,7 @@ export const SearchIcon = () => (
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
+ {...props}
>
<path
stroke-linecap="round"
diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
@@ -23,6 +23,7 @@ import {
opFixedSuccess,
opKnownFailure,
opKnownFailureWithBody,
+ succeedOrThrow,
} from "./operation.js";
import {
decodeCrock,
@@ -444,6 +445,9 @@ export namespace Paytos {
//////////////////////
// parsing function
///////////////////////
+ export function asString(p: FullPaytoString): Paytos.URI {
+ return succeedOrThrow(fromString(p))
+ }
export function fromString(
s: string,