import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; import { toast } from 'react-toastify'; import { Switch } from '~/components/ui/Switch'; import { logStore, type LogEntry } from '~/lib/stores/logs'; import { useStore } from '@nanostores/react'; import { classNames } from '~/utils/classNames'; import { motion } from 'framer-motion'; interface SelectOption { value: string; label: string; icon: string; color?: string; } const logLevelOptions: SelectOption[] = [ { value: 'all', label: 'All Levels', icon: 'i-ph:funnel' }, { value: 'info', label: 'Info', icon: 'i-ph:info', color: 'text-blue-500' }, { value: 'warning', label: 'Warning', icon: 'i-ph:warning', color: 'text-yellow-500' }, { value: 'error', label: 'Error', icon: 'i-ph:x-circle', color: 'text-red-500' }, { value: 'debug', label: 'Debug', icon: 'i-ph:bug', color: 'text-gray-500' }, ]; const logCategoryOptions: SelectOption[] = [ { value: 'all', label: 'All Categories', icon: 'i-ph:squares-four' }, { value: 'system', label: 'System', icon: 'i-ph:desktop' }, { value: 'provider', label: 'Provider', icon: 'i-ph:plug' }, { value: 'user', label: 'User', icon: 'i-ph:user' }, { value: 'error', label: 'Error', icon: 'i-ph:warning-octagon' }, ]; const SegmentedGroup = ({ value, onChange, options, className, }: { value: string; onChange: (value: string) => void; options: SelectOption[]; className?: string; }) => { const [isExpanded, setIsExpanded] = useState(false); const selectedOption = options.find((opt) => opt.value === value); if (!isExpanded) { return ( ); } return (
{options.map((option) => ( ))}
); }; const LogEntryItem = ({ log, isExpanded: forceExpanded, use24Hour, }: { log: LogEntry; isExpanded: boolean; use24Hour: boolean; }) => { const [isExpanded, setIsExpanded] = useState(forceExpanded); const [isCopied, setIsCopied] = useState(false); useEffect(() => { setIsExpanded(forceExpanded); }, [forceExpanded]); const handleCopy = useCallback(() => { const logText = `[${log.level.toUpperCase()}] ${log.message}\nTimestamp: ${new Date( log.timestamp, ).toLocaleString()}\nCategory: ${log.category}\nDetails: ${JSON.stringify(log.details, null, 2)}`; navigator.clipboard.writeText(logText).then(() => { setIsCopied(true); toast.success('Log copied to clipboard'); setTimeout(() => setIsCopied(false), 2000); }); }, [log]); const formattedTime = useMemo(() => { const date = new Date(log.timestamp); const now = new Date(); const isToday = date.toDateString() === now.toDateString(); const isYesterday = new Date(now.setDate(now.getDate() - 1)).toDateString() === date.toDateString(); const timeStr = date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: !use24Hour, }); if (isToday) { return { primary: timeStr, secondary: 'Today', }; } else if (isYesterday) { return { primary: timeStr, secondary: 'Yesterday', }; } else { const dateStr = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined, }); return { primary: dateStr, secondary: timeStr, }; } }, [log.timestamp, use24Hour]); return (
{log.level}

{log.message}

{log.details && ( )}
{formattedTime.primary} ยท {formattedTime.secondary}
{log.category}
{isExpanded && log.details && (
            {JSON.stringify(log.details, null, 2)}
          
)}
); }; /** * TODO: Future Enhancements * * 1. Advanced Features: * - Add export to JSON/CSV functionality * - Implement log retention policies * - Add custom alert rules and notifications * - Add pattern detection and highlighting * * 2. Visual Improvements: * - Add dark/light mode specific styling * - Implement collapsible JSON viewer * - Add timeline view with zoom capabilities * * 3. Performance Optimizations: * - Implement virtualized scrolling for large logs * - Add lazy loading for log details * - Optimize search with indexing */ export function EventLogsTab() { const logs = useStore(logStore.logs); const [logLevel, setLogLevel] = useState('all'); const [logCategory, setLogCategory] = useState('all'); const [autoScroll, setAutoScroll] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [expandAll, setExpandAll] = useState(false); const [use24Hour, setUse24Hour] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); const logsContainerRef = useRef(null); const [isScrolledToBottom, setIsScrolledToBottom] = useState(true); // Add refresh function const handleRefresh = useCallback(async () => { setIsRefreshing(true); try { // Since logStore doesn't have refresh, we'll re-fetch logs await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate refresh toast.success('Logs refreshed'); } catch (err) { console.error('Failed to refresh logs:', err); toast.error('Failed to refresh logs'); } finally { setIsRefreshing(false); } }, []); const filteredLogs = useMemo(() => { const allLogs = Object.values(logs); const filtered = allLogs.filter((log) => { const matchesLevel = logLevel === 'all' || log.level === logLevel; const matchesCategory = logCategory === 'all' || log.category === logCategory; const matchesSearch = !searchQuery || log.message?.toLowerCase().includes(searchQuery.toLowerCase()) || JSON.stringify(log.details)?.toLowerCase()?.includes(searchQuery?.toLowerCase()); return matchesLevel && matchesCategory && matchesSearch; }); return filtered.reverse(); }, [logs, logLevel, logCategory, searchQuery]); const handleClearLogs = useCallback(() => { if (confirm('Are you sure you want to clear all logs?')) { logStore.clearLogs(); toast.success('Logs cleared successfully'); } }, []); const handleExportLogs = useCallback(() => { try { const logText = logStore .getLogs() .map( (log) => `[${log.level.toUpperCase()}] ${log.timestamp} - ${log.message}${ log.details ? '\nDetails: ' + JSON.stringify(log.details, null, 2) : '' }`, ) .join('\n\n'); const blob = new Blob([logText], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `event-logs-${new Date().toISOString()}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success('Logs exported successfully'); } catch (error) { toast.error('Failed to export logs'); console.error('Export error:', error); } }, []); const handleScroll = () => { const container = logsContainerRef.current; if (!container) { return; } const { scrollTop, scrollHeight, clientHeight } = container; const isBottom = Math.abs(scrollHeight - clientHeight - scrollTop) < 10; setIsScrolledToBottom(isBottom); }; useEffect(() => { const container = logsContainerRef.current; if (container && (autoScroll || isScrolledToBottom)) { container.scrollTop = container.scrollHeight; } }, [filteredLogs, autoScroll, isScrolledToBottom]); return (
{/* Header Section */}
{/* Title and Refresh */}

Event Logs

Track system events and debug information

{/* Controls Section */}
Auto-scroll
24h Time
Expand All
{/* Header with Search */}
setSearchQuery(e.target.value)} />
{/* Filters Row */}
setLogLevel(value as LogEntry['level'] | 'all')} options={logLevelOptions} />
setLogCategory(value as LogEntry['category'] | 'all')} options={logCategoryOptions} />
{/* Logs Display */}
{filteredLogs.map((log) => ( ))}
{/* Status Bar */}
{filteredLogs.length} logs displayed {isScrolledToBottom ? 'Watching for new logs...' : 'Scroll to bottom to watch new logs'}
Export
Clear
); }