'use client'; import React, { useState, useEffect } from 'react'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; import { getGlobalApiLogger } from '@/lib/infrastructure/ApiRequestLogger'; import { getErrorAnalyticsStats, type ErrorStats } from '@/lib/services/error/ErrorAnalyticsService'; import { Activity, AlertTriangle, Clock, Copy, RefreshCw, Bug, Globe, Cpu, FileText, Trash2, Download, Search, ChevronDown, Zap, Terminal } from 'lucide-react'; import { Box } from '@/ui/Box'; import { Text } from '@/ui/Text'; import { Stack } from '@/ui/Stack'; import { Icon } from '@/ui/Icon'; import { IconButton } from '@/ui/IconButton'; import { Badge } from '@/ui/Badge'; import { Button } from '@/ui/Button'; import { Input } from '@/ui/Input'; interface ErrorAnalyticsDashboardProps { /** * Auto-refresh interval in milliseconds */ refreshInterval?: number; /** * Whether to show in production (default: false) */ showInProduction?: boolean; } function formatDuration(duration: number): string { return duration.toFixed(2) + 'ms'; } function formatPercentage(value: number, total: number): string { if (total === 0) return '0%'; return ((value / total) * 100).toFixed(1) + '%'; } function formatMemory(bytes: number): string { return (bytes / 1024 / 1024).toFixed(1) + 'MB'; } interface PerformanceWithMemory extends Performance { memory?: { usedJSHeapSize: number; totalJSHeapSize: number; jsHeapSizeLimit: number; }; } interface NavigatorWithConnection extends Navigator { connection?: { effectiveType: string; downlink: number; rtt: number; }; } /** * Comprehensive Error Analytics Dashboard * Shows real-time error statistics, API metrics, and environment details */ export function ErrorAnalyticsDashboard({ refreshInterval = 5000, showInProduction = false }: ErrorAnalyticsDashboardProps) { const [stats, setStats] = useState(null); const [isExpanded, setIsExpanded] = useState(true); const [selectedTab, setSelectedTab] = useState<'errors' | 'api' | 'environment' | 'raw'>('errors'); const [searchTerm, setSearchTerm] = useState(''); const [copied, setCopied] = useState(false); const isDev = process.env.NODE_ENV === 'development'; const shouldShow = isDev || showInProduction; const perf = typeof performance !== 'undefined' ? performance as PerformanceWithMemory : null; const nav = typeof navigator !== 'undefined' ? navigator as NavigatorWithConnection : null; useEffect(() => { if (!shouldShow) return; const update = () => { setStats(getErrorAnalyticsStats()); }; update(); if (refreshInterval > 0) { const interval = setInterval(update, refreshInterval); return () => clearInterval(interval); } }, [refreshInterval, shouldShow]); if (!shouldShow) { return null; } const updateStatsManual = () => { setStats(getErrorAnalyticsStats()); }; const copyToClipboard = async (data: unknown) => { try { await navigator.clipboard.writeText(JSON.stringify(data, null, 2)); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { // Silent failure } }; const exportAllData = () => { const globalHandler = getGlobalErrorHandler(); const apiLogger = getGlobalApiLogger(); const exportData = { timestamp: new Date().toISOString(), errors: globalHandler.getErrorHistory(), apiRequests: apiLogger.getHistory(), stats: stats, }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `gridpilot-error-report-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); }; const clearAllData = () => { if (confirm('Are you sure you want to clear all error and API logs?')) { const globalHandler = getGlobalErrorHandler(); const apiLogger = getGlobalApiLogger(); globalHandler.clearHistory(); apiLogger.clearHistory(); updateStatsManual(); } }; const filteredRecentErrors = stats?.recentErrors.filter(error => searchTerm === '' || error.message.toLowerCase().includes(searchTerm.toLowerCase()) || error.type.toLowerCase().includes(searchTerm.toLowerCase()) ) || []; if (!isExpanded) { return ( setIsExpanded(true)} variant="secondary" title="Open Error Analytics" size="lg" color="rgb(239, 68, 68)" /> ); } return ( {/* Header */} Error Analytics {isDev && ( DEV )} setIsExpanded(false)} variant="ghost" size="sm" title="Minimize" /> {/* Tabs */} {[ { id: 'errors', label: 'Errors', icon: AlertTriangle }, { id: 'api', label: 'API', icon: Globe }, { id: 'environment', label: 'Env', icon: Cpu }, { id: 'raw', label: 'Raw', icon: FileText }, ].map(tab => ( setSelectedTab(tab.id as 'errors' | 'api' | 'environment' | 'raw')} display="flex" flexGrow={1} alignItems="center" justifyContent="center" gap={1} px={2} py={2} cursor="pointer" transition bg={selectedTab === tab.id ? 'bg-deep-graphite' : ''} borderBottom={selectedTab === tab.id} borderColor={selectedTab === tab.id ? 'border-red-400' : ''} borderWidth={selectedTab === tab.id ? '2px' : '0'} > {tab.label} ))} {/* Content */} {/* Search Bar */} {selectedTab === 'errors' && ( setSearchTerm(e.target.value)} icon={} /> )} {/* Errors Tab */} {selectedTab === 'errors' && stats && ( {/* Error Summary */} Total Errors {stats.totalErrors} Error Types {Object.keys(stats.errorsByType).length} {/* Error Types Breakdown */} {Object.keys(stats.errorsByType).length > 0 && ( Error Types {Object.entries(stats.errorsByType).map(([type, count]) => ( {type} {count} ))} )} {/* Recent Errors */} {filteredRecentErrors.length > 0 && ( Recent Errors {filteredRecentErrors.map((error, idx) => ( {error.type} {new Date(error.timestamp).toLocaleTimeString()} {error.message} ))} )} {/* Error Timeline */} {stats.errorsByTime.length > 0 && ( Last 10 Minutes {stats.errorsByTime.map((point, idx) => ( {point.time} {point.count} errors ))} )} )} {/* API Tab */} {selectedTab === 'api' && stats && ( {/* API Summary */} Total Requests {stats.apiStats.totalRequests} Success Rate {formatPercentage(stats.apiStats.successful, stats.apiStats.totalRequests)} {/* API Stats */} API Metrics Successful {stats.apiStats.successful} Failed {stats.apiStats.failed} Avg Duration {formatDuration(stats.apiStats.averageDuration)} {/* Slowest Requests */} {stats.apiStats.slowestRequests.length > 0 && ( Slowest Requests {stats.apiStats.slowestRequests.map((req, idx) => ( {req.url} {formatDuration(req.duration)} ))} )} )} {/* Environment Tab */} {selectedTab === 'environment' && stats && ( {/* Environment Info */} Environment Node Environment {stats.environment.mode} {stats.environment.version && ( Version {stats.environment.version} )} {stats.environment.buildTime && ( Build Time {stats.environment.buildTime} )} {/* Browser Info */} Browser User Agent {navigator.userAgent} Language {navigator.language} Platform {navigator.platform} {/* Performance */} Performance Viewport {window.innerWidth}x{window.innerHeight} Screen {window.screen.width}x{window.screen.height} {perf?.memory && ( JS Heap {formatMemory(perf.memory.usedJSHeapSize)} )} {/* Connection */} {nav?.connection && ( Network Type {nav.connection.effectiveType} Downlink {nav.connection.downlink}Mbps RTT {nav.connection.rtt}ms )} )} {/* Raw Data Tab */} {selectedTab === 'raw' && stats && ( Export Options Maintenance Console Commands • window.__GRIDPILOT_GLOBAL_HANDLER__ • window.__GRIDPILOT_API_LOGGER__ • window.__GRIDPILOT_REACT_ERRORS__ )} {/* Footer */} Auto-refresh: {refreshInterval}ms {copied && Copied!} ); } /** * Hook for accessing error analytics programmatically */ export function useErrorAnalytics() { const globalHandler = getGlobalErrorHandler(); const apiLogger = getGlobalApiLogger(); return { getStats: () => { const errorStats = globalHandler.getStats(); const apiStats = apiLogger.getStats(); return { errors: errorStats, api: apiStats, }; }, getRecentErrors: (limit = 10) => { return globalHandler.getErrorHistory().slice(-limit); }, getSlowestApiRequests: (limit = 5) => { return apiLogger.getSlowestRequests(limit); }, exportData: () => { return { timestamp: new Date().toISOString(), errors: globalHandler.getErrorHistory(), apiRequests: apiLogger.getHistory(), }; }, clearAll: () => { globalHandler.clearHistory(); apiLogger.clearHistory(); }, }; }