import { useState, useEffect, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/lib/utils'; import { History, RefreshCw, Trash2, Play, ChevronDown, ChevronRight, CheckCircle, XCircle, Clock, AlertCircle, } from 'lucide-react'; import { useAppStore } from '@/store/app-store'; import type { StoredEventSummary, StoredEvent, EventHookTrigger } from '@automaker/types'; import { EVENT_HOOK_TRIGGER_LABELS } from '@automaker/types'; import { getHttpApiClient } from '@/lib/http-api-client'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; import { toast } from 'sonner'; export function EventHistoryView() { const currentProject = useAppStore((state) => state.currentProject); const projectPath = currentProject?.path; const [events, setEvents] = useState([]); const [loading, setLoading] = useState(false); const [expandedEvent, setExpandedEvent] = useState(null); const [expandedEventData, setExpandedEventData] = useState(null); const [replayingEvent, setReplayingEvent] = useState(null); const [clearDialogOpen, setClearDialogOpen] = useState(false); const loadEvents = useCallback(async () => { if (!projectPath) return; setLoading(true); try { const api = getHttpApiClient(); const result = await api.eventHistory.list(projectPath, { limit: 100 }); if (result.success && result.events) { setEvents(result.events); } } catch (error) { console.error('Failed to load events:', error); } finally { setLoading(false); } }, [projectPath]); useEffect(() => { loadEvents(); }, [loadEvents]); const handleExpand = async (eventId: string) => { if (expandedEvent === eventId) { setExpandedEvent(null); setExpandedEventData(null); return; } if (!projectPath) return; setExpandedEvent(eventId); try { const api = getHttpApiClient(); const result = await api.eventHistory.get(projectPath, eventId); if (result.success && result.event) { setExpandedEventData(result.event); } } catch (error) { console.error('Failed to load event details:', error); } }; const handleReplay = async (eventId: string) => { if (!projectPath) return; setReplayingEvent(eventId); try { const api = getHttpApiClient(); const result = await api.eventHistory.replay(projectPath, eventId); if (result.success && result.result) { const { hooksTriggered, hookResults } = result.result; const successCount = hookResults.filter((r) => r.success).length; const failCount = hookResults.filter((r) => !r.success).length; if (hooksTriggered === 0) { toast.info('No matching hooks found for this event trigger.'); } else if (failCount === 0) { toast.success(`Successfully ran ${successCount} hook(s).`); } else { toast.warning( `Ran ${hooksTriggered} hook(s): ${successCount} succeeded, ${failCount} failed.` ); } } } catch (error) { console.error('Failed to replay event:', error); toast.error('Failed to replay event. Check console for details.'); } finally { setReplayingEvent(null); } }; const handleDelete = async (eventId: string) => { if (!projectPath) return; try { const api = getHttpApiClient(); const result = await api.eventHistory.delete(projectPath, eventId); if (result.success) { setEvents((prev) => prev.filter((e) => e.id !== eventId)); if (expandedEvent === eventId) { setExpandedEvent(null); setExpandedEventData(null); } } } catch (error) { console.error('Failed to delete event:', error); } }; const handleClearAll = async () => { if (!projectPath) return; try { const api = getHttpApiClient(); const result = await api.eventHistory.clear(projectPath); if (result.success) { setEvents([]); setExpandedEvent(null); setExpandedEventData(null); } } catch (error) { console.error('Failed to clear events:', error); } setClearDialogOpen(false); }; const getTriggerIcon = (trigger: EventHookTrigger) => { switch (trigger) { case 'feature_created': return ; case 'feature_success': return ; case 'feature_error': return ; case 'auto_mode_complete': return ; case 'auto_mode_error': return ; default: return ; } }; const formatTimestamp = (timestamp: 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(); }; if (!projectPath) { return (

Select a project to view event history

); } return (
{/* Header with actions */}

{events.length} event{events.length !== 1 ? 's' : ''} recorded

{events.length > 0 && ( )}
{/* Events list */} {events.length === 0 ? (

No events recorded yet

Events will appear here when features are created or completed

) : (
{events.map((event) => (
{/* Event header */}
handleExpand(event.id)} > {getTriggerIcon(event.trigger)}

{EVENT_HOOK_TRIGGER_LABELS[event.trigger]}

{event.featureName && (

{event.featureName}

)}
{formatTimestamp(event.timestamp)} {/* Actions */}
e.stopPropagation()}>
{/* Expanded details */} {expandedEvent === event.id && expandedEventData && (
Event ID:

{expandedEventData.id}

Timestamp:

{new Date(expandedEventData.timestamp).toLocaleString()}

{expandedEventData.featureId && (
Feature ID:

{expandedEventData.featureId}

)} {expandedEventData.passes !== undefined && (
Passed:

{expandedEventData.passes ? 'Yes' : 'No'}

)}
{expandedEventData.error && (
Error:

{expandedEventData.error}

)}
Project:

{expandedEventData.projectPath}

)}
))}
)} {/* Clear confirmation dialog */}
); }