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 || '',
|
screenshot: tabData.screenshot || '',
|
||||||
thumbnails: tabData.thumbnails || [],
|
thumbnails: tabData.thumbnails || [],
|
||||||
passedAudits: tabData.passedAudits || [],
|
passedAudits: tabData.passedAudits || [],
|
||||||
|
treemapData: tabData.treemapData || [],
|
||||||
treemapPath: tabData.treemapPath || null
|
treemapPath: tabData.treemapPath || null
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { Treemap, ResponsiveContainer, Tooltip } from 'recharts';
|
||||||
import IconDesktop from '@/components/icon/icon-desktop';
|
import IconDesktop from '@/components/icon/icon-desktop';
|
||||||
import IconRefresh from '@/components/icon/icon-refresh';
|
import IconRefresh from '@/components/icon/icon-refresh';
|
||||||
import IconInfoCircle from '@/components/icon/icon-info-circle';
|
import IconInfoCircle from '@/components/icon/icon-info-circle';
|
||||||
@ -12,11 +13,42 @@ interface ReportMobileDesktopProps {
|
|||||||
loading: boolean;
|
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 ReportMobileDesktop: React.FC<ReportMobileDesktopProps> = ({ reports, loading }) => {
|
||||||
const [activeTab, setActiveTab] = useState<'mobile' | 'desktop'>('mobile');
|
const [activeTab, setActiveTab] = useState<'mobile' | 'desktop'>('mobile');
|
||||||
|
|
||||||
const currentReport = reports?.[activeTab]?.report || null;
|
|
||||||
|
|
||||||
const getScoreColor = (score: number) => {
|
const getScoreColor = (score: number) => {
|
||||||
if (score >= 90) return '#10b981'; // Green-500
|
if (score >= 90) return '#10b981'; // Green-500
|
||||||
if (score >= 50) return '#f59e0b'; // Amber-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 [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 getMetricColor = (label: string, value: string) => {
|
||||||
const num = parseFloat(value || "0");
|
const num = parseFloat(value || "0");
|
||||||
if (label === 'FCP') return num <= 1.8 ? '#10b981' : num <= 3.0 ? '#f59e0b' : '#ef4444';
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Treemap Final CTA */}
|
{/* Treemap Inline Visualization */}
|
||||||
{currentReport?.treemapPath && (
|
{treemapData && treemapData.length > 0 && (
|
||||||
<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="space-y-8 pt-10 border-t dark:border-gray-800">
|
||||||
<div className="relative p-12 bg-white dark:bg-black rounded-[39px] flex flex-col items-center text-center space-y-6 overflow-hidden">
|
<div className="flex flex-col items-center text-center space-y-2">
|
||||||
{/* Abstract background shape */}
|
<div className="flex items-center gap-2 px-2 text-[10px] font-black text-primary tracking-widest uppercase">
|
||||||
<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>
|
<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>
|
<div className="panel bg-white dark:bg-black border border-gray-100 dark:border-gray-800 rounded-[40px] p-8 shadow-xl overflow-hidden">
|
||||||
<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>
|
<div className="h-[400px] w-full">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<a
|
<Treemap
|
||||||
href={`http://localhost:3020${currentReport.treemapPath}`}
|
data={treemapData}
|
||||||
target="_blank"
|
dataKey="size"
|
||||||
rel="noreferrer"
|
aspectRatio={4 / 3}
|
||||||
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"
|
stroke="#fff"
|
||||||
>
|
fill="#3b82f6"
|
||||||
Explore Treemap
|
content={<CustomizedContent />}
|
||||||
</a>
|
>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user