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) ?? '-'}