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";
}