diff --git a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts index 0733692..faa16db 100644 --- a/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts +++ b/apps/frontend/src/widgets/broker-dashboard/lib/dashboardFilters.ts @@ -11,6 +11,7 @@ export type DashboardFilterState = { from: string to: string types: T[] + preset: DashboardDatePreset } export function defaultEventsFilters(): DashboardFilterState { @@ -19,6 +20,7 @@ export function defaultEventsFilters(): DashboardFilterState from: now.subtract(7, 'day').format('YYYY-MM-DD'), to: now.add(7, 'day').format('YYYY-MM-DD'), types: [...DASHBOARD_EVENT_TYPES], + preset: '7d', } } @@ -28,6 +30,7 @@ export function defaultIncomeFilters(): DashboardFilterState( preset: DashboardDatePreset, ): DashboardFilterState { const now = dayjs() - if (preset === 'all') return { ...filters, from: '', to: now.format('YYYY-MM-DD') } + const next = { ...filters, preset } + if (preset === 'all') return { ...next, from: '', to: '' } const amount = preset === '1y' ? 1 : Number.parseInt(preset, 10) const unit = preset === '1y' ? 'year' : 'day' return { - ...filters, + ...next, from: now.subtract(amount, unit).format('YYYY-MM-DD'), to: now.format('YYYY-MM-DD'), } diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx index f6d182b..50d6a85 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx @@ -40,10 +40,6 @@ vi.mock('@/entities/broker-operation', () => ({ useBrokerOperations: hookMocks.useBrokerOperations, })) -vi.mock('@/widgets/broker-allocation-chart', () => ({ - BrokerAllocationChart: () =>
allocation chart
, -})) - const portfolio: BrokerPortfolio = { account: { id: 'acc-1', @@ -99,14 +95,14 @@ describe('BrokerDashboard', () => { expect(screen.getByText('Доходы')).toBeInTheDocument() expect(screen.getByText('Аналитика доходности')).toBeInTheDocument() expect(screen.getByText('Аллокация')).toBeInTheDocument() - expect(screen.getByText('allocation chart')).toBeInTheDocument() }) - it('uses event chips as request filters', async () => { + it('applies event type chips immediately to useBrokerEvents', async () => { const user = userEvent.setup() render() - await user.click(screen.getAllByRole('button', { name: 'Купоны' })[0]) + const couponChips = screen.getAllByRole('button', { name: 'Купоны' }) + await user.click(couponChips[0]) await waitFor(() => { expect(hookMocks.useBrokerEvents).toHaveBeenLastCalledWith( @@ -117,11 +113,12 @@ describe('BrokerDashboard', () => { }) }) - it('uses income chips as request filters', async () => { + it('applies income type chips immediately to useBrokerOperations', async () => { const user = userEvent.setup() render() - await user.click(screen.getAllByRole('button', { name: 'Купоны' })[1]) + const couponChips = screen.getAllByRole('button', { name: 'Купоны' }) + await user.click(couponChips[1]) await waitFor(() => { expect(hookMocks.useBrokerOperations).toHaveBeenLastCalledWith( @@ -133,4 +130,21 @@ describe('BrokerDashboard', () => { ) }) }) + + it('shows date filter toggle button and expandable panel', async () => { + const user = userEvent.setup() + render() + + const filterButtons = screen.getAllByRole('button', { name: /Фильтр дат/ }) + expect(filterButtons).toHaveLength(2) + + await user.click(filterButtons[0]) + + expect(screen.getByText('7д')).toBeInTheDocument() + expect(screen.getByText('30д')).toBeInTheDocument() + expect(screen.getByText('90д')).toBeInTheDocument() + expect(screen.getByText('1г')).toBeInTheDocument() + expect(screen.getByText('Всё')).toBeInTheDocument() + expect(screen.getByText('Сбросить')).toBeInTheDocument() + }) }) diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx index 5bbdb2b..d4cac32 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.tsx @@ -1,11 +1,14 @@ import { Box } from '@mui/material' -import { useState } from 'react' +import dayjs from 'dayjs' +import { useCallback, useState } from 'react' import { useBrokerAnalytics } from '@/entities/broker-analytics' import { useBrokerEvents } from '@/entities/broker-event' import { useBrokerOperations } from '@/entities/broker-operation' import type { BrokerPortfolio } from '@/shared/api' import { useCursorPagination } from '@/shared/lib/useCursorPagination' import { + applyDatePreset, + type DashboardDatePreset, type DashboardEventType, type DashboardIncomeType, defaultEventsFilters, @@ -18,6 +21,15 @@ import { BrokerDashboardEventsCard } from './BrokerDashboardEventsCard' import { BrokerDashboardHero } from './BrokerDashboardHero' import { BrokerDashboardIncomeCard } from './BrokerDashboardIncomeCard' +function formatDateLabel(from: string, to: string): string | null { + if (!from && !to) return 'За всё время' + const fmt = (d: string) => dayjs(d).format('D MMM') + if (from && to) return `${fmt(from)} – ${fmt(to)}` + if (from) return `с ${fmt(from)}` + if (to) return `до ${fmt(to)}` + return null +} + export function BrokerDashboard({ accountId, portfolio, @@ -25,34 +37,39 @@ export function BrokerDashboard({ accountId: string portfolio: BrokerPortfolio }) { - const [eventFilters, setEventFilters] = useState(defaultEventsFilters) + const [appliedEventFilters, setAppliedEventFilters] = useState(defaultEventsFilters) + const [draftEventFilters, setDraftEventFilters] = useState(defaultEventsFilters) + const [eventFilterPanelOpen, setEventFilterPanelOpen] = useState(false) const [eventPage, setEventPage] = useState(1) - const [incomeFilters, setIncomeFilters] = useState(defaultIncomeFilters) + + const [appliedIncomeFilters, setAppliedIncomeFilters] = useState(defaultIncomeFilters) + const [draftIncomeFilters, setDraftIncomeFilters] = useState(defaultIncomeFilters) + const [incomeFilterPanelOpen, setIncomeFilterPanelOpen] = useState(false) const incomePagination = useCursorPagination() + const analytics = useBrokerAnalytics(accountId) - const hasEventTypes = eventFilters.types.length > 0 - const hasIncomeTypes = incomeFilters.types.length > 0 + const hasEventTypes = appliedEventFilters.types.length > 0 + const hasIncomeTypes = appliedIncomeFilters.types.length > 0 + const hasDraftEventTypes = draftEventFilters.types.length > 0 + const hasDraftIncomeTypes = draftIncomeFilters.types.length > 0 + const eventPageSize = 10 + const events = useBrokerEvents( accountId, { - from: eventFilters.from, - to: eventFilters.to, - types: eventFilters.types.join(','), + from: appliedEventFilters.from, + to: appliedEventFilters.to, + types: appliedEventFilters.types.join(','), }, { enabled: hasEventTypes }, ) - const eventItems = events.data?.items ?? [] - const eventPageSize = 10 - const eventPageItems = eventItems.slice( - (eventPage - 1) * eventPageSize, - eventPage * eventPageSize, - ) + const operations = useBrokerOperations( accountId, { - from: incomeFilters.from, - to: incomeFilters.to, - operationTypes: incomeTypesToOperationTypes(incomeFilters.types), + from: appliedIncomeFilters.from, + to: appliedIncomeFilters.to, + operationTypes: incomeTypesToOperationTypes(appliedIncomeFilters.types), cursor: incomePagination.cursor, limit: 10, }, @@ -63,26 +80,106 @@ export function BrokerDashboard({ ? (operations.data.nextCursor as unknown as string) : undefined + const eventItems = events.data?.items ?? [] + const eventPageItems = eventItems.slice( + (eventPage - 1) * eventPageSize, + eventPage * eventPageSize, + ) + function toggleEventType(type: DashboardEventType) { - setEventFilters((filters) => ({ - ...filters, - types: filters.types.includes(type) + setAppliedEventFilters((filters) => { + const nextTypes = filters.types.includes(type) ? filters.types.filter((item) => item !== type) - : [...filters.types, type], - })) + : [...filters.types, type] + return { ...filters, types: nextTypes } + }) + setDraftEventFilters((filters) => { + const nextTypes = filters.types.includes(type) + ? filters.types.filter((item) => item !== type) + : [...filters.types, type] + return { ...filters, types: nextTypes } + }) setEventPage(1) } function toggleIncomeType(type: DashboardIncomeType) { - setIncomeFilters((filters) => ({ - ...filters, - types: filters.types.includes(type) + setAppliedIncomeFilters((filters) => { + const nextTypes = filters.types.includes(type) ? filters.types.filter((item) => item !== type) - : [...filters.types, type], - })) + : [...filters.types, type] + return { ...filters, types: nextTypes } + }) + setDraftIncomeFilters((filters) => { + const nextTypes = filters.types.includes(type) + ? filters.types.filter((item) => item !== type) + : [...filters.types, type] + return { ...filters, types: nextTypes } + }) incomePagination.reset() } + const applyEventFilters = useCallback(() => { + setAppliedEventFilters((prev) => ({ + ...prev, + from: draftEventFilters.from, + to: draftEventFilters.to, + preset: draftEventFilters.preset, + })) + setEventFilterPanelOpen(false) + setEventPage(1) + }, [draftEventFilters.from, draftEventFilters.to, draftEventFilters.preset]) + + const resetEventFilters = useCallback(() => { + const defaults = defaultEventsFilters() + setAppliedEventFilters(defaults) + setDraftEventFilters(defaults) + setEventFilterPanelOpen(false) + setEventPage(1) + }, []) + + const applyIncomeFilters = useCallback(() => { + setAppliedIncomeFilters((prev) => ({ + ...prev, + from: draftIncomeFilters.from, + to: draftIncomeFilters.to, + preset: draftIncomeFilters.preset, + })) + setIncomeFilterPanelOpen(false) + incomePagination.reset() + }, [draftIncomeFilters.from, draftIncomeFilters.to, draftIncomeFilters.preset, incomePagination]) + + const resetIncomeFilters = useCallback(() => { + const defaults = defaultIncomeFilters() + setAppliedIncomeFilters(defaults) + setDraftIncomeFilters(defaults) + setIncomeFilterPanelOpen(false) + incomePagination.reset() + }, [incomePagination]) + + function handleDraftEventPresetChange(preset: DashboardDatePreset) { + setDraftEventFilters((filters) => applyDatePreset(filters, preset)) + } + + function handleDraftEventFromChange(value: string) { + setDraftEventFilters((filters) => ({ ...filters, from: value })) + } + + function handleDraftEventToChange(value: string) { + setDraftEventFilters((filters) => ({ ...filters, to: value })) + } + + function handleDraftIncomePresetChange(preset: DashboardDatePreset) { + setDraftIncomeFilters((filters) => applyDatePreset(filters, preset)) + } + + function handleDraftIncomeFromChange(value: string) { + setDraftIncomeFilters((filters) => ({ ...filters, from: value })) + } + + function handleDraftIncomeToChange(value: string) { + setDraftIncomeFilters((filters) => ({ ...filters, to: value })) + } + return ( @@ -91,8 +188,20 @@ export function BrokerDashboard({ data={events.data ? { ...events.data, items: eventPageItems } : undefined} isLoading={events.isLoading} isError={events.isError} - selectedTypes={eventFilters.types} + selectedTypes={draftEventFilters.types} onToggleType={toggleEventType} + appliedDateLabel={formatDateLabel(appliedEventFilters.from, appliedEventFilters.to)} + draftPreset={draftEventFilters.preset} + draftFrom={draftEventFilters.from} + draftTo={draftEventFilters.to} + onDraftPresetChange={handleDraftEventPresetChange} + onDraftFromChange={handleDraftEventFromChange} + onDraftToChange={handleDraftEventToChange} + onApplyFilters={applyEventFilters} + onResetFilters={resetEventFilters} + dateFilterOpen={eventFilterPanelOpen} + onToggleDateFilter={() => setEventFilterPanelOpen((v) => !v)} + hasDraftTypes={hasDraftEventTypes} page={eventPage} canGoBack={eventPage > 1} canGoForward={eventPage * eventPageSize < eventItems.length} @@ -104,8 +213,20 @@ export function BrokerDashboard({ page={operations.data} isLoading={operations.isLoading} isError={operations.isError} - selectedTypes={incomeFilters.types} + selectedTypes={draftIncomeFilters.types} onToggleType={toggleIncomeType} + appliedDateLabel={formatDateLabel(appliedIncomeFilters.from, appliedIncomeFilters.to)} + draftPreset={draftIncomeFilters.preset} + draftFrom={draftIncomeFilters.from} + draftTo={draftIncomeFilters.to} + onDraftPresetChange={handleDraftIncomePresetChange} + onDraftFromChange={handleDraftIncomeFromChange} + onDraftToChange={handleDraftIncomeToChange} + onApplyFilters={applyIncomeFilters} + onResetFilters={resetIncomeFilters} + dateFilterOpen={incomeFilterPanelOpen} + onToggleDateFilter={() => setIncomeFilterPanelOpen((v) => !v)} + hasDraftTypes={hasDraftIncomeTypes} pageNumber={incomePagination.pageNumber} canGoBack={incomePagination.pageNumber > 1} canGoForward={operations.data?.hasNext ?? false} diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx index a97c376..679e78f 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardAllocationCard.tsx @@ -1,19 +1,78 @@ import { Text } from '@moex-vibe/design-system' import { Box } from '@mui/material' +import { buildBrokerAllocation } from '@/entities/broker-position' import type { BrokerPortfolio } from '@/shared/api' -import { formatBrokerMoney } from '@/shared/lib/formatters' -import { BrokerAllocationChart } from '@/widgets/broker-allocation-chart' +import { formatBrokerCurrencyValue, formatBrokerMoney } from '@/shared/lib/formatters' import { BrokerDashboardCard } from './BrokerDashboardCard' +const ALLOCATION_COLORS: Record = { + shares: '#4969f5', + bonds: '#e5a33c', + etf: '#62b889', + cash: '#7b63cf', + other: '#aeb6c5', +} + export function BrokerDashboardAllocationCard({ portfolio }: { portfolio: BrokerPortfolio }) { + const { total, sectors, negative } = buildBrokerAllocation(portfolio) + const currency = + total >= 0 + ? (portfolio.totals.portfolio?.currency ?? + Object.values(portfolio.totals).find((t) => t?.currency)?.currency ?? + 'RUB') + : 'RUB' + return ( {formatBrokerMoney(portfolio.totals.portfolio)}} > - - - + {sectors.length === 0 && negative.length === 0 ? ( + Нет данных для распределения + ) : ( + + {sectors.map((sector) => ( + + + {sector.label} + + {formatBrokerCurrencyValue(currency, sector.value)} · {sector.percent.toFixed(1)}% + + + + + + + ))} + {negative.length > 0 && ( + + {negative.map((item) => ( + + {item.label}: + + {formatBrokerCurrencyValue(currency, item.value)} + + + ))} + + )} + + )} ) } diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx new file mode 100644 index 0000000..dd27c8e --- /dev/null +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardDateFilter.tsx @@ -0,0 +1,156 @@ +import { Button, Chip, Text } from '@moex-vibe/design-system' +import { Box } from '@mui/material' +import type { DashboardDatePreset } from '../lib/dashboardFilters' + +const PRESETS: { key: DashboardDatePreset; label: string }[] = [ + { key: '7d', label: '7д' }, + { key: '30d', label: '30д' }, + { key: '90d', label: '90д' }, + { key: '1y', label: '1г' }, + { key: 'all', label: 'Всё' }, +] + +type BrokerDashboardDateFilterProps = { + appliedLabel: string | null + preset: DashboardDatePreset + draftFrom: string + draftTo: string + hasDraftTypes: boolean + onPresetChange: (preset: DashboardDatePreset) => void + onFromChange: (value: string) => void + onToChange: (value: string) => void + onReset: () => void + onApply: () => void + isOpen: boolean + onToggle: () => void +} + +export function BrokerDashboardDateFilter({ + appliedLabel, + preset, + draftFrom, + draftTo, + hasDraftTypes, + onPresetChange, + onFromChange, + onToChange, + onReset, + onApply, + isOpen, + onToggle, +}: BrokerDashboardDateFilterProps) { + return ( + + + 📅 Фильтр дат + {appliedLabel && ( + + {appliedLabel} + + )} + + + {isOpen && ( + + + {PRESETS.map((p) => ( + onPresetChange(p.key)} + /> + ))} + + + onFromChange(e.target.value)} + style={{ + border: '1px solid #ccc', + borderRadius: 6, + padding: '4px 8px', + fontSize: 13, + flex: '0 1 auto', + }} + /> + + — + + onToChange(e.target.value)} + style={{ + border: '1px solid #ccc', + borderRadius: 6, + padding: '4px 8px', + fontSize: 13, + flex: '0 1 auto', + }} + /> + + Сбросить + + + + )} + + ) +} diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx index a189431..c0e3510 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardEventsCard.tsx @@ -3,9 +3,10 @@ import { Box } from '@mui/material' import { Link } from '@tanstack/react-router' import type { BrokerEventItem, BrokerEventsData } from '@/shared/api' import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters' -import type { DashboardEventType } from '../lib/dashboardFilters' +import type { DashboardDatePreset, DashboardEventType } from '../lib/dashboardFilters' import { eventStatusLabel, eventTypeLabel } from '../lib/dashboardFormatters' import { BrokerDashboardCard } from './BrokerDashboardCard' +import { BrokerDashboardDateFilter } from './BrokerDashboardDateFilter' const EVENT_FILTERS: Array<{ type: DashboardEventType @@ -25,6 +26,18 @@ type BrokerDashboardEventsCardProps = { isError: boolean selectedTypes: DashboardEventType[] onToggleType: (type: DashboardEventType) => void + appliedDateLabel: string | null + draftPreset: DashboardDatePreset + draftFrom: string + draftTo: string + onDraftPresetChange: (preset: DashboardDatePreset) => void + onDraftFromChange: (value: string) => void + onDraftToChange: (value: string) => void + onApplyFilters: () => void + onResetFilters: () => void + dateFilterOpen: boolean + onToggleDateFilter: () => void + hasDraftTypes: boolean page: number onPreviousPage: () => void onNextPage: () => void @@ -46,6 +59,18 @@ export function BrokerDashboardEventsCard({ isError, selectedTypes, onToggleType, + appliedDateLabel, + draftPreset, + draftFrom, + draftTo, + onDraftPresetChange, + onDraftFromChange, + onDraftToChange, + onApplyFilters, + onResetFilters, + dateFilterOpen, + onToggleDateFilter, + hasDraftTypes, page, onPreviousPage, onNextPage, @@ -58,18 +83,36 @@ export function BrokerDashboardEventsCard({ Все события} - > - - {EVENT_FILTERS.map((filter) => ( - onToggleType(filter.type)} + filters={ + + + {EVENT_FILTERS.map((filter) => ( + onToggleType(filter.type)} + /> + ))} + + - ))} - + + } + > {selectedTypes.length === 0 ? ( Выберите хотя бы один тип событий ) : isError ? ( diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx index db43dee..82c7768 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardHero.tsx @@ -32,7 +32,7 @@ export function BrokerDashboardHero({ portfolio, analytics }: BrokerDashboardHer display: 'grid', gap: 2, gridTemplateColumns: { xs: '1fr', md: 'minmax(240px, 1fr) repeat(3, auto)' }, - alignItems: 'center', + alignItems: 'stretch', }} > @@ -43,13 +43,19 @@ export function BrokerDashboardHero({ portfolio, analytics }: BrokerDashboardHer {accountName} - - - + + + + + + + + + ) } diff --git a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx index b3e882d..2cfd755 100644 --- a/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx +++ b/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboardIncomeCard.tsx @@ -3,9 +3,10 @@ import { Box } from '@mui/material' import { Link } from '@tanstack/react-router' import type { BrokerOperationsPage } from '@/shared/api' import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters' -import type { DashboardIncomeType } from '../lib/dashboardFilters' +import type { DashboardDatePreset, DashboardIncomeType } from '../lib/dashboardFilters' import { getDashboardIncomeRows, sumDashboardIncome } from '../lib/dashboardIncome' import { BrokerDashboardCard } from './BrokerDashboardCard' +import { BrokerDashboardDateFilter } from './BrokerDashboardDateFilter' const INCOME_FILTERS: Array<{ type: DashboardIncomeType @@ -23,6 +24,18 @@ type BrokerDashboardIncomeCardProps = { isError: boolean selectedTypes: DashboardIncomeType[] onToggleType: (type: DashboardIncomeType) => void + appliedDateLabel: string | null + draftPreset: DashboardDatePreset + draftFrom: string + draftTo: string + onDraftPresetChange: (preset: DashboardDatePreset) => void + onDraftFromChange: (value: string) => void + onDraftToChange: (value: string) => void + onApplyFilters: () => void + onResetFilters: () => void + dateFilterOpen: boolean + onToggleDateFilter: () => void + hasDraftTypes: boolean pageNumber: number canGoBack: boolean canGoForward: boolean @@ -37,6 +50,18 @@ export function BrokerDashboardIncomeCard({ isError, selectedTypes, onToggleType, + appliedDateLabel, + draftPreset, + draftFrom, + draftTo, + onDraftPresetChange, + onDraftFromChange, + onDraftToChange, + onApplyFilters, + onResetFilters, + dateFilterOpen, + onToggleDateFilter, + hasDraftTypes, pageNumber, canGoBack, canGoForward, @@ -50,18 +75,36 @@ export function BrokerDashboardIncomeCard({ Все операции} - > - - {INCOME_FILTERS.map((filter) => ( - onToggleType(filter.type)} + filters={ + + + {INCOME_FILTERS.map((filter) => ( + onToggleType(filter.type)} + /> + ))} + + - ))} - + + } + > {selectedTypes.length === 0 ? ( Выберите хотя бы один тип доходов ) : isError ? ( diff --git a/docs/features/broker-dashboard-redesign/tasks.md b/docs/features/broker-dashboard-redesign/tasks.md index 13a6842..8423510 100644 --- a/docs/features/broker-dashboard-redesign/tasks.md +++ b/docs/features/broker-dashboard-redesign/tasks.md @@ -26,11 +26,11 @@ - [x] Перенести навигацию счёта из левой колонки в горизонтальные вкладки над контентом. - [x] Перестроить dashboard на один блок на строке для `События`, `Доходы`, `Аналитика доходности`, `Аллокация`. - [x] Добавить кликабельные chip-фильтры типов для `События`. -- [ ] Добавить `BrokerDashboardDateFilter` — переиспользуемый expandable-компонент фильтра дат (пресеты 7д/30д/90д/1г/Всё, from/to поля, Сбросить/Показать). -- [ ] Подключить `BrokerDashboardDateFilter` в `События` с draft/applied состоянием. +- [x] Добавить `BrokerDashboardDateFilter` — переиспользуемый expandable-компонент фильтра дат (пресеты 7д/30д/90д/1г/Всё, from/to поля, Сбросить/Показать). +- [x] Подключить `BrokerDashboardDateFilter` в `События` с draft/applied состоянием. - [x] Добавить локальную пагинацию по 10 событий в `События`. - [x] Добавить кликабельные chip-фильтры типов для `Доходы`. -- [ ] Подключить `BrokerDashboardDateFilter` в `Доходы` с draft/applied состоянием. +- [x] Подключить `BrokerDashboardDateFilter` в `Доходы` с draft/applied состоянием. - [x] Добавить cursor-пагинацию по 10 операций в `Доходы`. - [x] Добавить `BrokerDashboardAnalyticsCard` на основе `useBrokerAnalytics`. - [x] Добавить `BrokerDashboardAllocationCard` на основе существующей аллокации. @@ -38,8 +38,8 @@ - [x] Добавить `BrokerDashboard` как top-level composition widget. - [x] Заменить текущий вертикальный обзор в `BrokerAccountOverviewPage` на `BrokerDashboard`. - [x] Добавить unit/component tests для helpers и базовой dashboard composition. -- [ ] Выровнять hero KPI: все Metric одной высоты, supportingText не раздвигает "Доходность" выше соседей. -- [ ] Заменить donut-диаграмму аллокации на горизонтальные бары в `BrokerDashboardAllocationCard`. +- [x] Выровнять hero KPI: все Metric одной высоты, supportingText не раздвигает "Доходность" выше соседей. +- [x] Заменить donut-диаграмму аллокации на горизонтальные бары в `BrokerDashboardAllocationCard`. - [ ] Проверить desktop layout `/broker/2084014113`. - [ ] Проверить mobile layout `/broker/2084014113`.