'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'; interface ReportMobileDesktopProps { reports: { mobile: { report: any }; desktop: { report: any }; } | null; loading: boolean; } const CustomizedContent = (props: any) => { const { root, depth, x, y, width, height, index, name, colors, bg, resourceSize } = props; return ( {width > 50 && height > 30 && ( {name.split('/').pop().split('?')[0].substring(0, 15)} )} ); }; const ReportMobileDesktop: React.FC = ({ reports, loading }) => { const [activeTab, setActiveTab] = useState<'mobile' | 'desktop'>('mobile'); const getScoreColor = (score: number) => { if (score >= 90) return '#10b981'; // Green-500 if (score >= 50) return '#f59e0b'; // Amber-500 return '#ef4444'; // Red-500 }; const getScoreLabel = (score: number) => { if (score >= 90) return 'Exceptional'; if (score >= 50) return 'Needs Improvement'; return 'Critical'; }; const [hoveredMetric, setHoveredMetric] = useState(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'; if (label === 'LCP') return num <= 2.5 ? '#10b981' : num <= 4.0 ? '#f59e0b' : '#ef4444'; if (label === 'TBT') return num <= 200 ? '#10b981' : num <= 600 ? '#f59e0b' : '#ef4444'; if (label === 'CLS') return num <= 0.1 ? '#10b981' : num <= 0.25 ? '#f59e0b' : '#ef4444'; if (label === 'SI') return num <= 3.4 ? '#10b981' : num <= 5.8 ? '#f59e0b' : '#ef4444'; return '#e5e7eb'; }; const renderScoreCircleLarge = (score: number, metrics: any) => { const radius = 100; const stroke = 12; const innerRadius = radius - stroke; const circumference = 2 * Math.PI * innerRadius; const mainColor = getScoreColor(score); // Weights: FCP (10%), SI (10%), LCP (25%), TBT (30%), CLS (25%) const segments = [ { id: 'FCP', label: 'FCP', weight: 0.10, value: metrics?.firstContentfulPaint }, { id: 'LCP', label: 'LCP', weight: 0.25, value: metrics?.largestContentfulPaint }, { id: 'TBT', label: 'TBT', weight: 0.30, value: metrics?.totalBlockingTime }, { id: 'CLS', label: 'CLS', weight: 0.25, value: metrics?.cumulativeLayoutShift }, { id: 'SI', label: 'SI', weight: 0.10, value: metrics?.speedIndex }, ]; let currentOffset = 0; const gap = 4; // Degrees of gap between segments return (
{/* Background glow */}
{segments.map((seg, idx) => { const segmentLength = (seg.weight * circumference) - (gap * (circumference / 360)); const dashArray = `${segmentLength} ${circumference - segmentLength}`; const dashOffset = -currentOffset; const color = getMetricColor(seg.id, seg.value); // Calculate position for labels const angle = (currentOffset / circumference) * 360 + (seg.weight * 180); const labelRadius = radius + 25; const lx = radius + labelRadius * Math.cos((angle * Math.PI) / 180); const ly = radius + labelRadius * Math.sin((angle * Math.PI) / 180); currentOffset += (seg.weight * circumference); return ( setHoveredMetric(seg.label)} onMouseLeave={() => setHoveredMetric(null)} className="cursor-pointer transition-all duration-300" > {/* Track */} {/* Progress */} ); })} {/* Central Score */}
{hoveredMetric ? (
{hoveredMetric}
s.label === hoveredMetric)?.value) }}> {segments.find(s => s.label === hoveredMetric)?.value?.split(' ')[0]}
) : (
{score}
)}
{/* Metric Labels (SI, FCP, LCP...) */}
{segments.map((seg, idx) => { // Manual angle positioning for exact match to image const angles = { SI: -140, FCP: -50, LCP: 20, TBT: 90, CLS: 180 }; const angle = angles[seg.id as keyof typeof angles]; const labelRadius = radius + 35; const x = radius + labelRadius * Math.cos((angle * Math.PI) / 180); const y = radius + labelRadius * Math.sin((angle * Math.PI) / 180); return (
{seg.label}
); })}

