diff --git a/apps/frontend/src/app/layouts/AppLayout.tsx b/apps/frontend/src/app/layouts/AppLayout.tsx index aefdb96..6bf4a31 100644 --- a/apps/frontend/src/app/layouts/AppLayout.tsx +++ b/apps/frontend/src/app/layouts/AppLayout.tsx @@ -18,65 +18,72 @@ export function AppLayout() { background: 'var(--color-surface)', borderBottom: '1px solid #e0e0e0', padding: '12px 24px', - display: 'flex', - alignItems: 'center', - flexWrap: 'wrap', - gap: 24, + display: 'grid', + gridTemplateColumns: 'minmax(0, 1fr) auto', + gap: 12, }} > - - MoexVibe - -
- +
+
+ + MoexVibe + +
+ +
+
+
- - Портфели - - - Брокер - - - Скринер - -
@@ -70,7 +69,7 @@ export function BrokerAccountLayout({ children }: { children: ReactNode }) { ...baseLinkStyle, color: 'var(--color-primary)', fontWeight: 700, - background: 'rgba(25, 118, 210, 0.08)', + background: 'rgba(25, 118, 210, 0.1)', }, }} > diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts index 39abe1e..06ad5d6 100644 --- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts +++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts @@ -18,7 +18,7 @@ export function defaultEventsFilters(): DashboardFilterState const now = dayjs() return { from: now.subtract(7, 'day').format('YYYY-MM-DD'), - to: now.format('YYYY-MM-DD'), + to: now.add(7, 'day').format('YYYY-MM-DD'), types: [...DASHBOARD_EVENT_TYPES], preset: '7d', } @@ -40,7 +40,9 @@ export function applyDatePreset( ): DashboardFilterState { const now = dayjs() const next = { ...filters, preset } - if (preset === 'all') return { ...next, from: '', to: '' } + if (preset === 'all') { + return { ...next, from: '2000-01-01', to: '2099-12-31' } + } const amount = preset === '1y' ? 1 : Number.parseInt(preset, 10) const unit = preset === '1y' ? 'year' : 'day' return { @@ -50,6 +52,25 @@ export function applyDatePreset( } } +export function applyEventDatePreset( + filters: DashboardFilterState, + preset: DashboardDatePreset, +): DashboardFilterState { + const now = dayjs() + const next = { ...filters, preset } + if (preset === 'all') { + return { ...next, from: '2000-01-01', to: '2099-12-31' } + } + + const amount = preset === '1y' ? 1 : Number.parseInt(preset, 10) + const unit = preset === '1y' ? 'year' : 'day' + return { + ...next, + from: now.subtract(amount, unit).format('YYYY-MM-DD'), + to: now.add(amount, unit).format('YYYY-MM-DD'), + } +} + export function validateDashboardFilters( filters: DashboardFilterState, ): string { 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 4082f8d..17e7de4 100644 --- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts +++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts @@ -96,10 +96,10 @@ describe('moneyToneToColor', () => { }) describe('incomeTypeTone', () => { - it('maps each income label to a stable tone', () => { + it('maps each income label to a stable tone matching HTML parity (coupon=info)', () => { expect(incomeTypeTone('Дивиденд')).toBe('success') - expect(incomeTypeTone('Дивиденд (внешний)')).toBe('info') - expect(incomeTypeTone('Купон')).toBe('warning') + expect(incomeTypeTone('Дивиденд (внешний)')).toBe('success') + expect(incomeTypeTone('Купон')).toBe('info') }) }) diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts index 04e31db..d1fdd00 100644 --- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts +++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts @@ -65,11 +65,10 @@ export function eventTypeTone(type: BrokerEventItem['type']): TypeTone { export function incomeTypeTone(label: DashboardIncomeTypeLabel): TypeTone { switch (label) { case 'Дивиденд': - return 'success' case 'Дивиденд (внешний)': - return 'info' + return 'success' case 'Купон': - return 'warning' + return 'info' } } 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 06c4755..3624b50 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx @@ -210,11 +210,31 @@ describe('BrokerDashboard', () => { }) }) - it('shows date filter toggle button with apply action and accessible name', async () => { + it('requests future events in the default dashboard range', () => { + vi.useFakeTimers() + try { + vi.setSystemTime(new Date('2026-06-27T12:00:00.000Z')) + + renderWithProviders() + + expect(hookMocks.useBrokerEvents).toHaveBeenCalledWith( + 'acc-1', + expect.objectContaining({ + from: '2026-06-20', + to: '2026-07-04', + }), + { enabled: true }, + ) + } finally { + vi.useRealTimers() + } + }) + + it('shows date filter toggle button with refresh action and accessible name', async () => { const user = userEvent.setup() renderWithProviders() - const applyButtons = screen.getAllByRole('button', { name: /Применить период/ }) + const applyButtons = screen.getAllByRole('button', { name: 'Обновить' }) expect(applyButtons).toHaveLength(2) const toggleButtons = screen.getAllByRole('button', { name: /^Период/ }) @@ -228,6 +248,7 @@ describe('BrokerDashboard', () => { expect(screen.getByText('1г')).toBeInTheDocument() expect(screen.getByText('Всё')).toBeInTheDocument() expect(screen.getByText('Сбросить')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Применить период' })).toBeInTheDocument() }) it('does not render a text chevron glyph inside the period toggle button', () => { @@ -386,6 +407,46 @@ describe('BrokerDashboard', () => { expect(screen.getByText('Ожидается')).toBeInTheDocument() }) + it('renders HTML-parity card headings, toolbar label and footer summary for events', () => { + mockEventsLoaded([ + buildEvent({ id: 'evt-1' }), + buildEvent({ id: 'evt-2', ticker: 'HEAD', name: 'HeadHunter Group', type: 'dividend' }), + ]) + hookMocks.useBrokerEvents.mockReturnValue({ + data: { + items: [ + buildEvent({ id: 'evt-1' }), + buildEvent({ id: 'evt-2', ticker: 'HEAD', name: 'HeadHunter Group', type: 'dividend' }), + ], + summary: { eventCount: 48 }, + asOf: '2026-06-27T00:00:00.000Z', + }, + isLoading: false, + isError: false, + }) + + renderWithProviders() + + const eventsSection = screen.getByLabelText('События') + expect(screen.getByText('2 из 48')).toBeInTheDocument() + expect(within(eventsSection).getAllByText('Тип').length).toBeGreaterThan(0) + expect( + within(eventsSection).getByText('Показано 2 событий за выбранный период'), + ).toBeInTheDocument() + }) + + it('renders HTML-parity count badge and footer summary for income', () => { + mockOperationsLoaded([ + buildOperation({ id: 'op-1' }), + buildOperation({ id: 'op-2', ticker: 'IRAO', name: 'Интер РАО' }), + ]) + + renderWithProviders() + + expect(screen.getByText('2 операции')).toBeInTheDocument() + expect(screen.getByText(/Показано 2 · Итого:/)).toBeInTheDocument() + }) + it('uses negative tone when event actual amount is negative', () => { mockEventsLoaded([ buildEvent({ diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx index ffcee25..6069b93 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx @@ -8,6 +8,7 @@ import type { BrokerPortfolio } from '@/shared/api' import { useCursorPagination } from '@/shared/lib/useCursorPagination' import { applyDatePreset, + applyEventDatePreset, type DashboardDatePreset, type DashboardEventType, type DashboardIncomeType, @@ -151,7 +152,7 @@ export function BrokerDashboard({ }, [incomePagination]) function handleDraftEventPresetChange(preset: DashboardDatePreset) { - setDraftEventFilters((filters) => applyDatePreset(filters, preset)) + setDraftEventFilters((filters) => applyEventDatePreset(filters, preset)) } function handleDraftEventFromChange(value: string) { @@ -194,6 +195,7 @@ export function BrokerDashboard({ onApplyFilters={applyEventFilters} onResetFilters={resetEventFilters} hasDraftTypes={hasDraftEventTypes} + totalCount={events.data?.summary?.eventCount} page={eventPage} canGoBack={eventPage > 1} canGoForward={eventPage * eventPageSize < eventItems.length} @@ -217,6 +219,7 @@ export function BrokerDashboard({ onApplyFilters={applyIncomeFilters} onResetFilters={resetIncomeFilters} hasDraftTypes={hasDraftIncomeTypes} + visibleCount={operations.data?.items?.length ?? 0} pageNumber={incomePagination.pageNumber} canGoBack={incomePagination.pageNumber > 1} canGoForward={operations.data?.hasNext ?? false} diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx index 679e78f..d7ed14e 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx @@ -31,6 +31,14 @@ export function BrokerDashboardAllocationCard({ portfolio }: { portfolio: Broker Нет данных для распределения ) : ( + + + Структура портфеля + + + {formatBrokerMoney(portfolio.totals.portfolio)} + + {sectors.map((sector) => ( diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAnalyticsCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAnalyticsCard.tsx index 5695160..40e9c5e 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAnalyticsCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAnalyticsCard.tsx @@ -72,20 +72,35 @@ export function BrokerDashboardAnalyticsCard({ const value = data[field] const tone = analyticsTone(field, value) return ( - - {analyticsDisplay(field, value, data.currency)} - - } - /> + sx={{ + borderRadius: 2, + border: '1px solid', + borderColor: tone === 'negative' ? 'error.light' : 'divider', + bgcolor: + tone === 'positive' + ? 'rgba(46, 125, 50, 0.06)' + : tone === 'negative' + ? 'rgba(211, 47, 47, 0.06)' + : 'grey.50', + p: 1.5, + }} + > + + {analyticsDisplay(field, value, data.currency)} + + } + /> + ) })} diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardCard.tsx index 3981c36..0cbe672 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardCard.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from 'react' type BrokerDashboardCardProps = { title: string + badge?: ReactNode action?: ReactNode filters?: ReactNode children: ReactNode @@ -13,6 +14,7 @@ type BrokerDashboardCardProps = { export function BrokerDashboardCard({ title, + badge, action, filters, children, @@ -38,15 +40,36 @@ export function BrokerDashboardCard({ - - {title} - + + + {title} + + {badge ? ( + + {badge} + + ) : null} + {action} {filters && {filters}} diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx index 6384840..16ea1d8 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx @@ -88,9 +88,16 @@ export function BrokerDashboardDateFilter({ setSelectingStage('from') } + function handleApply() { + onApply() + handleClose() + } + const selectingLabel = selectingStage === 'from' ? 'Выберите начало периода' : 'Выберите конец периода' const periodAriaLabel = appliedLabel ? `Период: ${appliedLabel}` : 'Период' + const rangeInverted = Boolean(draftFrom && draftTo) && dayjs(draftTo).isBefore(dayjs(draftFrom)) + const canApply = hasDraftTypes && !rangeInverted return ( @@ -151,29 +158,6 @@ export function BrokerDashboardDateFilter({ - - Применить период - - + Сбросить + + Применить период + + + Обновить + ) diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx index beb46c6..ab9d494 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx @@ -79,6 +79,7 @@ type BrokerDashboardEventsCardProps = { onApplyFilters: () => void onResetFilters: () => void hasDraftTypes: boolean + totalCount?: number page: number onPreviousPage: () => void onNextPage: () => void @@ -124,6 +125,7 @@ export function BrokerDashboardEventsCard({ onApplyFilters, onResetFilters, hasDraftTypes, + totalCount, page, onPreviousPage, onNextPage, @@ -135,6 +137,7 @@ export function BrokerDashboardEventsCard({ return ( 0 ? `${events.length} из ${totalCount ?? events.length}` : undefined} action={Все события} filters={ - - - - {page} + + + Показано {events.length} событий за выбранный период - + + + + {page} + + + )} diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx index b91902d..8101cc9 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx @@ -73,6 +73,7 @@ type BrokerDashboardIncomeCardProps = { onApplyFilters: () => void onResetFilters: () => void hasDraftTypes: boolean + visibleCount: number pageNumber: number canGoBack: boolean canGoForward: boolean @@ -97,6 +98,7 @@ export function BrokerDashboardIncomeCard({ onApplyFilters, onResetFilters, hasDraftTypes, + visibleCount, pageNumber, canGoBack, canGoForward, @@ -109,6 +111,7 @@ export function BrokerDashboardIncomeCard({ return ( 0 ? `${visibleCount} операции` : undefined} action={Все операции} filters={ - - Показано: {rows.length} · Итого:{' '} - {total - ? `${total.value >= 0 ? '+' : '−'}${formatDashboardCurrency({ - currency: total.currency, - value: Math.abs(total.value), - })}` - : '—'} - - - - - {pageNumber} + + + Показано {rows.length} · Итого:{' '} + {total + ? `${total.value >= 0 ? '+' : '−'}${formatDashboardCurrency({ + currency: total.currency, + value: Math.abs(total.value), + })}` + : '—'} - + + + + {pageNumber} + + + )} diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardTableToolbar.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardTableToolbar.tsx index 02b5e36..5493ac4 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardTableToolbar.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardTableToolbar.tsx @@ -11,7 +11,7 @@ export function BrokerDashboardTableToolbar({ chips, children }: BrokerDashboard - {chips} + + + Тип + + {chips} + {children} ) diff --git a/docs/features/broker-dashboard-redesign/plan.md b/docs/features/broker-dashboard-redesign/plan.md index 87edd2b..fd78c1d 100644 --- a/docs/features/broker-dashboard-redesign/plan.md +++ b/docs/features/broker-dashboard-redesign/plan.md @@ -65,7 +65,8 @@ ### 3. События - Типы событий переключаются chip-фильтрами с немедленным применением. -- Диапазон дат по умолчанию: `сегодня - 7 дней` / `сегодня`. +- Диапазон дат по умолчанию: `сегодня - 7 дней` / `сегодня + 7 дней`, чтобы обзор сразу показывал + ближайшие будущие события. - Диапазон дат редактируется отдельно от применённого состояния: draft state меняется локально, запрос уходит только по действию применения периода. - В шапке управления периодом не используется слово `Фильтр`; роль управления считывается через иконку календаря, применённый диапазон, раскрытие панели и пресеты. - Native `` не используется; период выбирается через одно визуальное поле и popover с community `DateCalendar` из `@mui/x-date-pickers`. @@ -73,6 +74,7 @@ - При пустом выборе типов запрос отключается, карточка показывает валидационное сообщение. - Пагинация локальная, по 10 событий на страницу, сбрасывается при смене применённых фильтров. - При загрузке карточка показывает skeleton таблицы событий с колонками дата, инструмент, тип, сумма, статус. +- В toolbar карточки используются count badge, label `Тип`, кнопка `Обновить` и footer-summary по паттерну HTML-эталона. ### 4. Доходы @@ -118,6 +120,7 @@ используя крупный `Heading size="title"`. - `BrokerDashboardDateFilter` должен использовать иконку раскрытия вместо текстового символа и сохранять единый toolbar-паттерн для `События` и `Доходы`. +- Для `События` period presets должны поддерживать будущую часть диапазона, а не обрезаться текущим днём. - Таблицы `События` и `Доходы` должны иметь `thead`, type badges, двухстрочный инструмент при наличии названия и semantic amount colors. - `BrokerDashboardAnalyticsCard` должен окрашивать KPI-карточки по смыслу и показывать RUB через `₽`. @@ -168,7 +171,7 @@ - [ ] Обновить `BrokerDashboardDateFilter`: убрать слово `Фильтр` из пользовательского текста, заменить `Показать` на понятное действие применения периода и визуально собрать chips, диапазон, сброс и применение в аккуратную шапку. - [ ] Заменить native date inputs на одно визуальное поле периода, которое открывает MUI `Popover` с community `DateCalendar`. - [ ] Реализовать локальную логику выбора диапазона: первый клик задаёт начало, второй — конец; если конец раньше начала, диапазон пересобирается от выбранной даты. -- [ ] Настроить default range событий на `сегодня - 7 дней` / `сегодня`. +- [ ] Настроить default range событий на `сегодня - 7 дней` / `сегодня + 7 дней`. - [ ] Подключить для событий draft/applied state: типы применяются сразу, даты только по действию применения периода. - [ ] Сохранять локальную пагинацию по 10 событий и сбрасывать её при смене применённых фильтров. - [ ] Заменить текстовую загрузку событий на skeleton таблицы. diff --git a/docs/features/broker-dashboard-redesign/spec.md b/docs/features/broker-dashboard-redesign/spec.md index d1d882f..ac37cc3 100644 --- a/docs/features/broker-dashboard-redesign/spec.md +++ b/docs/features/broker-dashboard-redesign/spec.md @@ -105,7 +105,7 @@ Hero показывает: ### 4. Блок `События` - Блок использует существующий источник `useBrokerEvents(accountId, query)`. -- По умолчанию применяется период `сегодня - 7 дней` / `сегодня` и типы +- По умолчанию применяется период `сегодня - 7 дней` / `сегодня + 7 дней` и типы `dividend,coupon,maturity,offer`, как в существующей вкладке событий. - Блок содержит кликабельные chip-фильтры типов событий: `Дивиденды`, `Купоны`, `Погашения`, `Оферты`. - Пользователь может выбрать несколько типов событий. @@ -114,7 +114,8 @@ Hero показывает: - Изменение chip-фильтров типов событий применяется сразу и возвращает локальную пагинацию на первую страницу. - Блок содержит фильтр периода `from` / `to`. -- По умолчанию применяется недельный период `сегодня - 7 дней` / `сегодня`. +- По умолчанию применяется недельный период `сегодня - 7 дней` / `сегодня + 7 дней`, чтобы обзор + сразу показывал ближайшие будущие события. - Изменение черновых фильтров не запускает запрос до применения периода пользователем. - В пользовательском тексте шапки периода не используется слово `Фильтр`; UI должен считываться как управление периодом за счёт иконки календаря, применённого диапазона, пресетов и affordance раскрытия. @@ -257,7 +258,7 @@ Hero показывает: - В таблице `События` инструмент отображается двумя строками при наличии названия, тип отображается бейджем, сумма и статус имеют семантические цвета. - Блок `События` поддерживает multi-select chip-фильтр типов и локальную пагинацию по 10 событий. -- Блок `События` по умолчанию запрашивает период `сегодня - 7 дней` / `сегодня` и использует одно поле +- Блок `События` по умолчанию запрашивает период `сегодня - 7 дней` / `сегодня + 7 дней` и использует одно поле периода с popover-календарём на базе community `DateCalendar`. - Блок `Доходы` показывает доходные операции дивидендов и купонов и итог по отображаемым строкам. - В таблице `Доходы` инструмент отображается двумя строками при наличии названия, тип отображается diff --git a/docs/features/broker-dashboard-redesign/tasks.md b/docs/features/broker-dashboard-redesign/tasks.md index 1ebdd33..30661d9 100644 --- a/docs/features/broker-dashboard-redesign/tasks.md +++ b/docs/features/broker-dashboard-redesign/tasks.md @@ -44,7 +44,8 @@ - [x] Переименовать действие `Показать` в управлении периодом и визуально улучшить шапку фильтров. - [x] Заменить native date inputs на одно поле периода с popover и community `DateCalendar` из `@mui/x-date-pickers`. - [x] Не использовать `@mui/x-date-pickers-pro` и `DateRangePicker`. -- [x] Настроить default range событий и доходов на `сегодня - 7 дней` / `сегодня`. +- [x] Настроить default range событий на `сегодня - 7 дней` / `сегодня + 7 дней`, доходов — на + `сегодня - 7 дней` / `сегодня`. - [x] Заменить текстовую загрузку `События` на skeleton таблицы. - [x] Заменить текстовую загрузку `Доходы` на skeleton таблицы. - [x] Обновить component tests под новые тексты, единое поле периода, popover-календарь и skeleton loading states. @@ -56,18 +57,27 @@ - [x] Согласовать статический визуальный эталон `docs/research/2026-06-27-broker-account-redesign.html`. - [x] Обновить `spec.md` под HTML parity: компактные заголовки, единый toolbar, бейджи, цвета, подписи инструментов, `₽`. - [x] Обновить `plan.md` под перенос HTML parity в React-компоненты. -- [ ] Добавить `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts` с helpers для money tone, type tone, currency symbol и instrument display. -- [ ] Добавить `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts`. +- [x] Добавить `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts` с helpers для money tone, type tone, currency symbol и instrument display. +- [x] Добавить `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts`. - [ ] Расширить income helpers так, чтобы строки доходов могли отдавать main/subtitle инструмента без дублирования текста. -- [ ] Обновить `BrokerDashboardHero`: semantic colors для доходности, дневного изменения и всего полученных доходов. -- [ ] Обновить `BrokerDashboardCard`: компактный card heading вместо крупного page-level heading. -- [ ] Обновить `BrokerDashboardDateFilter`: единый toolbar-паттерн и chevron-иконка вместо текстового `v`. -- [ ] Обновить `BrokerDashboardEventsCard`: `thead`, двухстрочный инструмент, type badges, semantic amount colors, status badges. -- [ ] Обновить `BrokerDashboardIncomeCard`: `thead`, двухстрочный инструмент, type badges, semantic amount colors. -- [ ] Обновить `BrokerDashboardTableSkeleton`: общий skeleton-паттерн для событий и доходов с корректной геометрией колонок. +- [x] Обновить `BrokerDashboardHero`: semantic colors для доходности, дневного изменения и всего полученных доходов. +- [x] Обновить `BrokerDashboardCard`: компактный card heading вместо крупного page-level heading. +- [x] Обновить `BrokerDashboardDateFilter`: единый toolbar-паттерн и chevron-иконка вместо текстового `v`. +- [x] Обновить `BrokerDashboardEventsCard`: `thead`, двухстрочный инструмент, type badges, semantic amount colors, status badges. +- [x] Обновить `BrokerDashboardIncomeCard`: `thead`, двухстрочный инструмент, type badges, semantic amount colors. + Бейдж `Купон` приведён к `info` (синий) согласно HTML-эталону `.type-badge.coupon` (`incomeTypeTone` в `dashboardVisual.ts`), + `Дивиденд (внешний)` отнесён к `success` (семейство дивидендов). +- [x] Обновить `BrokerDashboardTableSkeleton`: общий skeleton-паттерн для событий и доходов с корректной геометрией колонок. - [x] Обновить `BrokerDashboardAnalyticsCard`: `₽` для RUB и positive/negative tone карточек. - [x] Обновить component tests dashboard под HTML parity (analytics tones). -- [ ] Проверить, что блок `Доходы` не расширяет backend/API и остаётся в рамках текущих income-типов. +- [x] Проверить, что блок `Доходы` не расширяет backend/API и остаётся в рамках текущих income-типов. + Исправлен пресет «Всё»: `applyDatePreset('all')` теперь отдаёт широкий диапазон `2000-01-01`–`2099-12-31` + вместо пустых `from`/`to`, которые backend (`BrokerEventsQueryDto @Matches`) отвергал с 400. В DateFilter + кнопка «Применить период» блокируется при инвертированном диапазоне (`to < from`), чтобы избежать + молчаливо пустого ответа (например, будущий `from` при дефолтном `to=сегодня`). +- [x] Исправить default-range `События`, чтобы dashboard по умолчанию и через пресеты мог загружать будущие события. +- [x] Подтянуть card headers, toolbar label `Тип`, count badges и footer summaries ближе к + `docs/research/2026-06-27-broker-account-redesign.html`. - [ ] Проверить desktop layout `/broker/2084014113` против `docs/research/2026-06-27-broker-account-redesign.html`. - [ ] Проверить mobile layout `/broker/2084014113` на viewport `390x844` против `docs/research/2026-06-27-broker-account-redesign.html`. @@ -127,4 +137,3 @@ HTML-эталона (`docs/research/2026-06-27-broker-account-redesign.html:1145 - Реальный viewport на `390x844` для подтверждения отсутствия общего горизонтального overflow. Геометрия таблиц уже переключена на внутренний `overflowX: 'auto'` (`BrokerDashboardEventsCard.tsx:176`, `BrokerDashboardIncomeCard.tsx`), но фактическая вёрстка в браузере не сверялась с эталоном. -