import { useMemo, useState, useEffect, useRef } from 'react'; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { Button } from './ui/button'; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { ListFilter, ArrowDownCircle, Trash2, Download, X } from 'lucide-react'; import { Input } from './ui/input'; import { toast } from 'sonner'; import { useLog, LogEntry } from '@/contexts/LogContext'; interface LogViewerProps { onClose?: () => void; logs?: LogEntry[]; clearLogs?: () => void; title?: string; sourceInfo?: React.ReactNode; } const LogViewer: React.FC = ({ onClose, logs: propLogs, clearLogs: propClearLogs, title = "Activity Logs", sourceInfo }) => { const context = useLog(); const logs = propLogs || context.logs; const clearLogs = propClearLogs || context.clearLogs; const [autoScrollEnabled, setAutoScrollEnabled] = useState(true); const [activeTab, setActiveTab] = useState('all'); const scrollAreaRef = useRef(null); const messagesEndRef = useRef(null); const [searchTerm, setSearchTerm] = useState(''); const safeToString = (value: any): string => { if (typeof value === 'string') return value; if (value === null || typeof value === 'undefined') return ''; try { return JSON.stringify(value); } catch (e) { return '[Unserializable object]'; } }; const filteredLogs = useMemo(() => { let filtered = logs; if (searchTerm) { const searchTerms = searchTerm.toLowerCase().split(' ').filter(term => term.trim() !== ''); if (searchTerms.length > 0) { filtered = filtered.filter(log => searchTerms.every(term => (log.message && safeToString(log.message).toLowerCase().includes(term)) || (log.level && log.level.toLowerCase().includes(term)) || (log.category && safeToString(log.category).toLowerCase().includes(term)) ) ); } } return { all: filtered, info: filtered.filter(log => log.level === 'info'), debug: filtered.filter(log => log.level === 'debug'), warning: filtered.filter(log => log.level === 'warning'), error: filtered.filter(log => log.level === 'error'), success: filtered.filter(log => log.level === 'success'), }; }, [logs, searchTerm]); useEffect(() => { if (autoScrollEnabled && messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: 'auto', block: 'end' }); } }, [filteredLogs[activeTab as keyof typeof filteredLogs], activeTab, autoScrollEnabled]); const handleDownloadJson = () => { if (filteredLogs.all.length === 0) { toast.info("No logs to download based on the current filter."); return; } const jsonString = JSON.stringify(filteredLogs.all, null, 2); const blob = new Blob([jsonString], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); a.download = `wizard-logs-${timestamp}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success("Filtered logs have been downloaded."); }; const renderLogList = (level: keyof typeof filteredLogs) => ( {filteredLogs[level].length === 0 ? (
No {level !== 'all' ? `${level} ` : ''}logs available.
) : ( <> {filteredLogs[level].map((log) => (
{log.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })} [{log.level.toUpperCase()}] {log.category && ( [{safeToString(log.category)}] )} {safeToString(log.message)}
))}
)} ); const getLogLevelColor = (level: string) => { switch (level) { case 'error': return 'text-red-500'; case 'warning': return 'text-yellow-500'; case 'info': return 'text-blue-500'; case 'debug': return 'text-purple-400'; case 'success': return 'text-green-500'; default: return 'text-muted-foreground'; } }; return (
{/* Header - Mobile Optimized */}
{/* Title Row */}

{title} Logs {sourceInfo}

{onClose && ( )}
{/* Search Row */} setSearchTerm(e.target.value)} className="w-full h-9 text-sm" /> {/* Controls Row */}
All({filteredLogs.all.length}) Info({filteredLogs.info.length}) OK({filteredLogs.success.length}) Debug({filteredLogs.debug.length}) Warn({filteredLogs.warning.length}) Error ({filteredLogs.error.length})
{renderLogList('all')}
{renderLogList('info')}
{renderLogList('success')}
{renderLogList('debug')}
{renderLogList('warning')}
{renderLogList('error')}
); }; export default LogViewer;