import { ChevronDown, Download, FileDown, Globe, RotateCcw, Save, Sheet, SlidersHorizontal, } from "lucide-react"; import { KEYWORD_RESEARCH_HEADERS } from "@/client/features/keywords/state/keywordControllerActions"; import { exportTableToSheets } from "@/client/lib/exportToSheets"; import { AreaTrendChart, OverviewStats, SerpAnalysisCard, } from "@/client/features/keywords/components"; import type { KeywordResearchRow } from "@/types/keywords"; import type { KeywordResearchControllerState } from "./types"; import { FilterRangeInputs, FilterTextInput, } from "./keywordResearchDesktopFilters"; import { KeywordResearchDesktopTable } from "./KeywordResearchDesktopTable"; import { KeywordResearchPagination, useKeywordResearchPagination, } from "./KeywordResearchPagination"; const MONTH_SHORT_LABELS = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ] as const; function formatTrendRangeLabel(trend: KeywordResearchRow["trend"]): string { if (trend.length === 0) return "Last 12 available months"; const sorted = trend.toSorted( (a, b) => a.year * 100 + a.month - (b.year * 100 + b.month), ); const last12 = sorted.slice(-12); const start = last12[0]; const end = last12[last12.length - 1]; const toLabel = (month: number, year: number) => { const monthLabel = MONTH_SHORT_LABELS[month - 1] ?? `M${month}`; return `${monthLabel} ${year}`; }; const startLabel = toLabel(start.month, start.year); const endLabel = toLabel(end.month, end.year); return startLabel === endLabel ? startLabel : `${startLabel} - ${endLabel}`; } type Props = { controller: KeywordResearchControllerState; }; export function KeywordResearchDesktopResults({ controller }: Props) { return (
); } function DesktopKeywordPanel({ controller }: Props) { const { lastResultSource, lastUsedFallback, searchedKeyword, showApproximateMatchNotice, } = controller; return (
{showApproximateMatchNotice ? (
No exact match for{" "} "{searchedKeyword}". Showing closest related keywords instead. {lastUsedFallback ? ( {" "} Source: {lastResultSource} fallback. ) : null}
) : null} {controller.overviewKeyword ? ( ) : null}
); } function DesktopTableCard({ controller }: Props) { const { activeFilterCount, filteredRows, rows, selectedRows, sheetsExportRows, showFilters, } = controller; const { page, pageSize, pageRows, setPage, setPageSize } = useKeywordResearchPagination(filteredRows); const keywordCountLabel = selectedRows.size > 0 ? `${selectedRows.size} of ${filteredRows.length} selected` : activeFilterCount > 0 ? `Showing ${filteredRows.length} of ${rows.length} keywords` : `Showing ${filteredRows.length} keywords`; const canExport = filteredRows.length > 0; const handleExportToSheets = () => { void exportTableToSheets({ headers: KEYWORD_RESEARCH_HEADERS, rows: sheetsExportRows, feature: "keyword_research", }); }; return (
{keywordCountLabel}
Export
{showFilters ? : null} {filteredRows.length > 0 ? ( ) : null}
); } function DesktopFilters({ controller }: Props) { const { activeFilterCount, filtersForm } = controller; return (

Refine table results

{activeFilterCount > 0 ? ( {activeFilterCount} active ) : null}
); } function DesktopSerpPanel({ controller }: Props) { const { overviewKeyword } = controller; const trendRangeLabel = overviewKeyword ? formatTrendRangeLabel(overviewKeyword.trend) : "Last 12 available months"; return (
{overviewKeyword && overviewKeyword.trend.length > 0 ? (

Search Trends{" "} {trendRangeLabel}

) : null}

SERP Analysis {controller.activeSerpKeyword ? ( : {controller.activeSerpKeyword} ) : null}

void controller.serpQuery.refetch()} page={controller.serpPage} pageSize={controller.SERP_PAGE_SIZE} onPageChange={controller.setSerpPage} />
); }