feat: add date range selection to broker events page

This commit is contained in:
Sergey Krylov 2026-06-22 06:58:59 +03:00
parent 3027b4f0f0
commit 0ddd9b5f80
2 changed files with 112 additions and 20 deletions

View File

@ -12,9 +12,22 @@ vi.mock('@/widgets/broker-account-layout', () => ({
useBrokerAccountContext: () => ({ accountId: 'acc-1', portfolio: null }), 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', () => ({ vi.mock('@moex-vibe/design-system', () => ({
Heading: ({ children }: { children: ReactNode }) => <h2>{children}</h2>, Heading: ({ children }: { children: ReactNode }) => <h2>{children}</h2>,
Text: ({ children }: { children: ReactNode }) => <span>{children}</span>, Text: ({ children }: { children: ReactNode }) => <span>{children}</span>,
TextField: ({ label, value, onChange, ...props }: any) => (
<input
aria-label={label || ''}
value={value || ''}
onChange={onChange || (() => {})}
type={props.type || 'text'}
/>
),
})); }));
import { useBrokerEvents } from '@/entities/broker-event'; import { useBrokerEvents } from '@/entities/broker-event';
@ -179,7 +192,40 @@ describe('BrokerEventsPage', () => {
} as unknown as ReturnType<typeof useBrokerEvents>); } as unknown as ReturnType<typeof useBrokerEvents>);
render(<BrokerEventsPage />, { wrapper: createWrapper() }); render(<BrokerEventsPage />, { 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<typeof useBrokerEvents>);
render(<BrokerEventsPage />, { wrapper: createWrapper() });
expect(screen.getByLabelText('С')).toBeInTheDocument();
expect(screen.getByLabelText('По')).toBeInTheDocument();
}); });
it('renders events heading, summary and table', () => { it('renders events heading, summary and table', () => {

View File

@ -1,8 +1,11 @@
import { useState, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Box } from '@mui/material'; 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 { useBrokerEvents } from '@/entities/broker-event';
import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; 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 { function eventTypeLabel(type: string): string {
switch (type) { switch (type) {
@ -19,26 +22,41 @@ function eventTypeLabel(type: string): string {
} }
} }
function formatDate(value: string | null): string { function defaultPeriod(): { from: string; to: string } {
if (!value) return '-'; const now = dayjs();
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);
return { return {
from: from.toISOString().slice(0, 10), from: now.format('YYYY-MM-DD'),
to: to.toISOString().slice(0, 10), to: now.add(7, 'day').format('YYYY-MM-DD'),
}; };
} }
export function BrokerEventsPage() { export function BrokerEventsPage() {
const { accountId } = useBrokerAccountContext(); const { accountId } = useBrokerAccountContext();
const period = defaultPeriod(); const [searchParams, setSearchParams] = useSearchParams();
const events = useBrokerEvents(accountId, period); 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; const ev = events.data;
@ -50,6 +68,32 @@ export function BrokerEventsPage() {
<Heading level={2} id="broker-events-heading"> <Heading level={2} id="broker-events-heading">
События События
</Heading> </Heading>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<TextField
label="С"
type="date"
value={from}
onChange={(e) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('from', e.target.value);
setSearchParams(newParams, { replace: true });
}}
InputLabelProps={{ shrink: true }}
/>
<TextField
label="По"
type="date"
value={to}
onChange={(e) => {
const newParams = new URLSearchParams(searchParams);
newParams.set('to', e.target.value);
setSearchParams(newParams, { replace: true });
}}
InputLabelProps={{ shrink: true }}
error={!!dateError}
helperText={dateError}
/>
</Box>
</Box> </Box>
{events.error ? ( {events.error ? (
@ -62,7 +106,7 @@ export function BrokerEventsPage() {
</Text> </Text>
) : ev && ev.items.length === 0 ? ( ) : ev && ev.items.length === 0 ? (
<Text component="p" tone="muted"> <Text component="p" tone="muted">
На ближайшие 7 дней событий нет В выбранном диапазоне событий нет
</Text> </Text>
) : ev ? ( ) : ev ? (
<Box sx={{ display: 'grid', gap: 2 }}> <Box sx={{ display: 'grid', gap: 2 }}>
@ -86,7 +130,9 @@ export function BrokerEventsPage() {
<Text variant="caption" tone="secondary"> <Text variant="caption" tone="secondary">
Ближайшее Ближайшее
</Text> </Text>
<Box sx={{ fontWeight: 700 }}>{formatDate(ev.summary.nearestEventDate)}</Box> <Box sx={{ fontWeight: 700 }}>
{formatBrokerDate(ev.summary.nearestEventDate) ?? '-'}
</Box>
</Box> </Box>
<Box> <Box>
<Text variant="caption" tone="secondary"> <Text variant="caption" tone="secondary">
@ -166,7 +212,7 @@ export function BrokerEventsPage() {
component="td" component="td"
sx={{ p: 1, borderBottom: '1px solid', borderColor: 'divider' }} sx={{ p: 1, borderBottom: '1px solid', borderColor: 'divider' }}
> >
{formatDate(item.eventDate)} {formatBrokerDate(item.eventDate) ?? '-'}
</Box> </Box>
<Box <Box
component="td" component="td"