import { useMemo } from "react"; import { ArrowUp, ArrowDown } from "lucide-react"; import type { ColumnDef, SortingFn } from "@tanstack/react-table"; import type { RankTrackingRow } from "@/types/schemas/rank-tracking"; import { comparePositions, CpcCell, DeviceRankCell, DeviceUrlCell, DifficultyCell, SerpFeatureTags, VolumeCell, } from "./RankTrackingTableParts"; const HEADER_TOOLTIPS: Record = { keyword: "The search term being tracked in Google", volume: "Estimated monthly search volume from Google", kd: "Keyword difficulty score (0-100) — higher means harder to rank", cpc: "Average cost per click in Google Ads (USD)", desktopPosition: "Current Google ranking position, showing change from the comparison period", mobilePosition: "Current Google ranking position, showing change from the comparison period", url: "The page on your site that ranks for this keyword", serp: "Special result features appearing on the search results page (e.g. AI Overview, People Also Ask)", }; export function SortableHeader({ column, label, id, tooltip, }: { column: { getIsSorted: () => false | "asc" | "desc"; getToggleSortingHandler: () => ((event: unknown) => void) | undefined; }; label: string; id: string; tooltip?: string; }) { const sorted = column.getIsSorted(); return ( ); } const nullsLastNumeric: SortingFn = (rowA, rowB, columnId) => { const a = rowA.getValue(columnId); const b = rowB.getValue(columnId); if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; return a - b; }; const volumeColumn: ColumnDef = { id: "volume", accessorKey: "searchVolume", header: ({ column }) => ( ), size: 90, cell: ({ getValue }) => ()} />, sortingFn: nullsLastNumeric, }; const kdColumn: ColumnDef = { id: "kd", accessorKey: "keywordDifficulty", header: ({ column }) => , size: 70, cell: ({ getValue }) => ()} />, sortingFn: nullsLastNumeric, }; const cpcColumn: ColumnDef = { id: "cpc", accessorKey: "cpc", header: ({ column }) => ( ), size: 80, cell: ({ getValue }) => ()} />, sortingFn: nullsLastNumeric, }; const positionSort: SortingFn = (rowA, rowB, columnId) => { const device = columnId === "desktopPosition" ? "desktop" : "mobile"; return comparePositions( rowA.original[device].position, rowB.original[device].position, ); }; const selectColumn: ColumnDef = { id: "select", size: 32, enableSorting: false, header: ({ table }) => ( ), cell: ({ row }) => ( ), }; const keywordColumn: ColumnDef = { id: "keyword", accessorKey: "keyword", header: ({ column }) => ( ), cell: ({ getValue }) => ( {getValue()} ), sortingFn: "alphanumeric", }; function makeDeviceColumn( device: "desktop" | "mobile", ): ColumnDef { const id = device === "desktop" ? "desktopPosition" : "mobilePosition"; return { id, accessorFn: (row) => row[device].position, header: ({ column }) => ( ), size: 120, maxSize: 140, cell: ({ row }) => , sortingFn: positionSort, }; } function makeUrlColumn( device: "desktop" | "mobile", domain: string, ): ColumnDef { return { id: device === "desktop" ? "desktopUrl" : "mobileUrl", enableSorting: false, header: () => ( URL ), size: 240, cell: ({ row }) => ( ), }; } function makeSerpColumn( device: "desktop" | "mobile", ): ColumnDef { return { id: device === "desktop" ? "desktopSerp" : "mobileSerp", enableSorting: false, header: () => ( SERP Features ), cell: ({ row }) => { const features = row.original[device].serpFeatures; if (features.length === 0) return null; return ; }, }; } export function useRankTrackingColumns( showDesktop: boolean, showMobile: boolean, domain: string, ): ColumnDef[] { return useMemo(() => { const cols: ColumnDef[] = [selectColumn, keywordColumn]; if (showDesktop) { cols.push(makeDeviceColumn("desktop")); cols.push(makeUrlColumn("desktop", domain)); } if (showMobile) { cols.push(makeDeviceColumn("mobile")); cols.push(makeUrlColumn("mobile", domain)); } cols.push(volumeColumn, kdColumn, cpcColumn); if (showDesktop) { cols.push(makeSerpColumn("desktop")); } if (showMobile) { cols.push(makeSerpColumn("mobile")); } return cols; }, [showDesktop, showMobile, domain]); }