diff --git a/apps/docs/docs/frontend/overview.md b/apps/docs/docs/frontend/overview.md index 19c161f..46db3ab 100644 --- a/apps/docs/docs/frontend/overview.md +++ b/apps/docs/docs/frontend/overview.md @@ -14,9 +14,8 @@ React SPA, собранная с Vite. ## Структура исходников -Published-структура ниже описывает primary FSD entrypoints. Historical каталоги `api/`, `context/`, -`hooks/`, `components/` ещё присутствуют в кодовой базе, но для уже мигрированных областей они всё -чаще играют роль transitional shim-слоя, а не source of truth. +Published-структура ниже описывает primary FSD entrypoints. Исторические каталоги `api/`, `context/`, +`hooks/`, `components/` ещё присутствуют в кодовой базе для ещё не мигрированных доменов. ``` apps/frontend/src/ @@ -38,54 +37,63 @@ apps/frontend/src/ ├── shared/ │ ├── api/ # shared API client, response types, generated OpenAPI types │ └── ui/ # shared UI primitives without domain logic -├── entities/ -│ ├── session/ # FSD: auth/session (api, model/context + hook) -│ ├── search/ # FSD: market search query slice -│ ├── stock/ # FSD: stock api + query hooks -│ ├── bond/ # FSD: bond api + query hooks -│ ├── portfolio/ # FSD: portfolio api + query hooks -│ ├── broker-account/ # FSD: account slice (api, model, ui) -│ ├── broker-position/ # FSD: position slice (api, model) -│ └── broker-operation/ # FSD: operation slice (api, model) -├── widgets/ -│ ├── search-bar/ # FSD: instrument search widget -│ ├── price-chart/ # FSD: market chart widget -│ ├── stock-details/ # FSD: stock details widget -│ ├── bond-details/ # FSD: bond details widget -│ ├── dividends-table/ # FSD: stock dividends widget -│ ├── broker-account-card/ # FSD: account card widget -│ ├── broker-accounts-summary/ # FSD: accounts summary widget -│ ├── broker-allocation-chart/ # FSD: allocation chart widget -│ └── broker-operations-table/ # FSD: operations table widget -├── pages/ -│ ├── HomePage.tsx # re-export shim → pages/home -│ ├── StockPage.tsx # re-export shim → pages/stock -│ ├── BondPage.tsx # re-export shim → pages/bond -│ ├── home/ # FSD: home page entrypoint -│ ├── stock/ # FSD: stock page entrypoint -│ ├── bond/ # FSD: bond page entrypoint -│ ├── LoginPage.tsx -│ ├── RegisterPage.tsx -│ ├── ProfilePage.tsx -│ ├── portfolios/ -│ ├── screener/ +├── entities/ # FSD business entities +│ ├── session/ # Auth/session (api, model) +│ ├── search/ # Market search query slice +│ ├── stock/ # Акции (api, model) +│ ├── bond/ # Облигации (api, model) +│ ├── portfolio/ # Портфели (api, model) +│ ├── broker-account/ # Брокерский счёт (api, model, ui) +│ ├── broker-position/ # Брокерская позиция (api, model) +│ └── broker-operation/ # Брокерская операция (api, model) +├── widgets/ # FSD compositional widgets +│ ├── search-bar/ # Поиск инструментов +│ ├── price-chart/ # График цены +│ ├── stock-details/ # Детальная карточка акции +│ ├── bond-details/ # Детальная карточка облигации +│ ├── dividends-table/ # Таблица дивидендов +│ ├── broker-account-card/ # Карточка брокерского счёта +│ ├── broker-accounts-summary/ # Сводка брокерских счетов +│ ├── broker-allocation-chart/ # График распределения +│ ├── broker-operations-table/ # Таблица операций +│ ├── portfolio-card/ # Карточка портфеля +│ ├── portfolio-form/ # Форма портфеля +│ ├── portfolio-summary/ # Сводка портфеля +│ ├── portfolio-analytics/ # Метрики портфеля +│ ├── share-positions-table/ # Таблица позиций-акций +│ └── bond-positions-table/ # Таблица позиций-облигаций +├── pages/ # FSD page entrypoints +│ ├── home/ # Главная +│ ├── stock/ # Страница акции +│ ├── bond/ # Страница облигации +│ ├── LoginPage.tsx # Вход (не мигрирован) +│ ├── RegisterPage.tsx # Регистрация (не мигрирован) +│ ├── ProfilePage.tsx # Профиль (не мигрирован) +│ ├── portfolios/ # Список и детальная портфеля (не мигрирован) +│ ├── screener/ # Скринер (не мигрирован) │ ├── broker-accounts/ # FSD: page entrypoint │ ├── broker-account/ # FSD: page entrypoint │ ├── broker-positions/ # FSD: page entrypoint │ └── broker-operations/ # FSD: page entrypoint +├── api/ +│ └── screener.ts # Screener API helpers (не мигрирован) +├── hooks/ +│ └── useScreener.ts # Screener hook (не мигрирован) +├── components/ +│ └── screener/ # Screener UI (не мигрирован) ├── test/ +│ ├── factories.ts # Фабрики тестовых данных │ ├── handlers.ts # MSW handlers -│ └── test-utils.tsx -├── api/ # historical paths + compatibility shims around shared/entities -├── context/ # historical compatibility layer -├── hooks/ # historical compatibility layer -└── components/ # historical compatibility layer +│ ├── server.ts # MSW server +│ ├── test-utils.tsx # Обёртка рендера +│ ├── setup.ts # Настройка jsdom +│ └── README.md └── styles.css ``` ## FSD-миграция -### app-слой и session-сущность +### app-слой Создан `app/` слой FSD, в который перенесены инфраструктурные модули: @@ -94,52 +102,50 @@ apps/frontend/src/ - `app/layouts/` — AppLayout (шапка + Outlet) - `app/App.tsx` — BrowserRouter → AppRoutes -Домен auth переведён в `entities/session/`: +### entities -- `entities/session/api/` — login, register, logout, refresh, getMe, updateProfile -- `entities/session/model/` — SessionContext, useSession +Домены бизнес-сущностей переведены в FSD: -Старые файлы (`api/auth.ts`, `hooks/useAuth.ts`, `context/AuthContext.tsx`, `routes.tsx`, `components/Layout.tsx`, `components/ProtectedRoute.tsx`, `App.tsx`) сохранены как ре-экспорт шмы для обратной совместимости. +| Сущность | api | model | ui | +|----------|-----|-------|----| +| `session` | login, register, refresh, logout, getMe, updateProfile | SessionContext, useSession | — | +| `stock` | getStock, getStockCandles, getStockDividends | useStock, useStockCandles, useStockDividends | — | +| `bond` | getBond, getBondCandles | useBond, useBondCandles | — | +| `portfolio` | CRUD портфелей и позиций, аналитика | usePortfolio, usePortfolios, usePortfolioAnalytics, usePortfolioMutations, usePositionMutations | — | +| `search` | — | useSearch | — | +| `broker-account` | API брокерских счетов | useBrokerAccounts, useBrokerAccountPortfolios | BrokerAccountLayout | +| `broker-position` | API брокерских позиций | useBrokerPositions | — | +| `broker-operation` | API брокерских операций | useBrokerOperations | — | -### broker-домен (пилот) +Каждая сущность имеет barrel-файл `index.ts`, реэкспортирующий публичное API. -Broker-домен переведён в пилотный FSD срез: +### widgets -- `pages/broker-*` — тонкие route entrypoints -- `widgets/broker-*` — screen-level композиция -- `entities/broker-*` — доменные срезы (api, model, ui) +Композиционные блоки UI. Каждый виджет — директория с `index.ts` и `ui/`: -### market pages (следующая итерация) +- `search-bar` — поиск с автодополнением +- `price-chart` — график цены (lightweight-charts) +- `stock-details`, `bond-details` — детальные карточки бумаг +- `dividends-table` — таблица дивидендов +- `broker-*-*` — UI брокерского домена +- `portfolio-card`, `portfolio-form`, `portfolio-summary`, `portfolio-analytics` — UI портфелей +- `share-positions-table`, `bond-positions-table` — таблицы позиций -После broker pilot market-раздел также переведён на FSD entrypoints: +### pages -- `pages/home`, `pages/stock`, `pages/bond` — primary route entrypoints; -- `widgets/search-bar`, `widgets/price-chart`, `widgets/stock-details`, - `widgets/bond-details`, `widgets/dividends-table` — primary UI entrypoints; -- `entities/search` — search query slice; -- `entities/stock` и `entities/bond` — query hooks и domain API для market pages. +- `pages/home`, `pages/stock`, `pages/bond` — FSD page entrypoints для market pages +- `pages/broker-*` — FSD page entrypoints для брокерского домена +- Временно не мигрированы: `portfolios/`, `screener/`, `LoginPage.tsx`, `RegisterPage.tsx`, `ProfilePage.tsx` -При этом orchestration остаётся в page-layer: route params, вычисление диапазонов дат, загрузка -данных и loading/not-found состояния не переносятся в widgets. +### Не мигрировано -### Остальные домены +Скринер остаётся в исторической структуре: -`screener` в основном остаётся в historical технической структуре. `portfolio` находится в -промежуточном состоянии: page-level структура ещё не полностью унифицирована, но domain API/model -слой уже вынесен в `entities/portfolio`. +- `api/screener.ts` — API-вызовы +- `hooks/useScreener.ts` — логика с URLSearchParams +- `components/screener/` — FilterPanel, FilterPanelBond, FilterPanelShare, ScreenerTable +- `pages/screener/ScreenerPage.tsx` — страница -### Legacy coexistence +Страницы аутентификации (`LoginPage`, `RegisterPage`, `ProfilePage`) — плоские, но используют FSD-импорты из `entities/session`. -После app/session, broker и market migration в проекте ещё остаются top-level `api/`, `context/`, -`hooks/`, `components/` и плоские `pages/*.tsx`. Для уже мигрированных зон они служат главным -образом как compatibility shim-слой: - -- `components/SearchBar.tsx` → `widgets/search-bar` -- `components/PriceChart.tsx` → `widgets/price-chart` -- `components/StockDetails.tsx` → `widgets/stock-details` -- `components/BondDetails.tsx` → `widgets/bond-details` -- `hooks/useSearch.ts` → `entities/search` -- `pages/HomePage.tsx`, `pages/StockPage.tsx`, `pages/BondPage.tsx` → новые page entrypoints - -Published docs описывают primary FSD entrypoints; legacy paths остаются временной поверхностью -совместимости. +ESLint-правила на границы импортов FSD пока не введены. diff --git a/apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx b/apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx index 96f2ab6..7ea257c 100644 --- a/apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx +++ b/apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx @@ -5,11 +5,11 @@ import { usePortfolioMutations, usePositionMutations, } from '../../entities/portfolio'; -import { PortfolioForm } from '../../components/portfolios/PortfolioForm'; -import { PortfolioSummary } from '../../components/portfolios/PortfolioSummary'; -import { AnalyticsSummary } from '../../components/portfolios/AnalyticsSummary'; -import { SharePositionTable } from '../../components/portfolios/SharePositionTable'; -import { BondPositionTable } from '../../components/portfolios/BondPositionTable'; +import { PortfolioForm } from '../../widgets/portfolio-form'; +import { PortfolioSummary } from '../../widgets/portfolio-summary'; +import { AnalyticsSummary } from '../../widgets/portfolio-analytics'; +import { SharePositionTable } from '../../widgets/share-positions-table'; +import { BondPositionTable } from '../../widgets/bond-positions-table'; export function PortfolioDetailPage() { const { id } = useParams<{ id: string }>(); diff --git a/apps/frontend/src/pages/portfolios/PortfoliosListPage.tsx b/apps/frontend/src/pages/portfolios/PortfoliosListPage.tsx index 22eb49e..0bd4cc6 100644 --- a/apps/frontend/src/pages/portfolios/PortfoliosListPage.tsx +++ b/apps/frontend/src/pages/portfolios/PortfoliosListPage.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { usePortfolios, usePortfolioMutations } from '../../entities/portfolio'; -import { PortfolioCard } from '../../components/portfolios/PortfolioCard'; -import { PortfolioForm } from '../../components/portfolios/PortfolioForm'; +import { PortfolioCard } from '../../widgets/portfolio-card'; +import { PortfolioForm } from '../../widgets/portfolio-form'; export function PortfoliosListPage() { const { data: portfolios, isLoading, error } = usePortfolios(); diff --git a/apps/frontend/src/widgets/bond-positions-table/index.ts b/apps/frontend/src/widgets/bond-positions-table/index.ts new file mode 100644 index 0000000..add2bda --- /dev/null +++ b/apps/frontend/src/widgets/bond-positions-table/index.ts @@ -0,0 +1 @@ +export { BondPositionTable } from './ui/BondPositionTable'; diff --git a/apps/frontend/src/components/portfolios/BondPositionRow.tsx b/apps/frontend/src/widgets/bond-positions-table/ui/BondPositionRow.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/BondPositionRow.tsx rename to apps/frontend/src/widgets/bond-positions-table/ui/BondPositionRow.tsx diff --git a/apps/frontend/src/components/portfolios/BondPositionTable.tsx b/apps/frontend/src/widgets/bond-positions-table/ui/BondPositionTable.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/BondPositionTable.tsx rename to apps/frontend/src/widgets/bond-positions-table/ui/BondPositionTable.tsx diff --git a/apps/frontend/src/widgets/portfolio-analytics/index.ts b/apps/frontend/src/widgets/portfolio-analytics/index.ts new file mode 100644 index 0000000..e89a5f5 --- /dev/null +++ b/apps/frontend/src/widgets/portfolio-analytics/index.ts @@ -0,0 +1 @@ +export { AnalyticsSummary } from './ui/AnalyticsSummary'; diff --git a/apps/frontend/src/components/portfolios/AnalyticsSummary.tsx b/apps/frontend/src/widgets/portfolio-analytics/ui/AnalyticsSummary.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/AnalyticsSummary.tsx rename to apps/frontend/src/widgets/portfolio-analytics/ui/AnalyticsSummary.tsx diff --git a/apps/frontend/src/widgets/portfolio-card/index.ts b/apps/frontend/src/widgets/portfolio-card/index.ts new file mode 100644 index 0000000..e2914ab --- /dev/null +++ b/apps/frontend/src/widgets/portfolio-card/index.ts @@ -0,0 +1 @@ +export { PortfolioCard } from './ui/PortfolioCard'; diff --git a/apps/frontend/src/components/portfolios/PortfolioCard.tsx b/apps/frontend/src/widgets/portfolio-card/ui/PortfolioCard.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/PortfolioCard.tsx rename to apps/frontend/src/widgets/portfolio-card/ui/PortfolioCard.tsx diff --git a/apps/frontend/src/widgets/portfolio-form/index.ts b/apps/frontend/src/widgets/portfolio-form/index.ts new file mode 100644 index 0000000..a030012 --- /dev/null +++ b/apps/frontend/src/widgets/portfolio-form/index.ts @@ -0,0 +1 @@ +export { PortfolioForm } from './ui/PortfolioForm'; diff --git a/apps/frontend/src/components/portfolios/PortfolioForm.tsx b/apps/frontend/src/widgets/portfolio-form/ui/PortfolioForm.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/PortfolioForm.tsx rename to apps/frontend/src/widgets/portfolio-form/ui/PortfolioForm.tsx diff --git a/apps/frontend/src/widgets/portfolio-summary/index.ts b/apps/frontend/src/widgets/portfolio-summary/index.ts new file mode 100644 index 0000000..aea75ad --- /dev/null +++ b/apps/frontend/src/widgets/portfolio-summary/index.ts @@ -0,0 +1 @@ +export { PortfolioSummary } from './ui/PortfolioSummary'; diff --git a/apps/frontend/src/components/portfolios/AllocationChart.tsx b/apps/frontend/src/widgets/portfolio-summary/ui/AllocationChart.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/AllocationChart.tsx rename to apps/frontend/src/widgets/portfolio-summary/ui/AllocationChart.tsx diff --git a/apps/frontend/src/components/portfolios/PortfolioSummary.tsx b/apps/frontend/src/widgets/portfolio-summary/ui/PortfolioSummary.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/PortfolioSummary.tsx rename to apps/frontend/src/widgets/portfolio-summary/ui/PortfolioSummary.tsx diff --git a/apps/frontend/src/widgets/share-positions-table/index.ts b/apps/frontend/src/widgets/share-positions-table/index.ts new file mode 100644 index 0000000..59ee3d8 --- /dev/null +++ b/apps/frontend/src/widgets/share-positions-table/index.ts @@ -0,0 +1 @@ +export { SharePositionTable } from './ui/SharePositionTable'; diff --git a/apps/frontend/src/components/portfolios/SharePositionRow.tsx b/apps/frontend/src/widgets/share-positions-table/ui/SharePositionRow.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/SharePositionRow.tsx rename to apps/frontend/src/widgets/share-positions-table/ui/SharePositionRow.tsx diff --git a/apps/frontend/src/components/portfolios/SharePositionTable.tsx b/apps/frontend/src/widgets/share-positions-table/ui/SharePositionTable.tsx similarity index 100% rename from apps/frontend/src/components/portfolios/SharePositionTable.tsx rename to apps/frontend/src/widgets/share-positions-table/ui/SharePositionTable.tsx diff --git a/docs/features/frontend-fsd-portfolios/plan.md b/docs/features/frontend-fsd-portfolios/plan.md new file mode 100644 index 0000000..aae9aad --- /dev/null +++ b/docs/features/frontend-fsd-portfolios/plan.md @@ -0,0 +1,294 @@ +# Frontend FSD — Portfolio Pages Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate portfolio UI components from flat `components/portfolios/` to `widgets/` per FSD, following the broker domain pattern. + +**Architecture:** 6 widgets created under `widgets/`, each with `index.ts` barrel + `ui/Component.tsx`. Pages in `pages/portfolios/` switch imports to widgets. `components/portfolios/` deleted. `entities/portfolio/` unchanged. + +**Tech Stack:** React 18, TypeScript, Vite, Vitest + +--- + +### Task 1: Create widget directory structure and barrel files + +**Files:** +- Create: `apps/frontend/src/widgets/portfolio-card/index.ts` +- Create: `apps/frontend/src/widgets/portfolio-form/index.ts` +- Create: `apps/frontend/src/widgets/portfolio-summary/index.ts` +- Create: `apps/frontend/src/widgets/portfolio-analytics/index.ts` +- Create: `apps/frontend/src/widgets/share-positions-table/index.ts` +- Create: `apps/frontend/src/widgets/bond-positions-table/index.ts` +- Create: `apps/frontend/src/widgets/portfolio-card/ui/PortfolioCard.tsx` +- Create: `apps/frontend/src/widgets/portfolio-form/ui/PortfolioForm.tsx` +- Create: `apps/frontend/src/widgets/portfolio-summary/ui/PortfolioSummary.tsx` +- Create: `apps/frontend/src/widgets/portfolio-summary/ui/AllocationChart.tsx` +- Create: `apps/frontend/src/widgets/portfolio-analytics/ui/AnalyticsSummary.tsx` +- Create: `apps/frontend/src/widgets/share-positions-table/ui/SharePositionTable.tsx` +- Create: `apps/frontend/src/widgets/share-positions-table/ui/SharePositionRow.tsx` +- Create: `apps/frontend/src/widgets/bond-positions-table/ui/BondPositionTable.tsx` +- Create: `apps/frontend/src/widgets/bond-positions-table/ui/BondPositionRow.tsx` + +- [ ] **Step 1: Create all widget barrel files** + +```ts +// widgets/portfolio-card/index.ts +export { PortfolioCard } from './ui/PortfolioCard'; +``` + +```ts +// widgets/portfolio-form/index.ts +export { PortfolioForm } from './ui/PortfolioForm'; +``` + +```ts +// widgets/portfolio-summary/index.ts +export { PortfolioSummary } from './ui/PortfolioSummary'; +``` + +```ts +// widgets/portfolio-analytics/index.ts +export { AnalyticsSummary } from './ui/AnalyticsSummary'; +``` + +```ts +// widgets/share-positions-table/index.ts +export { SharePositionTable } from './ui/SharePositionTable'; +``` + +```ts +// widgets/bond-positions-table/index.ts +export { BondPositionTable } from './ui/BondPositionTable'; +``` + +- [ ] **Step 2: Copy and update imports for each component** + +For each component from `components/portfolios/`, copy to `widgets//ui/.tsx` and fix imports: + +**PortfolioCard.tsx** (copy from `components/portfolios/PortfolioCard.tsx`, no import changes needed): +```tsx +// Same content as original — imports @/shared/api/responses which stays valid +``` + +**PortfolioForm.tsx** (copy from `components/portfolios/PortfolioForm.tsx`, no import changes): +```tsx +// Same content — imports @/shared/api/responses +``` + +**PortfolioSummary.tsx** (copy from `components/portfolios/PortfolioSummary.tsx`, update import path): +```tsx +import { AllocationChart } from './AllocationChart'; +import type { PortfolioDetail } from '@/shared/api/responses'; +// rest identical to original +``` + +**AllocationChart.tsx** (copy from `components/portfolios/AllocationChart.tsx`, no import changes): +```tsx +// Same content — imports @/shared/api/responses +``` + +**AnalyticsSummary.tsx** (copy from `components/portfolios/AnalyticsSummary.tsx`, no import changes): +```tsx +// Same content — imports @/shared/api/responses +``` + +**SharePositionRow.tsx** (copy from `components/portfolios/SharePositionRow.tsx`, no import changes): +```tsx +// Same content — imports @/shared/api/responses +``` + +**SharePositionTable.tsx** (copy from `components/portfolios/SharePositionTable.tsx`, update import path): +```tsx +import { SharePositionRow } from './SharePositionRow'; +import type { PositionWithPrice } from '@/shared/api/responses'; +// rest identical to original +``` + +**BondPositionRow.tsx** (copy from `components/portfolios/BondPositionRow.tsx`, no import changes): +```tsx +// Same content — imports @/shared/api/responses +``` + +**BondPositionTable.tsx** (copy from `components/portfolios/BondPositionTable.tsx`, update import path): +```tsx +import { BondPositionRow } from './BondPositionRow'; +import type { PositionWithPrice } from '@/shared/api/responses'; +// rest identical to original +``` + +- [ ] **Step 3: Verify build** + +Run: `npm run build -w apps/frontend` +Expected: PASS + +- [ ] **Step 4: Run tests** + +Run: `npm test -w apps/frontend` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add apps/frontend/src/widgets/portfolio-card/ \ + apps/frontend/src/widgets/portfolio-form/ \ + apps/frontend/src/widgets/portfolio-summary/ \ + apps/frontend/src/widgets/portfolio-analytics/ \ + apps/frontend/src/widgets/share-positions-table/ \ + apps/frontend/src/widgets/bond-positions-table/ +git commit -m "feat(frontend): create portfolio widgets in FSD structure" +``` + +--- + +### Task 2: Update pages to use widgets + +**Files:** +- Modify: `apps/frontend/src/pages/portfolios/PortfoliosListPage.tsx` +- Modify: `apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx` + +- [ ] **Step 1: Update PortfoliosListPage.tsx** + +```tsx +import { useState } from 'react'; +import { usePortfolios, usePortfolioMutations } from '../../entities/portfolio'; +import { PortfolioCard } from '../../widgets/portfolio-card'; +import { PortfolioForm } from '../../widgets/portfolio-form'; +``` + +- [ ] **Step 2: Update PortfolioDetailPage.tsx** + +```tsx +import { useState } from 'react'; +import { useParams, Link } from 'react-router-dom'; +import { + usePortfolio, + usePortfolioMutations, + usePositionMutations, +} from '../../entities/portfolio'; +import { PortfolioForm } from '../../widgets/portfolio-form'; +import { PortfolioSummary } from '../../widgets/portfolio-summary'; +import { AnalyticsSummary } from '../../widgets/portfolio-analytics'; +import { SharePositionTable } from '../../widgets/share-positions-table'; +import { BondPositionTable } from '../../widgets/bond-positions-table'; +``` + +- [ ] **Step 3: Verify build** + +Run: `npm run build -w apps/frontend` +Expected: PASS + +- [ ] **Step 4: Run tests** + +Run: `npm test -w apps/frontend` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add apps/frontend/src/pages/portfolios/ +git commit -m "refactor(frontend): switch pages/portfolios to widget imports" +``` + +--- + +### Task 3: Remove flat components/portfolios/ + +**Files:** +- Delete: `apps/frontend/src/components/portfolios/AllocationChart.tsx` +- Delete: `apps/frontend/src/components/portfolios/AnalyticsSummary.tsx` +- Delete: `apps/frontend/src/components/portfolios/BondPositionRow.tsx` +- Delete: `apps/frontend/src/components/portfolios/BondPositionTable.tsx` +- Delete: `apps/frontend/src/components/portfolios/PortfolioCard.tsx` +- Delete: `apps/frontend/src/components/portfolios/PortfolioForm.tsx` +- Delete: `apps/frontend/src/components/portfolios/PortfolioSummary.tsx` +- Delete: `apps/frontend/src/components/portfolios/SharePositionRow.tsx` +- Delete: `apps/frontend/src/components/portfolios/SharePositionTable.tsx` + +- [ ] **Step 1: Delete all files in components/portfolios/** + +Run: `rm apps/frontend/src/components/portfolios/*.tsx` + +- [ ] **Step 2: Check for remaining imports from components/portfolios** + +Run: `rg "components/portfolios" apps/frontend/src` +Expected: no matches + +- [ ] **Step 3: Verify build** + +Run: `npm run build -w apps/frontend` +Expected: PASS + +- [ ] **Step 4: Run tests** + +Run: `npm test -w apps/frontend` +Expected: PASS + +- [ ] **Step 5: Lint check** + +Run: `npm run lint -w apps/frontend` +Expected: PASS + +- [ ] **Step 6: Commit** + +```bash +git rm apps/frontend/src/components/portfolios/*.tsx +git commit -m "refactor(frontend): remove flat components/portfolios" +``` + +--- + +### Task 4: Update documentation + +**Files:** +- Modify: `apps/docs/docs/frontend/hooks.md` +- Modify: `apps/docs/docs/frontend/routes.md` + +- [ ] **Step 1: Update hooks.md** + +Remove any references to legacy `components/portfolios/` paths. +Update import examples to use `@/widgets/portfolio-*` paths where applicable. + +- [ ] **Step 2: Update routes.md** + +Ensure portfolio route documentation reflects FSD structure. + +- [ ] **Step 3: Run doc build** + +Run: `npm run build -w apps/docs` +Expected: PASS + +- [ ] **Step 4: Commit documentation** + +```bash +git add apps/docs/docs/frontend/ +git commit -m "docs: update frontend docs for portfolio FSD structure" +``` + +--- + +### Task 5: Final verification + +- [ ] **Step 1: Test** + +Run: `npm test -w apps/frontend` +Expected: PASS + +- [ ] **Step 2: Lint** + +Run: `npm run lint -w apps/frontend` +Expected: PASS + +- [ ] **Step 3: Build** + +Run: `npm run build -w apps/frontend` +Expected: PASS + +- [ ] **Step 4: Deep import check** + +Run: `rg "components/portfolios" apps/frontend/src` +Expected: no matches + +- [ ] **Step 5: Docs build** + +Run: `npm run build -w apps/docs` +Expected: PASS diff --git a/docs/features/frontend-fsd-portfolios/spec.md b/docs/features/frontend-fsd-portfolios/spec.md new file mode 100644 index 0000000..f0165be --- /dev/null +++ b/docs/features/frontend-fsd-portfolios/spec.md @@ -0,0 +1,82 @@ +# Frontend FSD — Portfolio Pages + +Дата: 2026-06-20 +Статус: выполнено + +## Контекст + +FSD-миграция фронтенда выполнена для большинства доменов. Остались 3 домена, +намеренно не затронутые финальной cleanup-фазой: screener, portfolios, auth pages. + +Домен портфелей — второй по величине: entities/portfolio уже мигрирован в FSD, +но UI-компоненты остаются в плоской `components/portfolios/`, а страницы — +в `pages/portfolios/` с импортами из плоской структуры. + +## Цель + +Мигрировать portfolio pages и сопутствующие UI-компоненты в FSD, следуя тому же +паттерну, что и broker domain (entities → widgets → pages). + +## Требования + +1. Все UI-компоненты из `components/portfolios/` размещаются в `widgets/` по принципу + «один виджет — одна композиционная единица» +2. Страницы в `pages/portfolios/` переключаются на импорты из виджетов +3. Никакие внутренние компоненты виджетов не импортируются напрямую из других слоёв — + только через barrel-файл виджета +4. API-хуки и бизнес-логика остаются в `entities/portfolio/` — не дублируются +5. `components/portfolios/` удаляется после миграции всех потребителей + +## Область изменений + +### Мигрируемые файлы + +Из `components/portfolios/` в `widgets/`: + +- `AllocationChart.tsx` → внутренний компонент `widgets/portfolio-summary/ui/` +- `AnalyticsSummary.tsx` → `widgets/portfolio-analytics/ui/AnalyticsSummary.tsx` +- `BondPositionRow.tsx` → внутренний компонент `widgets/bond-positions-table/ui/` +- `BondPositionTable.tsx` → `widgets/bond-positions-table/ui/BondPositionTable.tsx` +- `PortfolioCard.tsx` → `widgets/portfolio-card/ui/PortfolioCard.tsx` +- `PortfolioForm.tsx` → `widgets/portfolio-form/ui/PortfolioForm.tsx` +- `PortfolioSummary.tsx` → `widgets/portfolio-summary/ui/PortfolioSummary.tsx` +- `SharePositionRow.tsx` → внутренний компонент `widgets/share-positions-table/ui/` +- `SharePositionTable.tsx` → `widgets/share-positions-table/ui/SharePositionTable.tsx` + +Обновляемые страницы: + +- `pages/portfolios/PortfoliosListPage.tsx` — импорты из `../../components/portfolios/*` → `@/widgets/portfolio-*` +- `pages/portfolios/PortfolioDetailPage.tsx` — импорты из `../../components/portfolios/*` → `@/widgets/portfolio-*` + +### Удаляемые файлы + +- `components/portfolios/` — весь каталог (9 файлов) + +## Ограничения + +- Изменения ограничены frontend-пакетом. +- `entities/portfolio/` не изменяется (уже FSD). +- Не мигрируется screener и auth pages. +- Не меняется поведение UI, API-контракты, роутинг. +- Не вводятся ESLint import boundaries. + +## Список виджетов + +| Виджет | Компоненты | Назначение | +|--------|-----------|------------| +| `portfolio-card` | PortfolioCard | Карточка портфеля для списка | +| `portfolio-form` | PortfolioForm | Форма создания/редактирования | +| `portfolio-summary` | PortfolioSummary, AllocationChart | Сводка: график + общая стоимость | +| `portfolio-analytics` | AnalyticsSummary | Метрики: инвестировано, P&L, доходность | +| `share-positions-table` | SharePositionTable, SharePositionRow | Таблица позиций-акций | +| `bond-positions-table` | BondPositionTable, BondPositionRow | Таблица позиций-облигаций | + +## Acceptance Criteria + +- Все 6 виджетов созданы в `widgets/` по FSD-структуре (index.ts + ui/) +- `components/portfolios/` удалён +- `pages/portfolios/` импортируют только из `@/widgets/*` и `@/entities/portfolio` +- `npm test -w apps/frontend` — PASS +- `npm run lint -w apps/frontend` — PASS +- `npm run build -w apps/frontend` — PASS +- Deep import check: `rg "components/portfolios" apps/frontend/src` — no matches diff --git a/docs/features/frontend-fsd-portfolios/tasks.md b/docs/features/frontend-fsd-portfolios/tasks.md new file mode 100644 index 0000000..ec8b57d --- /dev/null +++ b/docs/features/frontend-fsd-portfolios/tasks.md @@ -0,0 +1,42 @@ +# Frontend FSD — Portfolio Pages Tasks + +Статус: выполнено + +## 1. Create widget directory structure and barrel files + +- [x] Create 6 widget barrels in `widgets/` +- [x] Copy components to `widgets/*/ui/` with updated imports +- [x] `npm run build -w apps/frontend` — PASS +- [x] `npm test -w apps/frontend` — PASS +- [x] Commit `2ab4334` + +## 2. Update pages to use widgets + +- [x] Update `PortfoliosListPage.tsx` imports +- [x] Update `PortfolioDetailPage.tsx` imports +- [x] `npm run build -w apps/frontend` — PASS +- [x] `npm test -w apps/frontend` — PASS +- [x] Commit `0b3c9a9` + +## 3. Remove flat components/portfolios/ + +- [x] Delete `components/portfolios/*.tsx` +- [x] No remaining imports from `components/portfolios` +- [x] `npm run build -w apps/frontend` — PASS +- [x] `npm test -w apps/frontend` — PASS +- [x] `npm run lint -w apps/frontend` — PASS +- [x] Commit `4d6e662` + +## 4. Update documentation + +- [x] Update `apps/docs/docs/frontend/overview.md` +- [x] `npm run build -w apps/docs` — PASS +- [x] Отдельный коммит `697c4d7` + +## 5. Final verification + +- [x] `npm test -w apps/frontend` — PASS (198 tests) +- [x] `npm run lint -w apps/frontend` — PASS +- [x] `npm run build -w apps/frontend` — PASS +- [x] `rg "components/portfolios"` — no matches +- [x] `npm run build -w apps/docs` — PASS