mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-12 18:46:40 +07:00
fix: копеечные цены крипты не округлялись в $0
This commit is contained in:
@@ -7,7 +7,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
# title, /api/health (which the frontend reads to show it in Settings), and
|
||||
# embedded in exported backups so an old file can be told apart from a
|
||||
# current one.
|
||||
APP_VERSION = "1.1.4"
|
||||
APP_VERSION = "1.1.5"
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "aurum-frontend",
|
||||
"private": true,
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";
|
||||
import { formatCurrency, maskAmount } from "@/lib/format";
|
||||
import { formatCryptoAmount, maskAmount } from "@/lib/format";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import type { CryptoHolding } from "@/types";
|
||||
|
||||
@@ -65,7 +65,7 @@ function DonutTooltip({
|
||||
<div className="rounded-lg border border-border bg-surface-1 px-3 py-2 text-sm shadow-md">
|
||||
<p className="font-medium text-text-primary">{slice.key === "other" ? otherLabel : slice.name}</p>
|
||||
<p className="text-text-muted">
|
||||
{maskAmount(formatCurrency(slice.amount), hidden)} · {slice.percent.toFixed(1)}%
|
||||
{maskAmount(formatCryptoAmount(slice.amount), hidden)} · {slice.percent.toFixed(1)}%
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -132,7 +132,7 @@ export function CryptoAllocationBody({ holdings, isLoading, hidden }: CryptoAllo
|
||||
<td className="py-1.5 pr-3 text-right font-medium tabular-nums text-text-primary">
|
||||
{slice.percent.toFixed(1)}%
|
||||
</td>
|
||||
<td className="py-1.5 text-right tabular-nums text-text-muted">{maskAmount(formatCurrency(slice.amount), hidden)}</td>
|
||||
<td className="py-1.5 text-right tabular-nums text-text-muted">{maskAmount(formatCryptoAmount(slice.amount), hidden)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Bitcoin, Plus, Trash2 } from "lucide-react";
|
||||
import { formatCurrency, maskAmount } from "@/lib/format";
|
||||
import { formatCryptoAmount, maskAmount } from "@/lib/format";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import type { CryptoHolding, CryptoPortfolio } from "@/types";
|
||||
|
||||
@@ -106,7 +106,7 @@ export function CryptoHoldingsTable({
|
||||
</td>
|
||||
<td className="py-3 pr-3 text-right tabular-nums text-text-primary">
|
||||
{holding.current_price !== null
|
||||
? maskAmount(formatCurrency(holding.current_price), hidden)
|
||||
? maskAmount(formatCryptoAmount(holding.current_price), hidden)
|
||||
: t("crypto.pendingPrice")}
|
||||
</td>
|
||||
<td className="py-3 pr-3 text-right">
|
||||
@@ -120,20 +120,20 @@ export function CryptoHoldingsTable({
|
||||
</td>
|
||||
<td className="py-3 pr-3 text-right">
|
||||
<span className="block tabular-nums text-text-primary">
|
||||
{holding.value !== null ? maskAmount(formatCurrency(holding.value), hidden) : t("crypto.pendingPrice")}
|
||||
{holding.value !== null ? maskAmount(formatCryptoAmount(holding.value), hidden) : t("crypto.pendingPrice")}
|
||||
</span>
|
||||
<span className="block text-xs tabular-nums text-text-muted">
|
||||
{maskAmount(`${Number(holding.quantity)} ${holding.symbol}`, hidden)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-3 pr-3 text-right tabular-nums text-text-primary">
|
||||
{holding.avg_buy_price !== null ? maskAmount(formatCurrency(holding.avg_buy_price), hidden) : "—"}
|
||||
{holding.avg_buy_price !== null ? maskAmount(formatCryptoAmount(holding.avg_buy_price), hidden) : "—"}
|
||||
</td>
|
||||
<td className="py-3 pr-3 text-right">
|
||||
{holding.profit_loss !== null && holding.profit_loss_percent !== null ? (
|
||||
<>
|
||||
<span className="block tabular-nums" style={{ color: profitColor }}>
|
||||
{maskAmount(`${sign}${formatCurrency(holding.profit_loss)}`, hidden)}
|
||||
{maskAmount(`${sign}${formatCryptoAmount(holding.profit_loss)}`, hidden)}
|
||||
</span>
|
||||
<span className="block text-xs tabular-nums" style={{ color: profitColor }}>
|
||||
{sign}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import { Dialog } from "@/components/ui/Dialog";
|
||||
import { useCryptoTransactions, useDeleteCryptoTransaction } from "@/hooks/useCrypto";
|
||||
import { formatCurrency, formatTransactionDate, maskAmount } from "@/lib/format";
|
||||
import { formatCryptoAmount, formatTransactionDate, maskAmount } from "@/lib/format";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import type { CryptoHolding, CryptoTransaction } from "@/types";
|
||||
|
||||
@@ -47,7 +47,7 @@ export function CryptoTransactionHistoryModal({ open, onClose, holding, hidden,
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-sm text-text-primary">
|
||||
{maskAmount(`${Number(tx.quantity)} ${holding.symbol} · ${formatCurrency(tx.price_per_unit)}`, hidden)}
|
||||
{maskAmount(`${Number(tx.quantity)} ${holding.symbol} · ${formatCryptoAmount(tx.price_per_unit)}`, hidden)}
|
||||
</span>
|
||||
<span className="block text-xs text-text-muted">
|
||||
{formatTransactionDate(tx.date, true)}
|
||||
|
||||
@@ -17,6 +17,29 @@ export function formatCurrency(amount: number | string, currency: string = getCu
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
/** Same currency formatting as formatCurrency, but scales decimal precision
|
||||
* down to the value's own magnitude instead of always rounding to whole
|
||||
* units — a low-cap memecoin can genuinely price at $0.000000006894, and
|
||||
* formatCurrency's fixed 0 fraction digits would render that as "$0",
|
||||
* indistinguishable from actually being worthless. Values >= 1 still show
|
||||
* just 2 decimals (a coin price doesn't need more than cents once it's
|
||||
* above a dollar). For the Crypto tab's per-coin price/holdings/avg-buy-price
|
||||
* cells and per-transaction price — anywhere a single coin's own value
|
||||
* needs to be told apart from zero, not just a portfolio-wide total. */
|
||||
export function formatCryptoAmount(amount: number | string, currency: string = getCurrency()): string {
|
||||
const value = typeof amount === "string" ? Number(amount) : amount;
|
||||
const abs = Math.abs(value);
|
||||
const maximumFractionDigits =
|
||||
abs === 0 || abs >= 1
|
||||
? 2
|
||||
: // Leading zeros right after the decimal point before the first
|
||||
// significant digit, plus 4 more digits of real precision beyond
|
||||
// that — e.g. 0.000000006894 has 8 leading zeros, so this shows
|
||||
// 12 decimal places, landing exactly on "6894" and nothing more.
|
||||
Math.min(20, Math.max(0, -Math.floor(Math.log10(abs)) - 1) + 4);
|
||||
return new Intl.NumberFormat(getIntlLocale(), { style: "currency", currency, maximumFractionDigits }).format(value);
|
||||
}
|
||||
|
||||
export function formatSignedCurrency(amount: number | string, currency: string = getCurrency()): string {
|
||||
const value = typeof amount === "string" ? Number(amount) : amount;
|
||||
const sign = value > 0 ? "+" : "";
|
||||
|
||||
Reference in New Issue
Block a user