From d1f9128441e9351db933cfdfe194fceb364b161a Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 20 Jun 2026 23:14:24 +0300 Subject: [PATCH] refactor: extract BrokerOverview components to widgets layer --- .../ui/BrokerAccountOverviewPage.tsx | 124 +----------------- .../src/widgets/broker-overview/index.ts | 3 + .../broker-overview/ui/BrokerAssetCards.tsx | 59 +++++++++ .../ui/BrokerOverviewSkeleton.tsx | 30 +++++ .../broker-overview/ui/BrokerSummary.tsx | 36 +++++ 5 files changed, 129 insertions(+), 123 deletions(-) create mode 100644 apps/frontend/src/widgets/broker-overview/index.ts create mode 100644 apps/frontend/src/widgets/broker-overview/ui/BrokerAssetCards.tsx create mode 100644 apps/frontend/src/widgets/broker-overview/ui/BrokerOverviewSkeleton.tsx create mode 100644 apps/frontend/src/widgets/broker-overview/ui/BrokerSummary.tsx diff --git a/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx b/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx index f7909e6..e7b4b79 100644 --- a/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx +++ b/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx @@ -1,131 +1,9 @@ import { Link } from 'react-router-dom'; -import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'; -import { SkeletonBlock } from '@/shared/ui/SkeletonBlock'; import { useBrokerOperations } from '@/entities/broker-operation'; import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; import { BrokerAllocationChart } from '@/widgets/broker-allocation-chart'; import { BrokerOperationsTable } from '@/widgets/broker-operations-table'; -import { - formatBrokerMoney as formatMoney, - formatBrokerPercent as formatPercent, - pluralize, -} from '@/shared/lib/formatters'; - -function BrokerSummary({ portfolio }: { portfolio: BrokerPortfolio }) { - return ( -
-
- Стоимость портфеля - - {formatMoney(portfolio.totals.portfolio)} - - За день: {formatMoney(portfolio.yields.daily)} - Дневная доходность: {formatPercent(portfolio.yields.dailyPercent)} - Ожидаемая доходность: {formatPercent(portfolio.yields.expectedPercent)} -
-
- Денежный остаток - {portfolio.cash.length === 0 ? ( - Нет денежных остатков - ) : ( -
    - {portfolio.cash.map((money, index) => ( -
  • - {money.currency} - {formatMoney(money)} -
  • - ))} -
- )} -
-
- ); -} - -function allocationPercent(value: BrokerMoney | null, total: BrokerMoney | null) { - if (!value || !total || total.value <= 0) return null; - return (value.value / total.value) * 100; -} - -function formatAllocationPercent(value: number | null) { - return value === null ? '—' : `${value.toFixed(1)}%`; -} - -function BrokerAssetCards({ - accountId, - portfolio, -}: { - accountId: string; - portfolio: BrokerPortfolio; -}) { - const basePath = `/broker/${encodeURIComponent(accountId)}`; - const cards = [ - { - label: 'Акции', - count: portfolio.positionCounts.shares, - countLabel: pluralize(portfolio.positionCounts.shares, 'позиция', 'позиции', 'позиций'), - value: portfolio.totals.shares, - path: `${basePath}/shares`, - }, - { - label: 'Облигации', - count: portfolio.positionCounts.bonds, - countLabel: pluralize(portfolio.positionCounts.bonds, 'выпуск', 'выпуска', 'выпусков'), - value: portfolio.totals.bonds, - path: `${basePath}/bonds`, - }, - ]; - - return ( -
- {cards.map((card) => ( - - {card.label} - - {card.count} {card.countLabel} - - {formatMoney(card.value)} - - {formatAllocationPercent(allocationPercent(card.value, portfolio.totals.portfolio))} - - - ))} -
- ); -} - -function BrokerOverviewSkeleton() { - return ( -
-
- {[1, 2].map((item) => ( -
- - - -
- ))} -
-
- - -
-
- {[1, 2].map((item) => ( -
- - - -
- ))} -
-
- ); -} +import { BrokerSummary, BrokerAssetCards, BrokerOverviewSkeleton } from '@/widgets/broker-overview'; export function BrokerAccountOverviewPage() { const { accountId, portfolio } = useBrokerAccountContext(); diff --git a/apps/frontend/src/widgets/broker-overview/index.ts b/apps/frontend/src/widgets/broker-overview/index.ts new file mode 100644 index 0000000..8d49b7e --- /dev/null +++ b/apps/frontend/src/widgets/broker-overview/index.ts @@ -0,0 +1,3 @@ +export { BrokerSummary } from './ui/BrokerSummary'; +export { BrokerAssetCards } from './ui/BrokerAssetCards'; +export { BrokerOverviewSkeleton } from './ui/BrokerOverviewSkeleton'; diff --git a/apps/frontend/src/widgets/broker-overview/ui/BrokerAssetCards.tsx b/apps/frontend/src/widgets/broker-overview/ui/BrokerAssetCards.tsx new file mode 100644 index 0000000..662dbe9 --- /dev/null +++ b/apps/frontend/src/widgets/broker-overview/ui/BrokerAssetCards.tsx @@ -0,0 +1,59 @@ +import { Link } from 'react-router-dom'; +import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'; +import { formatBrokerMoney, pluralize } from '@/shared/lib/formatters'; + +function allocationPercent(value: BrokerMoney | null, total: BrokerMoney | null) { + if (!value || !total || total.value <= 0) return null; + return (value.value / total.value) * 100; +} + +function formatAllocationPercent(value: number | null) { + return value === null ? '\u2014' : `${value.toFixed(1)}%`; +} + +export function BrokerAssetCards({ + accountId, + portfolio, +}: { + accountId: string; + portfolio: BrokerPortfolio; +}) { + const basePath = `/broker/${encodeURIComponent(accountId)}`; + const cards = [ + { + label: 'Акции', + count: portfolio.positionCounts.shares, + countLabel: pluralize(portfolio.positionCounts.shares, 'позиция', 'позиции', 'позиций'), + value: portfolio.totals.shares, + path: `${basePath}/shares`, + }, + { + label: 'Облигации', + count: portfolio.positionCounts.bonds, + countLabel: pluralize(portfolio.positionCounts.bonds, 'выпуск', 'выпуска', 'выпусков'), + value: portfolio.totals.bonds, + path: `${basePath}/bonds`, + }, + ]; + + return ( +
+ {cards.map((card) => ( + + {card.label} + + {card.count} {card.countLabel} + + {formatBrokerMoney(card.value)} + + {formatAllocationPercent(allocationPercent(card.value, portfolio.totals.portfolio))} + + + ))} +
+ ); +} diff --git a/apps/frontend/src/widgets/broker-overview/ui/BrokerOverviewSkeleton.tsx b/apps/frontend/src/widgets/broker-overview/ui/BrokerOverviewSkeleton.tsx new file mode 100644 index 0000000..984978a --- /dev/null +++ b/apps/frontend/src/widgets/broker-overview/ui/BrokerOverviewSkeleton.tsx @@ -0,0 +1,30 @@ +import { SkeletonBlock } from '@/shared/ui/SkeletonBlock'; + +export function BrokerOverviewSkeleton() { + return ( +
+
+ {[1, 2].map((item) => ( +
+ + + +
+ ))} +
+
+ + +
+
+ {[1, 2].map((item) => ( +
+ + + +
+ ))} +
+
+ ); +} diff --git a/apps/frontend/src/widgets/broker-overview/ui/BrokerSummary.tsx b/apps/frontend/src/widgets/broker-overview/ui/BrokerSummary.tsx new file mode 100644 index 0000000..fd7dde4 --- /dev/null +++ b/apps/frontend/src/widgets/broker-overview/ui/BrokerSummary.tsx @@ -0,0 +1,36 @@ +import type { BrokerPortfolio } from '@/shared/api/responses'; +import { + formatBrokerMoney as formatMoney, + formatBrokerPercent as formatPercent, +} from '@/shared/lib/formatters'; + +export function BrokerSummary({ portfolio }: { portfolio: BrokerPortfolio }) { + return ( +
+
+ Стоимость портфеля + + {formatMoney(portfolio.totals.portfolio)} + + За день: {formatMoney(portfolio.yields.daily)} + Дневная доходность: {formatPercent(portfolio.yields.dailyPercent)} + Ожидаемая доходность: {formatPercent(portfolio.yields.expectedPercent)} +
+
+ Денежный остаток + {portfolio.cash.length === 0 ? ( + Нет денежных остатков + ) : ( +
    + {portfolio.cash.map((money, index) => ( +
  • + {money.currency} + {formatMoney(money)} +
  • + ))} +
+ )} +
+
+ ); +}