diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFormatters.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFormatters.ts
index dbf7f2e..bc05bfb 100644
--- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFormatters.ts
+++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFormatters.ts
@@ -19,6 +19,5 @@ export function eventTypeLabel(type: BrokerEventItem['type']): string {
export function eventStatusLabel(event: BrokerEventItem): string {
if (event.source === 'actual') return 'Поступило'
- if (event.type === 'offer') return 'Оферта'
- return 'Прогноз'
+ return 'Ожидается'
}
diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.test.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.test.ts
index 53e3a24..95f529c 100644
--- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.test.ts
+++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.test.ts
@@ -64,12 +64,11 @@ describe('dashboardIncome', () => {
expect(rows[0].amount.value).toBe(12)
})
- it('splits instrument into main and subtitle, keeping the legacy alias', () => {
+ it('splits instrument into main and subtitle', () => {
const rows = getDashboardIncomeRows([operation('OPERATION_TYPE_DIVIDEND', 10)])
expect(rows[0].instrumentMain).toBe('AAPL')
expect(rows[0].instrumentSubtitle).toBe('Apple Inc.')
- expect(rows[0].instrument).toBe('AAPL')
})
it('falls back to "—" with no subtitle when ticker, name, and description are missing', () => {
@@ -78,7 +77,6 @@ describe('dashboardIncome', () => {
expect(rows[0].instrumentMain).toBe('—')
expect(rows[0].instrumentSubtitle).toBeNull()
- expect(rows[0].instrument).toBe('—')
})
it('does not duplicate subtitle when name equals ticker', () => {
diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.ts
index 8d58e10..c377546 100644
--- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.ts
+++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.ts
@@ -14,7 +14,6 @@ export type DashboardIncomeRow = {
date: string | null
instrumentMain: string
instrumentSubtitle: string | null
- instrument: string
typeLabel: DashboardIncomeTypeLabel
amount: BrokerMoney
}
@@ -41,7 +40,6 @@ export function getDashboardIncomeRows(operations: BrokerOperation[]): Dashboard
date: typeof operation.date === 'string' ? operation.date : null,
instrumentMain: main,
instrumentSubtitle: subtitle,
- instrument: main,
typeLabel: typeLabel(operation.type),
amount: operation.payment!,
}
diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts
index aa1c4a3..4082f8d 100644
--- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts
+++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts
@@ -6,6 +6,7 @@ import {
incomeTypeTone,
instrumentDisplay,
moneyTone,
+ moneyToneToColor,
} from './dashboardVisual'
function money(currency: string, value: number): BrokerMoney {
@@ -85,6 +86,15 @@ describe('eventTypeTone', () => {
})
})
+describe('moneyToneToColor', () => {
+ it('maps each MoneyTone to a stable MUI color', () => {
+ expect(moneyToneToColor('positive')).toBe('success.main')
+ expect(moneyToneToColor('negative')).toBe('error.main')
+ expect(moneyToneToColor('planned')).toBe('text.disabled')
+ expect(moneyToneToColor('neutral')).toBe('text.disabled')
+ })
+})
+
describe('incomeTypeTone', () => {
it('maps each income label to a stable tone', () => {
expect(incomeTypeTone('Дивиденд')).toBe('success')
diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts
index 59dcdf3..325cce5 100644
--- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts
+++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts
@@ -17,6 +17,18 @@ export function moneyTone(value: number | null | undefined, source?: MoneySource
return source === 'forecast' ? 'planned' : 'positive'
}
+export function moneyToneToColor(tone: MoneyTone): string {
+ switch (tone) {
+ case 'positive':
+ return 'success.main'
+ case 'negative':
+ return 'error.main'
+ case 'planned':
+ case 'neutral':
+ return 'text.disabled'
+ }
+}
+
export function formatDashboardCurrency(money: DashboardMoneyLike | null | undefined): string {
if (!money) return '—'
try {
diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx
index 944d1ed..b60b003 100644
--- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx
+++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx
@@ -1,10 +1,10 @@
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'
-import { render, screen, waitFor } from '@testing-library/react'
+import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import type { ReactNode } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
-import type { BrokerPortfolio } from '@/shared/api'
+import type { BrokerEventItem, BrokerOperation, BrokerPortfolio } from '@/shared/api'
import { BrokerDashboard } from './BrokerDashboard'
const hookMocks = vi.hoisted(() => ({
@@ -74,6 +74,78 @@ function renderWithProviders(ui: ReactNode) {
return render({ui})
}
+function buildEvent(overrides: Partial = {}): BrokerEventItem {
+ return {
+ id: 'evt-1',
+ type: 'coupon',
+ source: 'actual',
+ category: 'cashflow',
+ eventDate: '2026-06-15T00:00:00.000Z',
+ paymentDate: null,
+ ticker: 'RU000A10AEF9',
+ name: 'РЖД 001Р-37R',
+ instrumentUid: null,
+ instrumentType: 'bond',
+ quantitySnapshot: null,
+ payoutPerUnit: null,
+ estimatedAmount: null,
+ actualAmount: 43.56,
+ currency: 'RUB',
+ estimateMode: null,
+ ...overrides,
+ }
+}
+
+function buildOperation(overrides: Partial = {}): BrokerOperation {
+ return {
+ cursor: null,
+ accountId: 'acc-1',
+ id: 'op-1',
+ parentOperationId: null,
+ date: '2026-06-15T00:00:00.000Z',
+ type: 'OPERATION_TYPE_COUPON',
+ category: 'income',
+ description: null,
+ name: 'ОФЗ 26241',
+ state: 'OPERATION_STATE_EXECUTED',
+ instrumentUid: null,
+ figi: null,
+ ticker: 'SU26249RMFS1',
+ classCode: null,
+ instrumentType: 'bond',
+ payment: { currency: 'RUB', units: '109', nano: 700000000, value: 109.7 },
+ price: null,
+ commission: null,
+ yield: null,
+ accruedInt: null,
+ quantity: null,
+ quantityDone: null,
+ ...overrides,
+ }
+}
+
+function mockEventsLoaded(items: BrokerEventItem[]) {
+ hookMocks.useBrokerEvents.mockReturnValue({
+ data: { items, summary: {}, asOf: '2026-06-26T00:00:00.000Z' },
+ isLoading: false,
+ isError: false,
+ })
+}
+
+function mockOperationsLoaded(items: BrokerOperation[]) {
+ hookMocks.useBrokerOperations.mockReturnValue({
+ data: {
+ accountId: 'acc-1',
+ items,
+ nextCursor: null,
+ hasNext: false,
+ asOf: '2026-06-26T00:00:00.000Z',
+ },
+ isLoading: false,
+ isError: false,
+ })
+}
+
describe('BrokerDashboard', () => {
beforeEach(() => {
hookMocks.useBrokerEvents.mockReturnValue({
@@ -211,4 +283,156 @@ describe('BrokerDashboard', () => {
expect(screen.queryByTestId('dashboard-table-skeleton')).not.toBeInTheDocument()
})
+
+ it('renders events table thead with semantic column headers', () => {
+ mockEventsLoaded([buildEvent()])
+
+ renderWithProviders()
+
+ const eventsTable = screen.getByLabelText('Таблица событий брокерского счёта')
+ expect(eventsTable.querySelector('thead')).not.toBeNull()
+ const headers = within(eventsTable).getAllByRole('columnheader')
+ expect(headers.map((h) => h.textContent)).toEqual([
+ 'Дата',
+ 'Инструмент',
+ 'Тип',
+ 'Сумма',
+ 'Статус',
+ ])
+ })
+
+ it('renders income table thead without status column', () => {
+ mockOperationsLoaded([buildOperation()])
+
+ renderWithProviders()
+
+ const incomeTable = screen.getByLabelText('Таблица доходов брокерского счёта')
+ expect(incomeTable.querySelector('thead')).not.toBeNull()
+ const headers = within(incomeTable).getAllByRole('columnheader')
+ expect(headers.map((h) => h.textContent)).toEqual(['Дата', 'Инструмент', 'Тип', 'Сумма'])
+ })
+
+ it('renders event type badge, instrument subtitle and status pill for loaded events', () => {
+ mockEventsLoaded([
+ buildEvent({ id: 'evt-actual', source: 'actual', type: 'coupon', actualAmount: 43.56 }),
+ buildEvent({
+ id: 'evt-forecast',
+ source: 'forecast',
+ type: 'dividend',
+ actualAmount: null,
+ estimatedAmount: 197.56,
+ }),
+ ])
+
+ renderWithProviders()
+
+ const rows = screen.getAllByTestId('dashboard-events-row')
+ expect(rows).toHaveLength(2)
+
+ const subtitles = screen.getAllByTestId('dashboard-events-instrument-subtitle')
+ expect(subtitles).toHaveLength(2)
+ for (const subtitle of subtitles) {
+ expect(subtitle.textContent).toBe('РЖД 001Р-37R')
+ }
+
+ const amounts = screen.getAllByTestId('dashboard-events-amount')
+ expect(amounts).toHaveLength(2)
+ expect(amounts[0].getAttribute('data-tone')).toBe('positive')
+ expect(amounts[0].textContent).toContain('+43,56')
+ expect(amounts[1].getAttribute('data-tone')).toBe('planned')
+ expect(amounts[1].textContent).toContain('~197,56')
+
+ expect(screen.getByText('Купон')).toBeInTheDocument()
+ expect(screen.getByText('Дивиденд')).toBeInTheDocument()
+ expect(screen.getByText('Поступило')).toBeInTheDocument()
+ expect(screen.getByText('Ожидается')).toBeInTheDocument()
+ })
+
+ it('uses negative tone when event actual amount is negative', () => {
+ mockEventsLoaded([
+ buildEvent({
+ id: 'evt-tax',
+ type: 'coupon',
+ source: 'actual',
+ actualAmount: -87.0,
+ }),
+ ])
+
+ renderWithProviders()
+
+ const [amount] = screen.getAllByTestId('dashboard-events-amount')
+ expect(amount.getAttribute('data-tone')).toBe('negative')
+ expect(amount.textContent).toContain('-87,00')
+ })
+
+ it('renders events and income amounts with the ₽ symbol and no "RUB" code', () => {
+ mockEventsLoaded([
+ buildEvent({ id: 'evt', source: 'actual', actualAmount: 43.56 }),
+ buildEvent({ id: 'evt-neg', source: 'actual', actualAmount: -12.34 }),
+ ])
+ mockOperationsLoaded([
+ buildOperation({
+ id: 'op-positive',
+ type: 'OPERATION_TYPE_COUPON',
+ ticker: 'SU26249RMFS1',
+ name: 'ОФЗ 26241',
+ payment: { currency: 'RUB', units: '109', nano: 700000000, value: 109.7 },
+ }),
+ buildOperation({
+ id: 'op-negative',
+ type: 'OPERATION_TYPE_DIVIDEND',
+ ticker: 'IRAO',
+ name: 'Интер РАО',
+ payment: { currency: 'RUB', units: '35', nano: 0, value: -35 },
+ }),
+ ])
+
+ renderWithProviders()
+
+ const eventsTable = screen.getByLabelText('Таблица событий брокерского счёта')
+ const incomeTable = screen.getByLabelText('Таблица доходов брокерского счёта')
+ for (const table of [eventsTable, incomeTable]) {
+ expect(table.textContent ?? '').toContain('₽')
+ expect(table.textContent ?? '').not.toContain('RUB')
+ }
+ })
+
+ it('renders income rows with main + subtitle, type badge and signed amount tone', () => {
+ mockOperationsLoaded([
+ buildOperation({
+ id: 'op-div',
+ type: 'OPERATION_TYPE_DIVIDEND',
+ ticker: 'IRAO',
+ name: 'Интер РАО',
+ payment: { currency: 'RUB', units: '649', nano: 250000000, value: 649.25 },
+ }),
+ buildOperation({
+ id: 'op-div-ext',
+ type: 'OPERATION_TYPE_DIV_EXT',
+ ticker: 'AAPL',
+ name: 'Apple Inc.',
+ payment: { currency: 'RUB', units: '100', nano: 0, value: -35 },
+ }),
+ ])
+
+ renderWithProviders()
+
+ const rows = screen.getAllByTestId('dashboard-income-row')
+ expect(rows).toHaveLength(2)
+
+ const mainLabels = screen.getAllByTestId('dashboard-income-instrument-main')
+ expect(mainLabels.map((el) => el.textContent)).toEqual(['IRAO', 'AAPL'])
+
+ const subtitles = screen.getAllByTestId('dashboard-income-instrument-subtitle')
+ expect(subtitles.map((el) => el.textContent)).toEqual(['Интер РАО', 'Apple Inc.'])
+
+ expect(screen.getByText('Дивиденд')).toBeInTheDocument()
+ expect(screen.getByText('Дивиденд (внешний)')).toBeInTheDocument()
+
+ const amounts = screen.getAllByTestId('dashboard-income-amount')
+ expect(amounts[0].getAttribute('data-tone')).toBe('positive')
+ expect(amounts[0].textContent).toContain('+649,25')
+ expect(amounts[1].getAttribute('data-tone')).toBe('negative')
+ expect(amounts[1].textContent).toContain('−35,00')
+ })
})
diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx
index 26cb870..13a3e71 100644
--- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx
+++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx
@@ -2,9 +2,18 @@ import { Button, Chip, Text } from '@moex-vibe/design-system'
import { Box } from '@mui/material'
import { Link } from '@tanstack/react-router'
import type { BrokerEventItem, BrokerEventsData } from '@/shared/api'
-import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters'
+import { formatBrokerDate } from '@/shared/lib/formatters'
import type { DashboardDatePreset, DashboardEventType } from '../lib/dashboardFilters'
import { eventStatusLabel, eventTypeLabel } from '../lib/dashboardFormatters'
+import {
+ eventTypeTone,
+ formatDashboardCurrency,
+ instrumentDisplay,
+ type MoneyTone,
+ moneyTone,
+ moneyToneToColor,
+ type TypeTone,
+} from '../lib/dashboardVisual'
import { BrokerDashboardCard } from './BrokerDashboardCard'
import { BrokerDashboardDateFilter } from './BrokerDashboardDateFilter'
import { BrokerDashboardTableSkeleton } from './BrokerDashboardTableSkeleton'
@@ -13,7 +22,7 @@ import { BrokerDashboardTableToolbar } from './BrokerDashboardTableToolbar'
const EVENT_FILTERS: Array<{
type: DashboardEventType
label: string
- tone: 'success' | 'info' | 'warning' | 'neutral'
+ tone: TypeTone
}> = [
{ type: 'dividend', label: 'Дивиденды', tone: 'success' },
{ type: 'coupon', label: 'Купоны', tone: 'info' },
@@ -21,6 +30,38 @@ const EVENT_FILTERS: Array<{
{ type: 'offer', label: 'Оферты', tone: 'neutral' },
]
+const TH_SX = {
+ textAlign: 'left' as const,
+ borderBottom: '1px solid',
+ borderColor: 'divider',
+ px: 1.25,
+ py: 1,
+ color: 'text.secondary',
+ fontWeight: 600,
+ fontSize: 12,
+ letterSpacing: 0.2,
+ textTransform: 'uppercase' as const,
+ whiteSpace: 'nowrap' as const,
+}
+
+const TD_SX_LEFT = {
+ px: 1.25,
+ py: 1,
+ borderBottom: '1px solid',
+ borderColor: 'divider',
+ verticalAlign: 'middle' as const,
+}
+
+const TD_SX_RIGHT = {
+ ...TD_SX_LEFT,
+ textAlign: 'right' as const,
+}
+
+const TD_SX_INSTRUMENT = {
+ ...TD_SX_LEFT,
+ minWidth: 180,
+}
+
type BrokerDashboardEventsCardProps = {
accountId: string
data: BrokerEventsData | undefined
@@ -45,11 +86,16 @@ type BrokerDashboardEventsCardProps = {
canGoForward: boolean
}
-function eventAmount(event: BrokerEventItem): string {
- const amount = event.source === 'actual' ? event.actualAmount : event.estimatedAmount
- if (amount === null || amount === undefined) return '\u2014'
- const prefix = event.source === 'actual' ? '+' : '~'
- return `${prefix}${formatBrokerCurrencyValue(event.currency ?? 'RUB', amount)}`
+function eventStatusTone(event: BrokerEventItem): TypeTone {
+ return event.source === 'actual' ? 'success' : 'neutral'
+}
+
+function eventAmountValue(event: BrokerEventItem): number | null {
+ return event.source === 'actual' ? event.actualAmount : event.estimatedAmount
+}
+
+function eventMoneyTone(event: BrokerEventItem): MoneyTone {
+ return moneyTone(eventAmountValue(event), event.source)
}
export function BrokerDashboardEventsCard({
@@ -119,58 +165,96 @@ export function BrokerDashboardEventsCard({
) : (
-
-
- {events.map((event) => (
-
-
- {formatBrokerDate(event.eventDate)}
-
-
- {event.ticker ?? event.name ?? '\u2014'}
-
-
- {eventTypeLabel(event.type)}
-
-
- {eventAmount(event)}
-
-
- {eventStatusLabel(event)}
-
+
+
+
+
+ Дата
- ))}
+
+ Инструмент
+
+
+ Тип
+
+
+ Сумма
+
+
+ Статус
+
+
+
+
+ {events.map((event) => {
+ const display = instrumentDisplay({
+ ticker: event.ticker,
+ name: event.name,
+ })
+ const amount = eventAmountValue(event)
+ const formattedAmount =
+ amount === null || amount === undefined
+ ? '—'
+ : `${event.source === 'actual' ? '+' : '~'}${formatDashboardCurrency({
+ currency: event.currency ?? 'RUB',
+ value: amount,
+ })}`
+ return (
+
+
+ {formatBrokerDate(event.eventDate) ?? '—'}
+
+
+
+
+ {display.main}
+
+ {display.subtitle ? (
+
+ {display.subtitle}
+
+ ) : null}
+
+
+
+
+
+
+ {formattedAmount}
+
+
+
+
+
+ )
+ })}
diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx
index 3cd4138..349da27 100644
--- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx
+++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx
@@ -2,7 +2,7 @@ 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, type MoneyTone, moneyTone } from '../lib/dashboardVisual'
+import { formatDashboardCurrency, moneyTone, moneyToneToColor } from '../lib/dashboardVisual'
type BrokerDashboardHeroProps = {
portfolio: BrokerPortfolio
@@ -13,18 +13,6 @@ function percentValue(value: unknown): string {
return typeof value === 'number' ? formatBrokerPercent(value) : '—'
}
-function toneToColor(tone: MoneyTone): string {
- switch (tone) {
- case 'positive':
- return 'success.main'
- case 'negative':
- return 'error.main'
- case 'planned':
- case 'neutral':
- return 'text.disabled'
- }
-}
-
export function BrokerDashboardHero({ portfolio, analytics }: BrokerDashboardHeroProps) {
const accountName = portfolio.account.name || 'Брокерский счёт'
const returnPercent = analytics?.totalReturnPercent ?? portfolio.yields.expectedPercent
@@ -66,12 +54,12 @@ export function BrokerDashboardHero({ portfolio, analytics }: BrokerDashboardHer
+
{percentValue(returnPercent)}
}
supportingText={
-
+
За день: {formatBrokerMoney(portfolio.yields.daily)}
}
@@ -81,7 +69,10 @@ export function BrokerDashboardHero({ portfolio, analytics }: BrokerDashboardHer
+
{totalReceivedDisplay}
}
diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx
index 412fcc4..2859589 100644
--- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx
+++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx
@@ -2,9 +2,15 @@ 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 { formatBrokerDate } from '@/shared/lib/formatters'
import type { DashboardDatePreset, DashboardIncomeType } from '../lib/dashboardFilters'
import { getDashboardIncomeRows, sumDashboardIncome } from '../lib/dashboardIncome'
+import {
+ formatDashboardCurrency,
+ incomeTypeTone,
+ moneyToneToColor,
+ type TypeTone,
+} from '../lib/dashboardVisual'
import { BrokerDashboardCard } from './BrokerDashboardCard'
import { BrokerDashboardDateFilter } from './BrokerDashboardDateFilter'
import { BrokerDashboardTableSkeleton } from './BrokerDashboardTableSkeleton'
@@ -13,12 +19,44 @@ import { BrokerDashboardTableToolbar } from './BrokerDashboardTableToolbar'
const INCOME_FILTERS: Array<{
type: DashboardIncomeType
label: string
- tone: 'success' | 'info'
+ tone: TypeTone
}> = [
{ type: 'dividend', label: 'Дивиденды', tone: 'success' },
{ type: 'coupon', label: 'Купоны', tone: 'info' },
]
+const TH_SX = {
+ textAlign: 'left' as const,
+ borderBottom: '1px solid',
+ borderColor: 'divider',
+ px: 1.25,
+ py: 1,
+ color: 'text.secondary',
+ fontWeight: 600,
+ fontSize: 12,
+ letterSpacing: 0.2,
+ textTransform: 'uppercase' as const,
+ whiteSpace: 'nowrap' as const,
+}
+
+const TD_SX_LEFT = {
+ px: 1.25,
+ py: 1,
+ borderBottom: '1px solid',
+ borderColor: 'divider',
+ verticalAlign: 'middle' as const,
+}
+
+const TD_SX_RIGHT = {
+ ...TD_SX_LEFT,
+ textAlign: 'right' as const,
+}
+
+const TD_SX_INSTRUMENT = {
+ ...TD_SX_LEFT,
+ minWidth: 200,
+}
+
type BrokerDashboardIncomeCardProps = {
accountId: string
page: BrokerOperationsPage | undefined
@@ -111,54 +149,89 @@ export function BrokerDashboardIncomeCard({
) : (
-
-
- {rows.map((row) => (
-
-
- {formatBrokerDate(row.date)}
-
-
- {row.instrument}
-
-
- {row.typeLabel}
-
-
- +{formatBrokerCurrencyValue(row.amount.currency, row.amount.value)}
-
+
+
+
+
+ Дата
- ))}
+
+ Инструмент
+
+
+ Тип
+
+
+ Сумма
+
+
+
+
+ {rows.map((row) => {
+ const formattedAmount = `${row.amount.value >= 0 ? '+' : '−'}${formatDashboardCurrency(
+ { currency: row.amount.currency, value: Math.abs(row.amount.value) },
+ )}`
+ const amountTone = row.amount.value >= 0 ? 'positive' : 'negative'
+ return (
+
+
+ {formatBrokerDate(row.date) ?? '—'}
+
+
+
+
+ {row.instrumentMain}
+
+ {row.instrumentSubtitle ? (
+
+ {row.instrumentSubtitle}
+
+ ) : null}
+
+
+
+
+
+
+ {formattedAmount}
+
+
+ )
+ })}
Показано: {rows.length} · Итого:{' '}
- {total ? formatBrokerCurrencyValue(total.currency, total.value) : '—'}
+ {total
+ ? `${total.value >= 0 ? '+' : '−'}${formatDashboardCurrency({
+ currency: total.currency,
+ value: Math.abs(total.value),
+ })}`
+ : '—'}