import { useMemo } from "react"; import { StatCard } from "@/client/features/audit/shared"; import { exportPages, exportPerformance, } from "@/client/features/audit/results/export"; import type { AuditResultsData } from "@/client/features/audit/results/types"; import { ExportDropdown, isLighthouseFailure, PagesTable, PerformanceTable, } from "@/client/features/audit/results/ResultsTables"; type ResultsTab = "pages" | "performance"; export function ResultsView({ projectId, data, onTabChange, tab, }: { projectId: string; data: AuditResultsData; tab: string; onTabChange: (tab: ResultsTab) => void; }) { const { audit, pages, lighthouse } = data; const hasPerformanceTab = lighthouse.length > 0; const activeTab = hasPerformanceTab ? tab : "pages"; const stats = useResultStats(pages, lighthouse); return ( <>
{ if (activeTab === "performance") { exportPerformance(lighthouse, pages, format); return; } exportPages(pages, format); }} /> {activeTab === "pages" && } {activeTab === "performance" && lighthouse.length > 0 && ( )}
); } function useResultStats( pages: AuditResultsData["pages"], lighthouse: AuditResultsData["lighthouse"], ) { const averageResponseMs = useMemo(() => { if (pages.length === 0) return 0; const total = pages.reduce( (sum: number, page: AuditResultsData["pages"][number]) => sum + (page.responseTimeMs ?? 0), 0, ); return Math.round(total / pages.length); }, [pages]); const lighthouseSummary = useMemo(() => { const failed = lighthouse.filter( (row: AuditResultsData["lighthouse"][number]) => isLighthouseFailure(row), ).length; const successful = lighthouse.filter( (row: AuditResultsData["lighthouse"][number]) => !isLighthouseFailure(row), ); const averageScore = ( key: "performanceScore" | "seoScore" | "accessibilityScore", ) => { const values = successful .map((row: AuditResultsData["lighthouse"][number]) => row[key]) .filter((value: number | null): value is number => value != null); if (values.length === 0) return null; const total = values.reduce((sum: number, value) => sum + value, 0); return Math.round(total / values.length); }; return { failed, avgPerformance: averageScore("performanceScore"), avgSeo: averageScore("seoScore"), avgAccessibility: averageScore("accessibilityScore"), }; }, [lighthouse]); return { averageResponseMs, lighthouseSummary }; } function ResultsHeader({ pageCount, lighthouseCount, hasPerformanceTab, activeTab, onTabChange, onExport, }: { pageCount: number; lighthouseCount: number; hasPerformanceTab: boolean; activeTab: string; onTabChange: (tab: ResultsTab) => void; onExport: (format: "csv" | "json" | "sheets") => void; }) { const tabs: Array<{ tab: ResultsTab; label: string }> = [ { tab: "pages", label: `Pages (${pageCount})` }, { tab: "performance", label: `Performance (${lighthouseCount})` }, ]; return (
{hasPerformanceTab ? (
{tabs.map(({ label, tab }) => { const isActive = activeTab === tab; return ( ); })}
) : (

Pages ({pageCount})

)}
); } function StatsGrid({ pagesCrawled, totalPages, totalLighthouse, averageResponseMs, lighthouseSummary, }: { pagesCrawled: number; totalPages: number; totalLighthouse: number; averageResponseMs: number; lighthouseSummary: { failed: number; avgPerformance: number | null; avgSeo: number | null; avgAccessibility: number | null; }; }) { return (
{totalLighthouse > 0 && ( <> 0 ? "text-error" : "text-success" } /> )}
); } function scoreClass(score: number | null) { if (score == null) return ""; if (score >= 90) return "text-success"; if (score >= 50) return "text-warning"; return "text-error"; }