'use client' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { CheckCircle2, Circle, Clock, XCircle, Trophy, Check } from 'lucide-react' import { cn } from '@/lib/utils' const roundStatusDisplay: Record = { ROUND_DRAFT: { label: 'Upcoming', variant: 'secondary' }, ROUND_ACTIVE: { label: 'In Progress', variant: 'default' }, ROUND_CLOSED: { label: 'Completed', variant: 'default' }, ROUND_ARCHIVED: { label: 'Completed', variant: 'default' }, } export function ApplicantCompetitionTimeline() { const { data, isLoading } = trpc.applicant.getMyCompetitionTimeline.useQuery() if (isLoading) { return (
{[1, 2, 3].map((i) => ( ))}
) } if (!data || data.entries.length === 0) { return ( Competition Timeline

No rounds available yet

) } return ( Competition Timeline {data.competitionName && (

{data.competitionName}

)}
{/* Vertical connecting line */}
{data.entries.map((entry) => { const isCompleted = entry.status === 'ROUND_CLOSED' || entry.status === 'ROUND_ARCHIVED' const isActive = entry.status === 'ROUND_ACTIVE' const isRejected = entry.projectState === 'REJECTED' const isGrandFinale = entry.roundType === 'GRAND_FINALE' // Determine icon let Icon = Circle let iconBg = 'bg-muted' let iconColor = 'text-muted-foreground' if (isRejected) { Icon = XCircle iconBg = 'bg-red-50' iconColor = 'text-red-600' } else if (isGrandFinale && isCompleted) { Icon = Trophy iconBg = 'bg-yellow-50' iconColor = 'text-yellow-600' } else if (isCompleted) { Icon = CheckCircle2 iconBg = 'bg-emerald-50' iconColor = 'text-emerald-600' } else if (isActive) { Icon = Clock iconBg = 'bg-brand-blue/10' iconColor = 'text-brand-blue' } // Project state display let stateLabel: string | null = null if (entry.projectState === 'REJECTED') { stateLabel = 'Not Selected' } else if (entry.projectState === 'PASSED' || entry.projectState === 'COMPLETED') { stateLabel = 'Advanced' } else if (entry.projectState === 'IN_PROGRESS') { stateLabel = 'Under Review' } else if (entry.projectState === 'PENDING') { stateLabel = 'Pending' } const statusInfo = roundStatusDisplay[entry.status] ?? { label: 'Upcoming', variant: 'secondary' as const } return (
{/* Icon */}
{/* Content */}

{entry.label}

{stateLabel && ( {stateLabel} )} {statusInfo.label}
{entry.windowOpenAt && entry.windowCloseAt && (

Opens: {new Date(entry.windowOpenAt).toLocaleDateString()}

Closes: {new Date(entry.windowCloseAt).toLocaleDateString()}

)}
) })}
) } /** * Compact sidebar variant for the dashboard. * Animated timeline with connector indicators between dots. */ export function CompetitionTimelineSidebar() { const { data, isLoading } = trpc.applicant.getMyCompetitionTimeline.useQuery() if (isLoading) { return (
{[1, 2, 3].map((i) => ( ))}
) } if (!data || data.entries.length === 0) { return

No rounds available

} // Find the index where elimination happened (first REJECTED entry) const eliminationIndex = data.entries.findIndex((e) => e.projectState === 'REJECTED') return (
{data.entries.map((entry, index) => { const isCompleted = entry.status === 'ROUND_CLOSED' || entry.status === 'ROUND_ARCHIVED' const isActive = entry.status === 'ROUND_ACTIVE' const isRejected = entry.projectState === 'REJECTED' const isGrandFinale = entry.roundType === 'GRAND_FINALE' const isPassed = entry.projectState === 'PASSED' || entry.projectState === 'COMPLETED' const isLast = index === data.entries.length - 1 // Is this entry after the elimination point? const isAfterElimination = eliminationIndex >= 0 && index > eliminationIndex // Is this the current round the project is in (regardless of round status)? const isCurrent = !!entry.projectState && entry.projectState !== 'PASSED' && entry.projectState !== 'COMPLETED' && entry.projectState !== 'REJECTED' // Determine connector segment color (no icons, just colored lines) let connectorColor = 'bg-border' if ((isPassed || isCompleted) && !isAfterElimination) connectorColor = 'bg-emerald-400' else if (isRejected) connectorColor = 'bg-destructive/30' // Dot inner content let dotInner: React.ReactNode = null let dotClasses = 'border-2 border-muted-foreground/20 bg-background' if (isAfterElimination) { dotClasses = 'border-2 border-muted/60 bg-muted/30' } else if (isRejected) { dotClasses = 'bg-destructive border-2 border-destructive' dotInner = } else if (isGrandFinale && (isCompleted || isPassed)) { dotClasses = 'bg-yellow-500 border-2 border-yellow-500' dotInner = } else if (isCompleted || isPassed) { dotClasses = 'bg-emerald-500 border-2 border-emerald-500' dotInner = } else if (isCurrent) { dotClasses = 'bg-amber-400 border-2 border-amber-400' dotInner = } // Status sub-label let statusLabel: string | null = null let statusColor = 'text-muted-foreground' if (isRejected) { statusLabel = 'Eliminated' statusColor = 'text-destructive' } else if (isAfterElimination) { statusLabel = null } else if (isPassed) { statusLabel = 'Advanced' statusColor = 'text-emerald-600' } else if (isCurrent) { statusLabel = 'You are here' statusColor = 'text-amber-600' } return (
{/* Row: dot + label */}
{/* Dot */}
{dotInner}
{/* Label */}

{entry.label}

{statusLabel && (

{statusLabel}

)}
{/* Connector line between dots */} {!isLast && (
)}
) })}
) }