123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import axios from "axios";
|
|
import { Card, CardBody, CardHeader, Divider } from "@heroui/react";
|
|
import { useLanguageStore } from "@/stores/languageStore";
|
|
import { type ContestSummary } from "@/stores/contestStore";
|
|
|
|
type ContestDetailProps = {
|
|
contest?: ContestSummary | null;
|
|
};
|
|
|
|
type ContestDetailData = ContestSummary & {
|
|
description?: string | null;
|
|
evaluator?: string | null;
|
|
email?: string | null;
|
|
email2?: string | null;
|
|
url?: string | null;
|
|
rule_set_id?: number | null;
|
|
rule_set?: { id: number; name: string } | null;
|
|
bands?: { id: number; name: string }[];
|
|
categories?: { id: number; name: string }[];
|
|
power_categories?: { id: number; name: string }[];
|
|
};
|
|
|
|
export default function ContestDetail({ contest }: ContestDetailProps) {
|
|
const locale = useLanguageStore((s) => s.locale);
|
|
const [detail, setDetail] = useState<ContestDetailData | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!contest) {
|
|
setDetail(null);
|
|
return;
|
|
}
|
|
|
|
// pokud už máme detailní data, použij je a nefetchuj
|
|
const hasDetailFields =
|
|
"evaluator" in contest ||
|
|
"bands" in contest ||
|
|
"categories" in contest ||
|
|
"power_categories" in contest ||
|
|
"rule_set" in contest ||
|
|
"url" in contest;
|
|
|
|
if (hasDetailFields) {
|
|
setDetail(contest as ContestDetailData);
|
|
setLoading(false);
|
|
setError(null);
|
|
return;
|
|
}
|
|
|
|
let active = true;
|
|
(async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const res = await axios.get<ContestDetailData>(`/api/contests/${contest.id}`, {
|
|
headers: { Accept: "application/json" },
|
|
params: { lang: locale },
|
|
withCredentials: true,
|
|
});
|
|
|
|
if (!active) return;
|
|
setDetail(res.data);
|
|
} catch {
|
|
if (!active) return;
|
|
setError("Nepodařilo se načíst detail závodu.");
|
|
} finally {
|
|
if (active) setLoading(false);
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, [contest, locale]);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex flex-col">
|
|
<span className="text-lg font-semibold">
|
|
{detail?.name ?? contest?.name ?? "Vyber závod"}
|
|
</span>
|
|
<span className="text-sm text-foreground-500">
|
|
{detail?.description ?? ""}
|
|
</span>
|
|
</div>
|
|
</CardHeader>
|
|
<Divider />
|
|
<CardBody>
|
|
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
{loading && <p className="text-sm text-foreground-500">Načítám detail…</p>}
|
|
{!contest && !loading && <p className="text-sm">Vyber závod vlevo.</p>}
|
|
{detail && !loading && (
|
|
<div className="grid gap-2 text-sm">
|
|
{detail.url && (
|
|
<div className="flex gap-2">
|
|
<span className="font-semibold">URL:</span>
|
|
<a
|
|
href={detail.url}
|
|
className="text-primary underline"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
{detail.url}
|
|
</a>
|
|
</div>
|
|
)}
|
|
{(detail.rule_set || detail.rule_set_id) && (
|
|
<div className="flex gap-2">
|
|
<span className="font-semibold">Ruleset:</span>
|
|
<span>{detail.rule_set?.name ?? `#${detail.rule_set_id}`}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardBody>
|
|
</Card>
|
|
);
|
|
}
|