taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

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:
Mpackages/aml-backoffice-ui/src/pages/AccountList.tsx | 173+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mpackages/taler-util/src/payto.ts | 4++++
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,