import { useEffect, useMemo, useState, useRef } from "react"; import axios from "axios"; import { Card, CardBody, Listbox, ListboxItem, type Selection } from "@heroui/react"; import { useTranslation } from "react-i18next"; import { useLanguageStore } from "@/stores/languageStore"; import { useContestStore, type ContestSummary } from "@/stores/contestStore"; import { useContestRefreshStore } from "@/stores/contestRefreshStore"; import { useNavigate } from "react-router-dom"; import RoundsOverview from "./RoundsOverview"; type ContestItem = ContestSummary & { description?: string | null; }; type PaginatedResponse = { data: T[]; }; type ContestsOverviewProps = { /** Zobraz pouze aktivní závody. Default: false */ onlyActive?: boolean; /** Zahrnout testovací závody. Default: false */ showTests?: boolean; className?: string; }; export default function ContestsOverview({ onlyActive = false, showTests = false, className }: ContestsOverviewProps) { const { t } = useTranslation("common"); const locale = useLanguageStore((s) => s.locale); const refreshKey = useContestRefreshStore((s) => s.refreshKey); const navigate = useNavigate(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedKeys, setSelectedKeys] = useState(new Set(["none"])); const lastFetchKey = useRef(null); const setSelectedContest = useContestStore((s) => s.setSelectedContest); const clearSelection = useContestStore((s) => s.clearSelection); const selectedContest = useContestStore((s) => s.selectedContest); // sync se store useEffect(() => { if (selectedContest) { setSelectedKeys(new Set([String(selectedContest.id)])); } else { setSelectedKeys(new Set(["none"])); } }, [selectedContest]); useEffect(() => { let active = true; const fetchKey = `${locale}-${refreshKey}`; // pokud už máme data pro tento klíč a seznam není prázdný, nefetchuj if (lastFetchKey.current === fetchKey && items.length > 0) { setLoading(false); return; } lastFetchKey.current = fetchKey; (async () => { try { setLoading(true); setError(null); const res = await axios.get | ContestItem[]>( "/api/contests", { withCredentials: true, headers: { Accept: "application/json" }, params: { lang: locale, only_active: onlyActive ? 1 : 0, include_tests: showTests ? 1 : 0, }, } ); if (!active) return; const data = Array.isArray(res.data) ? res.data : (res.data as PaginatedResponse).data; setItems(data); } catch { if (!active) return; setError(t("unable_to_load_contests") ?? "Nepodařilo se načíst seznam závodů."); } finally { if (active) setLoading(false); } })(); return () => { active = false; }; }, [locale, t, refreshKey, items.length, onlyActive, showTests]); const visibleItems = useMemo(() => items, [items]); const isSelected = (id: string | number) => { if (selectedKeys === "all") return false; return Array.from(selectedKeys).some((k) => String(k) === String(id)); }; const handleSelectionChange = (keys: Selection) => { setSelectedKeys(keys); if (keys === "all") return; const id = Array.from(keys)[0]; if (!id || id === "none") { clearSelection(); setSelectedKeys(new Set(["none"])); navigate("/contests"); return; } if (selectedContest && String(selectedContest.id) === String(id)) { return; } const selected = visibleItems.find((c) => String(c.id) === String(id)); if (selected) { setSelectedContest(selected); navigate(`/contests/${selected.id}`); } }; return ( {loading ? (
{t("contests_loading") ?? "Načítám závody…"}
) : error ? (
{error}
) : visibleItems.length === 0 ? (
{t("contests_empty") ?? "Žádné závody nejsou k dispozici."}
) : (
{t("contest_index_page") ?? "Přehled závodů"}
{visibleItems.map((item) => (
{item.name} {item.description || "—"}
))}
)}
); }