codex/broker-accounts-page #34

Merged
ksv741 merged 19 commits from codex/broker-accounts-page into main 2026-06-21 20:57:47 +03:00
3 changed files with 91 additions and 39 deletions
Showing only changes of commit ef97fd7136 - Show all commits

View File

@ -1,4 +1,6 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Box } from '@mui/material';
import { Text } from '@moex-vibe/design-system';
import { useBrokerOperations } from '@/entities/broker-operation'; import { useBrokerOperations } from '@/entities/broker-operation';
import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; import { useBrokerAccountContext } from '@/widgets/broker-account-layout';
import { BrokerAllocationChart } from '@/widgets/broker-allocation-chart'; import { BrokerAllocationChart } from '@/widgets/broker-allocation-chart';
@ -11,16 +13,22 @@ export function BrokerAccountOverviewPage() {
if (portfolio.isLoading) return <BrokerOverviewSkeleton />; if (portfolio.isLoading) return <BrokerOverviewSkeleton />;
if (portfolio.error || !portfolio.data) { if (portfolio.error || !portfolio.data) {
return <p role="alert">Не удалось загрузить сводку счёта</p>; return (
<Text component="p" role="alert" tone="negative">
Не удалось загрузить сводку счёта
</Text>
);
} }
return ( return (
<div className="broker-overview"> <Box component="div" sx={{ display: 'grid', gap: 3 }}>
<BrokerSummary portfolio={portfolio.data} /> <BrokerSummary portfolio={portfolio.data} />
<BrokerAllocationChart portfolio={portfolio.data} /> <BrokerAllocationChart portfolio={portfolio.data} />
<BrokerAssetCards accountId={accountId} portfolio={portfolio.data} /> <BrokerAssetCards accountId={accountId} portfolio={portfolio.data} />
{operations.error ? ( {operations.error ? (
<p role="alert">Не удалось загрузить последние операции</p> <Text component="p" role="alert" tone="negative">
Не удалось загрузить последние операции
</Text>
) : ( ) : (
<BrokerOperationsTable <BrokerOperationsTable
title="Последние операции" title="Последние операции"
@ -33,6 +41,6 @@ export function BrokerAccountOverviewPage() {
page={operations.data} page={operations.data}
/> />
)} )}
</div> </Box>
); );
} }

View File

@ -1,4 +1,6 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Box } from '@mui/material';
import { Text } from '@moex-vibe/design-system';
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'; import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses';
import { formatBrokerMoney, pluralize } from '@/shared/lib/formatters'; import { formatBrokerMoney, pluralize } from '@/shared/lib/formatters';
@ -11,6 +13,16 @@ function formatAllocationPercent(value: number | null) {
return value === null ? '\u2014' : `${value.toFixed(1)}%`; return value === null ? '\u2014' : `${value.toFixed(1)}%`;
} }
const cardSx = {
p: 2,
border: '1px solid',
borderColor: 'divider',
borderRadius: 2,
bgcolor: 'surface.default',
display: 'grid',
gap: 1,
};
export function BrokerAssetCards({ export function BrokerAssetCards({
accountId, accountId,
portfolio, portfolio,
@ -37,23 +49,23 @@ export function BrokerAssetCards({
]; ];
return ( return (
<section className="broker-overview__assets" aria-label="Основные классы активов"> <Box component="section" sx={{ display: 'grid', gap: 2 }} aria-label="Основные классы активов">
{cards.map((card) => ( {cards.map((card) => (
<Link <Link key={card.label} to={card.path}>
className="broker-overview__card broker-overview__asset-link" <Box sx={{ ...cardSx, color: 'inherit', textDecoration: 'none' }}>
key={card.label} <Box component="span" sx={{ color: 'primary.main', fontSize: 18, fontWeight: 700 }}>
to={card.path} {card.label}
> </Box>
<strong className="broker-overview__asset-title">{card.label}</strong> <Text>
<span>
{card.count} {card.countLabel} {card.count} {card.countLabel}
</span> </Text>
<span>{formatBrokerMoney(card.value)}</span> <Text>{formatBrokerMoney(card.value)}</Text>
<span> <Text>
{formatAllocationPercent(allocationPercent(card.value, portfolio.totals.portfolio))} {formatAllocationPercent(allocationPercent(card.value, portfolio.totals.portfolio))}
</span> </Text>
</Box>
</Link> </Link>
))} ))}
</section> </Box>
); );
} }

View File

@ -1,3 +1,5 @@
import { Box } from '@mui/material';
import { Text } from '@moex-vibe/design-system';
import type { BrokerPortfolio } from '@/shared/api/responses'; import type { BrokerPortfolio } from '@/shared/api/responses';
import { import {
formatBrokerMoney as formatMoney, formatBrokerMoney as formatMoney,
@ -6,31 +8,61 @@ import {
export function BrokerSummary({ portfolio }: { portfolio: BrokerPortfolio }) { export function BrokerSummary({ portfolio }: { portfolio: BrokerPortfolio }) {
return ( return (
<section className="broker-overview__summary" aria-label="Сводка счёта"> <Box component="section" sx={{ display: 'grid', gap: 2 }} aria-label="Сводка счёта">
<div className="broker-overview__card"> <Box
<span className="broker-overview__label">Стоимость портфеля</span> sx={{
<strong className="broker-overview__total"> p: 2,
border: '1px solid',
borderColor: 'divider',
borderRadius: 2,
bgcolor: 'surface.default',
display: 'grid',
gap: 1,
}}
>
<Text variant="label" tone="secondary">
Стоимость портфеля
</Text>
<Box component="span" sx={{ fontWeight: 700, fontSize: 24 }}>
{formatMoney(portfolio.totals.portfolio)} {formatMoney(portfolio.totals.portfolio)}
</strong> </Box>
<span>За день: {formatMoney(portfolio.yields.daily)}</span> <Text>За день: {formatMoney(portfolio.yields.daily)}</Text>
<span>Дневная доходность: {formatPercent(portfolio.yields.dailyPercent)}</span> <Text>Дневная доходность: {formatPercent(portfolio.yields.dailyPercent)}</Text>
<span>Ожидаемая доходность: {formatPercent(portfolio.yields.expectedPercent)}</span> <Text>Ожидаемая доходность: {formatPercent(portfolio.yields.expectedPercent)}</Text>
</div> </Box>
<div className="broker-overview__card"> <Box
<span className="broker-overview__label">Денежный остаток</span> sx={{
p: 2,
border: '1px solid',
borderColor: 'divider',
borderRadius: 2,
bgcolor: 'surface.default',
display: 'grid',
gap: 1,
}}
>
<Text variant="label" tone="secondary">
Денежный остаток
</Text>
{portfolio.cash.length === 0 ? ( {portfolio.cash.length === 0 ? (
<span>Нет денежных остатков</span> <Text>Нет денежных остатков</Text>
) : ( ) : (
<ul className="broker-overview__cash"> <Box component="ul" sx={{ listStyle: 'none', display: 'grid', gap: 1 }}>
{portfolio.cash.map((money, index) => ( {portfolio.cash.map((money, index) => (
<li key={`${money.currency}-${index}`}> <Box
<span>{money.currency}</span> component="li"
<strong>{formatMoney(money)}</strong> key={`${money.currency}-${index}`}
</li> sx={{ display: 'flex', justifyContent: 'space-between', gap: 1.5 }}
>
<Text>{money.currency}</Text>
<Box component="span" sx={{ fontWeight: 700 }}>
{formatMoney(money)}
</Box>
</Box>
))} ))}
</ul> </Box>
)} )}
</div> </Box>
</section> </Box>
); );
} }