import { useCallback, useEffect, useMemo, useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import { ApiError, deleteRace, getRaceById } from "../api";
import {
formatDistance,
formatRaceDate,
getPaceLabel,
getRaceStatusClassName,
getRaceStatusLabel,
raceNeedsResultEntry,
} from "../lib";
import type { Race } from "../api";
function getErrorMessage(error: unknown): string {
if (error instanceof ApiError) {
return error.message;
}
return "Не удалось загрузить карточку старта.";
}
function DetailItem(props: { label: string; value: string | null | undefined }): JSX.Element | null {
const text = props.value?.trim();
if (!text) {
return null;
}
return (
{props.label}
{text}
);
}
function DetailLink(props: { label: string; url: string | null | undefined }): JSX.Element | null {
const href = props.url?.trim();
if (!href) {
return null;
}
return (
);
}
export function RaceDetailsPage(): JSX.Element {
const { raceId } = useParams<{ raceId: string }>();
const navigate = useNavigate();
const [race, setRace] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
useEffect(() => {
const ac = new AbortController();
let isMounted = true;
async function loadRace(): Promise {
if (!raceId) {
setErrorMessage("Не найден идентификатор старта.");
setIsLoading(false);
return;
}
try {
const item = await getRaceById(raceId, { signal: ac.signal });
if (!isMounted || ac.signal.aborted) {
return;
}
setRace(item);
setErrorMessage(null);
} catch (error) {
if (ac.signal.aborted || !isMounted) {
return;
}
setErrorMessage(getErrorMessage(error));
} finally {
if (isMounted && !ac.signal.aborted) {
setIsLoading(false);
}
}
}
void loadRace();
return () => {
isMounted = false;
ac.abort();
};
}, [raceId]);
const paceLabel = useMemo(() => {
if (!race || race.status !== "completed") {
return null;
}
return getPaceLabel(race.finishTime, race.distanceKm);
}, [race]);
const handleDelete = useCallback(async () => {
if (!raceId) {
return;
}
setIsDeleting(true);
try {
await deleteRace(raceId);
navigate("/races", { replace: true });
} catch (error) {
setErrorMessage(error instanceof ApiError ? error.message : "Не удалось удалить старт.");
setShowDeleteConfirm(false);
} finally {
setIsDeleting(false);
}
}, [raceId, navigate]);
if (isLoading) {
return (
Карточка старта
Загружаем данные старта...
);
}
if (errorMessage || !race) {
return (
Карточка старта
{errorMessage ?? "Старт не найден."}
Вернуться к списку стартов
);
}
const isCompleted = race.status === "completed";
return (
{race.title}
{formatRaceDate(race.date)} · {formatDistance(race.distanceKm)}
{getRaceStatusLabel(race.status, race.date)}
{raceNeedsResultEntry(race) ? (
Дата старта уже прошла —{" "}
внесите результат или обновите статус
.
) : null}
Редактировать
{showDeleteConfirm ? (
Удалить «{race.title}»? Это действие необратимо.
) : null}
Основная информация
- Дата
- {formatRaceDate(race.date)}
- Дистанция
- {formatDistance(race.distanceKm)}
- Статус
- {getRaceStatusLabel(race.status, race.date)}
Результаты
{isCompleted ? (
- Время
- {race.finishTime ?? "время не указано"}
- Темп
- {paceLabel ?? "не удалось вычислить"}
) : (
Метрики появятся после завершения старта и ввода результата.
)}
Заметки
{race.notes?.trim() ? race.notes : "Заметок пока нет."}
Назад к календарю стартов
);
}