"use client"; import { useMemo, useState } from "react"; import { ScrollText, Loader2, CheckCircle, XCircle, ChevronDown, } from "lucide-react"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { useCharacters } from "@/hooks/use-characters"; import { useLogs } from "@/hooks/use-analytics"; import type { ActionLog } from "@/lib/types"; const ACTION_TYPE_COLORS: Record = { move: "bg-blue-500/20 text-blue-400", fight: "bg-red-500/20 text-red-400", gather: "bg-green-500/20 text-green-400", rest: "bg-yellow-500/20 text-yellow-400", deposit: "bg-purple-500/20 text-purple-400", withdraw: "bg-cyan-500/20 text-cyan-400", craft: "bg-emerald-500/20 text-emerald-400", buy: "bg-teal-500/20 text-teal-400", sell: "bg-pink-500/20 text-pink-400", equip: "bg-orange-500/20 text-orange-400", unequip: "bg-orange-500/20 text-orange-400", use: "bg-amber-500/20 text-amber-400", task: "bg-indigo-500/20 text-indigo-400", }; function getActionColor(type: string): string { return ACTION_TYPE_COLORS[type] ?? "bg-muted text-muted-foreground"; } function formatDate(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleDateString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", }); } function getDetailsString(log: ActionLog): string { const d = log.details; if (!d || Object.keys(d).length === 0) return "-"; const parts: string[] = []; if (d.reason && typeof d.reason === "string") return d.reason; if (d.message && typeof d.message === "string") return d.message; if (d.error && typeof d.error === "string") return d.error; if (d.result && typeof d.result === "string") return d.result; if (d.monster) parts.push(`monster: ${d.monster}`); if (d.resource) parts.push(`resource: ${d.resource}`); if (d.item) parts.push(`item: ${d.item}`); if (d.x !== undefined && d.y !== undefined) parts.push(`(${d.x}, ${d.y})`); if (d.xp) parts.push(`xp: +${d.xp}`); if (d.gold) parts.push(`gold: ${d.gold}`); if (d.quantity) parts.push(`qty: ${d.quantity}`); return parts.length > 0 ? parts.join(" | ") : JSON.stringify(d); } const ALL_ACTION_TYPES = [ "move", "fight", "gather", "rest", "deposit", "withdraw", "craft", "buy", "sell", "equip", "unequip", "use", "task", ]; export default function LogsPage() { const { data: characters } = useCharacters(); const [characterFilter, setCharacterFilter] = useState("_all"); const [actionFilter, setActionFilter] = useState("_all"); const [visibleCount, setVisibleCount] = useState(50); const { data: logs, isLoading, error } = useLogs({ character: characterFilter === "_all" ? undefined : characterFilter, }); const filteredLogs = useMemo(() => { let items = logs ?? []; if (actionFilter !== "_all") { items = items.filter((log) => log.action_type === actionFilter); } // Sort by created_at descending return [...items].sort( (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ); }, [logs, actionFilter]); const visibleLogs = filteredLogs.slice(0, visibleCount); const hasMore = visibleCount < filteredLogs.length; return (

Action Logs

View detailed action logs across all characters

{error && (

Failed to load logs. Make sure the backend is running.

)} {/* Filters */}
{filteredLogs.length > 0 && (
Showing {visibleLogs.length} of {filteredLogs.length} entries
)}
{isLoading && (
)} {/* Log Table */} {visibleLogs.length > 0 && ( Time Character Action Details Status {visibleLogs.map((log) => ( {formatDate(log.created_at)} {log.character_name ?? "-"} {log.action_type} {getDetailsString(log)} {log.success ? ( ) : ( )} ))}
)} {/* Load More */} {hasMore && (
)} {/* Empty state */} {filteredLogs.length === 0 && !isLoading && (

No log entries found. Actions performed by characters or automations will appear here.

)}
); }