fix: polish accounts portfolio summary (#159)
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 10s
Frontend verify / verify (push) Failing after 7m26s

This commit was merged in pull request #160.
This commit is contained in:
2026-11-06 15:17:27 +03:00
parent 2fd7d59e65
commit 134c951178
13 changed files with 146 additions and 101 deletions
@@ -15,6 +15,11 @@ import classes from "./BrokerageAccountList.module.css";
const DECIMAL_PART_COUNT = 2;
const STRUCTURE_CATEGORY_ORDER = ["STOCK", "BOND", "OTHER", "CASH"] as const;
const percentFormatter = new Intl.NumberFormat("ru-RU", {
maximumFractionDigits: 1,
minimumFractionDigits: 1,
});
type AvailableSummary = Extract<
BrokerageAccountCard["summary"],
{ state: "available" }
@@ -37,20 +42,15 @@ type StructureCurrency = PortfolioSummary["structure"]["currencies"][number];
function formatPercent(value: null | string) {
if (value === null) return "—";
const formatted = `${value.replace(".", ",")}\u{A0}%`;
if (isZero(value))
return value.startsWith("-") ? formatted.slice(1) : formatted;
const rounded = percentFormatter.format(Math.abs(Number(value)));
let sign = "";
if (rounded !== "0,0") sign = value.startsWith("-") ? "−" : "+";
return value.startsWith("-") ? `−${formatted.slice(1)}` : `+${formatted}`;
return `${sign}${rounded}\u{A0}%`;
}
function formatStructurePercent(value: string) {
let trimmed = value;
while (trimmed.includes(".") && trimmed.endsWith("0"))
trimmed = trimmed.slice(0, -1);
if (trimmed.endsWith(".")) trimmed = trimmed.slice(0, -1);
return trimmed.replace(".", ",");
return percentFormatter.format(Number(value));
}
function isZero(value: string) {
@@ -65,22 +65,18 @@ const ReturnValue: FC<ReturnValueProps> = (props) => {
const { t } = useTranslation();
const { item } = props;
const isNegative = item.percent !== null
&& item.percent.startsWith("-")
&& !isZero(item.percent);
const isPositive = item.percent !== null
&& !item.percent.startsWith("-")
&& !isZero(item.percent);
const displayedPercent = formatPercent(item.percent);
const isNegative = displayedPercent.startsWith("−");
const isPositive = displayedPercent.startsWith("+");
let toneClass = classes.neutral;
if (isNegative) toneClass = classes.negative;
else if (isPositive) toneClass = classes.positive;
return (
<Group className={classes.return} gap="xs" wrap="nowrap">
<Group className={classes.return} gap="xs" wrap="wrap">
<Text className={toneClass} fw={700} size="sm">
{formatPercent(item.percent)}
{displayedPercent}
</Text>
<Text className={toneClass} size="sm">
@@ -200,35 +196,35 @@ const BrokerageAccountSummary: FC<BrokerageAccountSummaryProps> = (props) => {
return (
<Stack className={classes.balance} gap="xs">
<Group align="flex-start" gap="lg" justify="flex-end" wrap="nowrap">
<Stack className={classes.summary} gap={4}>
{summary.totals.map(total => (
<Text className={classes.total} fw={700} key={total.currency}>
{formatMoney(total, { withRoundedCents: true })}
</Text>
))}
<Stack className={classes.summary} gap="sm">
{summary.totals.map((total) => {
const item = summary.portfolio.unrealizedReturnByCurrency.find(
returnItem => returnItem.currency === total.currency,
);
{!!summary.remainingCurrencyCount && (
<Text c="dimmed" size="sm">
{t("accounts.remainingCurrencies", {
count: summary.remainingCurrencyCount,
})}
</Text>
)}
</Stack>
return (
<Stack
className={classes.currencySummary}
gap={2}
key={total.currency}
>
<Text className={classes.total} fw={700}>
{formatMoney(total, { withRoundedCents: true })}
</Text>
<Stack gap={4}>
{summary.totals.map((total) => {
const item = summary.portfolio.unrealizedReturnByCurrency.find(
returnItem => returnItem.currency === total.currency,
);
{item && <ReturnValue item={item} />}
</Stack>
);
})}
return item
? <ReturnValue item={item} key={total.currency} />
: null;
})}
</Stack>
</Group>
{!!summary.remainingCurrencyCount && (
<Text c="dimmed" size="sm">
{t("accounts.remainingCurrencies", {
count: summary.remainingCurrencyCount,
})}
</Text>
)}
</Stack>
{summary.portfolio.cashBalances.length > 0 && (
<Group className={classes.cash} gap="xs" justify="flex-end">