refactor: move PortfolioStructure to widgets layer
This commit is contained in:
+8
-6
@@ -8,7 +8,7 @@ Vitest — отдельную JSDOM конфигурацию. Точные versi
|
||||
[package.json](../package.json), реализация — source of truth.
|
||||
|
||||
```text
|
||||
app → pages → features → entities → shared
|
||||
app → pages → widgets → features → entities → shared
|
||||
page container → page VM → entity/feature query → shared/api → backend /api/v1
|
||||
↓
|
||||
props → state/section views
|
||||
@@ -30,13 +30,15 @@ contract; frontend не читает его checkout для генерации
|
||||
| --- | --- |
|
||||
| Bootstrap, глобальные providers, routes | `app` |
|
||||
| Композиция маршрута и поведение, нужное только этому экрану | `pages/<page>` |
|
||||
| Крупная переиспользуемая композиция экрана | `widgets/<widget>` |
|
||||
| Самостоятельная пользовательская возможность с собственным смыслом | `features/<capability>` |
|
||||
| Представление/поведение бизнес-сущности | `entities/<entity>` |
|
||||
| Переиспользуемый код без WealthLedger domain meaning | `shared` |
|
||||
|
||||
Локальный FSD profile: `app → pages → features → entities → shared`, без `widgets`.
|
||||
Крупный уникальный блок остаётся в своей page. Form, mutation или click handler
|
||||
сами по себе не требуют feature. Два callers не делают domain helper generic shared.
|
||||
Локальный FSD profile: `app → pages → widgets → features → entities → shared`.
|
||||
Widget собирает несколько нижних слоёв в самостоятельный UI-блок; компонент для
|
||||
одной page остаётся у page. Form, mutation или click handler сами по себе не
|
||||
требуют feature. Два callers не делают domain helper generic shared.
|
||||
Slice именуется по ответственности, а не `helpers`, `hooks` или `types`.
|
||||
|
||||
**Безопасный путь:** при сомнении оставь код у ближайшего допустимого owner.
|
||||
@@ -47,12 +49,12 @@ Slice именуется по ответственности, а не `helpers`,
|
||||
|
||||
Cross-slice consumer использует `@/` и `index.ts` владельца, например
|
||||
`@/entities/portfolio`. Alias Vite и TypeScript согласован. Другой slice не
|
||||
импортирует внутренний файл напрямую. Slices `pages`, `features`, `entities` не зависят от соседнего slice своего слоя;
|
||||
импортирует внутренний файл напрямую. Slices `pages`, `widgets`, `features`, `entities` не зависят от соседнего slice своего слоя;
|
||||
композиция принадлежит вышележащему owner. Existing меж-slice исключения
|
||||
не являются разрешением добавлять новые.
|
||||
|
||||
Направление зависимостей, public imports и запрет соседних slices в
|
||||
`pages`, `features`, `entities` проверяет `npm run deps`. Внутренние imports
|
||||
`pages`, `widgets`, `features`, `entities` проверяет `npm run deps`. Внутренние imports
|
||||
одного slice и связи shared segments этот запрет не ограничивает.
|
||||
Knip проверяет использование exports, а не смысл placement.
|
||||
|
||||
|
||||
+11
-5
@@ -29,17 +29,17 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
from: {
|
||||
path: "^src/(app|pages|features|entities|shared)/([^/]+)/",
|
||||
path: "^src/(app|pages|widgets|features|entities|shared)/([^/]+)/",
|
||||
},
|
||||
name: "slices-must-use-public-api",
|
||||
severity: "error",
|
||||
to: {
|
||||
path: String.raw`^src/(app|pages|features|entities|shared)/([^/]+)/(?!index\.ts$)`,
|
||||
path: String.raw`^src/(app|pages|widgets|features|entities|shared)/([^/]+)/(?!index\.ts$)`,
|
||||
pathNot: "^src/$1/$2/",
|
||||
},
|
||||
},
|
||||
{
|
||||
from: { path: "^src/(pages|features|entities)/([^/]+)/" },
|
||||
from: { path: "^src/(pages|widgets|features|entities)/([^/]+)/" },
|
||||
name: "no-same-layer-cross-slice-imports",
|
||||
severity: "error",
|
||||
to: { path: "^src/$1/", pathNot: "^src/$1/$2/" },
|
||||
@@ -48,18 +48,24 @@ module.exports = {
|
||||
from: { path: "^src/shared/" },
|
||||
name: "shared-must-not-depend-on-layers",
|
||||
severity: "error",
|
||||
to: { path: "^src/(entities|features|pages|app)/" },
|
||||
to: { path: "^src/(entities|features|widgets|pages|app)/" },
|
||||
},
|
||||
{
|
||||
from: { path: "^src/entities/" },
|
||||
name: "entities-must-not-depend-on-higher-layers",
|
||||
severity: "error",
|
||||
to: { path: "^src/(features|pages|app)/" },
|
||||
to: { path: "^src/(features|widgets|pages|app)/" },
|
||||
},
|
||||
{
|
||||
from: { path: "^src/features/" },
|
||||
name: "features-must-not-depend-on-higher-layers",
|
||||
severity: "error",
|
||||
to: { path: "^src/(widgets|pages|app)/" },
|
||||
},
|
||||
{
|
||||
from: { path: "^src/widgets/" },
|
||||
name: "widgets-must-not-depend-on-higher-layers",
|
||||
severity: "error",
|
||||
to: { path: "^src/(pages|app)/" },
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import type { BrokerageAccount } from "@/entities/brokerage-account";
|
||||
|
||||
import { BrokerageEventsPreview } from "@/entities/brokerage-event";
|
||||
import { PortfolioStructure } from "@/widgets/portfolio-structure";
|
||||
|
||||
import type {
|
||||
AccountPageIncomeSummary,
|
||||
@@ -16,7 +17,6 @@ import type {
|
||||
|
||||
import { AccountHeading } from "./AccountHeading.view";
|
||||
import { IncomeSummary } from "./IncomeSummary.view";
|
||||
import { PortfolioStructure } from "./PortfolioStructure/PortfolioStructure";
|
||||
import { RecentOperations } from "./RecentOperations.view";
|
||||
|
||||
type AccountPageEmptyProps = {
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { BrokerageAccount } from "@/entities/brokerage-account";
|
||||
import type { Portfolio } from "@/entities/portfolio";
|
||||
|
||||
import { BrokerageEventsPreview } from "@/entities/brokerage-event";
|
||||
import { PortfolioStructure } from "@/widgets/portfolio-structure";
|
||||
|
||||
import type { AccountPagePositionSection } from "../../../model/positions";
|
||||
import type {
|
||||
@@ -19,7 +20,6 @@ import classes from "../AccountPage.module.css";
|
||||
import { AccountHeading } from "./AccountHeading.view";
|
||||
import { CurrencyCards } from "./CurrencyCards/CurrencyCards.view";
|
||||
import { IncomeSummary } from "./IncomeSummary.view";
|
||||
import { PortfolioStructure } from "./PortfolioStructure/PortfolioStructure";
|
||||
import { PositionSection } from "./PositionSection.view";
|
||||
import { RecentOperations } from "./RecentOperations.view";
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { PortfolioStructure } from "./ui/PortfolioStructure";
|
||||
+1
-1
@@ -28,7 +28,7 @@ const meta = {
|
||||
},
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
title: "Product/PortfolioStructure",
|
||||
title: "Widgets/PortfolioStructure",
|
||||
} satisfies Meta<typeof PortfolioStructure>;
|
||||
|
||||
export default meta;
|
||||
Reference in New Issue
Block a user