Sergey Krylov 3bad694dce feat(frontend): align dashboard tables with HTML parity
- Add thead with semantic column headers to events and income tables
- Render instruments as main (ticker/ISIN) + subtitle (name) via instrumentDisplay
- Type column uses compact Chip with eventTypeTone/incomeTypeTone; DIV_EXT
  shows distinct label 'Дивиденд (внешний)'
- Amount column applies moneyTone (positive=green, negative=red, planned=neutral)
  via moneyToneToColor; sign prefixes (+/-/~) preserved per spec
- Status column: 'Поступило' (success) vs 'Ожидается' (neutral)
- Income sum semantics preserved (sum of current page rows)
- Skeleton: unified column-width and variant pattern for both tables
- Drop legacy DashboardIncomeRow.instrument alias (now uses instrumentMain/Subtitle)
- Extract moneyToneToColor helper from hero (single source of MUI color mapping)
- Dashboard tests cover thead, badges, subtitles, signed tones, and that
  tables show ₽ instead of RUB for RUB amounts
2026-06-27 13:33:02 +03:00

84 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Metric, Text } from '@moex-vibe/design-system'
import { Box } from '@mui/material'
import type { BrokerAnalytics, BrokerPortfolio } from '@/shared/api'
import { formatBrokerMoney, formatBrokerPercent } from '@/shared/lib/formatters'
import { formatDashboardCurrency, moneyTone, moneyToneToColor } from '../lib/dashboardVisual'
type BrokerDashboardHeroProps = {
portfolio: BrokerPortfolio
analytics: BrokerAnalytics | undefined
}
function percentValue(value: unknown): string {
return typeof value === 'number' ? formatBrokerPercent(value) : '—'
}
export function BrokerDashboardHero({ portfolio, analytics }: BrokerDashboardHeroProps) {
const accountName = portfolio.account.name || 'Брокерский счёт'
const returnPercent = analytics?.totalReturnPercent ?? portfolio.yields.expectedPercent
const returnTone = moneyTone(typeof returnPercent === 'number' ? returnPercent : null)
const dailyTone = moneyTone(portfolio.yields.daily?.value ?? null)
const totalReceivedTone = moneyTone(analytics?.totalReceived ?? null)
const totalReceivedDisplay = analytics
? formatDashboardCurrency({ currency: analytics.currency, value: analytics.totalReceived })
: '—'
return (
<Box
component="section"
aria-label="Ключевые показатели брокерского счёта"
sx={{
border: '1px solid',
borderColor: 'success.light',
borderRadius: 4,
bgcolor: 'rgba(46, 125, 50, 0.06)',
p: { xs: 2, md: 3 },
display: 'grid',
gap: 2,
gridTemplateColumns: { xs: '1fr', md: 'minmax(240px, 1fr) repeat(3, auto)' },
alignItems: 'stretch',
}}
>
<Box>
<Text variant="label" tone="secondary">
Инвестиционный дашборд
</Text>
<Box sx={{ fontWeight: 800, fontSize: { xs: 22, md: 28 }, lineHeight: 1.15 }}>
{accountName}
</Box>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Metric label="Стоимость портфеля" value={formatBrokerMoney(portfolio.totals.portfolio)} />
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Metric
label="Доходность"
value={
<Box component="span" sx={{ color: moneyToneToColor(returnTone), fontWeight: 700 }}>
{percentValue(returnPercent)}
</Box>
}
supportingText={
<Box component="span" sx={{ color: moneyToneToColor(dailyTone) }}>
За день: {formatBrokerMoney(portfolio.yields.daily)}
</Box>
}
/>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Metric
label="Всего доходов"
value={
<Box
component="span"
sx={{ color: moneyToneToColor(totalReceivedTone), fontWeight: 700 }}
>
{totalReceivedDisplay}
</Box>
}
/>
</Box>
</Box>
)
}