import { useEffect, useMemo, useState } from "react"; import type { Race } from "../api"; import { ApiError, getRaces } from "../api"; import { formatDistance, formatRaceDate, getRaceCountdownLabel, parseFinishTimeToSeconds, splitRacesByDate, } from "../lib"; function getErrorMessage(error: unknown): string { if (error instanceof ApiError) { return error.message; } return "Не удалось загрузить данные dashboard."; } export function DashboardPage(): JSX.Element { const [races, setRaces] = useState([]); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { let isMounted = true; async function loadDashboardData(): Promise { try { const items = await getRaces(); if (!isMounted) { return; } setRaces(items); setErrorMessage(null); } catch (error) { if (!isMounted) { return; } setErrorMessage(getErrorMessage(error)); } finally { if (isMounted) { setIsLoading(false); } } } void loadDashboardData(); return () => { isMounted = false; }; }, []); const dashboardMetrics = useMemo(() => { const { upcoming, past } = splitRacesByDate(races); const completed = races.filter((race) => race.status === "completed"); const nextRace = upcoming[0] ?? null; const lastResult = past.find((race) => race.status === "completed") ?? null; let personalRecord: Race | null = null; let personalRecordSeconds = Number.POSITIVE_INFINITY; for (const race of completed) { const finishSeconds = parseFinishTimeToSeconds(race.finishTime); if (!finishSeconds) { continue; } const candidate = finishSeconds / race.distanceKm; if (candidate < personalRecordSeconds) { personalRecordSeconds = candidate; personalRecord = race; } } const currentYear = new Date().getFullYear(); const seasonRaces = races.filter((race) => new Date(`${race.date}T00:00:00`).getFullYear() === currentYear); const seasonCompleted = seasonRaces.filter((race) => race.status === "completed"); return { nextRace, lastResult, personalRecord, seasonTotal: seasonRaces.length, seasonCompletedCount: seasonCompleted.length, }; }, [races]); if (isLoading) { return (

Dashboard

Загружаем ваши старты...

); } if (errorMessage) { return (

Dashboard

{errorMessage}

); } return (

Dashboard

Ключевые метрики по вашему календарю стартов.

Ближайший старт

{dashboardMetrics.nextRace ? ( <>

{dashboardMetrics.nextRace.title}

{formatRaceDate(dashboardMetrics.nextRace.date)} · {formatDistance(dashboardMetrics.nextRace.distanceKm)}

{getRaceCountdownLabel(dashboardMetrics.nextRace.date)}

) : (

Нет запланированных стартов.

)}

Последний результат

{dashboardMetrics.lastResult ? ( <>

{dashboardMetrics.lastResult.finishTime ?? "время не указано"}

{dashboardMetrics.lastResult.title} · {formatDistance(dashboardMetrics.lastResult.distanceKm)}

{formatRaceDate(dashboardMetrics.lastResult.date)}

) : (

Пока нет завершённых стартов.

)}

Личный рекорд

{dashboardMetrics.personalRecord ? ( <>

{dashboardMetrics.personalRecord.finishTime ?? "время не указано"}

{dashboardMetrics.personalRecord.title} · {formatDistance(dashboardMetrics.personalRecord.distanceKm)}

Лучший темп среди завершённых стартов.

) : (

Недостаточно данных для PR.

)}

Сезон

{dashboardMetrics.seasonTotal}

стартов в этом году

Завершено: {dashboardMetrics.seasonCompletedCount}

); }