Skrytí osobních údajů #1

Nezobrazovat detail logu anonymnímu uživateli #2
This commit is contained in:
Zdeněk Burda
2026-01-10 12:50:45 +01:00
parent 41e3ce6f25
commit 1e484aef47
7 changed files with 419 additions and 203 deletions

View File

@@ -4,6 +4,7 @@ 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;
@@ -14,13 +15,11 @@ type LogItem = {
tdate?: string | null;
pcall?: string | null;
rcall?: string | null; // pokud backend vrátí, jinak zobrazíme prázdně
pwwlo?: string | null;
psect?: string | null;
pband?: string | null;
power_watt?: number | null;
claimed_qso_count?: number | null;
claimed_score?: number | null;
remarks_eval?: string | null;
file_id?: number | null;
file?: {
id: number;
@@ -46,6 +45,7 @@ type LogsTableProps = {
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<LogItem[]>([]);
@@ -53,6 +53,49 @@ export default function LogsTable({ roundId, perPage = 50, refreshKey = 0, conte
const [error, setError] = useState<string | null>(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;
@@ -101,13 +144,11 @@ export default function LogsTable({ roundId, perPage = 50, refreshKey = 0, conte
const columns = [
{ key: "parsed", label: "" },
{ key: "pcall", label: "PCall" },
{ key: "pwwlo", label: "PWWLo" },
{ key: "pband", label: "PBand" },
{ key: "psect", label: "PSect" },
{ key: "power_watt", label: "SPowe" },
{ key: "claimed_qso_count", label: "QSO" },
{ key: "claimed_score", label: "Body" },
{ key: "remarks_eval", label: "remarks_eval" },
{ key: "claimed_score", label: "Deklarované body" },
...(user ? [{ key: "actions", label: "" }] : []),
];
@@ -116,23 +157,7 @@ export default function LogsTable({ roundId, perPage = 50, refreshKey = 0, conte
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 renderRemarksEval = (raw: string | null | undefined) => {
if (!raw) return "—";
try {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
const lines = parsed
.filter((item) => typeof item === "string" && item.trim() !== "")
.map((item, idx) => <div key={idx}>{item}</div>);
if (lines.length > 0) return lines;
}
} catch {
// fall through to show raw string
}
return <div>{raw}</div>;
};
const canNavigate = isAdmin || finalPublished;
const handleDelete = async (id: number, e?: React.MouseEvent) => {
e?.stopPropagation();
@@ -162,13 +187,15 @@ export default function LogsTable({ roundId, perPage = 50, refreshKey = 0, conte
<TableRow
key={item.id}
onClick={() => {
if (contestId && roundId) {
if (contestId && roundId && canNavigate) {
navigate(`/contests/${contestId}/rounds/${roundId}/logs/${item.id}`, {
state: { from: `${location.pathname}${location.search}` },
});
}
}}
className={contestId && roundId ? "cursor-pointer hover:bg-default-100" : undefined}
className={
contestId && roundId && canNavigate ? "cursor-pointer hover:bg-default-100" : undefined
}
>
{(columnKey) => {
if (columnKey === "actions") {
@@ -206,7 +233,6 @@ export default function LogsTable({ roundId, perPage = 50, refreshKey = 0, conte
}
if (columnKey === "parsed") {
const parsedClaimed = !!item.parsed_claimed;
const parsedAny = !!item.parsed;
const symbol = parsedClaimed ? "✓" : "↻";
const color = parsedClaimed ? "text-green-600" : "text-blue-600";
return (
@@ -215,9 +241,6 @@ export default function LogsTable({ roundId, perPage = 50, refreshKey = 0, conte
</TableCell>
);
}
if (columnKey === "remarks_eval") {
return <TableCell>{renderRemarksEval(item.remarks_eval)}</TableCell>;
}
if (columnKey === "power_watt" || columnKey === "claimed_qso_count" || columnKey === "claimed_score") {
return <TableCell>{formatNumber((item as any)[columnKey as string])}</TableCell>;
}