import { ChevronDown, Copy, Download, FileWarning, Info, Sheet, TriangleAlert, } from "lucide-react"; import { PortalMenu } from "@/client/components/PortalMenu"; import type { CategoryTab, ExportPayload, LighthouseIssue, LighthouseMetrics, LighthouseScores, } from "./types"; import { LighthouseIssueRow } from "./LighthouseIssueRow"; import { LighthouseIssuesSummary } from "./LighthouseIssuesSummary"; import { categoryLabel } from "./utils"; import { categoryTabs } from "./types"; export function LighthouseIssuesHeader({ backLabel, onBack, scannedAt, finalUrl, scores, metrics, severityCounts, }: { backLabel: string; onBack: () => void; scannedAt?: string; finalUrl?: string; scores?: LighthouseScores | null; metrics?: LighthouseMetrics | null; severityCounts: { critical: number; warning: number; info: number }; }) { return ( <>
{scannedAt ? `Scanned ${new Date(scannedAt).toLocaleString()}` : "Reading latest issues..."}

Lighthouse Issues

{finalUrl ?? "Loading URL..."}

Critical {severityCounts.critical} Warning {severityCounts.warning} Info {severityCounts.info}
); } export function LighthouseIssuesToolbar({ category, categoryCounts, selectedCategoryLabel, isBusy, visibleIssues, allIssues, onCategoryChange, onCopy, onExport, onExportCsv, onExportSheets, }: { category: CategoryTab; categoryCounts: Record; selectedCategoryLabel: string; isBusy: boolean; visibleIssues: LighthouseIssue[]; allIssues: LighthouseIssue[]; onCategoryChange: (next: CategoryTab) => void; onCopy: (data: ExportPayload, toastMessage: string) => void; onExport: (data: ExportPayload) => void; onExportCsv: (issues: LighthouseIssue[], variant: "all" | "current") => void; onExportSheets: ( issues: LighthouseIssue[], variant: "all" | "current", ) => void; }) { const exportCurrentCategory: ExportPayload = category === "all" ? { mode: "issues" } : { mode: "category", category }; const categoryLabelLower = selectedCategoryLabel.toLowerCase(); return (
); } function CategoryTabs({ category, categoryCounts, onCategoryChange, }: { category: CategoryTab; categoryCounts: Record; onCategoryChange: (next: CategoryTab) => void; }) { return (
{categoryTabs.map((tab) => ( ))}
); } function ExportMenu({ allIssues, categoryLabelLower, exportCurrentCategory, isBusy, onCopy, onExport, onExportCsv, onExportSheets, visibleIssues, }: { allIssues: LighthouseIssue[]; categoryLabelLower: string; exportCurrentCategory: ExportPayload; isBusy: boolean; onCopy: (data: ExportPayload, toastMessage: string) => void; onExport: (data: ExportPayload) => void; onExportCsv: (issues: LighthouseIssue[], variant: "all" | "current") => void; onExportSheets: ( issues: LighthouseIssue[], variant: "all" | "current", ) => void; visibleIssues: LighthouseIssue[]; }) { return ( Export } menuClassName="w-72 max-h-[min(30rem,70vh)] flex-nowrap overflow-y-auto" > {(close) => ( <>
  • Export to Sheets
  • Copy
  • Download JSON
  • Download CSV
  • )}
    ); } export function LighthouseIssueList({ issues, isLoading, emptyMessage, }: { issues: LighthouseIssue[]; isLoading: boolean; emptyMessage?: string; }) { if (isLoading) { return

    Loading issues...

    ; } if (!issues.length) { return (

    {emptyMessage ?? "No actionable issues for this category."}

    ); } return ( {issues.map((issue, issueIndex) => ( ))}
    Severity Issue Category Impact Score
    ); }