import { useMemo, useState } from "react"; import { type SortingState } from "@tanstack/react-table"; import { ChevronDown, Download, Sheet, SlidersHorizontal } from "lucide-react"; import { useAppTable } from "@/client/components/table/AppDataTable"; import { exportTableToSheets } from "@/client/lib/exportToSheets"; import { buildBrandLookupExport, downloadBrandLookupCsv, } from "@/client/features/ai-search/components/brandLookupExport"; import { BrandLookupFilterPanel } from "@/client/features/ai-search/components/BrandLookupFilterPanel"; import { TopPagesTable, TopQueriesTable, buildTopPagesColumns, buildTopQueriesColumns, } from "@/client/features/ai-search/components/BrandLookupCitationTables"; import { formatPlatformLabel, PLATFORM_DOT_CLASS, } from "@/client/features/ai-search/platformLabels"; import { filterQueries, filterTopPages, } from "@/client/features/ai-search/brandLookupFiltering"; import { useBrandLookupFilters } from "@/client/features/ai-search/useBrandLookupFilters"; import type { CitationTab } from "@/client/features/ai-search/brandLookupFilterTypes"; import type { BrandLookupResult } from "@/types/schemas/ai-search"; const DEFAULT_PAGES_SORT: SortingState = [{ id: "capturedVolume", desc: true }]; const DEFAULT_QUERIES_SORT: SortingState = [ { id: "aiSearchVolume", desc: true }, ]; // DaisyUI focus-dropdowns stay open until the active element blurs. function closeExportMenu(): void { const active = document.activeElement; if (active instanceof HTMLElement) active.blur(); } export function CitationTabsCard({ result, projectId, }: { result: BrandLookupResult; projectId: string; }) { const [activeTab, setActiveTab] = useState("queries"); const [pagesSort, setPagesSort] = useState(DEFAULT_PAGES_SORT); const [queriesSort, setQueriesSort] = useState(DEFAULT_QUERIES_SORT); const filters = useBrandLookupFilters(); // The platform column only earns its place when a tab actually spans >1 // platform; otherwise it repeats one value on every row. const queryPlatforms = [ ...new Set(result.topQueries.map((query) => query.platform)), ]; const pagePlatforms = [ ...new Set(result.topPages.map((page) => page.platform)), ]; const showQueryPlatform = queryPlatforms.length > 1; const showPagePlatform = pagePlatforms.length > 1; const targetDomain = result.detectedTargetType === "domain" ? result.resolvedTarget : null; const filteredPages = useMemo( () => filterTopPages(result.topPages, filters.pages.values), [result.topPages, filters.pages.values], ); const filteredQueries = useMemo( () => filterQueries(result.topQueries, filters.queries.values), [result.topQueries, filters.queries.values], ); const pagesColumns = useMemo( () => buildTopPagesColumns({ showPlatform: showPagePlatform, targetDomain, projectId, brand: result.resolvedTarget, }), [showPagePlatform, targetDomain, projectId, result.resolvedTarget], ); const queriesColumns = useMemo( () => buildTopQueriesColumns({ showPlatform: showQueryPlatform, projectId, brand: result.resolvedTarget, }), [showQueryPlatform, projectId, result.resolvedTarget], ); const pagesTable = useAppTable({ data: filteredPages, columns: pagesColumns, state: { sorting: pagesSort }, onSortingChange: setPagesSort, withSorting: true, // Stable identity (default is the array index): KeywordsCell holds // expanded state, which must follow the page when filtering/sorting // reorders rows, not stick to whatever row lands in the same slot. getRowId: (row) => `${row.platform}:${row.url}`, }); const queriesTable = useAppTable({ data: filteredQueries, columns: queriesColumns, state: { sorting: queriesSort }, onSortingChange: setQueriesSort, withSorting: true, }); // Not memoized: TanStack's `getSortedRowModel()` is internally cached, and // memoing on the table refs alone (which are stable across renders) would // serve stale data when sort or filters change. const exportTable = buildBrandLookupExport( activeTab, pagesTable.getSortedRowModel().rows.map((row) => row.original), queriesTable.getSortedRowModel().rows.map((row) => row.original), ); const handleExportCsv = () => { downloadBrandLookupCsv(activeTab, result.resolvedTarget, exportTable); closeExportMenu(); }; const handleExportSheets = () => { void exportTableToSheets({ headers: exportTable.headers, rows: exportTable.rows, feature: `brand_lookup_${activeTab}`, }); closeExportMenu(); }; const canExport = exportTable.rows.length > 0; const currentFilterCount = filters[activeTab].activeFilterCount; const queriesActive = activeTab === "queries"; const pagesActive = activeTab === "pages"; // When the active tab's platform column is hidden, surface the lone platform // once here instead of repeating it on every row. const activePlatforms = pagesActive ? pagePlatforms : queryPlatforms; const captionPlatform = activePlatforms.length === 1 ? activePlatforms[0] : null; return (
Export
{activeTab === "pages" ? ( <> Pages cited alongside{" "} {result.resolvedTarget} {" "} in AI answers. Prompt examples come from the fetched sample. ) : ( <> Fetched sample of prompts whose AI answer cited{" "} {result.resolvedTarget} {" "} in its text or sources. )} {captionPlatform ? ( {formatPlatformLabel(captionPlatform)} ) : null}
{filters.showFilters ? ( ) : null} {activeTab === "pages" ? ( ) : ( )}
); }