145 lines
4.8 KiB
TypeScript
145 lines
4.8 KiB
TypeScript
|
|
'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 } from 'lucide-react'
|
||
|
|
import { toast } from 'sonner'
|
||
|
|
|
||
|
|
interface ApplicantCompetitionTimelineProps {
|
||
|
|
competitionId: string
|
||
|
|
}
|
||
|
|
|
||
|
|
const statusIcons: Record<string, React.ElementType> = {
|
||
|
|
completed: CheckCircle2,
|
||
|
|
current: Clock,
|
||
|
|
upcoming: Circle,
|
||
|
|
}
|
||
|
|
|
||
|
|
const statusColors: Record<string, string> = {
|
||
|
|
completed: 'text-emerald-600',
|
||
|
|
current: 'text-brand-blue',
|
||
|
|
upcoming: 'text-muted-foreground',
|
||
|
|
}
|
||
|
|
|
||
|
|
const statusBgColors: Record<string, string> = {
|
||
|
|
completed: 'bg-emerald-50',
|
||
|
|
current: 'bg-brand-blue/10',
|
||
|
|
upcoming: 'bg-muted',
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ApplicantCompetitionTimeline({ competitionId }: ApplicantCompetitionTimelineProps) {
|
||
|
|
const { data: competition, isLoading } = trpc.competition.getById.useQuery(
|
||
|
|
{ id: competitionId },
|
||
|
|
{ enabled: !!competitionId }
|
||
|
|
)
|
||
|
|
|
||
|
|
if (isLoading) {
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<Skeleton className="h-6 w-48" />
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="space-y-4">
|
||
|
|
{[1, 2, 3].map((i) => (
|
||
|
|
<Skeleton key={i} className="h-20" />
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!competition || !competition.rounds || competition.rounds.length === 0) {
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Competition Timeline</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className="text-center py-8">
|
||
|
|
<Circle className="h-12 w-12 text-muted-foreground/50 mx-auto mb-3" />
|
||
|
|
<p className="text-sm text-muted-foreground">No rounds available</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
const rounds = competition.rounds || []
|
||
|
|
const currentRoundIndex = rounds.findIndex(r => r.status === 'ROUND_ACTIVE')
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Competition Timeline</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="relative space-y-6">
|
||
|
|
{/* Vertical connecting line */}
|
||
|
|
<div className="absolute left-5 top-5 bottom-5 w-0.5 bg-border" />
|
||
|
|
|
||
|
|
{rounds.map((round, index) => {
|
||
|
|
const isActive = round.status === 'ROUND_ACTIVE'
|
||
|
|
const isCompleted = index < currentRoundIndex || round.status === 'ROUND_CLOSED' || round.status === 'ROUND_ARCHIVED'
|
||
|
|
const isCurrent = index === currentRoundIndex || isActive
|
||
|
|
const status = isCompleted ? 'completed' : isCurrent ? 'current' : 'upcoming'
|
||
|
|
const Icon = statusIcons[status]
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div key={round.id} className="relative flex items-start gap-4">
|
||
|
|
{/* Icon */}
|
||
|
|
<div
|
||
|
|
className={`relative z-10 flex h-10 w-10 items-center justify-center rounded-full ${statusBgColors[status]} shrink-0`}
|
||
|
|
>
|
||
|
|
<Icon className={`h-5 w-5 ${statusColors[status]}`} />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Content */}
|
||
|
|
<div className="flex-1 min-w-0 pb-6">
|
||
|
|
<div className="flex items-start justify-between flex-wrap gap-2 mb-2">
|
||
|
|
<div>
|
||
|
|
<h3 className="font-semibold">{round.name}</h3>
|
||
|
|
</div>
|
||
|
|
<Badge
|
||
|
|
variant={
|
||
|
|
status === 'completed'
|
||
|
|
? 'default'
|
||
|
|
: status === 'current'
|
||
|
|
? 'default'
|
||
|
|
: 'secondary'
|
||
|
|
}
|
||
|
|
className={
|
||
|
|
status === 'completed'
|
||
|
|
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
|
||
|
|
: status === 'current'
|
||
|
|
? 'bg-brand-blue text-white'
|
||
|
|
: ''
|
||
|
|
}
|
||
|
|
>
|
||
|
|
{status === 'completed' && 'Completed'}
|
||
|
|
{status === 'current' && 'In Progress'}
|
||
|
|
{status === 'upcoming' && 'Upcoming'}
|
||
|
|
</Badge>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{round.windowOpenAt && round.windowCloseAt && (
|
||
|
|
<div className="text-sm text-muted-foreground space-y-1">
|
||
|
|
<p>
|
||
|
|
Opens: {new Date(round.windowOpenAt).toLocaleDateString()}
|
||
|
|
</p>
|
||
|
|
<p>
|
||
|
|
Closes: {new Date(round.windowCloseAt).toLocaleDateString()}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|