import type { Metadata } from 'next' import { Suspense } from 'react' import Link from 'next/link' import { auth } from '@/lib/auth' import { prisma } from '@/lib/prisma' export const metadata: Metadata = { title: 'Jury Dashboard' } export const dynamic = 'force-dynamic' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { ClipboardList, CheckCircle2, Clock, ArrowRight, GitCompare, Zap, BarChart3, Target, Waves, } from 'lucide-react' import { formatDateOnly } from '@/lib/utils' import { CountdownTimer } from '@/components/shared/countdown-timer' import { AnimatedCard } from '@/components/shared/animated-container' import { cn } from '@/lib/utils' function getGreeting(): string { const hour = new Date().getHours() if (hour < 12) return 'Good morning' if (hour < 18) return 'Good afternoon' return 'Good evening' } async function JuryDashboardContent() { const session = await auth() const userId = session?.user?.id if (!userId) { return null } // Get assignments and grace periods in parallel const [assignments, gracePeriods] = await Promise.all([ prisma.assignment.findMany({ where: { userId }, include: { project: { select: { id: true, title: true, teamName: true, country: true, }, }, stage: { select: { id: true, name: true, status: true, windowOpenAt: true, windowCloseAt: true, track: { select: { pipeline: { select: { program: { select: { name: true, year: true, }, }, }, }, }, }, }, }, evaluation: { select: { id: true, status: true, submittedAt: true, criterionScoresJson: true, form: { select: { criteriaJson: true }, }, }, }, }, orderBy: [ { stage: { windowCloseAt: 'asc' } }, { createdAt: 'asc' }, ], }), prisma.gracePeriod.findMany({ where: { userId, extendedUntil: { gte: new Date() }, }, select: { stageId: true, extendedUntil: true, }, }), ]) // Calculate stats const totalAssignments = assignments.length const completedAssignments = assignments.filter( (a) => a.evaluation?.status === 'SUBMITTED' ).length const inProgressAssignments = assignments.filter( (a) => a.evaluation?.status === 'DRAFT' ).length const pendingAssignments = totalAssignments - completedAssignments - inProgressAssignments const completionRate = totalAssignments > 0 ? (completedAssignments / totalAssignments) * 100 : 0 // Group assignments by stage const assignmentsByStage = assignments.reduce( (acc, assignment) => { const stageId = assignment.stage.id if (!acc[stageId]) { acc[stageId] = { stage: assignment.stage, assignments: [], } } acc[stageId].assignments.push(assignment) return acc }, {} as Record ) const graceByStage = new Map() for (const gp of gracePeriods) { const existing = graceByStage.get(gp.stageId) if (!existing || gp.extendedUntil > existing) { graceByStage.set(gp.stageId, gp.extendedUntil) } } // Active stages (voting window open) const now = new Date() const activeStages = Object.values(assignmentsByStage).filter( ({ stage }) => stage.status === 'STAGE_ACTIVE' && stage.windowOpenAt && stage.windowCloseAt && new Date(stage.windowOpenAt) <= now && new Date(stage.windowCloseAt) >= now ) // Find next unevaluated assignment in an active stage const nextUnevaluated = assignments.find((a) => { const isActive = a.stage.status === 'STAGE_ACTIVE' && a.stage.windowOpenAt && a.stage.windowCloseAt && new Date(a.stage.windowOpenAt) <= now && new Date(a.stage.windowCloseAt) >= now const isIncomplete = !a.evaluation || a.evaluation.status === 'NOT_STARTED' || a.evaluation.status === 'DRAFT' return isActive && isIncomplete }) // Recent assignments for the quick list (latest 5) const recentAssignments = assignments.slice(0, 6) // Get active stage remaining count const activeRemaining = assignments.filter((a) => { const isActive = a.stage.status === 'STAGE_ACTIVE' && a.stage.windowOpenAt && a.stage.windowCloseAt && new Date(a.stage.windowOpenAt) <= now && new Date(a.stage.windowCloseAt) >= now const isIncomplete = !a.evaluation || a.evaluation.status !== 'SUBMITTED' return isActive && isIncomplete }).length const stats = [ { label: 'Total Assignments', value: totalAssignments, icon: ClipboardList, accentColor: 'border-l-blue-500', iconBg: 'bg-blue-50 dark:bg-blue-950/40', iconColor: 'text-blue-600 dark:text-blue-400', }, { label: 'Completed', value: completedAssignments, icon: CheckCircle2, accentColor: 'border-l-emerald-500', iconBg: 'bg-emerald-50 dark:bg-emerald-950/40', iconColor: 'text-emerald-600 dark:text-emerald-400', }, { label: 'In Progress', value: inProgressAssignments, icon: Clock, accentColor: 'border-l-amber-500', iconBg: 'bg-amber-50 dark:bg-amber-950/40', iconColor: 'text-amber-600 dark:text-amber-400', }, { label: 'Pending', value: pendingAssignments, icon: Target, accentColor: 'border-l-slate-400', iconBg: 'bg-slate-50 dark:bg-slate-800/50', iconColor: 'text-slate-500 dark:text-slate-400', }, ] // Zero-assignment state: compact welcome card if (totalAssignments === 0) { return (

No assignments yet

Your project assignments will appear here once an administrator assigns them to you.

All Assignments

View evaluations

Compare Projects

Side-by-side view

) } return ( <> {/* Hero CTA - Jump to next evaluation */} {nextUnevaluated && activeRemaining > 0 && (

{activeRemaining} evaluation{activeRemaining > 1 ? 's' : ''} remaining

Continue with "{nextUnevaluated.project.title}"

)} {/* Stats + Overall Completion in one row */}
{stats.map((stat, i) => (

{stat.value}

{stat.label}

))} {/* Overall completion as 5th stat card */}

{completionRate.toFixed(0)}%

{/* Main content -- two column layout */}
{/* Left column */}
{/* Recent Assignments */}
My Assignments
{recentAssignments.length > 0 ? (
{recentAssignments.map((assignment, idx) => { const evaluation = assignment.evaluation const isCompleted = evaluation?.status === 'SUBMITTED' const isDraft = evaluation?.status === 'DRAFT' const isVotingOpen = assignment.stage.status === 'STAGE_ACTIVE' && assignment.stage.windowOpenAt && assignment.stage.windowCloseAt && new Date(assignment.stage.windowOpenAt) <= now && new Date(assignment.stage.windowCloseAt) >= now return (

{assignment.project.title}

{assignment.project.teamName} {assignment.stage.name}
{isCompleted ? ( Done ) : isDraft ? ( Draft ) : ( Pending )} {isCompleted ? ( ) : isVotingOpen ? ( ) : ( )}
) })}
) : (

No assignments yet

Assignments will appear here once an administrator assigns projects to you.

)}
{/* Quick Actions */}
Quick Actions

All Assignments

View and manage evaluations

Compare Projects

Side-by-side comparison

{/* Right column */}
{/* Active Stages */} {activeStages.length > 0 && (
Active Voting Stages Stages currently open for evaluation
{activeStages.map(({ stage, assignments: stageAssignments }) => { const stageCompleted = stageAssignments.filter( (a) => a.evaluation?.status === 'SUBMITTED' ).length const stageTotal = stageAssignments.length const stageProgress = stageTotal > 0 ? (stageCompleted / stageTotal) * 100 : 0 const isAlmostDone = stageProgress >= 80 const deadline = graceByStage.get(stage.id) ?? (stage.windowCloseAt ? new Date(stage.windowCloseAt) : null) const isUrgent = deadline && (deadline.getTime() - now.getTime()) < 24 * 60 * 60 * 1000 const program = stage.track.pipeline.program return (

{stage.name}

{program.name} · {program.year}

{isAlmostDone ? ( Almost done ) : ( Active )}
Progress {stageCompleted}/{stageTotal}
{deadline && (
{stage.windowCloseAt && ( ({formatDateOnly(stage.windowCloseAt)}) )}
)}
) })} )} {/* No active stages */} {activeStages.length === 0 && (

No active voting stages

Check back later when a voting window opens

)} {/* Completion Summary by Stage */} {Object.keys(assignmentsByStage).length > 0 && (
Stage Summary
{Object.values(assignmentsByStage).map(({ stage, assignments: stageAssignments }) => { const done = stageAssignments.filter((a) => a.evaluation?.status === 'SUBMITTED').length const total = stageAssignments.length const pct = total > 0 ? Math.round((done / total) * 100) : 0 return (
{stage.name}
{pct}% ({done}/{total})
) })} )}
) } function DashboardSkeleton() { return ( <> {/* Stats skeleton */}
{[...Array(4)].map((_, i) => (
))}
{/* Progress bar skeleton */}
{/* Two-column skeleton */}
{[...Array(4)].map((_, i) => (
))}
{[...Array(2)].map((_, i) => (
))}
) } export default async function JuryDashboardPage() { const session = await auth() return (
{/* Header */}

{getGreeting()}, {session?.user?.name || 'Juror'}

Here's an overview of your evaluation progress

{/* Content */} }>
) }