'use client'; import { useState, useEffect } from 'react'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; import { getGlobalApiLogger } from '@/lib/infrastructure/ApiRequestLogger'; import { ApiError } from '@/lib/api/base/ApiError'; import { Activity, AlertTriangle, Clock, Copy, RefreshCw, Terminal, Database, Zap, Bug, Shield, Globe, Cpu, FileText, Trash2, Download, Search } from 'lucide-react'; interface ErrorAnalyticsDashboardProps { /** * Auto-refresh interval in milliseconds */ refreshInterval?: number; /** * Whether to show in production (default: false) */ showInProduction?: boolean; } interface ErrorStats { totalErrors: number; errorsByType: Record; errorsByTime: Array<{ time: string; count: number }>; recentErrors: Array<{ timestamp: string; message: string; type: string; context?: unknown; }>; apiStats: { totalRequests: number; successful: number; failed: number; averageDuration: number; slowestRequests: Array<{ url: string; duration: number }>; }; environment: { mode: string; version?: string; buildTime?: string; }; } /** * 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'; // Don't show in production unless explicitly enabled if (!isDev && !showInProduction) { return null; } useEffect(() => { updateStats(); if (refreshInterval > 0) { const interval = setInterval(updateStats, refreshInterval); return () => clearInterval(interval); } }, [refreshInterval]); const updateStats = () => { const globalHandler = getGlobalErrorHandler(); const apiLogger = getGlobalApiLogger(); const errorHistory = globalHandler.getErrorHistory(); const errorStats = globalHandler.getStats(); const apiHistory = apiLogger.getHistory(); const apiStats = apiLogger.getStats(); // Group errors by time (last 10 minutes) const timeGroups = new Map(); const now = Date.now(); const tenMinutesAgo = now - (10 * 60 * 1000); errorHistory.forEach(entry => { const entryTime = new Date(entry.timestamp).getTime(); if (entryTime >= tenMinutesAgo) { const timeKey = new Date(entry.timestamp).toLocaleTimeString(); timeGroups.set(timeKey, (timeGroups.get(timeKey) || 0) + 1); } }); const errorsByTime = Array.from(timeGroups.entries()) .map(([time, count]) => ({ time, count })) .sort((a, b) => a.time.localeCompare(b.time)); const recentErrors = errorHistory.slice(-10).reverse().map(entry => ({ timestamp: entry.timestamp, message: entry.error.message, type: entry.error instanceof ApiError ? entry.error.type : entry.error.name || 'Error', context: entry.context, })); const slowestRequests = apiLogger.getSlowestRequests(5).map(log => ({ url: log.url, duration: log.response?.duration || 0, })); setStats({ totalErrors: errorStats.total, errorsByType: errorStats.byType, errorsByTime, recentErrors, apiStats: { totalRequests: apiStats.total, successful: apiStats.successful, failed: apiStats.failed, averageDuration: apiStats.averageDuration, slowestRequests, }, environment: { mode: process.env.NODE_ENV || 'unknown', version: process.env.NEXT_PUBLIC_APP_VERSION, buildTime: process.env.NEXT_PUBLIC_BUILD_TIME, }, }); }; 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(); updateStats(); } }; const filteredRecentErrors = stats?.recentErrors.filter(error => searchTerm === '' || error.message.toLowerCase().includes(searchTerm.toLowerCase()) || error.type.toLowerCase().includes(searchTerm.toLowerCase()) ) || []; if (!isExpanded) { return ( ); } return (
{/* Header */}
Error Analytics {isDev && ( DEV )}
{/* 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 => ( ))}
{/* Content */}
{/* Search Bar */} {selectedTab === 'errors' && (
setSearchTerm(e.target.value)} className="w-full bg-iron-gray border border-charcoal-outline rounded px-3 py-2 pl-8 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-red-400" />
)} {/* 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
{stats.apiStats.totalRequests > 0 ? ((stats.apiStats.successful / stats.apiStats.totalRequests) * 100).toFixed(1) : 0}%
{/* API Stats */}
API Metrics
Successful {stats.apiStats.successful}
Failed {stats.apiStats.failed}
Avg Duration {stats.apiStats.averageDuration.toFixed(2)}ms
{/* Slowest Requests */} {stats.apiStats.slowestRequests.length > 0 && (
Slowest Requests
{stats.apiStats.slowestRequests.map((req, idx) => (
{req.url} {req.duration.toFixed(2)}ms
))}
)}
)} {/* 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}
{(performance as any).memory && (
JS Heap {((performance as any).memory.usedJSHeapSize / 1024 / 1024).toFixed(1)}MB
)}
{/* Connection */} {(navigator as any).connection && (
Network
Type {(navigator as any).connection.effectiveType}
Downlink {(navigator as any).connection.downlink}Mbps
RTT {(navigator as any).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(); }, }; }