import { useState, useEffect, useCallback, useRef } from "react"; import { X, History, Trash2, Filter, RefreshCw, CheckCircle, XCircle, ArrowRight, Plus, Settings, AlertCircle, Loader2 } from "lucide-react"; import { fetchActivityLog, clearActivityLog, type ActivityLogEntry, type ActivityEventType } from "../api"; import type { Task } from "@fusion/core"; interface ActivityLogModalProps { isOpen: boolean; onClose: () => void; tasks: Task[]; onOpenTaskDetail?: (taskId: string) => void; } const EVENT_TYPE_LABELS: Record = { "task:created": "Task Created", "task:moved": "Task Moved", "task:updated": "Task Updated", "task:deleted": "Task Deleted", "task:merged": "Task Merged", "task:failed": "Task Failed", "settings:updated": "Settings Updated", }; const EVENT_TYPE_ICONS: Record = { "task:created": , "task:moved": , "task:updated": , "task:deleted": , "task:merged": , "task:failed": , "settings:updated": , }; function formatTimestamp(timestamp: string): string { const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return "Just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString(undefined, { month: "short", day: "numeric" }); } export function ActivityLogModal({ isOpen, onClose, tasks, onOpenTaskDetail }: ActivityLogModalProps) { const [entries, setEntries] = useState([]); const [filteredType, setFilteredType] = useState("all"); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [showConfirmClear, setShowConfirmClear] = useState(false); const [hasMore, setHasMore] = useState(false); const pollingRef = useRef(null); const loadActivityLog = useCallback(async (since?: string) => { try { setIsLoading(true); setError(null); const options: { limit: number; since?: string; type?: ActivityEventType } = { limit: 100, since, }; if (filteredType !== "all") { options.type = filteredType; } const data = await fetchActivityLog(options); if (since) { // Append older entries setEntries((prev) => [...prev, ...data]); } else { // Replace with fresh entries setEntries(data); } setHasMore(data.length === 100); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load activity log"); } finally { setIsLoading(false); } }, [filteredType]); // Initial load and filter change useEffect(() => { if (isOpen) { loadActivityLog(); } }, [isOpen, loadActivityLog]); // Auto-refresh every 30 seconds when modal is open useEffect(() => { if (isOpen) { pollingRef.current = setInterval(() => { loadActivityLog(); }, 30000); } return () => { if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } }; }, [isOpen, loadActivityLog]); const handleLoadMore = () => { if (entries.length > 0) { const lastEntry = entries[entries.length - 1]; loadActivityLog(lastEntry.timestamp); } }; const handleClearLog = async () => { try { await clearActivityLog(); setEntries([]); setShowConfirmClear(false); } catch (err) { setError(err instanceof Error ? err.message : "Failed to clear activity log"); } }; const handleTaskClick = (taskId: string) => { if (onOpenTaskDetail) { onOpenTaskDetail(taskId); } }; // Handle escape key to close useEffect(() => { if (!isOpen) return; const handleKey = (e: KeyboardEvent) => { if (e.key === "Escape") { if (showConfirmClear) { setShowConfirmClear(false); } else { onClose(); } } }; document.addEventListener("keydown", handleKey); return () => document.removeEventListener("keydown", handleKey); }, [isOpen, onClose, showConfirmClear]); if (!isOpen) return null; return (
{ if (e.target === e.currentTarget) onClose(); }} data-testid="activity-log-modal-overlay" >
{/* Header */}
Activity Log
{/* Filter dropdown */}
{/* Refresh button */} {/* Clear button */} {entries.length > 0 && ( )} {/* Close button */}
{/* Content */}
{error && (
{error}
)} {entries.length === 0 && !isLoading && !error && (

No activity recorded yet

)}
{entries.map((entry) => (
{EVENT_TYPE_ICONS[entry.type]}
{EVENT_TYPE_LABELS[entry.type]} {formatTimestamp(entry.timestamp)}
{entry.taskId && ( )} {entry.taskTitle && ( {entry.taskTitle} )} {entry.details}
{entry.metadata && Object.keys(entry.metadata).length > 0 && (
{typeof entry.metadata.from === "string" && typeof entry.metadata.to === "string" && ( {entry.metadata.from} → {entry.metadata.to} )} {typeof entry.metadata.merged === "boolean" && ( {entry.metadata.merged ? "Merged" : "Not merged"} )}
)}
))}
{hasMore && !isLoading && ( )} {isLoading && entries.length > 0 && (
)}
{/* Confirmation dialog for clear */} {showConfirmClear && (

Clear Activity Log?

This will permanently delete all activity log entries. This action cannot be undone.

)}
); }