diff --git a/apps/frontend/src/app/routing/AppRoutes.tsx b/apps/frontend/src/app/routing/AppRoutes.tsx index b11cf6a..3f38091 100644 --- a/apps/frontend/src/app/routing/AppRoutes.tsx +++ b/apps/frontend/src/app/routing/AppRoutes.tsx @@ -9,7 +9,7 @@ import { RegisterPage } from '@/pages/RegisterPage'; import { ProfilePage } from '@/pages/ProfilePage'; import { PortfoliosListPage } from '@/pages/portfolios/PortfoliosListPage'; import { PortfolioDetailPage } from '@/pages/portfolios/PortfolioDetailPage'; -import { ScreenerPage } from '@/pages/screener/ScreenerPage'; +import { ScreenerPage } from '@/pages/screener'; import { BrokerAccountsPage } from '@/pages/broker-accounts'; import { BrokerAccountLayout } from '@/entities/broker-account/ui/BrokerAccountLayout'; import { BrokerAccountOverviewPage } from '@/pages/broker-account'; diff --git a/apps/frontend/src/api/screener.ts b/apps/frontend/src/features/screener/api/screenerApi.ts similarity index 100% rename from apps/frontend/src/api/screener.ts rename to apps/frontend/src/features/screener/api/screenerApi.ts diff --git a/apps/frontend/src/features/screener/index.ts b/apps/frontend/src/features/screener/index.ts new file mode 100644 index 0000000..174c184 --- /dev/null +++ b/apps/frontend/src/features/screener/index.ts @@ -0,0 +1,3 @@ +export { useScreener } from './model/useScreener'; +export { FilterPanel, FilterPanelShare, FilterPanelBond, ScreenerTable } from './ui'; +export type { ScreenerQuery } from './api/screenerApi'; diff --git a/apps/frontend/src/features/screener/model/index.ts b/apps/frontend/src/features/screener/model/index.ts new file mode 100644 index 0000000..65fee28 --- /dev/null +++ b/apps/frontend/src/features/screener/model/index.ts @@ -0,0 +1 @@ +export { useScreener } from './useScreener'; diff --git a/apps/frontend/src/hooks/useScreener.ts b/apps/frontend/src/features/screener/model/useScreener.ts similarity index 97% rename from apps/frontend/src/hooks/useScreener.ts rename to apps/frontend/src/features/screener/model/useScreener.ts index 457a47e..9eea5a0 100644 --- a/apps/frontend/src/hooks/useScreener.ts +++ b/apps/frontend/src/features/screener/model/useScreener.ts @@ -1,7 +1,7 @@ import { useSearchParams } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; -import { getScreenerResults } from '../api/screener'; -import type { ScreenerQuery } from '../api/screener'; +import { getScreenerResults } from '../api/screenerApi'; +import type { ScreenerQuery } from '../api/screenerApi'; export function useScreener() { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/apps/frontend/src/components/screener/FilterPanel.tsx b/apps/frontend/src/features/screener/ui/FilterPanel.tsx similarity index 98% rename from apps/frontend/src/components/screener/FilterPanel.tsx rename to apps/frontend/src/features/screener/ui/FilterPanel.tsx index fd7e92a..b91b2b8 100644 --- a/apps/frontend/src/components/screener/FilterPanel.tsx +++ b/apps/frontend/src/features/screener/ui/FilterPanel.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import type { ScreenerQuery } from '../../api/screener'; +import type { ScreenerQuery } from '../api/screenerApi'; import { FilterPanelShare } from './FilterPanelShare'; import { FilterPanelBond } from './FilterPanelBond'; diff --git a/apps/frontend/src/components/screener/FilterPanelBond.tsx b/apps/frontend/src/features/screener/ui/FilterPanelBond.tsx similarity index 100% rename from apps/frontend/src/components/screener/FilterPanelBond.tsx rename to apps/frontend/src/features/screener/ui/FilterPanelBond.tsx diff --git a/apps/frontend/src/components/screener/FilterPanelShare.tsx b/apps/frontend/src/features/screener/ui/FilterPanelShare.tsx similarity index 100% rename from apps/frontend/src/components/screener/FilterPanelShare.tsx rename to apps/frontend/src/features/screener/ui/FilterPanelShare.tsx diff --git a/apps/frontend/src/components/screener/ScreenerTable.tsx b/apps/frontend/src/features/screener/ui/ScreenerTable.tsx similarity index 100% rename from apps/frontend/src/components/screener/ScreenerTable.tsx rename to apps/frontend/src/features/screener/ui/ScreenerTable.tsx diff --git a/apps/frontend/src/features/screener/ui/index.ts b/apps/frontend/src/features/screener/ui/index.ts new file mode 100644 index 0000000..75910ff --- /dev/null +++ b/apps/frontend/src/features/screener/ui/index.ts @@ -0,0 +1,4 @@ +export { FilterPanel } from './FilterPanel'; +export { FilterPanelShare } from './FilterPanelShare'; +export { FilterPanelBond } from './FilterPanelBond'; +export { ScreenerTable } from './ScreenerTable'; diff --git a/apps/frontend/src/pages/screener/index.ts b/apps/frontend/src/pages/screener/index.ts new file mode 100644 index 0000000..e0db44a --- /dev/null +++ b/apps/frontend/src/pages/screener/index.ts @@ -0,0 +1 @@ +export { ScreenerPage } from './ui/ScreenerPage'; diff --git a/apps/frontend/src/pages/screener/ScreenerPage.tsx b/apps/frontend/src/pages/screener/ui/ScreenerPage.tsx similarity index 89% rename from apps/frontend/src/pages/screener/ScreenerPage.tsx rename to apps/frontend/src/pages/screener/ui/ScreenerPage.tsx index e0b13c6..bba51bd 100644 --- a/apps/frontend/src/pages/screener/ScreenerPage.tsx +++ b/apps/frontend/src/pages/screener/ui/ScreenerPage.tsx @@ -1,6 +1,4 @@ -import { useScreener } from '../../hooks/useScreener'; -import { FilterPanel } from '../../components/screener/FilterPanel'; -import { ScreenerTable } from '../../components/screener/ScreenerTable'; +import { useScreener, FilterPanel, ScreenerTable } from '@/features/screener'; export function ScreenerPage() { const { params, result, isLoading, error, setFilters, setPage, setSort, resetFilters } = diff --git a/docs/features/frontend-fsd-screener/plan.md b/docs/features/frontend-fsd-screener/plan.md new file mode 100644 index 0000000..ce90f65 --- /dev/null +++ b/docs/features/frontend-fsd-screener/plan.md @@ -0,0 +1,69 @@ +# Frontend FSD Screener — Implementation Plan + +> **For agentic workers:** Implement task-by-task using the tasks.md checklist. Steps use checkbox (`- [ ]`) syntax. + +**Goal:** Перевести скринер в FSD-структуру с созданием первого feature-slice (`features/screener/`). + +**Architecture:** Скринер — пользовательский сценарий (фильтрация + сортировка + пагинация), а не бизнес-сущность, поэтому размещается в `features/` слое. `features/screener/` содержит API, модель (URLSearchParams + TanStack Query) и UI. `pages/screener/` — тонкая композиция. Legacy-файлы удаляются без shim-совместимости. + +**Tech Stack:** React 18, TypeScript, React Router v6, TanStack Query v5, Vitest, ESLint, Vite. + +## Карта файлов + +### Create +- `docs/features/frontend-fsd-screener/spec.md` +- `docs/features/frontend-fsd-screener/plan.md` +- `docs/features/frontend-fsd-screener/tasks.md` +- `apps/frontend/src/features/screener/api/screenerApi.ts` — ScreenerQuery, getScreenerResults +- `apps/frontend/src/features/screener/model/useScreener.ts` — URLSearchParams + TanStack Query +- `apps/frontend/src/features/screener/model/index.ts` — barrel +- `apps/frontend/src/features/screener/ui/FilterPanel.tsx` — фильтры +- `apps/frontend/src/features/screener/ui/FilterPanelShare.tsx` — share-фильтры +- `apps/frontend/src/features/screener/ui/FilterPanelBond.tsx` — bond-фильтры +- `apps/frontend/src/features/screener/ui/ScreenerTable.tsx` — таблица +- `apps/frontend/src/features/screener/ui/index.ts` — barrel +- `apps/frontend/src/features/screener/index.ts` — public API +- `apps/frontend/src/pages/screener/ui/ScreenerPage.tsx` — page entrypoint +- `apps/frontend/src/pages/screener/index.ts` — public API + +### Modify +- `apps/frontend/src/app/routing/AppRoutes.tsx` — import from `@/pages/screener` + +### Delete +- `apps/frontend/src/api/screener.ts` +- `apps/frontend/src/hooks/useScreener.ts` +- `apps/frontend/src/components/screener/FilterPanel.tsx` +- `apps/frontend/src/components/screener/FilterPanelShare.tsx` +- `apps/frontend/src/components/screener/FilterPanelBond.tsx` +- `apps/frontend/src/components/screener/ScreenerTable.tsx` +- `apps/frontend/src/pages/screener/ScreenerPage.tsx` + +## Порядок реализации + +1. Создать `features/screener/api/screenerApi.ts` — перенести `ScreenerQuery` + `getScreenerResults` из `api/screener.ts`, обновить импорты на `@/shared/api/client` +2. Создать `features/screener/model/useScreener.ts` — перенести хук из `hooks/useScreener.ts`, обновить импорты +3. Создать `features/screener/ui/` — перенести 4 компонента из `components/screener/`, обновить импорты +4. Создать barrel-файлы (`model/index.ts`, `ui/index.ts`, `features/screener/index.ts`) +5. Создать `pages/screener/ui/ScreenerPage.tsx` — обновить импорты на `@/features/screener` +6. Создать `pages/screener/index.ts` — barrel export +7. Обновить `AppRoutes.tsx` — `import { ScreenerPage } from '@/pages/screener'` +8. Удалить legacy-файлы +9. Verification: `test`, `lint`, `build` + +## Verification + +```bash +npm test -w apps/frontend +npm run lint -w apps/frontend +npm run build -w apps/frontend + +# Проверка отсутствия deep imports в новые model/ и ui/ +rg -n "@features/screener/(model|ui)" apps/frontend/src --type ts --type tsx || echo "OK: no deep imports from outside" +# Проверка импорта pages/screener только через barrel +rg -n "@/pages/screener/(ui|ScreenerPage)" apps/frontend/src --type ts --type tsx || echo "OK: no deep page imports" + +# Проверка, что legacy удалены +! test -f apps/frontend/src/api/screener.ts || echo "FAIL" +! test -f apps/frontend/src/hooks/useScreener.ts || echo "FAIL" +! test -d apps/frontend/src/components/screener || echo "FAIL" +``` diff --git a/docs/features/frontend-fsd-screener/spec.md b/docs/features/frontend-fsd-screener/spec.md new file mode 100644 index 0000000..21a1420 --- /dev/null +++ b/docs/features/frontend-fsd-screener/spec.md @@ -0,0 +1,49 @@ +# Frontend FSD Screener + +Дата: 2026-06-20 +Статус: спецификация + +## Контекст + +Предыдущие FSD-итерации мигрировали broker-домен, shared-слой, entities, app-слой c auth, market pages и portfolios. + +Скринер остаётся единственным крупным блоком кода вне FSD-структуры: `api/screener.ts`, `hooks/useScreener.ts`, `components/screener/` (4 файла), плоская `pages/screener/ScreenerPage.tsx`. Также это первая возможность создать слой `features/` — первый feature-slice в проекте. + +## Цель + +Перевести скринер в FSD, создав `features/screener/` — первый feature-slice, и мигрировав `pages/screener/` в FSD-структуру. + +## Область изменений + +- `features/screener/` — новый слой; включает API-функцию, хук с URLSearchParams + TanStack Query, UI-компоненты (FilterPanel, FilterPanelShare, FilterPanelBond, ScreenerTable) +- `pages/screener/` — миграция из плоской страницы в FSD с barrel export +- `app/routing/AppRoutes.tsx` — обновление импорта +- Удаление legacy: `api/screener.ts`, `hooks/useScreener.ts`, `components/screener/` (4 файла), `pages/screener/ScreenerPage.tsx` + +## Требования + +### 1. Features слой + +- `features/screener/` содержит полную реализацию скринера: API, модель (URLSearchParams + TanStack Query), UI (фильтры, таблица) +- Public API (`index.ts`) экспортирует: `useScreener`, `FilterPanel`, `FilterPanelShare`, `FilterPanelBond`, `ScreenerTable` +- Запрещены глубокие импорты в `model/` или `ui/` извне `features/screener/` + +### 2. Pages слой + +- `pages/screener/` — тонкая композиция, только импортирует из `features/screener` +- Public API (`index.ts`) экспортирует `ScreenerPage` + +### 3. Совместимость + +- Никаких shim-файлов не требуется — скринер не имеет внешних потребителей кроме `AppRoutes` +- Поведение страницы скринера не меняется + +## Критерии приемки (Acceptance Criteria) + +- [ ] `npm test -w apps/frontend` — PASS +- [ ] `npm run lint -w apps/frontend` — PASS +- [ ] `npm run build -w apps/frontend` — PASS +- [ ] Нет deep imports в `features/screener/model/` или `features/screener/ui/` извне +- [ ] `pages/screener/` импортируется только через barrel (`@/pages/screener`) +- [ ] `features/screener/` импортируется только через barrel (`@/features/screener`) +- [ ] Все legacy-файлы удалены diff --git a/docs/features/frontend-fsd-screener/tasks.md b/docs/features/frontend-fsd-screener/tasks.md new file mode 100644 index 0000000..5a51cfe --- /dev/null +++ b/docs/features/frontend-fsd-screener/tasks.md @@ -0,0 +1,47 @@ +# Frontend FSD Screener — задачи + +Статус: в работе + +Подробные шаги, карта файлов и verification-команды находятся в [plan.md](plan.md). + +## 1. API слой + +- [ ] Создать `features/screener/api/screenerApi.ts` — перенос `ScreenerQuery` + `getScreenerResults` +- [ ] Обновить импорты: `@/shared/api/client`, `@/shared/api/responses` + +## 2. Модель + +- [ ] Создать `features/screener/model/useScreener.ts` — перенос хука +- [ ] Создать `features/screener/model/index.ts` + +## 3. UI компоненты + +- [ ] Перенести `FilterPanel.tsx` в `features/screener/ui/` +- [ ] Перенести `FilterPanelShare.tsx` в `features/screener/ui/` +- [ ] Перенести `FilterPanelBond.tsx` в `features/screener/ui/` +- [ ] Перенести `ScreenerTable.tsx` в `features/screener/ui/` +- [ ] Создать `features/screener/ui/index.ts` +- [ ] Создать `features/screener/index.ts` + +## 4. Pages + +- [ ] Создать `pages/screener/ui/ScreenerPage.tsx` — обновить импорты на `@/features/screener` +- [ ] Создать `pages/screener/index.ts` + +## 5. Роутинг + +- [ ] Обновить `app/routing/AppRoutes.tsx` — import from `@/pages/screener` + +## 6. Удаление legacy + +- [ ] Удалить `api/screener.ts` +- [ ] Удалить `hooks/useScreener.ts` +- [ ] Удалить `components/screener/` (директория) + +## 7. Verification + +- [ ] `npm test -w apps/frontend` — PASS +- [ ] `npm run lint -w apps/frontend` — PASS +- [ ] `npm run build -w apps/frontend` — PASS +- [ ] Нет deep imports в `features/screener/` извне +- [ ] `pages/screener/` импортируется только через barrel