format amount so it is align to fractional digitls
This commit is contained in:
parent
eb8bcc9532
commit
ff49e3477e
@ -444,4 +444,28 @@ export class Amounts {
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of fractional digits needed to fully represent the amount
|
||||
* @param a amount
|
||||
* @returns
|
||||
*/
|
||||
static maxFractionalDigits(a: AmountJson): number {
|
||||
if (a.fraction === 0) return 0;
|
||||
if (a.fraction < 0) {
|
||||
console.error("amount fraction can not be negative", a);
|
||||
return 0;
|
||||
}
|
||||
let i = 0;
|
||||
let check = true;
|
||||
let rest = a.fraction;
|
||||
while (rest > 0 && check) {
|
||||
check = rest % 10 === 0;
|
||||
rest = rest / 10;
|
||||
i++;
|
||||
}
|
||||
return amountFractionalLength - i + 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 { styled } from "@linaria/react";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { Amount } from "./Amount.js";
|
||||
|
||||
export default {
|
||||
title: "components/amount",
|
||||
component: Amount,
|
||||
};
|
||||
|
||||
const Table = styled.table`
|
||||
td {
|
||||
padding: 4px;
|
||||
}
|
||||
td {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
`;
|
||||
|
||||
function ProductTable(
|
||||
prods: string[],
|
||||
AmountRender: (p: { value: string; index: number }) => VNode = Amount,
|
||||
): VNode {
|
||||
return (
|
||||
<Table>
|
||||
<tr>
|
||||
<td>product</td>
|
||||
<td>price</td>
|
||||
</tr>
|
||||
{prods.map((value, i) => {
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td>p{i}</td>
|
||||
<td>
|
||||
<AmountRender value={value} index={i} />
|
||||
{/* <Amount value={value} fracSize={fracSize} /> */}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export const WithoutFixedSizeDefault = (): VNode =>
|
||||
ProductTable(["ARS:19", "ARS:0.1", "ARS:10.02"]);
|
||||
|
||||
export const WithFixedSizeZero = (): VNode =>
|
||||
ProductTable(["ARS:19", "ARS:0.1", "ARS:10.02"], ({ value }) => {
|
||||
return <Amount value={value} maxFracSize={0} />;
|
||||
});
|
||||
|
||||
export const WithFixedSizeFour = (): VNode =>
|
||||
ProductTable(
|
||||
["ARS:19", "ARS:0.1", "ARS:10.02", "ARS:10.0123", "ARS:10.0123123"],
|
||||
({ value }) => {
|
||||
return <Amount value={value} maxFracSize={4} />;
|
||||
},
|
||||
);
|
||||
|
||||
export const WithFixedSizeFourNegative = (): VNode =>
|
||||
ProductTable(
|
||||
["ARS:19", "ARS:0.1", "ARS:10.02", "ARS:10.0123", "ARS:10.0123123"],
|
||||
({ value, index }) => {
|
||||
return (
|
||||
<Amount value={value} maxFracSize={4} negative={index % 2 === 0} />
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const WithFixedSizeFourOverflow = (): VNode =>
|
||||
ProductTable(
|
||||
["ARS:19", "ARS:0.1", "ARS:10123123.02", "ARS:10.0123", "ARS:10.0123123"],
|
||||
({ value, index }) => {
|
||||
return (
|
||||
<Amount value={value} maxFracSize={4} negative={index % 2 === 0} />
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const WithFixedSizeFourAccounting = (): VNode =>
|
||||
ProductTable(
|
||||
["ARS:19", "ARS:0.1", "ARS:10123123.02", "ARS:10.0123", "ARS:10.0123123"],
|
||||
({ value, index }) => {
|
||||
return (
|
||||
<Amount
|
||||
value={value}
|
||||
signType="accounting"
|
||||
maxFracSize={4}
|
||||
negative={index % 2 === 0}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
@ -13,15 +13,95 @@
|
||||
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/>
|
||||
*/
|
||||
import { AmountJson, Amounts, AmountString } from "@gnu-taler/taler-util";
|
||||
import { h, VNode, Fragment } from "preact";
|
||||
import {
|
||||
amountFractionalBase,
|
||||
amountFractionalLength,
|
||||
AmountJson,
|
||||
Amounts,
|
||||
AmountString,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
|
||||
export function Amount({ value }: { value: AmountJson | AmountString }): VNode {
|
||||
export function Amount({
|
||||
value,
|
||||
maxFracSize,
|
||||
negative,
|
||||
hideCurrency,
|
||||
signType = "standard",
|
||||
signDisplay = "auto",
|
||||
}: {
|
||||
negative?: boolean;
|
||||
value: AmountJson | AmountString;
|
||||
maxFracSize?: number;
|
||||
hideCurrency?: boolean;
|
||||
signType?: "accounting" | "standard";
|
||||
signDisplay?: "auto" | "always" | "never" | "exceptZero";
|
||||
}): VNode {
|
||||
const aj = Amounts.jsonifyAmount(value);
|
||||
const amount = Amounts.stringifyValue(aj, 2);
|
||||
const minFractional =
|
||||
maxFracSize !== undefined && maxFracSize < 2 ? maxFracSize : 2;
|
||||
const af = aj.fraction % amountFractionalBase;
|
||||
let s = "";
|
||||
if ((af && maxFracSize) || minFractional > 0) {
|
||||
s += ".";
|
||||
let n = af;
|
||||
for (
|
||||
let i = 0;
|
||||
(maxFracSize === undefined || i < maxFracSize) &&
|
||||
i < amountFractionalLength;
|
||||
i++
|
||||
) {
|
||||
if (!n && i >= minFractional) {
|
||||
break;
|
||||
}
|
||||
s = s + Math.floor((n / amountFractionalBase) * 10).toString();
|
||||
n = (n * 10) % amountFractionalBase;
|
||||
}
|
||||
}
|
||||
const fontSize = 18;
|
||||
const letterSpacing = 0;
|
||||
const mult = 0.7;
|
||||
return (
|
||||
<Fragment>
|
||||
{amount} {aj.currency}
|
||||
</Fragment>
|
||||
<span style={{ textAlign: "right", whiteSpace: "nowrap" }}>
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
fontFamily: "monospace",
|
||||
fontSize,
|
||||
}}
|
||||
>
|
||||
{negative ? (signType === "accounting" ? "(" : "-") : ""}
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
textAlign: "right",
|
||||
fontFamily: "monospace",
|
||||
fontSize,
|
||||
letterSpacing,
|
||||
}}
|
||||
>
|
||||
{aj.value}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: !maxFracSize ? undefined : `${(maxFracSize + 1) * mult}em`,
|
||||
textAlign: "left",
|
||||
fontFamily: "monospace",
|
||||
fontSize,
|
||||
letterSpacing,
|
||||
}}
|
||||
>
|
||||
{s}
|
||||
{negative && signType === "accounting" ? ")" : ""}
|
||||
</span>
|
||||
</span>
|
||||
{hideCurrency ? undefined : (
|
||||
<Fragment>
|
||||
|
||||
<span>{aj.currency}</span>
|
||||
</Fragment>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -77,12 +77,16 @@ export function BankDetailsByPaytoType({
|
||||
<table>
|
||||
<tr>
|
||||
<td>{payto.targetPath}</td>
|
||||
<td>{Amounts.stringifyValue(amount)} BTC</td>
|
||||
<td>
|
||||
<Amount value={amount} hideCurrency /> BTC
|
||||
</td>
|
||||
</tr>
|
||||
{payto.segwitAddrs.map((addr, i) => (
|
||||
<tr key={i}>
|
||||
<td>{addr}</td>
|
||||
<td>{Amounts.stringifyValue(min)} BTC</td>
|
||||
<td>
|
||||
<Amount value={min} hideCurrency /> BTC
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
|
@ -19,7 +19,8 @@
|
||||
* @author Sebastian Javier Marchano (sebasjm)
|
||||
*/
|
||||
|
||||
import * as a1 from "./Banner.stories.js";
|
||||
import * as a1 from "./Banner.stories.js";
|
||||
import * as a2 from "./PendingTransactions.stories.js";
|
||||
import * as a3 from "./Amount.stories.js";
|
||||
|
||||
export default [a1, a2];
|
||||
export default [a1, a2, a3];
|
||||
|
@ -78,7 +78,11 @@ export const WithSomeBankAccounts = createExample(TestedComponent, {
|
||||
value: "10:USD",
|
||||
},
|
||||
cancelHandler: {},
|
||||
depositHandler: {},
|
||||
depositHandler: {
|
||||
onClick: async () => {
|
||||
return;
|
||||
},
|
||||
},
|
||||
totalFee: Amounts.getZero("USD"),
|
||||
totalToDeposit: Amounts.parseOrThrow("USD:10"),
|
||||
// onCalculateFee: alwaysReturnFeeToOne,
|
||||
|
@ -18,6 +18,7 @@ import { AmountJson, Amounts, PaytoUri } from "@gnu-taler/taler-util";
|
||||
import { DepositGroupFees } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { Amount } from "../components/Amount.js";
|
||||
import { Loading } from "../components/Loading.js";
|
||||
import { LoadingError } from "../components/LoadingError.js";
|
||||
import { SelectList } from "../components/SelectList.js";
|
||||
@ -361,7 +362,7 @@ export function View({ state }: ViewProps): VNode {
|
||||
) : (
|
||||
<Button variant="contained" onClick={state.depositHandler.onClick}>
|
||||
<i18n.Translate>
|
||||
Deposit {Amounts.stringifyValue(state.totalToDeposit)}{" "}
|
||||
Deposit {Amounts.stringifyValue(state.totalToDeposit)}{" "}
|
||||
{state.currency}
|
||||
</i18n.Translate>
|
||||
</Button>
|
||||
|
@ -428,6 +428,8 @@ export const DepositTalerBank = createExample(TestedComponent, {
|
||||
export const DepositBitcoin = createExample(TestedComponent, {
|
||||
transaction: {
|
||||
...exampleData.deposit,
|
||||
amountRaw: "BITCOINBTC:0.0000011",
|
||||
amountEffective: "BITCOINBTC:0.00000092",
|
||||
targetPaytoUri:
|
||||
"payto://bitcoin/bcrt1q6ps8qs6v8tkqrnru4xqqqa6rfwcx5ufpdfqht4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
|
||||
},
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import {
|
||||
AbsoluteTime,
|
||||
amountFractionalLength,
|
||||
AmountJson,
|
||||
Amounts,
|
||||
Location,
|
||||
@ -749,7 +750,7 @@ function PurchaseDetails({
|
||||
<tr>
|
||||
<td>Refunded</td>
|
||||
<td>
|
||||
<Amount value={transaction.totalRefundRaw} />
|
||||
<Amount value={transaction.totalRefundRaw} negative />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -828,17 +829,20 @@ function RefundDetails({
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
const fee = Amounts.sub(
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount;
|
||||
const r = Amounts.parseOrThrow(transaction.amountRaw);
|
||||
const e = Amounts.parseOrThrow(transaction.amountEffective);
|
||||
const fee = Amounts.sub(r, e).amount;
|
||||
|
||||
const maxFrac = [r, e, fee]
|
||||
.map((a) => Amounts.maxFractionalDigits(a))
|
||||
.reduce((c, p) => Math.max(c, p), 0);
|
||||
|
||||
return (
|
||||
<PurchaseDetailsTable>
|
||||
<tr>
|
||||
<td>Amount</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountRaw} />
|
||||
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -846,7 +850,7 @@ function RefundDetails({
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>
|
||||
<Amount value={fee} />
|
||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -858,7 +862,7 @@ function RefundDetails({
|
||||
<tr>
|
||||
<td>Total</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountEffective} />
|
||||
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
</PurchaseDetailsTable>
|
||||
@ -871,18 +875,20 @@ function DepositDetails({
|
||||
transaction: TransactionDeposit;
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const r = Amounts.parseOrThrow(transaction.amountRaw);
|
||||
const e = Amounts.parseOrThrow(transaction.amountEffective);
|
||||
const fee = Amounts.sub(r, e).amount;
|
||||
|
||||
const fee = Amounts.sub(
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount;
|
||||
const maxFrac = [r, e, fee]
|
||||
.map((a) => Amounts.maxFractionalDigits(a))
|
||||
.reduce((c, p) => Math.max(c, p), 0);
|
||||
|
||||
return (
|
||||
<PurchaseDetailsTable>
|
||||
<tr>
|
||||
<td>Amount</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountRaw} />
|
||||
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -890,7 +896,7 @@ function DepositDetails({
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>
|
||||
<Amount value={fee} />
|
||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -902,7 +908,7 @@ function DepositDetails({
|
||||
<tr>
|
||||
<td>Total transfer</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountEffective} />
|
||||
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
</PurchaseDetailsTable>
|
||||
@ -915,17 +921,26 @@ function RefreshDetails({
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
const fee = Amounts.sub(
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount;
|
||||
const r = Amounts.parseOrThrow(transaction.amountRaw);
|
||||
const e = Amounts.parseOrThrow(transaction.amountEffective);
|
||||
const fee = Amounts.sub(r, e).amount;
|
||||
|
||||
const maxFrac = [r, e, fee]
|
||||
.map((a) => Amounts.maxFractionalDigits(a))
|
||||
.reduce((c, p) => Math.max(c, p), 0);
|
||||
|
||||
return (
|
||||
<PurchaseDetailsTable>
|
||||
<tr>
|
||||
<td>Amount</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountRaw} />
|
||||
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>
|
||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -934,9 +949,9 @@ function RefreshDetails({
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>Total</td>
|
||||
<td>
|
||||
<Amount value={fee} />
|
||||
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
</PurchaseDetailsTable>
|
||||
@ -946,17 +961,20 @@ function RefreshDetails({
|
||||
function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
const fee = Amounts.sub(
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount;
|
||||
const r = Amounts.parseOrThrow(transaction.amountRaw);
|
||||
const e = Amounts.parseOrThrow(transaction.amountEffective);
|
||||
const fee = Amounts.sub(r, e).amount;
|
||||
|
||||
const maxFrac = [r, e, fee]
|
||||
.map((a) => Amounts.maxFractionalDigits(a))
|
||||
.reduce((c, p) => Math.max(c, p), 0);
|
||||
|
||||
return (
|
||||
<PurchaseDetailsTable>
|
||||
<tr>
|
||||
<td>Amount</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountRaw} />
|
||||
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -964,7 +982,7 @@ function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>
|
||||
<Amount value={fee} />
|
||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -976,7 +994,7 @@ function TipDetails({ transaction }: { transaction: TransactionTip }): VNode {
|
||||
<tr>
|
||||
<td>Total</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountEffective} />
|
||||
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
</PurchaseDetailsTable>
|
||||
@ -990,17 +1008,20 @@ function WithdrawDetails({
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
|
||||
const fee = Amounts.sub(
|
||||
Amounts.parseOrThrow(transaction.amountRaw),
|
||||
Amounts.parseOrThrow(transaction.amountEffective),
|
||||
).amount;
|
||||
const r = Amounts.parseOrThrow(transaction.amountRaw);
|
||||
const e = Amounts.parseOrThrow(transaction.amountEffective);
|
||||
const fee = Amounts.sub(r, e).amount;
|
||||
|
||||
const maxFrac = [r, e, fee]
|
||||
.map((a) => Amounts.maxFractionalDigits(a))
|
||||
.reduce((c, p) => Math.max(c, p), 0);
|
||||
|
||||
return (
|
||||
<PurchaseDetailsTable>
|
||||
<tr>
|
||||
<td>Withdraw</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountRaw} />
|
||||
<Amount value={transaction.amountRaw} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -1008,7 +1029,7 @@ function WithdrawDetails({
|
||||
<tr>
|
||||
<td>Transaction fees</td>
|
||||
<td>
|
||||
<Amount value={fee} />
|
||||
<Amount value={fee} negative maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -1020,7 +1041,7 @@ function WithdrawDetails({
|
||||
<tr>
|
||||
<td>Total</td>
|
||||
<td>
|
||||
<Amount value={transaction.amountEffective} />
|
||||
<Amount value={transaction.amountEffective} maxFracSize={maxFrac} />
|
||||
</td>
|
||||
</tr>
|
||||
</PurchaseDetailsTable>
|
||||
@ -1059,9 +1080,8 @@ function Header({
|
||||
<SubTitle>
|
||||
<Part
|
||||
title={type}
|
||||
text={<Amount value={total} />}
|
||||
text={<Amount value={total} negative={kind === "negative"} />}
|
||||
kind={kind}
|
||||
showSign
|
||||
/>
|
||||
</SubTitle>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user