codex/broker-accounts-overview #22
439
docs/features/broker-accounts-overview/plan.md
Normal file
439
docs/features/broker-accounts-overview/plan.md
Normal file
@ -0,0 +1,439 @@
|
||||
# Broker Accounts Overview Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Превратить `/broker` в информативный обзор одного–трёх брокерских счетов с общей сводкой, независимой загрузкой карточек и устойчивыми состояниями ошибок.
|
||||
|
||||
**Architecture:** `BrokerAccountsPage` получает список счетов и передаёт его в новый hook на основе TanStack Query `useQueries`; query key совпадает с `useBrokerPortfolio`, поэтому детальная страница переиспользует кеш. Чистый модуль агрегации группирует денежные значения по валютам и рассчитывает единственный новый финансовый показатель по формуле из spec. UI разбит на общую сводку, карточку счёта и компактную полосу распределения; backend и OpenAPI не меняются согласно ADR-012.
|
||||
|
||||
**Tech Stack:** React 18, TypeScript, TanStack Query v5, React Router v6, Vitest, Testing Library, CSS custom properties.
|
||||
|
||||
---
|
||||
|
||||
## Карта файлов
|
||||
|
||||
- Create `apps/frontend/src/pages/broker/brokerAccountsOverview.ts` — чистые типы, агрегация валютных сумм и форматирование дат/денег/процентов.
|
||||
- Create `apps/frontend/src/pages/broker/brokerAccountsOverview.test.ts` — unit-тесты финансовой агрегации и edge cases.
|
||||
- Create `apps/frontend/src/hooks/useBrokerAccountPortfolios.ts` — параллельные portfolio queries с общими query keys.
|
||||
- Create `apps/frontend/src/hooks/useBrokerAccountPortfolios.test.tsx` — проверка независимых query-состояний и кеша.
|
||||
- Create `apps/frontend/src/pages/broker/BrokerAccountsSummary.tsx` — общая сводка и частичное состояние.
|
||||
- Create `apps/frontend/src/pages/broker/BrokerAccountCard.tsx` — успешная карточка, skeleton и локальная ошибка с retry.
|
||||
- Create `apps/frontend/src/pages/broker/BrokerAllocationBar.tsx` — доступная горизонтальная полоса распределения.
|
||||
- Create `apps/frontend/src/pages/broker/BrokerAccountsPage.test.tsx` — page/component tests.
|
||||
- Modify `apps/frontend/src/pages/broker/BrokerAccountsPage.tsx` — orchestration новой страницы.
|
||||
- Modify `apps/frontend/src/pages/broker/BrokerPages.test.tsx` — удалить старый поверхностный тест списка счетов.
|
||||
- Modify `apps/frontend/src/styles.css` — визуальная система overview, focus/hover, skeleton и responsive rules.
|
||||
- Modify `docs/features/broker-accounts-overview/tasks.md` — отмечать выполненные задачи.
|
||||
- Modify `docs/roadmap.md` — отметить фичу реализованной только после всех проверок.
|
||||
|
||||
### Task 1: Чистая модель агрегации
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `apps/frontend/src/pages/broker/brokerAccountsOverview.test.ts`
|
||||
- Create: `apps/frontend/src/pages/broker/brokerAccountsOverview.ts`
|
||||
|
||||
- [ ] **Step 1: Написать падающие unit-тесты**
|
||||
|
||||
Покрыть одним набором тестов:
|
||||
|
||||
```ts
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { BrokerPortfolio } from '../../api/responses';
|
||||
import { aggregateBrokerAccounts } from './brokerAccountsOverview';
|
||||
|
||||
function portfolio(
|
||||
id: string,
|
||||
currency: string,
|
||||
total: number,
|
||||
daily: number | null,
|
||||
cash: number,
|
||||
): BrokerPortfolio {
|
||||
return {
|
||||
account: {
|
||||
id,
|
||||
type: 'brokerage',
|
||||
name: id,
|
||||
status: 'ACCOUNT_STATUS_OPEN',
|
||||
openedAt: '2022-06-16T00:00:00.000Z',
|
||||
accessLevel: null,
|
||||
},
|
||||
positionCounts: { shares: 1, bonds: 1, etf: 0, other: 0 },
|
||||
totals: {
|
||||
shares: { currency, units: '0', nano: 0, value: total * 0.5 },
|
||||
bonds: { currency, units: '0', nano: 0, value: total * 0.3 },
|
||||
etf: null,
|
||||
currencies: { currency, units: '0', nano: 0, value: total * 0.2 },
|
||||
futures: null,
|
||||
options: null,
|
||||
structuredProducts: null,
|
||||
dfa: null,
|
||||
portfolio: { currency, units: '0', nano: 0, value: total },
|
||||
},
|
||||
yields: {
|
||||
expectedPercent: 10,
|
||||
daily: daily === null ? null : { currency, units: '0', nano: 0, value: daily },
|
||||
dailyPercent: null,
|
||||
},
|
||||
cash: [{ currency, units: '0', nano: 0, value: cash }],
|
||||
blockedCash: [],
|
||||
asOf: '2026-06-19T10:00:00.000Z',
|
||||
};
|
||||
}
|
||||
|
||||
describe('aggregateBrokerAccounts', () => {
|
||||
it('sums comparable portfolios and uses the specified daily percent formula', () => {
|
||||
const result = aggregateBrokerAccounts([
|
||||
portfolio('a', 'RUB', 1_100, 100, 200),
|
||||
portfolio('b', 'RUB', 2_200, 200, 300),
|
||||
]);
|
||||
|
||||
expect(result.portfolios).toEqual([
|
||||
expect.objectContaining({
|
||||
currency: 'RUB',
|
||||
total: 3_300,
|
||||
daily: 300,
|
||||
dailyPercent: 10,
|
||||
allocation: { shares: 1_650, bonds: 990, etf: 0, cash: 660, other: 0 },
|
||||
}),
|
||||
]);
|
||||
expect(result.cash).toEqual([{ currency: 'RUB', value: 500 }]);
|
||||
});
|
||||
|
||||
it('keeps different currencies separate', () => {
|
||||
const result = aggregateBrokerAccounts([
|
||||
portfolio('rub', 'RUB', 1_100, 100, 200),
|
||||
portfolio('usd', 'USD', 550, 50, 25),
|
||||
]);
|
||||
|
||||
expect(result.portfolios.map(({ currency, total }) => ({ currency, total }))).toEqual([
|
||||
{ currency: 'RUB', total: 1_100 },
|
||||
{ currency: 'USD', total: 550 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not expose a daily percent when one account lacks daily data', () => {
|
||||
const result = aggregateBrokerAccounts([
|
||||
portfolio('a', 'RUB', 1_100, 100, 200),
|
||||
portfolio('b', 'RUB', 2_000, null, 300),
|
||||
]);
|
||||
|
||||
expect(result.portfolios[0]).toMatchObject({ daily: null, dailyPercent: null });
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Запустить unit-тест и подтвердить RED**
|
||||
|
||||
Run: `npx vitest run src/pages/broker/brokerAccountsOverview.test.ts -w apps/frontend`
|
||||
|
||||
Expected: FAIL с ошибкой импорта `./brokerAccountsOverview`.
|
||||
|
||||
- [ ] **Step 3: Реализовать минимальную чистую модель**
|
||||
|
||||
Создать публичные типы `BrokerAccountsAggregate`, `BrokerCurrencyPortfolioSummary`,
|
||||
`BrokerCurrencyCashSummary` и функцию:
|
||||
|
||||
```ts
|
||||
export function aggregateBrokerAccounts(portfolios: BrokerPortfolio[]): BrokerAccountsAggregate;
|
||||
```
|
||||
|
||||
Правила реализации:
|
||||
|
||||
- пропускать портфель без `totals.portfolio` или без currency;
|
||||
- группировать `totals.portfolio`, `yields.daily` и классы активов по currency портфеля;
|
||||
- считать `other` как `max(0, portfolio - shares - bonds - etf - currencies)`;
|
||||
- группировать `portfolio.cash` независимо по валюте;
|
||||
- если хотя бы в одном портфеле валютной группы нет сопоставимого `yields.daily`, возвращать для
|
||||
группы `daily: null` и `dailyPercent: null`;
|
||||
- иначе считать `dailyPercent = daily / (total - daily) * 100`, только если знаменатель положителен;
|
||||
- сортировать валютные группы по первому появлению во входном массиве, не по алфавиту.
|
||||
|
||||
- [ ] **Step 4: Запустить unit-тест и подтвердить GREEN**
|
||||
|
||||
Run: `npx vitest run src/pages/broker/brokerAccountsOverview.test.ts -w apps/frontend`
|
||||
|
||||
Expected: PASS, 3 tests.
|
||||
|
||||
- [ ] **Step 5: Добавить edge cases**
|
||||
|
||||
Добавить тесты для неположительной стоимости начала дня, отрицательного residual `other`, пустого
|
||||
массива, отсутствующего `totals.portfolio` и cash в нескольких валютах. Реализация должна возвращать
|
||||
конечные числа и никогда не смешивать валюты.
|
||||
|
||||
- [ ] **Step 6: Запустить unit-тесты и commit**
|
||||
|
||||
Run: `npx vitest run src/pages/broker/brokerAccountsOverview.test.ts -w apps/frontend`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
```bash
|
||||
git add apps/frontend/src/pages/broker/brokerAccountsOverview.ts apps/frontend/src/pages/broker/brokerAccountsOverview.test.ts
|
||||
git commit -m "feat: aggregate broker account summaries"
|
||||
```
|
||||
|
||||
### Task 2: Независимые portfolio queries
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `apps/frontend/src/hooks/useBrokerAccountPortfolios.ts`
|
||||
- Create: `apps/frontend/src/hooks/useBrokerAccountPortfolios.test.tsx`
|
||||
|
||||
- [ ] **Step 1: Написать падающий hook-тест**
|
||||
|
||||
Mock `getBrokerPortfolio`, отрендерить hook с двумя счетами и проверить, что вызываются `acc-1` и
|
||||
`acc-2`, а результат сохраняет соответствие `account → query` независимо от порядка завершения
|
||||
Promise. Второй тест должен создать `QueryClient`, заранее положить портфель в key
|
||||
`['broker', 'portfolio', 'acc-1']` и подтвердить, что hook использует то же кешированное значение.
|
||||
|
||||
- [ ] **Step 2: Запустить hook-тест и подтвердить RED**
|
||||
|
||||
Run: `npx vitest run src/hooks/useBrokerAccountPortfolios.test.tsx -w apps/frontend`
|
||||
|
||||
Expected: FAIL с ошибкой импорта `useBrokerAccountPortfolios`.
|
||||
|
||||
- [ ] **Step 3: Реализовать hook через `useQueries`**
|
||||
|
||||
```ts
|
||||
import { useQueries } from '@tanstack/react-query';
|
||||
import { getBrokerPortfolio } from '../api/broker';
|
||||
import type { BrokerAccount, BrokerPortfolio } from '../api/responses';
|
||||
|
||||
export function useBrokerAccountPortfolios(accounts: BrokerAccount[]) {
|
||||
const queries = useQueries({
|
||||
queries: accounts.map((account) => ({
|
||||
queryKey: ['broker', 'portfolio', account.id],
|
||||
queryFn: async (): Promise<BrokerPortfolio> => (await getBrokerPortfolio(account.id)).data,
|
||||
staleTime: 60_000,
|
||||
retry: 2,
|
||||
refetchOnWindowFocus: false,
|
||||
})),
|
||||
});
|
||||
|
||||
return accounts.map((account, index) => ({ account, query: queries[index] }));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Запустить hook-тест и подтвердить GREEN**
|
||||
|
||||
Run: `npx vitest run src/hooks/useBrokerAccountPortfolios.test.tsx -w apps/frontend`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add apps/frontend/src/hooks/useBrokerAccountPortfolios.ts apps/frontend/src/hooks/useBrokerAccountPortfolios.test.tsx
|
||||
git commit -m "feat: load broker account portfolios in parallel"
|
||||
```
|
||||
|
||||
### Task 3: Компоненты и состояния страницы
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `apps/frontend/src/pages/broker/BrokerAccountsSummary.tsx`
|
||||
- Create: `apps/frontend/src/pages/broker/BrokerAllocationBar.tsx`
|
||||
- Create: `apps/frontend/src/pages/broker/BrokerAccountCard.tsx`
|
||||
- Create: `apps/frontend/src/pages/broker/BrokerAccountsPage.test.tsx`
|
||||
- Modify: `apps/frontend/src/pages/broker/BrokerAccountsPage.tsx`
|
||||
- Modify: `apps/frontend/src/pages/broker/BrokerPages.test.tsx`
|
||||
|
||||
- [ ] **Step 1: Написать page/component tests до реализации**
|
||||
|
||||
Mock `useBrokerAccounts` и `useBrokerAccountPortfolios`. Проверить отдельными тестами:
|
||||
|
||||
- heading, агрегированную сумму, дневной результат и две карточки;
|
||||
- подписи `Брокерский счёт` и `ИИС`, форматированную дату открытия и отсутствие ID/raw enum;
|
||||
- href всей карточки `/broker/:encodedAccountId`;
|
||||
- skeleton при загрузке списка;
|
||||
- пустое состояние при `accounts: []`;
|
||||
- частичную сводку `Доступно по 1 из 2 счетов`;
|
||||
- локальный alert и кнопку `Повторить` для ошибочного query;
|
||||
- вызов `query.refetch()` по кнопке retry;
|
||||
- раздельное отображение RUB и USD без суммирования.
|
||||
|
||||
Для денежных assertions использовать regexp с обычным и non-breaking space, например:
|
||||
|
||||
```ts
|
||||
expect(screen.getByText(/3[\s\u00a0]?300[\s\u00a0]?₽/)).toBeInTheDocument();
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Запустить page-тест и подтвердить RED**
|
||||
|
||||
Run: `npx vitest run src/pages/broker/BrokerAccountsPage.test.tsx -w apps/frontend`
|
||||
|
||||
Expected: FAIL, потому что новые компоненты и состояния отсутствуют.
|
||||
|
||||
- [ ] **Step 3: Реализовать `BrokerAllocationBar`**
|
||||
|
||||
Компонент получает `BrokerAllocationItem[]`, строит сегменты с inline `width: percent%`, добавляет
|
||||
`role="img"`, осмысленный `aria-label` со всеми долями и текстовую легенду. Нулевые сегменты не
|
||||
рендерятся; цвет не является единственным способом различить классы.
|
||||
|
||||
- [ ] **Step 4: Реализовать `BrokerAccountsSummary`**
|
||||
|
||||
Компонент получает aggregate, `loadedCount` и `totalCount`. Он показывает:
|
||||
|
||||
- статус `Совокупный капитал · N счетов` либо `Доступно по N из M счетов`;
|
||||
- по одному блоку стоимости/дневного результата на валюту;
|
||||
- свободные деньги отдельным списком валют;
|
||||
- allocation bar только когда доступна одна portfolio currency;
|
||||
- placeholder `—`, если ни один портфель ещё не загружен.
|
||||
|
||||
- [ ] **Step 5: Реализовать `BrokerAccountCard`**
|
||||
|
||||
Компонент получает `account` и query result. Три ветки должны иметь стабильную геометрию:
|
||||
|
||||
```tsx
|
||||
if (query.isPending) return <BrokerAccountCardSkeleton account={account} />;
|
||||
if (query.error || !query.data) {
|
||||
return <BrokerAccountCardError account={account} onRetry={() => query.refetch()} />;
|
||||
}
|
||||
return <BrokerAccountCardContent account={account} portfolio={query.data} />;
|
||||
```
|
||||
|
||||
Успешная карточка — одна `Link` на `/broker/${encodeURIComponent(account.id)}`. Внутри показать
|
||||
название, тип, дату открытия, стоимость, `yields.daily`, `dailyPercent`, `expectedPercent` и allocation
|
||||
bar из существующего `buildBrokerAllocation(portfolio).sectors`.
|
||||
|
||||
- [ ] **Step 6: Переписать orchestration `BrokerAccountsPage`**
|
||||
|
||||
Страница должна:
|
||||
|
||||
1. вызвать `useBrokerAccounts()`;
|
||||
2. передать `accounts ?? []` в `useBrokerAccountPortfolios` без условного вызова hooks;
|
||||
3. построить aggregate только из `query.data` успешных записей;
|
||||
4. показать page error только при ошибке списка;
|
||||
5. показать отдельное empty state для пустого списка;
|
||||
6. отрендерить summary и вертикальный список карточек.
|
||||
|
||||
- [ ] **Step 7: Удалить устаревший тест из `BrokerPages.test.tsx`**
|
||||
|
||||
Удалить test case `renders broker and IIS accounts` и неиспользуемые imports `accountHook` и
|
||||
`BrokerAccountsPage`; покрытие новой страницы живёт в `BrokerAccountsPage.test.tsx`.
|
||||
|
||||
- [ ] **Step 8: Запустить component tests и подтвердить GREEN**
|
||||
|
||||
Run: `npx vitest run src/pages/broker/BrokerAccountsPage.test.tsx src/pages/broker/BrokerPages.test.tsx -w apps/frontend`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add apps/frontend/src/pages/broker/BrokerAccountsPage.tsx apps/frontend/src/pages/broker/BrokerAccountsPage.test.tsx apps/frontend/src/pages/broker/BrokerAccountsSummary.tsx apps/frontend/src/pages/broker/BrokerAccountCard.tsx apps/frontend/src/pages/broker/BrokerAllocationBar.tsx apps/frontend/src/pages/broker/BrokerPages.test.tsx
|
||||
git commit -m "feat: add informative broker accounts overview"
|
||||
```
|
||||
|
||||
### Task 4: Визуальная система и responsive QA
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `apps/frontend/src/styles.css`
|
||||
- Modify: `apps/frontend/src/pages/broker/BrokerAccountsPage.test.tsx`
|
||||
|
||||
- [ ] **Step 1: Добавить семантические CSS-классы**
|
||||
|
||||
Добавить блоки `.broker-accounts`, `__header`, `__summary`, `__summary-metrics`, `__list`,
|
||||
`.broker-account-card`, `__topline`, `__value`, `__metrics`, `.broker-allocation-bar`, `__track`,
|
||||
`__legend`, `__error` и `__empty`.
|
||||
|
||||
Визуальное направление:
|
||||
|
||||
- тёплый нейтральный фон страницы и глубокий зелёный summary без градиента;
|
||||
- serif-акцент только для крупных денежных значений, основной текст наследует текущую гарнитуру;
|
||||
- тонкие границы и мягкая тень карточек, без вложенных «карточек в карточке»;
|
||||
- один заметный hover карточки: небольшой подъём и усиление тени;
|
||||
- `:focus-visible` с контрастным outline;
|
||||
- positive/negative цвета всегда сопровождаются знаком и текстом;
|
||||
- `prefers-reduced-motion: reduce` отключает transform/transition.
|
||||
|
||||
- [ ] **Step 2: Добавить responsive rules**
|
||||
|
||||
При `max-width: 720px` summary metrics и card metrics переходят в одну колонку, легенда allocation
|
||||
переносится, денежные значения уменьшаются через `clamp()`, а карточка и кнопка retry сохраняют
|
||||
минимальную интерактивную высоту 44px. Горизонтальный overflow на `.broker-accounts` запрещён.
|
||||
|
||||
- [ ] **Step 3: Запустить frontend проверки**
|
||||
|
||||
Run: `npm run test -w apps/frontend`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
Run: `npm run lint -w apps/frontend`
|
||||
|
||||
Expected: PASS без warnings.
|
||||
|
||||
Run: `npm run build -w apps/frontend`
|
||||
|
||||
Expected: успешный TypeScript и Vite build.
|
||||
|
||||
- [ ] **Step 4: Проверить страницу в локальном браузере**
|
||||
|
||||
Запустить frontend и backend по README. Проверить `/broker` при ширинах 1280px и 390px:
|
||||
|
||||
- нет горизонтального overflow;
|
||||
- summary визуально доминирует, но карточки остаются читаемыми;
|
||||
- карточки и retry доступны с клавиатуры;
|
||||
- loading не меняет геометрию страницы;
|
||||
- partial error не скрывает успешные счета;
|
||||
- названия, крупные суммы и allocation legend не перекрываются.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add apps/frontend/src/styles.css apps/frontend/src/pages/broker/BrokerAccountsPage.test.tsx
|
||||
git commit -m "style: polish broker accounts overview"
|
||||
```
|
||||
|
||||
### Task 5: Финальная документация и Definition of Done
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/features/broker-accounts-overview/tasks.md`
|
||||
- Modify: `docs/roadmap.md`
|
||||
|
||||
- [ ] **Step 1: Отметить выполненные tasks**
|
||||
|
||||
Поставить `[x]` только после соответствующих commit и проверок. Не менять ADR-012: решение уже
|
||||
Accepted и реализация ему соответствует.
|
||||
|
||||
- [ ] **Step 2: Обновить roadmap**
|
||||
|
||||
Изменить строку фичи на:
|
||||
|
||||
```md
|
||||
- [x] [Информативный обзор брокерских счетов](features/broker-accounts-overview/spec.md) — реализовано.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Выполнить полный verification gate**
|
||||
|
||||
Run: `npm run test -w apps/frontend && npm run lint -w apps/frontend && npm run build -w apps/frontend`
|
||||
|
||||
Expected: все команды завершаются с exit code 0.
|
||||
|
||||
Run: `npm run build -w apps/docs`
|
||||
|
||||
Expected: Docusaurus build succeeds.
|
||||
|
||||
- [ ] **Step 4: Провести code review**
|
||||
|
||||
Использовать `superpowers:requesting-code-review`. Исправить замечания только после технической
|
||||
проверки; при изменении поведения сначала синхронизировать spec/plan.
|
||||
|
||||
- [ ] **Step 5: Финальный commit документации**
|
||||
|
||||
```bash
|
||||
git add docs/features/broker-accounts-overview/tasks.md docs/roadmap.md
|
||||
git commit -m "docs: complete broker accounts overview"
|
||||
```
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- Все Acceptance Criteria из `spec.md` имеют component или unit coverage.
|
||||
- Frontend tests, lint и build проходят.
|
||||
- Docusaurus build проходит.
|
||||
- Desktop и mobile UI проверены в браузере.
|
||||
- `tasks.md` и roadmap соответствуют реализации.
|
||||
- Нет изменений backend, OpenAPI или `apps/frontend/src/api/types.ts`.
|
||||
- Нет незавершённых маркеров или отложенных требований.
|
||||
40
docs/features/broker-accounts-overview/tasks.md
Normal file
40
docs/features/broker-accounts-overview/tasks.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Информативный обзор брокерских счетов — задачи
|
||||
|
||||
Статус: готово к реализации
|
||||
|
||||
Подробные шаги, команды и ожидаемые результаты находятся в [plan.md](plan.md).
|
||||
|
||||
## 1. Агрегация
|
||||
|
||||
- [ ] Добавить чистую валютно-безопасную агрегацию портфелей.
|
||||
- [ ] Покрыть формулу дневного процента и edge cases unit-тестами.
|
||||
- [ ] Не смешивать валюты и не выполнять неявную конвертацию.
|
||||
|
||||
## 2. Загрузка данных
|
||||
|
||||
- [ ] Добавить параллельные portfolio queries для списка счетов.
|
||||
- [ ] Переиспользовать query keys детальной страницы.
|
||||
- [ ] Проверить независимое завершение запросов и кеш.
|
||||
|
||||
## 3. Интерфейс
|
||||
|
||||
- [ ] Добавить общую сводку по успешно загруженным счетам.
|
||||
- [ ] Добавить информативную карточку счёта и allocation bar.
|
||||
- [ ] Добавить skeleton, empty state и локальную ошибку с retry.
|
||||
- [ ] Убрать технический ID и сырые T-Bank enum-значения.
|
||||
- [ ] Обеспечить keyboard navigation и текстовые признаки доходности.
|
||||
|
||||
## 4. Визуальная проверка
|
||||
|
||||
- [ ] Реализовать согласованное зелёно-нейтральное визуальное направление.
|
||||
- [ ] Проверить desktop 1280px и mobile 390px без horizontal overflow.
|
||||
- [ ] Проверить loading и partial-error states в браузере.
|
||||
|
||||
## 5. Definition of Done
|
||||
|
||||
- [ ] Frontend tests проходят.
|
||||
- [ ] Frontend lint проходит.
|
||||
- [ ] Frontend build проходит.
|
||||
- [ ] Docusaurus build проходит.
|
||||
- [ ] Code review завершён.
|
||||
- [ ] Roadmap отмечает фичу реализованной.
|
||||
Loading…
x
Reference in New Issue
Block a user