Initial commit
This commit is contained in:
122
resources/js/components/ContestDetail.tsx
Normal file
122
resources/js/components/ContestDetail.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user