codex/frontend-fsd-portfolios #26
@ -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 пока не введены.
|
||||
|
||||
@ -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 }>();
|
||||
|
||||
@ -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();
|
||||
|
||||
1
apps/frontend/src/widgets/bond-positions-table/index.ts
Normal file
1
apps/frontend/src/widgets/bond-positions-table/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { BondPositionTable } from './ui/BondPositionTable';
|
||||
1
apps/frontend/src/widgets/portfolio-analytics/index.ts
Normal file
1
apps/frontend/src/widgets/portfolio-analytics/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { AnalyticsSummary } from './ui/AnalyticsSummary';
|
||||
1
apps/frontend/src/widgets/portfolio-card/index.ts
Normal file
1
apps/frontend/src/widgets/portfolio-card/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { PortfolioCard } from './ui/PortfolioCard';
|
||||
1
apps/frontend/src/widgets/portfolio-form/index.ts
Normal file
1
apps/frontend/src/widgets/portfolio-form/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { PortfolioForm } from './ui/PortfolioForm';
|
||||
1
apps/frontend/src/widgets/portfolio-summary/index.ts
Normal file
1
apps/frontend/src/widgets/portfolio-summary/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { PortfolioSummary } from './ui/PortfolioSummary';
|
||||
1
apps/frontend/src/widgets/share-positions-table/index.ts
Normal file
1
apps/frontend/src/widgets/share-positions-table/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { SharePositionTable } from './ui/SharePositionTable';
|
||||
294
docs/features/frontend-fsd-portfolios/plan.md
Normal file
294
docs/features/frontend-fsd-portfolios/plan.md
Normal file
@ -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/<name>/ui/<Component>.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
|
||||
82
docs/features/frontend-fsd-portfolios/spec.md
Normal file
82
docs/features/frontend-fsd-portfolios/spec.md
Normal file
@ -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
|
||||
42
docs/features/frontend-fsd-portfolios/tasks.md
Normal file
42
docs/features/frontend-fsd-portfolios/tasks.md
Normal file
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user