import type { ColumnDef } from '@tanstack/react-table'; import { ExternalLinkIcon } from 'lucide-react'; import { useMemo } from 'react'; import { PageSparkline } from '@/components/pages/page-sparkline'; import { createHeaderColumn } from '@/components/ui/data-table/data-table-helpers'; import { useAppContext } from '@/hooks/use-app-context'; import { fancyMinutes, useNumber } from '@/hooks/use-numer-formatter'; import type { RouterOutputs } from '@/trpc/client'; export type PageRow = RouterOutputs['event']['pages'][number] & { gsc?: { clicks: number; impressions: number; ctr: number; position: number }; }; export function useColumns({ projectId, isGscConnected, previousMap, }: { projectId: string; isGscConnected: boolean; previousMap?: Map; }): ColumnDef[] { const number = useNumber(); const { apiUrl } = useAppContext(); return useMemo[]>(() => { const cols: ColumnDef[] = [ { id: 'page', accessorFn: (row) => `${row.origin}${row.path} ${row.title ?? ''}`, header: createHeaderColumn('Page'), size: 400, meta: { bold: true }, cell: ({ row }) => { const page = row.original; return (
{ (e.target as HTMLImageElement).style.display = 'none'; }} src={`${apiUrl}/misc/favicon?url=${page.origin}`} />
{page.title && (
{page.title}
)}
); }, }, { id: 'trend', header: 'Trend', enableSorting: false, size: 96, cell: ({ row }) => ( ), }, { accessorKey: 'pageviews', header: createHeaderColumn('Views'), size: 80, cell: ({ row }) => ( {number.short(row.original.pageviews)} ), }, { accessorKey: 'sessions', header: createHeaderColumn('Sessions'), size: 90, cell: ({ row }) => { const prev = previousMap?.get( row.original.origin + row.original.path ); if (prev == null) { return ; } if (prev === 0) { return (
{number.short(row.original.sessions)} new
); } const pct = ((row.original.sessions - prev) / prev) * 100; const isPos = pct >= 0; return (
{number.short(row.original.sessions)} {isPos ? '+' : ''} {pct.toFixed(1)}%
); }, }, { accessorKey: 'bounce_rate', header: createHeaderColumn('Bounce'), size: 80, cell: ({ row }) => ( {row.original.bounce_rate.toFixed(0)}% ), }, { accessorKey: 'avg_duration', header: createHeaderColumn('Duration'), size: 90, cell: ({ row }) => ( {fancyMinutes(row.original.avg_duration)} ), }, ]; if (isGscConnected) { cols.push( { id: 'gsc_impressions', accessorFn: (row) => row.gsc?.impressions ?? 0, header: createHeaderColumn('Impr.'), size: 80, cell: ({ row }) => row.original.gsc ? ( {number.short(row.original.gsc.impressions)} ) : ( ), }, { id: 'gsc_ctr', accessorFn: (row) => row.gsc?.ctr ?? 0, header: createHeaderColumn('CTR'), size: 70, cell: ({ row }) => row.original.gsc ? ( {(row.original.gsc.ctr * 100).toFixed(1)}% ) : ( ), }, { id: 'gsc_clicks', accessorFn: (row) => row.gsc?.clicks ?? 0, header: createHeaderColumn('Clicks'), size: 80, cell: ({ row }) => row.original.gsc ? ( {number.short(row.original.gsc.clicks)} ) : ( ), } ); } return cols; }, [isGscConnected, number, apiUrl, projectId, previousMap]); }