import { useCallback, useEffect, useMemo, useState } from "react"; import axios from "axios"; import { Spinner, useDisclosure } from "@heroui/react"; import RoundEvaluationLogOverridesTable from "@/components/RoundEvaluationLogOverridesTable"; import RoundEvaluationLogOverridesModal from "@/components/RoundEvaluationLogOverridesModal"; import RoundEvaluationLogOverridesSearch from "@/components/RoundEvaluationLogOverridesSearch"; import type { LogOverride, LogResult, OverrideForm, } from "@/components/RoundEvaluationLogOverrides.types"; type RoundDetail = { id: number; bands?: { id: number; name: string }[]; categories?: { id: number; name: string }[]; power_categories?: { id: number; name: string }[]; powerCategories?: { id: number; name: string }[]; }; type PaginatedResponse = { data: T[]; current_page: number; last_page: number; total: number; }; type Props = { roundId: number | null; evaluationRunId: number; }; export default function RoundEvaluationLogOverrides({ roundId, evaluationRunId }: Props) { const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure(); const [items, setItems] = useState([]); const [overrides, setOverrides] = useState>({}); const [forms, setForms] = useState>({}); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(""); const [roundDetail, setRoundDetail] = useState(null); const [activeLogId, setActiveLogId] = useState(null); const perPage = 5000; 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] ); const resolveName = (list: { id: number; name: string }[], id?: number | null) => { if (!id) return "AUTO"; return list.find((item) => item.id === id)?.name ?? `#${id}`; }; 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]); const fetchOverrides = useCallback(async (active?: { value: boolean }) => { try { const res = await axios.get>("/api/log-overrides", { headers: { Accept: "application/json" }, params: { evaluation_run_id: evaluationRunId, per_page: 5000 }, withCredentials: true, }); if (active && !active.value) return; const map: Record = {}; res.data.data.forEach((item) => { map[item.log_id] = item; }); setOverrides(map); } catch { if (active && !active.value) return; setOverrides({}); } }, [evaluationRunId]); useEffect(() => { const active = { value: true }; fetchOverrides(active); return () => { active.value = false; }; }, [fetchOverrides]); const fetchItems = useCallback(async (active?: { value: boolean }) => { try { setLoading(true); const res = await axios.get>("/api/log-results", { headers: { Accept: "application/json" }, params: { evaluation_run_id: evaluationRunId, per_page: perPage }, withCredentials: true, }); if (active && !active.value) return; setItems(res.data.data); } catch { if (active && !active.value) return; setItems([]); } finally { if (!active || active.value) setLoading(false); } }, [evaluationRunId, perPage]); useEffect(() => { const active = { value: true }; fetchItems(active); return () => { active.value = false; }; }, [fetchItems]); useEffect(() => { if (!items.length) return; setForms((prev) => { const next = { ...prev }; for (const item of items) { const override = overrides[item.log_id]; if (!next[item.log_id]) { next[item.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) : "", reason: override?.reason ?? "", saving: false, error: null, success: null, }; } } return next; }); }, [items, overrides]); const filteredItems = useMemo(() => { const query = search.trim().toLowerCase(); if (!query) return items; return items.filter((item) => { const log = item.log; const parts = [ String(item.log_id), log?.pcall ?? "", log?.pband ?? "", log?.psect ?? "", ] .join(" ") .toLowerCase(); return parts.includes(query); }); }, [items, search]); const activeItem = useMemo( () => (activeLogId ? items.find((item) => item.log_id === activeLogId) ?? null : null), [items, activeLogId] ); const openEditor = useCallback( (logId: number) => { setActiveLogId(logId); onOpen(); }, [onOpen] ); const handleFieldChange = (logId: number, field: keyof OverrideForm, value: string) => { const emptyForm: OverrideForm = { status: "AUTO", bandId: "", categoryId: "", powerCategoryId: "", reason: "", saving: false, error: null, success: null, }; setForms((prev) => ({ ...prev, [logId]: { ...(prev[logId] ?? emptyForm), [field]: value, error: null, success: null, }, })); }; const saveOverride = async (logId: number) => { 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 hasAny = hasStatus || hasBand || hasCategory || hasPower; const reason = 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) : "", }; const hasChanges = form.status !== baseline.status || form.bandId !== baseline.bandId || form.categoryId !== baseline.categoryId || form.powerCategoryId !== baseline.powerCategoryId; 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: "Bez změn." }, })); return; } if (!hasChanges && override) { setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, success: "Bez změn." }, })); return; } if (!reason) { setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, error: "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, 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: "Uloženo." }, })); await Promise.all([fetchOverrides(), fetchItems()]); onClose(); } catch (e: any) { const msg = e?.response?.data?.message || "Nepodařilo se uložit override."; setForms((prev) => ({ ...prev, [logId]: { ...prev[logId], saving: false, error: msg }, })); } }; if (!roundId) return null; return (
Ruční zásahy po agregaci
Slouží pro korekce klasifikace a statusu logu před publikací.
{loading && (
Načítám výsledky…
)} {!loading && filteredItems.length === 0 && (
Žádné položky k úpravě.
)} {filteredItems.length > 0 && (
)}
); }