import React, { useEffect, useState } from "react"; import axios from "axios"; import { useTranslation } from "react-i18next"; import { useNavigate, useLocation } from "react-router-dom"; import { Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Pagination } from "@heroui/react"; import { useUserStore } from "@/stores/userStore"; import { useContestStore } from "@/stores/contestStore"; type LogItem = { id: number; round_id: number; parsed?: boolean; parsed_claimed?: boolean; tname?: string | null; tdate?: string | null; pcall?: string | null; rcall?: string | null; // pokud backend vrátí, jinak zobrazíme prázdně psect?: string | null; pband?: string | null; power_watt?: number | null; claimed_qso_count?: number | null; claimed_score?: number | null; file_id?: number | null; file?: { id: number; filename: string; mimetype?: string | null; } | null; }; type PaginatedResponse = { data: T[]; current_page: number; last_page: number; total: number; }; type LogsTableProps = { roundId: number | null; perPage?: number; refreshKey?: number; contestId?: number | null; }; export default function LogsTable({ roundId, perPage = 50, refreshKey = 0, contestId = null }: LogsTableProps) { const { t } = useTranslation("common"); const user = useUserStore((s) => s.user); const selectedRound = useContestStore((s) => s.selectedRound); const navigate = useNavigate(); const location = useLocation(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [page, setPage] = useState(1); const [lastPage, setLastPage] = useState(1); const [finalPublished, setFinalPublished] = useState(false); const isAdmin = !!user?.is_admin; useEffect(() => { if (isAdmin) { setFinalPublished(true); return; } if (!roundId) { setFinalPublished(false); return; } const officialRunId = selectedRound?.id === roundId ? selectedRound?.official_evaluation_run_id ?? null : null; if (!officialRunId) { setFinalPublished(false); return; } let active = true; (async () => { try { const res = await axios.get<{ status?: string | null; result_type?: string | null }>( `/api/evaluation-runs/${officialRunId}`, { headers: { Accept: "application/json" }, withCredentials: true, } ); if (!active) return; const isFinal = res.data?.status === "SUCCEEDED" && res.data?.result_type === "FINAL"; setFinalPublished(isFinal); } catch { if (!active) return; setFinalPublished(false); } })(); return () => { active = false; }; }, [roundId, selectedRound?.official_evaluation_run_id, isAdmin]); useEffect(() => { if (!roundId) return; let active = true; (async () => { try { setLoading(true); setError(null); const res = await axios.get>("/api/logs", { headers: { Accept: "application/json" }, params: { round_id: roundId, per_page: perPage, page }, withCredentials: true, }); if (!active) return; setItems(res.data.data); setLastPage(res.data.last_page ?? 1); } catch (e: any) { if (!active) return; const message = e?.response?.data?.message ?? (t("unable_to_load_logs") as string) ?? "Nepodařilo se načíst logy."; setError(message); } finally { if (active) setLoading(false); } })(); return () => { active = false; }; }, [roundId, perPage, page, t, refreshKey]); useEffect(() => { setPage(1); }, [roundId, perPage, refreshKey]); if (!roundId) return null; if (loading) return
{t("logs_loading") ?? "Načítám logy…"}
; if (error) return
{error}
; if (!items.length) { return
{t("logs_empty") ?? "Žádné logy nejsou k dispozici."}
; } const columns = [ { key: "parsed", label: "" }, { key: "pcall", label: "PCall" }, { key: "pband", label: "PBand" }, { key: "psect", label: "PSect" }, { key: "power_watt", label: "SPowe" }, { key: "claimed_qso_count", label: "QSO" }, { key: "claimed_score", label: "Deklarované body" }, ...(user ? [{ key: "actions", label: "" }] : []), ]; const format = (value: string | null | undefined) => value || "—"; const formatPcall = (value: string | null | undefined, waiting: boolean) => waiting ? (t("logs_waiting_processing") as string) || "Čekám na zpracování" : value || "—"; const formatNumber = (value: number | null | undefined) => (value === null || value === undefined ? "—" : String(value)); const canNavigate = isAdmin || finalPublished; const handleDelete = async (id: number, e?: React.MouseEvent) => { e?.stopPropagation(); const confirmed = window.confirm(t("confirm_delete_log") ?? "Opravdu smazat log?"); if (!confirmed) return; try { await axios.delete(`/api/logs/${id}`, { headers: { Accept: "application/json" }, withCredentials: true, withXSRFToken: true, }); setItems((prev) => prev.filter((i) => i.id !== id)); } catch (e: any) { const message = e?.response?.data?.message ?? (t("unable_to_delete_log") as string) ?? "Nepodařilo se smazat log."; setError(message); } }; return (
{(column) => {column.label}} {(item) => ( { if (contestId && roundId && canNavigate) { navigate(`/contests/${contestId}/rounds/${roundId}/logs/${item.id}`, { state: { from: `${location.pathname}${location.search}` }, }); } }} className={ contestId && roundId && canNavigate ? "cursor-pointer hover:bg-default-100" : undefined } > {(columnKey) => { if (columnKey === "actions") { return (
{item.file_id && ( e.stopPropagation()} className="inline-flex items-center p-1 rounded hover:bg-default-100 text-default-500 hover:text-default-700" aria-label={t("download_file") ?? "Stáhnout soubor"} > )}
); } if (columnKey === "parsed") { const parsedClaimed = !!item.parsed_claimed; const symbol = parsedClaimed ? "✓" : "↻"; const color = parsedClaimed ? "text-green-600" : "text-blue-600"; return ( {symbol} ); } if (columnKey === "power_watt" || columnKey === "claimed_qso_count" || columnKey === "claimed_score") { return {formatNumber((item as any)[columnKey as string])}; } if (columnKey === "pcall") { const waiting = !item.parsed_claimed; return {formatPcall(item.pcall, waiting)}; } return {format((item as any)[columnKey as string])}; }}
)}
{lastPage > 1 && (
)}
); }