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 (
|
Severity |
Issue |
Category |
Impact
|
Score |
{issues.map((issue, issueIndex) => (
))}
);
}