feat: add date range selection to broker events page
This commit is contained in:
parent
3027b4f0f0
commit
0ddd9b5f80
@ -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 }) => <h2>{children}</h2>,
|
||||
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';
|
||||
@ -179,7 +192,40 @@ describe('BrokerEventsPage', () => {
|
||||
} as unknown as ReturnType<typeof useBrokerEvents>);
|
||||
|
||||
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', () => {
|
||||
|
||||
@ -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() {
|
||||
<Heading level={2} id="broker-events-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>
|
||||
|
||||
{events.error ? (
|
||||
@ -62,7 +106,7 @@ export function BrokerEventsPage() {
|
||||
</Text>
|
||||
) : ev && ev.items.length === 0 ? (
|
||||
<Text component="p" tone="muted">
|
||||
На ближайшие 7 дней событий нет
|
||||
В выбранном диапазоне событий нет
|
||||
</Text>
|
||||
) : ev ? (
|
||||
<Box sx={{ display: 'grid', gap: 2 }}>
|
||||
@ -86,7 +130,9 @@ export function BrokerEventsPage() {
|
||||
<Text variant="caption" tone="secondary">
|
||||
Ближайшее
|
||||
</Text>
|
||||
<Box sx={{ fontWeight: 700 }}>{formatDate(ev.summary.nearestEventDate)}</Box>
|
||||
<Box sx={{ fontWeight: 700 }}>
|
||||
{formatBrokerDate(ev.summary.nearestEventDate) ?? '-'}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text variant="caption" tone="secondary">
|
||||
@ -166,7 +212,7 @@ export function BrokerEventsPage() {
|
||||
component="td"
|
||||
sx={{ p: 1, borderBottom: '1px solid', borderColor: 'divider' }}
|
||||
>
|
||||
{formatDate(item.eventDate)}
|
||||
{formatBrokerDate(item.eventDate) ?? '-'}
|
||||
</Box>
|
||||
<Box
|
||||
component="td"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user