Performance

Values are estimated and may vary. The performance score is calculated directly from these metrics.

0-49
50-89
90-100
); }; const renderMiniScore = (score: number, title: string) => { const color = getScoreColor(score); return (
{score}
{title}
); }; const renderMetricPSI = (label: string, value: string) => { let statusColor = "#10b981"; const num = parseFloat(value || "0"); if (label.includes('Paint') || label.includes('Interactive')) { const limit = label.includes('Largest') ? 2.5 : 1.5; if (num > limit) statusColor = "#f59e0b"; if (num > limit * 2) statusColor = "#ef4444"; } return (
{label}
{value || '---'}
); }; if (loading) { return (
🚀

Quantifying speed...

Simulated device runs are operating in parallel to provide instant feedback.

); } if (!reports) return null; return (
{/* Environment Control */}
{/* Core Section: Large Score and Summary */}
{renderScoreCircleLarge(currentReport?.scores?.performance, currentReport?.metrics)}
{renderMiniScore(currentReport?.scores?.accessibility, "Accessibility")} {renderMiniScore(currentReport?.scores?.bestPractices, "Best Practices")} {renderMiniScore(currentReport?.scores?.seo, "SEO")} {currentReport?.scores?.pwa !== null && renderMiniScore(currentReport?.scores?.pwa, "PWA")}
Site Screenshot
Device: {activeTab.toUpperCase()}
Status: AUDIT COMPLETE
{/* Metrics Section */}

Vital Metrics

{renderMetricPSI("First Contentful Paint", currentReport?.metrics?.firstContentfulPaint)} {renderMetricPSI("Speed Index", currentReport?.metrics?.speedIndex)} {renderMetricPSI("Largest Contentful Paint", currentReport?.metrics?.largestContentfulPaint)} {renderMetricPSI("Time to Interactive", currentReport?.metrics?.timeToInteractive)} {renderMetricPSI("Total Blocking Time", currentReport?.metrics?.totalBlockingTime)} {renderMetricPSI("Cumulative Layout Shift", currentReport?.metrics?.cumulativeLayoutShift)}
{/* Timeline Filmstrip */} {currentReport?.thumbnails?.length > 0 && (
Filmstrip
Visual Loading sequence
{currentReport.thumbnails.map((thumb: any, i: number) => (
{`Frame
{(thumb.timing / 1000).toFixed(1)}s
))}
)} {/* Detailed Audits Grid */}
{/* Opportunities */}

Performance Opportunities

{currentReport?.opportunities?.length > 0 ? ( currentReport.opportunities.map((op: any, i: number) => (

{op.title}

-{op.estimatedSavings}

{op.description.split('[')[0]}

)) ) : (
No performance bottlenecks identified! ✨
)}
{/* Diagnostics */}
🛡️

Security & Best Practices

{Object.entries(currentReport?.diagnostics || {}).map(([key, pass]: any, i: number) => (
{pass ? "✓" : "!"}
{key.replace(/([A-Z])/g, ' $1').replace(/^./, (str: string) => str.toUpperCase())}
{pass ? "Secure" : "Warning"}
))}
{/* Passed Audits Recap */}
Passed Checks
All successful audits
{currentReport?.passedAudits?.map((title: string, i: number) => (
{title}
))}
{/* Treemap Inline Visualization */} {treemapData && treemapData.length > 0 && (
Resource Map
Javascript Distribution

Script Treemap Analysis

Visual breakdown of your top 15 scripts by resource size. Identifying large third-party scripts is key to performance.

} > { if (active && payload && payload.length) { const data = payload[0].payload; return (

Resource Info

{data.name}

Total Size

{data.resourceSize}

); } return null; }} />
)}
); }; export default ReportMobileDesktop;