Implement page speed test feature with detailed mobile and desktop performance reports.
This commit is contained in:
parent
8d5b4798c7
commit
38f56fc6aa
@ -53,6 +53,7 @@ const PageSpeedTest = () => {
|
||||
screenshot: tabData.screenshot || '',
|
||||
thumbnails: tabData.thumbnails || [],
|
||||
passedAudits: tabData.passedAudits || [],
|
||||
treemapData: tabData.treemapData || [],
|
||||
treemapPath: tabData.treemapPath || null
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import { Treemap, ResponsiveContainer, Tooltip } from 'recharts';
|
||||
import IconDesktop from '@/components/icon/icon-desktop';
|
||||
import IconRefresh from '@/components/icon/icon-refresh';
|
||||
import IconInfoCircle from '@/components/icon/icon-info-circle';
|
||||
@ -12,11 +13,42 @@ interface ReportMobileDesktopProps {
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const CustomizedContent = (props: any) => {
|
||||
const { root, depth, x, y, width, height, index, name, colors, bg, resourceSize } = props;
|
||||
|
||||
return (
|
||||
<g>
|
||||
<rect
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height}
|
||||
style={{
|
||||
fill: bg || '#3b82f633',
|
||||
stroke: '#fff',
|
||||
strokeWidth: 2 / (depth + 1),
|
||||
strokeOpacity: 1 / (depth + 1),
|
||||
}}
|
||||
/>
|
||||
{width > 50 && height > 30 && (
|
||||
<text
|
||||
x={x + width / 2}
|
||||
y={y + height / 2}
|
||||
textAnchor="middle"
|
||||
fill="#fff"
|
||||
fontSize={12}
|
||||
fontFamily="inherit"
|
||||
fontWeight="bold"
|
||||
>
|
||||
{name.split('/').pop().split('?')[0].substring(0, 15)}
|
||||
</text>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
const ReportMobileDesktop: React.FC<ReportMobileDesktopProps> = ({ reports, loading }) => {
|
||||
const [activeTab, setActiveTab] = useState<'mobile' | 'desktop'>('mobile');
|
||||
|
||||
const currentReport = reports?.[activeTab]?.report || null;
|
||||
|
||||
const getScoreColor = (score: number) => {
|
||||
if (score >= 90) return '#10b981'; // Green-500
|
||||
if (score >= 50) return '#f59e0b'; // Amber-500
|
||||
@ -31,6 +63,25 @@ const ReportMobileDesktop: React.FC<ReportMobileDesktopProps> = ({ reports, load
|
||||
|
||||
const [hoveredMetric, setHoveredMetric] = useState<string | null>(null);
|
||||
|
||||
const currentReport = reports?.[activeTab]?.report || null;
|
||||
|
||||
// Format data for Recharts Treemap
|
||||
const formatTreemapData = (data: any[]) => {
|
||||
if (!data || !Array.isArray(data)) return [];
|
||||
return data
|
||||
.filter(node => node.resourceSize > 1000) // Only show relevant scripts > 1KB
|
||||
.map((node, i) => ({
|
||||
name: node.name || 'Unknown',
|
||||
size: node.resourceSize || 0,
|
||||
bg: i % 2 === 0 ? '#3b82f6' : '#2563eb', // Alternating blues
|
||||
resourceSize: (node.resourceSize / 1024).toFixed(1) + ' KB'
|
||||
}))
|
||||
.sort((a, b) => b.size - a.size)
|
||||
.slice(0, 15); // Top 15 scripts
|
||||
};
|
||||
|
||||
const treemapData = formatTreemapData(currentReport?.treemapData);
|
||||
|
||||
const getMetricColor = (label: string, value: string) => {
|
||||
const num = parseFloat(value || "0");
|
||||
if (label === 'FCP') return num <= 1.8 ? '#10b981' : num <= 3.0 ? '#f59e0b' : '#ef4444';
|
||||
@ -394,24 +445,53 @@ const ReportMobileDesktop: React.FC<ReportMobileDesktopProps> = ({ reports, load
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Treemap Final CTA */}
|
||||
{currentReport?.treemapPath && (
|
||||
<div className="relative p-1 overflow-hidden rounded-[40px] bg-gradient-to-r from-primary via-blue-400 to-primary group shadow-2xl">
|
||||
<div className="relative p-12 bg-white dark:bg-black rounded-[39px] flex flex-col items-center text-center space-y-6 overflow-hidden">
|
||||
{/* Abstract background shape */}
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-primary/5 rounded-full blur-3xl -mr-48 -mt-48 group-hover:bg-primary/10 transition-colors"></div>
|
||||
{/* Treemap Inline Visualization */}
|
||||
{treemapData && treemapData.length > 0 && (
|
||||
<div className="space-y-8 pt-10 border-t dark:border-gray-800">
|
||||
<div className="flex flex-col items-center text-center space-y-2">
|
||||
<div className="flex items-center gap-2 px-2 text-[10px] font-black text-primary tracking-widest uppercase">
|
||||
<span>Resource Map</span>
|
||||
<div className="w-1 h-1 bg-primary rounded-full"></div>
|
||||
<span>Javascript Distribution</span>
|
||||
</div>
|
||||
<h3 className="text-3xl font-black text-gray-800 dark:text-white uppercase tracking-tighter">Script Treemap Analysis</h3>
|
||||
<p className="text-gray-500 max-w-xl text-sm font-medium">Visual breakdown of your top 15 scripts by resource size. Identifying large third-party scripts is key to performance.</p>
|
||||
</div>
|
||||
|
||||
<h4 className="text-4xl font-black tracking-tighter text-black dark:text-white max-w-xl leading-[1.1]">In-Depth Resource Treemap Analysis</h4>
|
||||
<p className="text-gray-500 font-medium max-w-lg text-lg">Uncover hidden third-party scripts and Bloated assets that are stealing your performance. Interactive visual report awaits.</p>
|
||||
|
||||
<a
|
||||
href={`http://localhost:3020${currentReport.treemapPath}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="bg-primary hover:bg-blue-700 text-white px-12 py-5 rounded-[2rem] font-black uppercase tracking-widest shadow-[0_20px_50px_rgba(59,130,246,0.3)] hover:shadow-none transition-all hover:scale-105 active:scale-95"
|
||||
>
|
||||
Explore Treemap
|
||||
</a>
|
||||
<div className="panel bg-white dark:bg-black border border-gray-100 dark:border-gray-800 rounded-[40px] p-8 shadow-xl overflow-hidden">
|
||||
<div className="h-[400px] w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<Treemap
|
||||
data={treemapData}
|
||||
dataKey="size"
|
||||
aspectRatio={4 / 3}
|
||||
stroke="#fff"
|
||||
fill="#3b82f6"
|
||||
content={<CustomizedContent />}
|
||||
>
|
||||
<Tooltip
|
||||
content={({ active, payload }) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload;
|
||||
return (
|
||||
<div className="bg-black/90 backdrop-blur-xl border border-white/20 p-4 rounded-2xl shadow-2xl text-white">
|
||||
<p className="text-[10px] font-black uppercase text-primary tracking-widest mb-1">Resource Info</p>
|
||||
<p className="font-bold text-sm mb-2 max-w-[200px] truncate">{data.name}</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<div>
|
||||
<p className="text-[10px] text-gray-400 uppercase font-bold">Total Size</p>
|
||||
<p className="text-lg font-black text-green-400">{data.resourceSize}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
</Treemap>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user