moex-vibe/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx
Sergey Krylov c429d6a43a feat(frontend): redesign broker account overview as investment dashboard
Replace vertical overview with dashboard composition:
- BrokerDashboardHero with portfolio KPI, return, daily change, total income
- BrokerDashboardEventsCard with compact events table and local pagination
- BrokerDashboardIncomeCard with income operations table and cursor pagination
- BrokerDashboardAnalyticsCard with analytics Metric grid
- BrokerDashboardAllocationCard wrapping existing donut chart
- BrokerDashboardSkeleton matching dashboard shape
- BrokerDashboardCard local card pattern
- Pure lib helpers: dashboardIncome, dashboardFilters, dashboardFormatters
- Unit tests for income helpers (6) and dashboard composition (1)
2026-06-26 10:18:57 +03:00

118 lines
4.2 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 { Button, Chip, Text } from '@moex-vibe/design-system'
import { Box } from '@mui/material'
import { Link } from '@tanstack/react-router'
import type { BrokerOperationsPage } from '@/shared/api'
import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters'
import { getDashboardIncomeRows, sumDashboardIncome } from '../lib/dashboardIncome'
import { BrokerDashboardCard } from './BrokerDashboardCard'
type BrokerDashboardIncomeCardProps = {
accountId: string
page: BrokerOperationsPage | undefined
isLoading: boolean
isError: boolean
pageNumber: number
canGoBack: boolean
canGoForward: boolean
onPreviousPage: () => void
onNextPage: () => void
}
export function BrokerDashboardIncomeCard({
accountId,
page,
isLoading,
isError,
pageNumber,
canGoBack,
canGoForward,
onPreviousPage,
onNextPage,
}: BrokerDashboardIncomeCardProps) {
const rows = getDashboardIncomeRows(page?.items ?? []).slice(0, 10)
const total = sumDashboardIncome(rows)
return (
<BrokerDashboardCard
title="Доходы"
action={<Link to={`/broker/${encodeURIComponent(accountId)}/operations`}>Все операции</Link>}
>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 1.5 }}>
<Chip label="Дивиденды" tone="success" />
<Chip label="Купоны" tone="info" />
</Box>
{isError ? (
<Text tone="negative">Не удалось загрузить доходные операции</Text>
) : isLoading ? (
<Text tone="muted">Загрузка доходов</Text>
) : rows.length === 0 ? (
<Text tone="muted">Дивидендов и купонов в последних операциях нет</Text>
) : (
<Box sx={{ display: 'grid', gap: 1 }}>
<Box sx={{ overflowX: 'auto' }}>
<Box component="table" sx={{ width: '100%', borderCollapse: 'collapse', fontSize: 14 }}>
<Box component="tbody">
{rows.map((row) => (
<Box component="tr" key={row.id}>
<Box
component="td"
sx={{ py: 1, borderBottom: '1px solid', borderColor: 'divider' }}
>
{formatBrokerDate(row.date)}
</Box>
<Box
component="td"
sx={{
py: 1,
borderBottom: '1px solid',
borderColor: 'divider',
fontWeight: 700,
}}
>
{row.instrument}
</Box>
<Box
component="td"
sx={{ py: 1, borderBottom: '1px solid', borderColor: 'divider' }}
>
{row.typeLabel}
</Box>
<Box
component="td"
sx={{
py: 1,
borderBottom: '1px solid',
borderColor: 'divider',
textAlign: 'right',
fontWeight: 700,
color: 'success.main',
}}
>
+{formatBrokerCurrencyValue(row.amount.currency, row.amount.value)}
</Box>
</Box>
))}
</Box>
</Box>
</Box>
<Text variant="caption" tone="secondary">
Показано: {rows.length} · Итого:{' '}
{total ? formatBrokerCurrencyValue(total.currency, total.value) : '—'}
</Text>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1, alignItems: 'center' }}>
<Button variant="secondary" size="small" onClick={onPreviousPage} disabled={!canGoBack}>
</Button>
<Text variant="body" tone="secondary">
{pageNumber}
</Text>
<Button variant="secondary" size="small" onClick={onNextPage} disabled={!canGoForward}>
</Button>
</Box>
</Box>
)}
</BrokerDashboardCard>
)
}