From 0ddd9b5f80cfa693b4f51240e2e73fbfee533752 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Mon, 22 Jun 2026 06:58:59 +0300 Subject: [PATCH] feat: add date range selection to broker events page --- .../ui/BrokerEventsPage.test.tsx | 48 ++++++++++- .../broker-events/ui/BrokerEventsPage.tsx | 84 ++++++++++++++----- 2 files changed, 112 insertions(+), 20 deletions(-) diff --git a/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.test.tsx b/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.test.tsx index 5457673..3618303 100644 --- a/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.test.tsx +++ b/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.test.tsx @@ -12,9 +12,22 @@ vi.mock('@/widgets/broker-account-layout', () => ({ useBrokerAccountContext: () => ({ accountId: 'acc-1', portfolio: null }), })); +const mockSetSearchParams = vi.fn(); +vi.mock('react-router-dom', () => ({ + useSearchParams: () => [new URLSearchParams(), mockSetSearchParams], +})); + vi.mock('@moex-vibe/design-system', () => ({ Heading: ({ children }: { children: ReactNode }) =>

{children}

, Text: ({ children }: { children: ReactNode }) => {children}, + TextField: ({ label, value, onChange, ...props }: any) => ( + {})} + type={props.type || 'text'} + /> + ), })); import { useBrokerEvents } from '@/entities/broker-event'; @@ -179,7 +192,40 @@ describe('BrokerEventsPage', () => { } as unknown as ReturnType); render(, { wrapper: createWrapper() }); - expect(screen.getByText('На ближайшие 7 дней событий нет')).toBeInTheDocument(); + expect(screen.getByText('В выбранном диапазоне событий нет')).toBeInTheDocument(); + }); + + it('renders date range inputs', () => { + vi.mocked(useBrokerEvents).mockReturnValue({ + data: mockData, + isLoading: false, + isError: false, + error: null, + isSuccess: true, + isPending: false, + dataUpdatedAt: Date.now(), + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isFetched: true, + isFetchedAfterMount: true, + isFetching: false, + isInitialLoading: false, + isPaused: false, + isLoadingError: false, + isRefetchError: false, + isPlaceholderData: false, + isStale: false, + refetch: vi.fn(), + promise: Promise.resolve(mockData), + status: 'success', + fetchStatus: 'idle', + } as unknown as ReturnType); + + render(, { wrapper: createWrapper() }); + expect(screen.getByLabelText('С')).toBeInTheDocument(); + expect(screen.getByLabelText('По')).toBeInTheDocument(); }); it('renders events heading, summary and table', () => { diff --git a/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.tsx b/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.tsx index fb97604..aed5ec4 100644 --- a/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.tsx +++ b/apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.tsx @@ -1,8 +1,11 @@ +import { useState, useEffect } from 'react'; +import { useSearchParams } from 'react-router-dom'; import { Box } from '@mui/material'; -import { Heading, Text } from '@moex-vibe/design-system'; +import { Heading, Text, TextField } from '@moex-vibe/design-system'; +import dayjs from 'dayjs'; import { useBrokerEvents } from '@/entities/broker-event'; import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; -import { formatBrokerCurrencyValue } from '@/shared/lib/formatters'; +import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters'; function eventTypeLabel(type: string): string { switch (type) { @@ -19,26 +22,41 @@ function eventTypeLabel(type: string): string { } } -function formatDate(value: string | null): string { - if (!value) return '-'; - return new Date(value).toLocaleDateString('ru-RU'); -} - -function defaultPeriod() { - const now = new Date(); - const from = new Date(now); - const to = new Date(now); - to.setDate(to.getDate() + 7); +function defaultPeriod(): { from: string; to: string } { + const now = dayjs(); return { - from: from.toISOString().slice(0, 10), - to: to.toISOString().slice(0, 10), + from: now.format('YYYY-MM-DD'), + to: now.add(7, 'day').format('YYYY-MM-DD'), }; } export function BrokerEventsPage() { const { accountId } = useBrokerAccountContext(); - const period = defaultPeriod(); - const events = useBrokerEvents(accountId, period); + const [searchParams, setSearchParams] = useSearchParams(); + const [initialized, setInitialized] = useState(false); + + useEffect(() => { + const from = searchParams.get('from'); + const to = searchParams.get('to'); + if (!from || !to || !dayjs(from).isValid() || !dayjs(to).isValid()) { + const def = defaultPeriod(); + setSearchParams({ from: def.from, to: def.to }, { replace: true }); + } + setInitialized(true); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const from = searchParams.get('from') ?? ''; + const to = searchParams.get('to') ?? ''; + const period = initialized ? { from, to } : defaultPeriod(); + + const validFrom = dayjs(from); + const validTo = dayjs(to); + const dateError = + from && to && validFrom.isValid() && validTo.isValid() && validTo.isBefore(validFrom) + ? '"По" не может быть раньше "С"' + : ''; + + const events = useBrokerEvents(dateError ? undefined : accountId, period); const ev = events.data; @@ -50,6 +68,32 @@ export function BrokerEventsPage() { События + + { + const newParams = new URLSearchParams(searchParams); + newParams.set('from', e.target.value); + setSearchParams(newParams, { replace: true }); + }} + InputLabelProps={{ shrink: true }} + /> + { + const newParams = new URLSearchParams(searchParams); + newParams.set('to', e.target.value); + setSearchParams(newParams, { replace: true }); + }} + InputLabelProps={{ shrink: true }} + error={!!dateError} + helperText={dateError} + /> + {events.error ? ( @@ -62,7 +106,7 @@ export function BrokerEventsPage() { ) : ev && ev.items.length === 0 ? ( - На ближайшие 7 дней событий нет + В выбранном диапазоне событий нет ) : ev ? ( @@ -86,7 +130,9 @@ export function BrokerEventsPage() { Ближайшее - {formatDate(ev.summary.nearestEventDate)} + + {formatBrokerDate(ev.summary.nearestEventDate) ?? '-'} + @@ -166,7 +212,7 @@ export function BrokerEventsPage() { component="td" sx={{ p: 1, borderBottom: '1px solid', borderColor: 'divider' }} > - {formatDate(item.eventDate)} + {formatBrokerDate(item.eventDate) ?? '-'}