- 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
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
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>
|
||
)
|
||
}
|