141 lines
5.6 KiB
TypeScript
141 lines
5.6 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { performanceService } from '../services/performanceService.ts';
|
|
|
|
const PerformanceDebug: React.FC = () => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [metrics, setMetrics] = useState(() => performanceService.getMetrics());
|
|
const [apiTimings, setApiTimings] = useState(() => performanceService.getApiTimings());
|
|
const [componentTimings, setComponentTimings] = useState(() =>
|
|
performanceService.getComponentTimings()
|
|
);
|
|
|
|
const refreshMetrics = useCallback(() => {
|
|
setMetrics(performanceService.getMetrics());
|
|
setApiTimings(performanceService.getApiTimings());
|
|
setComponentTimings(performanceService.getComponentTimings());
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return undefined;
|
|
refreshMetrics(); // Refresh immediately on open
|
|
const interval = setInterval(refreshMetrics, 2000);
|
|
return () => clearInterval(interval);
|
|
}, [isOpen, refreshMetrics]);
|
|
|
|
const formatMs = (value: number | null) => (value === null ? 'N/A' : `${Math.round(value)} ms`);
|
|
const formatCls = (value: number | null) => (value === null ? 'N/A' : value.toFixed(4));
|
|
|
|
if (!isOpen) {
|
|
return (
|
|
<button
|
|
onClick={() => setIsOpen(true)}
|
|
className="fixed bottom-6 right-24 bg-indigo-600 text-white w-16 h-16 rounded-full shadow-lg flex items-center justify-center hover:bg-indigo-700 transition-transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 z-40"
|
|
aria-label="Open Performance Debug"
|
|
>
|
|
<i className="fa-solid fa-tachometer-alt-fast text-2xl"></i>
|
|
</button>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="fixed bottom-6 right-6 w-96 bg-white rounded-2xl shadow-2xl z-40 border border-gray-200 transform transition-all text-sm font-sans">
|
|
<div className="p-4 border-b flex justify-between items-center bg-gray-50 rounded-t-2xl">
|
|
<h3 className="font-bold text-gray-800 flex items-center">
|
|
<i className="fa-solid fa-tachometer-alt-fast mr-2 text-indigo-600"></i> Performance Debug
|
|
</h3>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={refreshMetrics}
|
|
className="text-gray-500 hover:text-indigo-600"
|
|
title="Refresh Metrics"
|
|
>
|
|
<i className="fa-solid fa-sync"></i>
|
|
</button>
|
|
<button
|
|
onClick={() => setIsOpen(false)}
|
|
className="text-gray-400 hover:text-gray-600 text-xl"
|
|
aria-label="Close"
|
|
>
|
|
×
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="p-4 space-y-3 max-h-96 overflow-y-auto">
|
|
<div>
|
|
<h4 className="font-semibold text-gray-700 mb-1">Core Web Vitals & Loading</h4>
|
|
<ul className="space-y-1 pl-2 text-gray-600">
|
|
<li className="flex justify-between">
|
|
<span>Page Load Time:</span>{' '}
|
|
<span className="font-mono bg-gray-100 px-2 rounded">
|
|
{formatMs(metrics.pageLoad)}
|
|
</span>
|
|
</li>
|
|
<li className="flex justify-between">
|
|
<span>Time to First Byte (TTFB):</span>{' '}
|
|
<span className="font-mono bg-gray-100 px-2 rounded">{formatMs(metrics.ttfb)}</span>
|
|
</li>
|
|
<li className="flex justify-between">
|
|
<span>First Contentful Paint (FCP):</span>{' '}
|
|
<span className="font-mono bg-gray-100 px-2 rounded">{formatMs(metrics.fcp)}</span>
|
|
</li>
|
|
<li className="flex justify-between">
|
|
<span>Largest Contentful Paint (LCP):</span>{' '}
|
|
<span className="font-mono bg-gray-100 px-2 rounded">{formatMs(metrics.lcp)}</span>
|
|
</li>
|
|
<li className="flex justify-between">
|
|
<span>First Input Delay (FID):</span>{' '}
|
|
<span className="font-mono bg-gray-100 px-2 rounded">{formatMs(metrics.fid)}</span>
|
|
</li>
|
|
<li className="flex justify-between">
|
|
<span>Cumulative Layout Shift (CLS):</span>{' '}
|
|
<span className="font-mono bg-gray-100 px-2 rounded">{formatCls(metrics.cls)}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className="font-semibold text-gray-700 mb-1 pt-2 border-t mt-3">
|
|
Component Mount Timings
|
|
</h4>
|
|
<ul className="space-y-1 pl-2 text-gray-600">
|
|
{componentTimings.length > 0 ? (
|
|
componentTimings.map((timing) => (
|
|
<li key={timing.timestamp} className="flex justify-between">
|
|
<span>{timing.name}:</span>
|
|
<span className="font-mono bg-gray-100 px-2 rounded">
|
|
{formatMs(timing.duration)}
|
|
</span>
|
|
</li>
|
|
))
|
|
) : (
|
|
<li>No components timed yet.</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className="font-semibold text-gray-700 mb-1 pt-2 border-t mt-3">
|
|
API Timings (Last 10)
|
|
</h4>
|
|
<ul className="space-y-1 pl-2 text-gray-600">
|
|
{apiTimings.length > 0 ? (
|
|
apiTimings.map((timing) => (
|
|
<li key={timing.timestamp} className="flex justify-between">
|
|
<span>{timing.name}:</span>
|
|
<span className="font-mono bg-gray-100 px-2 rounded">
|
|
{formatMs(timing.duration)}
|
|
</span>
|
|
</li>
|
|
))
|
|
) : (
|
|
<li>No API calls recorded yet.</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PerformanceDebug;
|