import { useCallback, useEffect, useMemo, useState } from "react"; import axios from "axios"; import { useDisclosure } from "@heroui/react"; import { useTranslation } from "react-i18next"; import RoundEvaluationOverrideRow from "@/components/RoundEvaluationOverrideRow"; import RoundEvaluationOverrideDetailModal from "@/components/RoundEvaluationOverrideDetailModal"; import RoundEvaluationOverridesPagination from "@/components/RoundEvaluationOverridesPagination"; import type { LogItem, LogOverride, OverrideForm, RoundDetail, } from "@/components/RoundEvaluationOverrides.types"; type PaginatedResponse = { data: T[]; current_page: number; last_page: number; total: number; }; type Props = { roundId: number | null; evaluationRunId: number; }; export default function RoundEvaluationOverrides({ roundId, evaluationRunId }: Props) { const [logs, setLogs] = useState([]); const [overrides, setOverrides] = useState>({}); const [forms, setForms] = useState>({}); const [page, setPage] = useState(1); const [lastPage, setLastPage] = useState(1); const [roundDetail, setRoundDetail] = useState(null); const [loading, setLoading] = useState(false); const [activeLogId, setActiveLogId] = useState(null); const perPage = 30; const { isOpen, onOpen, onOpenChange } = useDisclosure(); const { t } = useTranslation("common"); const bands = useMemo(() => roundDetail?.bands ?? [], [roundDetail?.bands]); const categories = useMemo(() => roundDetail?.categories ?? [], [roundDetail?.categories]); const powerCategories = useMemo( () => roundDetail?.powerCategories ?? roundDetail?.power_categories ?? [], [roundDetail?.powerCategories, roundDetail?.power_categories] ); useEffect(() => { if (!roundId) return; let active = true; (async () => { try { const res = await axios.get(`/api/rounds/${roundId}`, { headers: { Accept: "application/json" }, withCredentials: true, }); if (!active) return; setRoundDetail(res.data); } catch { if (!active) return; setRoundDetail(null); } })(); return () => { active = false; }; }, [roundId]); useEffect(() => { if (!roundId) return; let active = true; (async () => { try { setLoading(true); const res = await axios.get>("/api/logs", { headers: { Accept: "application/json" }, params: { round_id: roundId, per_page: perPage, page }, withCredentials: true, }); if (!active) return; setLogs(res.data.data); setLastPage(res.data.last_page ?? 1); } catch { if (!active) return; setLogs([]); } finally { if (active) setLoading(false); } })(); return () => { active = false; }; }, [roundId, page]); useEffect(() => { let active = true; (async () => { try { const res = await axios.get>("/api/log-overrides", { headers: { Accept: "application/json" }, params: { evaluation_run_id: evaluationRunId, per_page: 500 }, withCredentials: true, }); if (!active) return; const map: Record = {}; res.data.data.forEach((item) => { map[item.log_id] = item; }); setOverrides(map); } catch { if (!active) return; setOverrides({}); } })(); return () => { active = false; }; }, [evaluationRunId]); useEffect(() => { if (!logs.length) return; setForms((prev) => { const next = { ...prev }; for (const log of logs) { const override = overrides[log.id]; if (!next[log.id]) { next[log.id] = { status: override?.forced_log_status ?? "AUTO", bandId: override?.forced_band_id ? String(override.forced_band_id) : "", categoryId: override?.forced_category_id ? String(override.forced_category_id) : "", powerCategoryId: override?.forced_power_category_id ? String(override.forced_power_category_id) : "", sixhrCategory: override?.forced_sixhr_category === null || override?.forced_sixhr_category === undefined ? "" : override.forced_sixhr_category ? "1" : "0", reason: override?.reason ?? "", saving: false, error: null, success: null, }; } } return next; }); }, [logs, overrides]); useEffect(() => { if (!isOpen) { setActiveLogId(null); } }, [isOpen]); const openDetail = useCallback( (logId: number) => { setActiveLogId(logId); onOpen(); }, [onOpen] ); const handleFieldChange = useCallback( (logId: number, field: keyof OverrideForm, value: string) => { setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], [field]: value, error: null, success: null, }, })); }, [] ); const commitReason = useCallback((logId: number, reason: string) => { setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], reason, error: null, success: null, }, })); }, []); const saveOverride = async (logId: number, reasonOverride?: string) => { const form = forms[logId]; if (!form) return; const override = overrides[logId]; const hasStatus = form.status && form.status !== "AUTO"; const hasBand = form.bandId !== ""; const hasCategory = form.categoryId !== ""; const hasPower = form.powerCategoryId !== ""; const hasSixhr = form.sixhrCategory !== ""; const hasAny = hasStatus || hasBand || hasCategory || hasPower || hasSixhr; if (reasonOverride !== undefined) { commitReason(logId, reasonOverride); } const reason = (reasonOverride ?? form.reason).trim(); const baseline = { status: override?.forced_log_status ?? "AUTO", bandId: override?.forced_band_id ? String(override.forced_band_id) : "", categoryId: override?.forced_category_id ? String(override.forced_category_id) : "", powerCategoryId: override?.forced_power_category_id ? String(override.forced_power_category_id) : "", sixhrCategory: override?.forced_sixhr_category === null || override?.forced_sixhr_category === undefined ? "" : override.forced_sixhr_category ? "1" : "0", }; const hasChanges = form.status !== baseline.status || form.bandId !== baseline.bandId || form.categoryId !== baseline.categoryId || form.powerCategoryId !== baseline.powerCategoryId || form.sixhrCategory !== baseline.sixhrCategory; setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: true, error: null, success: null }, })); try { if (!hasChanges && !override) { setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, success: t("override_no_changes") ?? "Bez změn." }, })); return; } if (!hasChanges && override) { setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, success: t("override_no_changes") ?? "Bez změn." }, })); return; } if (!reason) { setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, error: t("override_reason_required") ?? "Doplň důvod změny.", }, })); return; } const payload = { evaluation_run_id: evaluationRunId, log_id: logId, forced_log_status: form.status || "AUTO", forced_band_id: form.bandId ? Number(form.bandId) : null, forced_category_id: form.categoryId ? Number(form.categoryId) : null, forced_power_category_id: form.powerCategoryId ? Number(form.powerCategoryId) : null, forced_sixhr_category: form.sixhrCategory === "" ? null : form.sixhrCategory === "1", reason, }; if (override) { const res = await axios.put(`/api/log-overrides/${override.id}`, payload, { headers: { Accept: "application/json" }, withCredentials: true, withXSRFToken: true, }); setOverrides((prev) => ({ ...prev, [logId]: res.data })); } else { const res = await axios.post("/api/log-overrides", payload, { headers: { Accept: "application/json" }, withCredentials: true, withXSRFToken: true, }); setOverrides((prev) => ({ ...prev, [logId]: res.data })); } setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, success: t("override_saved") ?? "Uloženo." }, })); } catch (e: any) { const msg = e?.response?.data?.message || (t("override_save_failed") ?? "Nepodařilo se uložit override."); setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, error: msg }, })); } }; if (!roundId) return null; return (
{t("override_pre_match_title") ?? "Ruční zásahy před matchingem"}
{t("override_pre_match_hint") ?? "Změny se projeví po kliknutí na „Pokračovat“. IGNORED vyřadí log z matchingu."}
{loading && (
{t("override_loading_logs") ?? "Načítám logy…"}
)} {!loading && logs.length === 0 && (
{t("override_no_logs") ?? "Žádné logy k úpravě."}
)} {logs.length > 0 && (
{logs.map((log) => ( ))}
setPage((p) => Math.max(1, p - 1))} onNext={() => setPage((p) => Math.min(lastPage, p + 1))} />
)}
); }