import { useCallback, useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import type { Race, RacesQuery } from "../api"; import { ApiError, getRaces } from "../api"; import { RacesCalendar } from "../components/RacesCalendar"; import { formatDistance, formatRaceDate, getRaceStatusClassName, getRaceStatusLabel, splitRacesByDate, } from "../lib"; const MONTH_OPTIONS: { value: string; label: string }[] = [ { value: "", label: "Все месяцы" }, { value: "1", label: "Январь" }, { value: "2", label: "Февраль" }, { value: "3", label: "Март" }, { value: "4", label: "Апрель" }, { value: "5", label: "Май" }, { value: "6", label: "Июнь" }, { value: "7", label: "Июль" }, { value: "8", label: "Август" }, { value: "9", label: "Сентябрь" }, { value: "10", label: "Октябрь" }, { value: "11", label: "Ноябрь" }, { value: "12", label: "Декабрь" }, ]; const VIEW_STORAGE_KEY = "races-view-mode"; type ViewMode = "list" | "calendar"; function yearSelectOptions(): number[] { const current = new Date().getFullYear(); const start = current - 2; const end = current + 4; const years: number[] = []; for (let y = start; y <= end; y += 1) { years.push(y); } return years; } function getErrorMessage(error: unknown): string { if (error instanceof ApiError) { return error.message; } return "Не удалось загрузить календарь стартов."; } function readInitialViewMode(): ViewMode { try { const v = sessionStorage.getItem(VIEW_STORAGE_KEY); return v === "calendar" ? "calendar" : "list"; } catch { return "list"; } } function RaceList(props: { title: string; races: Race[] }): JSX.Element { const { title, races } = props; return (

{title}

{races.length > 0 ? ( ) : (

Пока нет данных в этом разделе.

)}
); } export function RacesPage(): JSX.Element { const [races, setRaces] = useState([]); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(null); const [yearFilter, setYearFilter] = useState(""); const [monthFilter, setMonthFilter] = useState(""); const [viewMode, setViewMode] = useState(() => readInitialViewMode()); const setViewModePersist = useCallback((mode: ViewMode) => { setViewMode(mode); try { sessionStorage.setItem(VIEW_STORAGE_KEY, mode); } catch { /* ignore */ } }, []); const handleViewList = useCallback(() => { setViewModePersist("list"); }, [setViewModePersist]); const handleViewCalendar = useCallback(() => { setViewModePersist("calendar"); setYearFilter((prev) => (prev === "" ? String(new Date().getFullYear()) : prev)); }, [setViewModePersist]); const listQuery = useMemo((): RacesQuery | undefined => { if (viewMode === "calendar") { const y = yearFilter !== "" ? parseInt(yearFilter, 10) : new Date().getFullYear(); if (!Number.isNaN(y)) { return { year: y }; } return undefined; } const q: RacesQuery = {}; if (yearFilter !== "") { const y = parseInt(yearFilter, 10); if (!Number.isNaN(y)) { q.year = y; } } if (monthFilter !== "") { const m = parseInt(monthFilter, 10); if (!Number.isNaN(m)) { q.month = m; } } return Object.keys(q).length > 0 ? q : undefined; }, [viewMode, yearFilter, monthFilter]); const displayYear = useMemo(() => { if (yearFilter !== "") { const y = parseInt(yearFilter, 10); return Number.isNaN(y) ? new Date().getFullYear() : y; } return new Date().getFullYear(); }, [yearFilter]); useEffect(() => { const ac = new AbortController(); let isMounted = true; async function loadRaces(): Promise { setIsLoading(true); try { const items = await getRaces(listQuery, { signal: ac.signal }); if (!isMounted || ac.signal.aborted) { return; } setRaces(items); setErrorMessage(null); } catch (error) { if (ac.signal.aborted || !isMounted) { return; } setErrorMessage(getErrorMessage(error)); } finally { if (isMounted && !ac.signal.aborted) { setIsLoading(false); } } } void loadRaces(); return () => { isMounted = false; ac.abort(); }; }, [listQuery]); const { upcoming, past } = useMemo(() => splitRacesByDate(races), [races]); if (errorMessage && races.length === 0 && !isLoading) { return (

Календарь стартов

{errorMessage}

); } return (

Календарь стартов

Будущие и прошедшие старты в одном месте.

{errorMessage && !isLoading ? (

{errorMessage}

) : null}
{viewMode === "calendar" && monthFilter === "" ? (

Выберите месяц, чтобы увидеть его крупным планом.

) : null} {isLoading ? (

Загружаем данные...

) : null} {viewMode === "list" ? (
) : (
)}
); }