fix: separate portfolio cash presentation

This commit is contained in:
2026-09-09 16:44:57 +03:00
parent bcc034f198
commit 3017fc810e
8 changed files with 181 additions and 62 deletions
@@ -12,10 +12,9 @@ import type {
AccountPageRecentOperations,
} from "../AccountPage.vm";
import { AccountHeading } from "./AccountHeading.view";
import { CashBalances } from "./CashBalances.view";
import { CurrencyCards } from "./CurrencyCards.view";
import { IncomeSummary } from "./IncomeSummary.view";
import { PortfolioRefreshState } from "./PortfolioRefreshState.view";
import { PortfolioTotals } from "./PortfolioTotals.view";
import { PositionSection } from "./PositionSection.view";
import { RecentEvents } from "./RecentEvents.view";
import { RecentOperations } from "./RecentOperations.view";
@@ -50,7 +49,7 @@ const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
date: formatSnapshotTime(props.portfolio.lastSuccessfulAt),
})}
</Text>
<PortfolioTotals totals={props.portfolio.totalsByCurrency} />
<CurrencyCards amounts={props.portfolio.totalsByCurrency} />
<Accordion
multiple
defaultValue={[
@@ -80,7 +79,7 @@ const AccountPageLoaded: FC<AccountPageLoadedProps> = (props) => {
<Accordion.Item value="cash">
<Accordion.Control>{t("portfolio.cashBalances")}</Accordion.Control>
<Accordion.Panel>
<CashBalances balances={props.portfolio.cashBalances} />
<CurrencyCards amounts={props.portfolio.cashBalances} />
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="recentOperations">
@@ -1,26 +0,0 @@
import { Paper, Text } from "@mantine/core";
import type { FC } from "react";
import type { Portfolio } from "@/entities/portfolio";
import { formatMoney } from "@/shared/lib";
import classes from "../AccountPage.module.css";
type CashBalancesProps = {
balances: Portfolio["cashBalances"];
};
const CashBalances: FC<CashBalancesProps> = (props) => {
const { balances } = props;
return (
<>
{balances.map((balance) => (
<Paper className={classes.cash} key={balance.currency} withBorder>
<Text>{balance.currency}</Text>
<Text fw={700}>{formatMoney(balance)}</Text>
</Paper>
))}
</>
);
};
export { CashBalances };
@@ -1,9 +1,9 @@
import { MantineProvider } from "@mantine/core";
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { PortfolioTotals } from "./PortfolioTotals.view";
import { CurrencyCards } from "./CurrencyCards.view";
describe("PortfolioTotals currency presentation", () => {
describe("CurrencyCards currency presentation", () => {
it.each([0, 1, 3, 5])(
"renders only the %i supplied totals in upstream order",
(count) => {
@@ -16,7 +16,7 @@ describe("PortfolioTotals currency presentation", () => {
].slice(0, count);
const { container } = render(
<MantineProvider>
<PortfolioTotals totals={totals} />
<CurrencyCards amounts={totals} />
</MantineProvider>,
);
expect(
@@ -52,7 +52,7 @@ describe("PortfolioTotals currency presentation", () => {
(currency, value, expected, name, symbol) => {
render(
<MantineProvider>
<PortfolioTotals totals={[{ currency, value }]} />
<CurrencyCards amounts={[{ currency, value }]} />
</MantineProvider>,
);
const term = screen.getByRole("term");
@@ -70,8 +70,8 @@ describe("PortfolioTotals currency presentation", () => {
it("keeps USD and CAD as distinct supplied values", () => {
render(
<MantineProvider>
<PortfolioTotals
totals={[
<CurrencyCards
amounts={[
{ currency: "USD", value: "1.00" },
{ currency: "CAD", value: "2.00" },
]}
@@ -5,12 +5,12 @@ import type { Portfolio } from "@/entities/portfolio";
import { formatDecimal } from "@/shared/lib";
import classes from "../AccountPage.module.css";
type PortfolioTotalsProps = {
totals: Portfolio["totalsByCurrency"];
type CurrencyCardsProps = {
amounts: Portfolio["totalsByCurrency"];
};
const PortfolioTotals: FC<PortfolioTotalsProps> = (props) => {
const { totals } = props;
const CurrencyCards: FC<CurrencyCardsProps> = (props) => {
const { amounts } = props;
const { t } = useTranslation();
const currencies: Record<string, { symbol: string; name: string }> = {
RUB: { symbol: "₽", name: t("portfolio.currencies.RUB") },
@@ -20,22 +20,22 @@ const PortfolioTotals: FC<PortfolioTotalsProps> = (props) => {
return (
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} spacing={10}>
{totals.map((total) => {
const amount = formatDecimal(total.value);
{amounts.map((money) => {
const amount = formatDecimal(money.value);
return (
<Paper
component="dl"
m={0}
className={classes.total}
key={total.currency}
key={money.currency}
withBorder
>
<Text component="dt" className={classes.totalBadge}>
<span aria-hidden="true">
{currencies[total.currency]?.symbol ?? total.currency}
{currencies[money.currency]?.symbol ?? money.currency}
</span>
<VisuallyHidden>
{currencies[total.currency]?.name ?? total.currency}
{currencies[money.currency]?.name ?? money.currency}
</VisuallyHidden>
</Text>
<Text component="dd" m={0} className={classes.totalAmount}>
@@ -49,7 +49,7 @@ const PortfolioTotals: FC<PortfolioTotalsProps> = (props) => {
) : (
amount
)}{" "}
{currencies[total.currency]?.symbol ?? total.currency}
{currencies[money.currency]?.symbol ?? money.currency}
</Text>
</Paper>
);
@@ -58,4 +58,4 @@ const PortfolioTotals: FC<PortfolioTotalsProps> = (props) => {
);
};
export { PortfolioTotals };
export { CurrencyCards };
@@ -1,8 +1,8 @@
import { Stack } from "@mantine/core";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { fn } from "storybook/test";
import { CurrencyCards } from "./CurrencyCards.view";
import { PortfolioRefreshState } from "./PortfolioRefreshState.view";
import { PortfolioTotals } from "./PortfolioTotals.view";
const meta = {
title: "Product/PortfolioRefreshState",
@@ -18,14 +18,14 @@ const meta = {
docs: {
description: {
component:
"Обновление уже показанных данных портфеля. Реальные PortfolioTotals остаются видимыми вместе с PortfolioRefreshState при refetch и его ошибке. Суммы синтетические; это presentation Client Cached Snapshot, без QueryClient и запроса API. Retry регистрирует локальное действие в Actions и не доказывает восстановление API.",
"Обновление уже показанных данных портфеля. Реальные CurrencyCards остаются видимыми вместе с PortfolioRefreshState при refetch и его ошибке. Суммы синтетические; это presentation Client Cached Snapshot, без QueryClient и запроса API. Retry регистрирует локальное действие в Actions и не доказывает восстановление API.",
},
},
},
render: (args) => (
<Stack>
<PortfolioTotals
totals={[
<CurrencyCards
amounts={[
{ currency: "RUB", value: "125000.50" },
{ currency: "USD", value: "320" },
]}