import { round } from '@openpanel/common'; import type { IServiceSession } from '@openpanel/db'; import type { ColumnDef } from '@tanstack/react-table'; import { Video } from 'lucide-react'; import { ColumnCreatedAt } from '@/components/column-created-at'; import { ProjectLink } from '@/components/links'; import { ProfileAvatar } from '@/components/profiles/profile-avatar'; import { SerieIcon } from '@/components/report-chart/common/serie-icon'; import { getProfileName } from '@/utils/getters'; function formatDuration(milliseconds: number): string { const seconds = milliseconds / 1000; if (seconds < 60) { return `${round(seconds, 1)}s`; } const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes < 60) { return remainingSeconds > 0 ? `${minutes}m ${round(remainingSeconds, 0)}s` : `${minutes}m`; } const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; return `${hours}h ${round(remainingMinutes, 0)}m`; } export function useColumns() { const columns: ColumnDef[] = [ { accessorKey: 'createdAt', header: 'Started', size: ColumnCreatedAt.size, cell: ({ row }) => { const item = row.original; return {item.createdAt}; }, }, { accessorKey: 'id', header: 'Session ID', size: 120, cell: ({ row }) => { const session = row.original; return (
{session.id.slice(0, 8)}... {session.hasReplay && ( )}
); }, }, { accessorKey: 'profileId', header: 'Profile', size: 150, cell: ({ row }) => { const session = row.original; if (session.profile) { return ( {getProfileName(session.profile)} ); } return ( {session.profileId} ); }, }, { accessorKey: 'entryPath', header: 'Entry Page', size: 200, cell: ({ row }) => { const session = row.original; return (
{session.entryPath || '/'}
); }, }, { accessorKey: 'exitPath', header: 'Exit Page', size: 200, cell: ({ row }) => { const session = row.original; return (
{session.exitPath || session.entryPath || '/'}
); }, }, { accessorKey: 'duration', header: 'Duration', size: 100, cell: ({ row }) => { const session = row.original; return (
{formatDuration(session.duration)}
); }, }, { accessorKey: 'isBounce', header: 'Bounce', size: 80, cell: ({ row }) => { const session = row.original; return (
{session.isBounce ? ( Yes ) : ( No )}
); }, }, { accessorKey: 'referrerName', header: 'Referrer', size: 150, cell: ({ row }) => { const session = row.original; const ref = session.referrerName || session.referrer || 'Direct'; return (
{ref}
); }, }, { accessorKey: 'country', header: 'Location', size: 150, cell: ({ row }) => { const session = row.original; return (
{session.city || session.country}
); }, }, { accessorKey: 'os', header: 'OS', size: 120, cell: ({ row }) => { const session = row.original; return (
{session.os}
); }, }, { accessorKey: 'browser', header: 'Browser', size: 120, cell: ({ row }) => { const session = row.original; return (
{session.browser}
); }, }, { accessorKey: 'device', header: 'Device', size: 150, cell: ({ row }) => { const session = row.original; let deviceInfo = session.brand || session.model ? [session.brand, session.model].filter(Boolean).join(' / ') : session.device; if (deviceInfo === 'K') { deviceInfo = session.device; } return (
{deviceInfo}
); }, }, { accessorKey: 'screenViewCount', header: 'Page views', size: 100, cell: ({ row }) => { const session = row.original; return (
{session.screenViewCount}
); }, }, { accessorKey: 'eventCount', header: 'Events', size: 90, cell: ({ row }) => { const session = row.original; return (
{session.eventCount}
); }, }, { accessorKey: 'revenue', header: 'Revenue', size: 100, cell: ({ row }) => { const session = row.original; return session.revenue > 0 ? (
${session.revenue.toFixed(2)}
) : (
-
); }, }, { accessorKey: 'deviceId', header: 'Device ID', size: 120, }, { accessorKey: 'groups', header: 'Groups', size: 200, cell: ({ row }) => { const { groups } = row.original; if (!groups?.length) return null; return (
{groups.map((g) => ( {g} ))}
); }, }, ]; return columns; }