Files
vkv/resources/js/components/ContestDetail.tsx
Zdeněk Burda 41e3ce6f25 Initial commit
2026-01-09 21:26:40 +01:00

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>
);
}