Sergey Krylov fee179d8e9
All checks were successful
CI / ci (pull_request) Successful in 15m41s
CI / ci (push) Successful in 13m50s
fix(frontend): align broker dashboard HTML parity
2026-06-27 15:23:55 +03:00

305 lines
28 KiB
Markdown
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.

# План реализации редизайна обзора брокерского счёта
> **Для агентных исполнителей:** ОБЯЗАТЕЛЬНЫЙ SUB-SKILL: использовать `superpowers:subagent-driven-development` (предпочтительно) или `superpowers:executing-plans` для пошагового выполнения. Шаги ведутся чекбоксами `- [ ]`.
**Цель:** превратить `/broker/:accountId` в компактный инвестиционный дашборд на текущей светлой теме без изменения backend-контрактов и URL-структуры.
**Архитектура:** маршрут и FSD-границы остаются прежними. Композиция собирается в `widgets/broker-dashboard`, данные продолжают приходить из существующих `entities/*` hooks. Навигация счёта остаётся в `BrokerAccountLayout`, а dashboard использует только локальные presentation/helpers без выноса брокерской логики в design system.
**Технологии:** React 18, TanStack Router, TanStack Query, MUI через `@moex-vibe/design-system`, MUI X DateCalendar community (`@mui/x-date-pickers`) с Day.js, Vitest, Testing Library.
**HTML parity update:** согласованный визуальный эталон находится в `docs/research/2026-06-27-broker-account-redesign.html`.
Следующая итерация переносит его детали в реальную страницу без изменения backend-контрактов.
---
## Область реализации
### Файлы
- Modify: `apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx` — горизонтальные вкладки над контентом обзора и подробных разделов.
- Modify: `apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx` — точка входа обзора через dashboard.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/index.ts` — публичный API dashboard-виджета.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx` — верхнеуровневая композиция и управление filter state.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx` — hero KPI с fallback-значениями и выравниванием `Metric`.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardCard.tsx` — локальный паттерн карточки.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx` — карточка событий.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx` — переиспользуемое управление периодом с draft/apply поведением.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx` — карточка доходов.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAnalyticsCard.tsx` — карточка аналитики доходности.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx` — карточка аллокации с горизонтальными барами.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardSkeleton.tsx` — skeleton-форма dashboard.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx` — компонентные тесты композиции и фильтров.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.ts` — чистые helpers доходных операций.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.test.ts` — unit-тесты income helpers.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts` — пресеты дат, validate и mapping income types.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/lib/dashboardFormatters.ts` — локальные formatter/helpers для fallback и event labels.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts` — локальные helpers визуальной семантики dashboard: tone сумм, tone типов, отображение инструмента, символ валюты.
- Create/Modify: `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts` — unit-тесты визуальных helpers.
- Modify: `apps/frontend/package.json` — добавить community-пакет `@mui/x-date-pickers`, если зависимость ещё не подключена; `@mui/x-date-pickers-pro` не добавлять.
- Modify: `docs/features/broker-dashboard-redesign/tasks.md` — фиксация статусов выполнения.
### Источники данных
- Портфель: `useBrokerAccountContext().portfolio`.
- События: `useBrokerEvents(accountId, { from, to, types })`.
- Операции: `useBrokerOperations(accountId, { from, to, operationTypes, cursor, limit: 10 })`.
- Аналитика: `useBrokerAnalytics(accountId)`.
- Аллокация: `buildBrokerAllocation(portfolio)`.
## Технические решения
### 1. Композиция страницы
- Dashboard на desktop и mobile строится в одну колонку: `Hero`, `События`, `Доходы`, `Аналитика доходности`, `Аллокация`.
- Двухколоночный layout из прототипа не переносится.
- Навигация счёта находится над контентом в `BrokerAccountLayout` и не дублируется внутри dashboard.
### 2. Hero KPI
- Hero показывает название счёта, стоимость портфеля, доходность, дневное изменение и всего полученных доходов.
- Приоритет доходности: `analytics.totalReturnPercent`, затем `portfolio.yields.expectedPercent`, иначе `—`.
- Дневное изменение берётся из `portfolio.yields.daily` и `portfolio.yields.dailyPercent`, при недоступности показывается `—`.
- Все `Metric` должны иметь одинаковую высоту; supporting text не должен ломать вертикальный ритм.
### 3. События
- Типы событий переключаются chip-фильтрами с немедленным применением.
- Диапазон дат по умолчанию: `сегодня - 7 дней` / `сегодня + 7 дней`, чтобы обзор сразу показывал
ближайшие будущие события.
- Диапазон дат редактируется отдельно от применённого состояния: draft state меняется локально, запрос уходит только по действию применения периода.
- В шапке управления периодом не используется слово `Фильтр`; роль управления считывается через иконку календаря, применённый диапазон, раскрытие панели и пресеты.
- Native `<input type="date">` не используется; период выбирается через одно визуальное поле и popover с community `DateCalendar` из `@mui/x-date-pickers`.
- `DateRangePicker` и `@mui/x-date-pickers-pro` не используются; выбор начала/конца периода реализуется локальной логикой dashboard.
- При пустом выборе типов запрос отключается, карточка показывает валидационное сообщение.
- Пагинация локальная, по 10 событий на страницу, сбрасывается при смене применённых фильтров.
- При загрузке карточка показывает skeleton таблицы событий с колонками дата, инструмент, тип, сумма, статус.
- В toolbar карточки используются count badge, label `Тип`, кнопка `Обновить` и footer-summary по паттерну HTML-эталона.
### 4. Доходы
- Доходы строятся на существующем endpoint операций только для `OPERATION_TYPE_DIVIDEND`, `OPERATION_TYPE_DIV_EXT`, `OPERATION_TYPE_COUPON`.
- Типы доходов переключаются chip-фильтрами с немедленным применением.
- Диапазон дат по умолчанию: `сегодня - 7 дней` / `сегодня`, чтобы не загружать большой объём операций на первом открытии.
- Диапазон дат использует тот же draft/apply паттерн, что и события.
- В шапке управления периодом не используется слово `Фильтр`; действие применения периода не называется `Показать`.
- Native `<input type="date">` не используется; период выбирается через одно визуальное поле и popover с community `DateCalendar` из `@mui/x-date-pickers`.
- `DateRangePicker` и `@mui/x-date-pickers-pro` не используются; выбор начала/конца периода реализуется локальной логикой dashboard.
- Пагинация cursor-based, размер страницы 10, сбрасывается при смене применённых фильтров.
- При загрузке карточка показывает skeleton таблицы доходов с колонками дата, инструмент, тип, сумма.
### 5. Аналитика и аллокация
- Карточка аналитики использует существующий analytics endpoint и показывает спокойное empty state при отсутствии данных.
- Карточка аллокации не использует donut chart. Она строит список горизонтальных bar rows по `buildBrokerAllocation`.
- Отрицательные значения показываются текстом без полосы.
### 6. Ошибки и пустые состояния
- Ошибка `portfolio` роняет весь обзор.
- Ошибки `events`, `income`, `analytics` локальны соответствующим карточкам.
- Пустые данные показываются отдельными сообщениями, а не нулевыми значениями.
### 7. HTML parity visual layer
- Реальная страница `/broker/:accountId` должна визуально соответствовать `docs/research/2026-06-27-broker-account-redesign.html`,
но использовать существующие React-компоненты и FSD-границы.
- Не менять backend и OpenAPI: блок `Доходы` остаётся на текущем endpoint операций и текущем наборе
income-типов. Отрицательный tone должен поддерживаться для строк, которые уже отображаются или будут
отображаться без расширения контракта.
- Ввести локальные helpers в `widgets/broker-dashboard/lib/dashboardVisual.ts`:
`moneyTone(value, source?) -> 'positive' | 'negative' | 'planned' | 'neutral'`,
`eventTypeTone(type)`, `incomeTypeTone(typeLabel)`, `formatDashboardCurrency(moneyOrValue)`,
`instrumentDisplay({ ticker, name, description })`.
- `formatDashboardCurrency` для RUB должен выводить `₽`. Для неизвестных валют использовать код валюты.
- `instrumentDisplay` должен возвращать основную строку и опциональную подпись: для событий приоритет
`ticker/isin` как main и `name` как subtitle; для операций приоритет `ticker` как main и
`name/description` как subtitle. Если ticker отсутствует, main берётся из name/description, subtitle не
дублируется.
- `BrokerDashboardCard` должен поддержать компактный заголовок карточки уровня HTML-прототипа, не
используя крупный `Heading size="title"`.
- `BrokerDashboardDateFilter` должен использовать иконку раскрытия вместо текстового символа и сохранять
единый toolbar-паттерн для `События` и `Доходы`.
- Для `События` period presets должны поддерживать будущую часть диапазона, а не обрезаться текущим днём.
- Таблицы `События` и `Доходы` должны иметь `thead`, type badges, двухстрочный инструмент при наличии
названия и semantic amount colors.
- `BrokerDashboardAnalyticsCard` должен окрашивать KPI-карточки по смыслу и показывать RUB через `₽`.
- Skeleton таблиц событий и доходов должен использовать один компонент/паттерн и различаться только
числом колонок.
## Задачи
### Задача 1: Базовые helpers и локальные dashboard-patterns
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.ts`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.test.ts`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardFormatters.ts`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardCard.tsx`
- [ ] Убедиться, что income helpers покрывают только допустимые типы доходов и умеют считать итог отображаемых строк.
- [ ] Убедиться, что filter helpers содержат default state, date presets, validate и mapping income type -> operation types.
- [ ] Убедиться, что formatters покрывают fallback-значения, label типов событий и label статусов.
- [ ] Использовать локальный `BrokerDashboardCard` как основной контейнер карточек; design system расширять только если без этого нельзя реализовать требования спецификации.
### Задача 2: Hero и layout overview
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardSkeleton.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx`
- `apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx`
- `apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx`
- [ ] Собрать обзор через `BrokerDashboard` и `BrokerDashboardSkeleton`.
- [ ] Оставить навигацию счёта в `BrokerAccountLayout` как горизонтальные вкладки над контентом.
- [ ] Исправить hero так, чтобы все `Metric` были одной высоты и поддерживающий текст не поднимал одну ячейку выше остальных.
- [ ] Проверить, что dashboard остаётся одноколоночным и на desktop, и на mobile.
### Задача 3: Карточка событий
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts`
- `apps/frontend/package.json`
- [ ] Добавить или подтвердить зависимость community-пакета `@mui/x-date-pickers` и использовать Day.js adapter.
- [ ] Не добавлять `@mui/x-date-pickers-pro` и не использовать `DateRangePicker`.
- [ ] Обновить `BrokerDashboardDateFilter`: убрать слово `Фильтр` из пользовательского текста, заменить `Показать` на понятное действие применения периода и визуально собрать chips, диапазон, сброс и применение в аккуратную шапку.
- [ ] Заменить native date inputs на одно визуальное поле периода, которое открывает MUI `Popover` с community `DateCalendar`.
- [ ] Реализовать локальную логику выбора диапазона: первый клик задаёт начало, второй — конец; если конец раньше начала, диапазон пересобирается от выбранной даты.
- [ ] Настроить default range событий на `сегодня - 7 дней` / `сегодня + 7 дней`.
- [ ] Подключить для событий draft/applied state: типы применяются сразу, даты только по действию применения периода.
- [ ] Сохранять локальную пагинацию по 10 событий и сбрасывать её при смене применённых фильтров.
- [ ] Заменить текстовую загрузку событий на skeleton таблицы.
- [ ] Оставить локальные error/empty states внутри карточки.
### Задача 4: Карточка доходов
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts`
- [ ] Подключить тот же `BrokerDashboardDateFilter` для доходов с draft/applied state.
- [ ] Настроить default range доходов на `сегодня - 7 дней` / `сегодня` вместо периода с начала года.
- [ ] Оставить chip-фильтры типов доходов с немедленным применением.
- [ ] Сохранить cursor pagination по 10 операций и сбрасывать её при смене применённых фильтров.
- [ ] Заменить текстовую загрузку доходов на skeleton таблицы.
- [ ] Если endpoint операций даёт недостаточно релевантных строк для dashboard, зафиксировать ограничение в заметках по реализации, а не расширять backend в рамках этой фичи.
### Задача 5: Карточки аналитики и аллокации
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAnalyticsCard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx`
- [ ] Довести карточку аналитики до соответствия спецификации по полям и состояниям.
- [ ] Заменить donut chart на горизонтальные бары, построенные из `buildBrokerAllocation`.
- [ ] Отрицательные значения аллокации выводить отдельно текстом без bar.
### Задача 6: Тесты и верификация
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx`
- `docs/features/broker-dashboard-redesign/tasks.md`
- [ ] Обновить компонентные тесты dashboard так, чтобы они покрывали текущую композицию, фильтры и основные empty/error states.
- [ ] Обновить тесты, которые ищут `Фильтр дат` или `Показать`, под новые тексты и accessible labels единого поля периода.
- [ ] Добавить/обновить тесты default weekly range для событий и доходов.
- [ ] Добавить/обновить тесты skeleton-таблиц для loading state событий и доходов.
- [ ] Прогнать `rtk npm run test:frontend -- --run src/widgets/broker-dashboard`.
- [ ] Прогнать `rtk npm run test:frontend`.
- [ ] Прогнать `rtk npm run test:design-system && rtk npm run lint -w apps/frontend && rtk npm run build:frontend`.
- [ ] Проверить вручную desktop layout `/broker/2084014113`.
- [ ] Проверить вручную mobile layout `/broker/2084014113` на viewport `390x844`.
- [ ] После завершения обновить `docs/features/broker-dashboard-redesign/tasks.md` и выполнить `graphify update .`.
### Задача 7: Visual helpers для HTML parity
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.ts`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardVisual.test.ts`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.ts`
- `apps/frontend/src/widgets/broker-dashboard/lib/dashboardIncome.test.ts`
- [ ] Добавить `moneyTone`, который возвращает `negative` для отрицательных значений, `positive` для
положительных фактических значений, `planned` для прогнозных/нефактических значений и `neutral` для
нуля/недоступного значения.
- [ ] Добавить `formatDashboardCurrency`, который для `RUB` выводит `₽`, а для неизвестной валюты
оставляет код валюты.
- [ ] Добавить `instrumentDisplay` с приоритетами main/subtitle из технического решения 7.
- [ ] Добавить type tone helpers для event types и income labels.
- [ ] Расширить `DashboardIncomeRow`: хранить `instrumentMain` и `instrumentSubtitle`, сохранив
совместимость через существующий `instrument` только если это нужно текущим тестам.
- [ ] Покрыть helpers unit-тестами: RUB symbol, unknown currency fallback, negative/positive/planned
tones, event/income type tones, отсутствие дублирования subtitle.
### Задача 8: Hero, карточка и toolbar parity
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardCard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx`
- [ ] Применить semantic color к hero: отрицательная доходность красная, положительная зелёная,
дневное изменение окрашивается по знаку, `Всего доходов` зелёный при значении больше нуля.
- [ ] Сделать заголовки dashboard-card компактными, соответствующими HTML-прототипу.
- [ ] Собрать filters area карточек в единый toolbar: слева типы, справа поле периода и действие.
- [ ] Заменить текстовую стрелку раскрытия периода на иконку: использовать уже подключённые
`CalendarTodayRounded` и `ExpandMoreRounded` из `@mui/icons-material`, без добавления новой icon
dependency.
- [ ] Обновить component tests: проверять отсутствие текста `RUB` для RUB-значений, отсутствие символа
`v` в period button и наличие accessible name у управления периодом.
### Задача 9: Таблицы событий и доходов как в HTML
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardTableSkeleton.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx`
- [ ] Добавить `thead` в обе dashboard-таблицы с колонками из спецификации.
- [ ] В колонке инструмента выводить main и subtitle из `instrumentDisplay`.
- [ ] Заменить текстовые типы на компактные бейджи с tone из visual helpers.
- [ ] Окрашивать суммы через `moneyTone`: поступления зелёным, списания красным, прогноз/ожидание серым.
- [ ] Окрашивать статусные бейджи: `Поступило` зелёный, прогноз/ожидание серый.
- [ ] Сохранить горизонтальный scroll только внутри таблицы на mobile, без общего page overflow.
- [ ] Обновить skeleton так, чтобы `События` и `Доходы` использовали один визуальный паттерн строк.
- [ ] Обновить tests на наличие type badges, subtitle инструмента, semantic amount classes и skeleton.
### Задача 10: Analytics parity и визуальная проверка
**Файлы:**
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAnalyticsCard.tsx`
- `apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx`
- `docs/features/broker-dashboard-redesign/tasks.md`
- [ ] Отображать RUB как `₽` во всех analytics KPI.
- [ ] Окрашивать analytics cards: positive для пополнений/дивидендов/купонов/всего получено,
negative для выводов и отрицательного нетто, neutral для нулевых/недоступных значений.
- [ ] Обновить component tests для analytics: `₽` вместо `RUB`, positive/negative tone cards.
- [ ] Прогнать `rtk npm run test:frontend -- --run src/widgets/broker-dashboard`.
- [ ] Прогнать `rtk npm run test:frontend`.
- [ ] Прогнать `rtk npm run lint -w apps/frontend && rtk npm run build:frontend`.
- [ ] Проверить `/broker/2084014113` вручную на desktop и mobile `390x844` против
`docs/research/2026-06-27-broker-account-redesign.html`.
- [ ] После проверки обновить `docs/features/broker-dashboard-redesign/tasks.md`.
## Проверка покрытия спецификации
- Общая композиция и горизонтальная навигация: задачи 2 и 6.
- Hero KPI и equal-height `Metric`: задача 2.
- События с chip filters, date filters и локальной пагинацией: задача 3.
- Доходы с chip filters, date filters и cursor pagination: задача 4.
- Community DateCalendar range control, weekly default range и skeleton loading tables: задачи 3, 4 и 6.
- Аналитика и аллокация с горизонтальными барами: задача 5.
- Ошибки, empty states, тесты и ручная верификация: задача 6.
- HTML parity: visual helpers — задача 7, hero/card/toolbar — задача 8, dashboard tables — задача 9,
analytics — задача 10.