|
3 | 3 | import React, { useMemo } from 'react'; |
4 | 4 | import Link from 'next/link'; |
5 | 5 | import { Toaster } from 'react-hot-toast'; |
6 | | -import { Activity, AlertTriangle, ArrowLeft, Eraser, Trash2 } from 'lucide-react'; |
| 6 | +import { |
| 7 | + Activity, |
| 8 | + AlertTriangle, |
| 9 | + ArrowLeft, |
| 10 | + BarChart3, |
| 11 | + Eraser, |
| 12 | + Globe, |
| 13 | + ShieldCheck, |
| 14 | + Trash2, |
| 15 | +} from 'lucide-react'; |
7 | 16 | import { |
8 | 17 | CartesianGrid, |
9 | 18 | Line, |
@@ -43,6 +52,10 @@ export const PerformanceDashboard: React.FC = () => { |
43 | 52 | const { metrics, alerts, suggestions, trend, clearAlerts, refreshTrendFromStorage } = |
44 | 53 | usePerformanceMonitoring(); |
45 | 54 |
|
| 55 | + const isAnalyticsEnabled = |
| 56 | + process.env.NEXT_PUBLIC_ENABLE_PERF_ANALYTICS === 'true' || |
| 57 | + process.env.NODE_ENV === 'production'; |
| 58 | + |
46 | 59 | const seriesByVital = useMemo(() => { |
47 | 60 | const map: Record<string, ReturnType<typeof buildSeries>> = {}; |
48 | 61 | for (const n of VITAL_NAMES) { |
@@ -87,7 +100,58 @@ export const PerformanceDashboard: React.FC = () => { |
87 | 100 | </button> |
88 | 101 | </header> |
89 | 102 |
|
90 | | - <CoreWebVitals metrics={metrics} /> |
| 103 | + <div className="grid gap-6 md:grid-cols-3"> |
| 104 | + <div className="md:col-span-2"> |
| 105 | + <CoreWebVitals metrics={metrics} /> |
| 106 | + </div> |
| 107 | + <div className="rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900/50 p-4"> |
| 108 | + <h2 className="text-sm font-semibold flex items-center gap-2 mb-4"> |
| 109 | + <BarChart3 className="w-4 h-4 text-indigo-500" aria-hidden /> |
| 110 | + Analytics Status |
| 111 | + </h2> |
| 112 | + <div className="space-y-4"> |
| 113 | + <div className="flex items-center justify-between"> |
| 114 | + <span className="text-xs text-gray-500 dark:text-gray-400">Status</span> |
| 115 | + <span |
| 116 | + className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium ${ |
| 117 | + isAnalyticsEnabled |
| 118 | + ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400' |
| 119 | + : 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400' |
| 120 | + }`} |
| 121 | + > |
| 122 | + {isAnalyticsEnabled ? <ShieldCheck className="w-3 h-3" /> : null} |
| 123 | + {isAnalyticsEnabled ? 'Active' : 'Disabled (Dev)'} |
| 124 | + </span> |
| 125 | + </div> |
| 126 | + <div className="flex items-center justify-between"> |
| 127 | + <span className="text-xs text-gray-500 dark:text-gray-400">Endpoint</span> |
| 128 | + <span className="text-xs font-mono text-gray-600 dark:text-gray-300"> |
| 129 | + /api/performance/vitals |
| 130 | + </span> |
| 131 | + </div> |
| 132 | + <div className="pt-2 border-t border-gray-100 dark:border-gray-800"> |
| 133 | + <p className="text-[11px] text-gray-500 dark:text-gray-400 mb-2"> |
| 134 | + <Globe className="inline w-3 h-3 mr-1" /> |
| 135 | + Simulated Global Average (7d) |
| 136 | + </p> |
| 137 | + <div className="space-y-1.5"> |
| 138 | + {[ |
| 139 | + { name: 'LCP', val: '1.2s', status: 'good' }, |
| 140 | + { name: 'CLS', val: '0.04', status: 'good' }, |
| 141 | + { name: 'INP', val: '180ms', status: 'good' }, |
| 142 | + ].map((m) => ( |
| 143 | + <div key={m.name} className="flex justify-between text-[11px]"> |
| 144 | + <span className="text-gray-600 dark:text-gray-400">{m.name}</span> |
| 145 | + <span className="font-semibold text-emerald-600 dark:text-emerald-400"> |
| 146 | + {m.val} |
| 147 | + </span> |
| 148 | + </div> |
| 149 | + ))} |
| 150 | + </div> |
| 151 | + </div> |
| 152 | + </div> |
| 153 | + </div> |
| 154 | + </div> |
91 | 155 |
|
92 | 156 | <section aria-labelledby="perf-alerts-heading"> |
93 | 157 | <div className="flex items-center justify-between mb-3"> |
|
0 commit comments