import { ChevronRight, RefreshCw, ShieldAlert } from "lucide-react"; import { useEffect, useState } from "react"; import { api, errorMessage } from "../api"; import { Alert, Badge, EmptyRow, Metric, PageFrame, QueryPanel } from "../components/ui"; import { useI18n, type TFunction } from "../i18n"; import { formatDate } from "../lib/format"; import type { Navigate } from "../routing"; import type { ModerationCaseRow } from "../types"; const defaultStatuses = "open,in_review,action_pending,action_failed,appeal_review"; export function ModerationCasesPage({ navigate }: { navigate: Navigate }) { const { t } = useI18n(); const [statuses, setStatuses] = useState(defaultStatuses); const [assignedTo, setAssignedTo] = useState(""); const [rows, setRows] = useState([]); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); async function load() { setBusy(true); setError(""); try { const params = new URLSearchParams({ statuses, limit: "100" }); if (assignedTo.trim()) params.set("assigned_to", assignedTo.trim()); setRows((await api.moderationCases(params)).cases); } catch (err) { setError(errorMessage(err)); } finally { setBusy(false); } } useEffect(() => { void load(); }, []); const pendingActions = rows.filter((row) => row.Status === "action_pending" || row.Status === "action_failed").length; const critical = rows.filter((row) => row.Severity === 4).length; return ( {t("common.refresh")} } > {error && {error}}
{ event.preventDefault(); void load(); }}>
{rows.map((row) => ( ))} {rows.length === 0 && }
{t("moderation.case")} {t("moderation.target")} {t("common.status")} {t("moderation.severity")} {t("moderation.reportsAndReporters")} {t("moderation.assignee")} {t("moderation.latestReport")}
#{row.ID} {moderationTargetLabel(t, row.Target.Type, row.Target.ID)} {row.ReportCount} / {row.DistinctReporterCount} {row.AssignedTo || "-"} {formatDate(row.LastReportAt)}
); } export function CaseStatus({ status }: { status: string }) { const { t } = useI18n(); const tone = status === "resolved" || status === "dismissed" ? "good" : status === "action_failed" ? "danger" : status === "action_pending" ? "warn" : "neutral"; return {moderationEnumLabel(t, "status", status)}; } export function CaseSeverity({ value }: { value: number }) { const { t } = useI18n(); const keys = ["", "low", "medium", "high", "critical"]; const key = keys[value]; return ( = 4 ? "danger" : value >= 3 ? "warn" : "neutral"}> {key ? t(`moderation.severity.${key}`) : value} ); } export function moderationEnumLabel(t: TFunction, group: string, value: string): string { const key = `moderation.${group}.${value}`; const translated = t(key); return translated === key ? value : translated; } export function moderationTargetLabel(t: TFunction, type: string, id: number): string { return `${moderationEnumLabel(t, "targetType", type)} #${id}`; }