"use client"; import { AlertTriangle, Ban, CheckCircle2, Loader2, MicOff, ShieldAlert, Trash2, UserX, XCircle, } from "lucide-react"; import { useState } from "react"; import { Badge } from "@/components/primitives/badge"; import { EmptyState, LoadingSkeleton } from "@/components/shared"; import { useModerationActions, useModerationStats } from "@/hooks"; import { renderMessageContent } from "@/lib/format"; import type { ModerationAction, ModerationActionType, ModerationStats, } from "@/lib/types"; import { cn } from "@/lib/utils"; const ACTION_META: Record< ModerationActionType, { label: string; Icon: typeof Trash2; tone: "vermilion" | "amber" } > = { delete_message: { label: "Delete message", Icon: Trash2, tone: "vermilion" }, mute_user: { label: "Mute user", Icon: MicOff, tone: "amber" }, warn_user: { label: "Warn user", Icon: AlertTriangle, tone: "amber" }, kick_user: { label: "Kick user", Icon: UserX, tone: "amber" }, ban_user: { label: "Ban user", Icon: Ban, tone: "vermilion" }, }; const STATUS_META: Record< ModerationAction["status"], { label: string; tone: "signal" | "vermilion" | "amber" } > = { executed: { label: "Executed", tone: "signal" }, failed: { label: "Failed", tone: "vermilion" }, pending: { label: "Pending", tone: "amber" }, }; function fmtTime(ts: number | null): string { if (!ts) return "—"; const d = new Date(ts); const diff = Date.now() - ts; const hours = Math.floor(diff / 3600000); const rel = hours < 1 ? "baru saja" : hours < 24 ? `${hours} jam lalu` : `${Math.floor(hours / 24)} hari lalu`; return `${d.toLocaleString("id-ID")} (${rel})`; } const EMPTY_ACTION_RATE = { total: 0, executed: 0, failed: 0, pending: 0, failed_rate: 0, }; export function ModerationSection({ initialStats, initialActions, }: { initialStats?: ModerationStats; initialActions?: ModerationAction[]; } = {}) { const [status, setStatus] = useState(""); const [actionType, setActionType] = useState(""); const { data: stats } = useModerationStats(initialStats); const { data: actions, isLoading: actionsLoading } = useModerationActions( status, actionType, initialActions, ); const s = stats ?? EMPTY_ACTION_RATE; const statusFilters = ["", "executed", "failed", "pending"]; const typeFilters = [ "", "delete_message", "warn_user", "kick_user", "ban_user", "mute_user", ]; return (
{/* Summary cards */}
0 ? `${s.failed_rate}%` : undefined} />
{/* Filters */}
Status {statusFilters.map((f) => ( setStatus(f)} /> ))} Tipe {typeFilters.map((f) => ( setActionType(f)} /> ))}
{/* Timeline */} {actionsLoading && !actions ? ( ) : !actions || actions.length === 0 ? (
) : (
{actions.map((a) => ( ))}
)}

{actions?.length ?? 0} aksi ditampilkan · log moderasi gateway Discord

); } function SummaryCard({ label, value, tone, hint, }: { label: string; value: number; tone?: "signal" | "vermilion" | "amber"; hint?: string; }) { return (

{label}

{value} {hint && ( ({hint}) )}

); } function FilterChip({ active, label, onClick, }: { active: boolean; label: string; onClick: () => void; }) { return ( ); } function ActionRow({ action }: { action: ModerationAction }) { const meta = ACTION_META[action.action_type] ?? ACTION_META.delete_message; const st = STATUS_META[action.status]; const Icon = meta.Icon; return (
{meta.label} {action.username && ( @{action.username} )} {st.label}
{action.content && (

{renderMessageContent(action.content, null)}

)} {action.reason && (

Alasan: {action.reason}

)} {action.error && (

Error: {action.error}

)}

dibuat {fmtTime(action.created_at)} {action.executed_at ? ` · dieksekusi ${fmtTime(action.executed_at)}` : ""}

{action.status === "executed" ? ( ) : action.status === "failed" ? ( ) : ( )}
); } export default ModerationSection;