import { Button, Checkbox, Chip, Heading, Text, TextField } from '@moex-vibe/design-system' import { Box } from '@mui/material' import dayjs from 'dayjs' import { useEffect, useState } from 'react' import { useBrokerEvents } from '@/entities/broker-event' import { formatBrokerCurrencyValue, formatBrokerDate } from '@/shared/lib/formatters' import { useSearchParamsCompat } from '@/shared/lib/router/useSearchParams' import { useBrokerAccountContext } from '@/widgets/broker-account-layout' const EVENT_TYPES = ['dividend', 'coupon', 'maturity', 'offer'] as const type EventType = (typeof EVENT_TYPES)[number] type Filters = { from: string to: string types: EventType[] } const EVENT_TYPE_OPTIONS: { value: EventType; label: string }[] = [ { value: 'dividend', label: 'Дивиденды' }, { value: 'coupon', label: 'Купоны' }, { value: 'maturity', label: 'Погашения' }, { value: 'offer', label: 'Оферты' }, ] function eventTypeLabel(type: string): string { switch (type) { case 'dividend': return 'Дивиденд' case 'coupon': return 'Купон' case 'maturity': return 'Погашение' case 'offer': return 'Оферта' default: return type } } function defaultPeriod(): { from: string; to: string } { const now = dayjs() return { from: now.subtract(7, 'day').format('YYYY-MM-DD'), to: now.add(7, 'day').format('YYYY-MM-DD'), } } function parseTypes(value: string | null): EventType[] { if (!value) return [...EVENT_TYPES] const parsed = value .split(',') .map((type) => type.trim()) .filter((type): type is EventType => EVENT_TYPES.includes(type as EventType)) return parsed.length > 0 ? parsed : [...EVENT_TYPES] } function filtersFromSearchParams(searchParams: URLSearchParams): Filters { const def = defaultPeriod() const from = searchParams.get('from') const to = searchParams.get('to') return { from: from && dayjs(from).isValid() ? from : def.from, to: to && dayjs(to).isValid() ? to : def.to, types: parseTypes(searchParams.get('types')), } } function filtersToSearchParams(filters: Filters): URLSearchParams { const next = new URLSearchParams() next.set('from', filters.from) next.set('to', filters.to) next.set('types', filters.types.join(',')) return next } function sourceLabel(source: string): string { return source === 'actual' ? 'Факт' : 'Прогноз' } export function BrokerEventsPage() { const { accountId } = useBrokerAccountContext() const [searchParams, setSearchParams] = useSearchParamsCompat() const [appliedFilters, setAppliedFilters] = useState(() => filtersFromSearchParams(searchParams), ) const [draftFilters, setDraftFilters] = useState(() => filtersFromSearchParams(searchParams), ) useEffect(() => { const next = filtersFromSearchParams(searchParams) setAppliedFilters(next) setDraftFilters(next) }, [searchParams]) const from = draftFilters.from const to = draftFilters.to const validFrom = dayjs(from) const validTo = dayjs(to) const dateError = from && to && validFrom.isValid() && validTo.isValid() && validTo.isBefore(validFrom) ? '"По" не может быть раньше "С"' : '' const typeError = draftFilters.types.length === 0 ? 'Выберите хотя бы один тип события' : '' const filterError = dateError || typeError const events = useBrokerEvents(filterError ? undefined : accountId, { from: appliedFilters.from, to: appliedFilters.to, types: appliedFilters.types.join(','), }) const ev = events.data function toggleType(type: EventType, checked: boolean) { setDraftFilters((current) => ({ ...current, types: checked ? [...new Set([...current.types, type])] : current.types.filter((t) => t !== type), })) } function applyFilters() { if (filterError) return setAppliedFilters(draftFilters) setSearchParams(filtersToSearchParams(draftFilters), { replace: true }) } return ( События { setDraftFilters((current) => ({ ...current, from: e.target.value })) }} InputLabelProps={{ shrink: true }} /> { setDraftFilters((current) => ({ ...current, to: e.target.value })) }} InputLabelProps={{ shrink: true }} error={!!dateError} helperText={dateError} /> Типы событий {EVENT_TYPE_OPTIONS.map((option) => ( toggleType(option.value, checked)} error={!!typeError} /> ))} {typeError && ( {typeError} )} {events.error ? ( Не удалось загрузить календарь событий ) : events.isLoading ? ( Загрузка событий… ) : ev && ev.items.length === 0 ? ( В выбранном диапазоне событий нет ) : ev ? ( Событий {ev.summary.eventCount} Ближайшее {formatBrokerDate(ev.summary.nearestEventDate) ?? '-'} Прогноз выплат ~{formatBrokerCurrencyValue('RUB', ev.summary.forecastEstimatedCashflow)} оценка* Поступило +{formatBrokerCurrencyValue('RUB', ev.summary.actualCashflow)} Дата Тип Статус Инструмент Сумма {ev.items.map((item) => ( {formatBrokerDate(item.eventDate) ?? '-'} {eventTypeLabel(item.type)} {item.ticker && {item.ticker}} {item.name && item.name !== item.ticker && ( {item.name} )} {item.source === 'actual' && item.actualAmount != null ? ( <> +{formatBrokerCurrencyValue(item.currency ?? 'RUB', item.actualAmount)} Поступило ) : item.estimatedAmount != null ? ( <> ~ {formatBrokerCurrencyValue( item.currency ?? 'RUB', item.estimatedAmount, )} оценка* ) : ( )} ))} ) : null} ) }