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 all assignments for this jury member const assignments = await prisma.assignment.findMany({ where: { userId }, include: { project: { select: { id: true, title: true, teamName: true, country: true, }, }, round: { select: { id: true, name: true, status: true, votingStartAt: true, votingEndAt: true, program: { select: { name: true, year: true, }, }, }, }, evaluation: { select: { id: true, status: true, submittedAt: true, criterionScoresJson: true, form: { select: { criteriaJson: true }, }, }, }, }, orderBy: [ { round: { votingEndAt: 'asc' } }, { createdAt: 'asc' }, ], }) // 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 round const assignmentsByRound = assignments.reduce( (acc, assignment) => { const roundId = assignment.round.id if (!acc[roundId]) { acc[roundId] = { round: assignment.round, assignments: [], } } acc[roundId].assignments.push(assignment) return acc }, {} as Record ) // Get grace periods for this user const gracePeriods = await prisma.gracePeriod.findMany({ where: { userId, extendedUntil: { gte: new Date() }, }, select: { roundId: true, extendedUntil: true, }, }) const graceByRound = new Map() for (const gp of gracePeriods) { const existing = graceByRound.get(gp.roundId) if (!existing || gp.extendedUntil > existing) { graceByRound.set(gp.roundId, gp.extendedUntil) } } // Active rounds (voting window open) const now = new Date() const activeRounds = Object.values(assignmentsByRound).filter( ({ round }) => round.status === 'ACTIVE' && round.votingStartAt && round.votingEndAt && new Date(round.votingStartAt) <= now && new Date(round.votingEndAt) >= now ) // Find next unevaluated assignment in an active round const nextUnevaluated = assignments.find((a) => { const isActive = a.round.status === 'ACTIVE' && a.round.votingStartAt && a.round.votingEndAt && new Date(a.round.votingStartAt) <= now && new Date(a.round.votingEndAt) >= 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 round remaining count const activeRemaining = assignments.filter((a) => { const isActive = a.round.status === 'ACTIVE' && a.round.votingStartAt && a.round.votingEndAt && new Date(a.round.votingStartAt) <= now && new Date(a.round.votingEndAt) >= 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', }, ] return ( <> {/* Hero CTA - Jump to next evaluation */} {nextUnevaluated && activeRemaining > 0 && (

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

Continue with "{nextUnevaluated.project.title}"

)} {/* Stats */}
{stats.map((stat, i) => (

{stat.value}

{stat.label}

))}
{/* Overall Progress */}
Overall Completion
{completionRate.toFixed(0)}% ({completedAssignments}/{totalAssignments})
{/* 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.round.status === 'ACTIVE' && assignment.round.votingStartAt && assignment.round.votingEndAt && new Date(assignment.round.votingStartAt) <= now && new Date(assignment.round.votingEndAt) >= now return (

{assignment.project.title}

{assignment.project.teamName} {assignment.round.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 Rounds */} {activeRounds.length > 0 && (
Active Voting Rounds Rounds currently open for evaluation
{activeRounds.map(({ round, assignments: roundAssignments }) => { const roundCompleted = roundAssignments.filter( (a) => a.evaluation?.status === 'SUBMITTED' ).length const roundTotal = roundAssignments.length const roundProgress = roundTotal > 0 ? (roundCompleted / roundTotal) * 100 : 0 const isAlmostDone = roundProgress >= 80 const deadline = graceByRound.get(round.id) ?? (round.votingEndAt ? new Date(round.votingEndAt) : null) const isUrgent = deadline && (deadline.getTime() - now.getTime()) < 24 * 60 * 60 * 1000 return (

{round.name}

{round.program.name} · {round.program.year}

{isAlmostDone ? ( Almost done ) : ( Active )}
Progress {roundCompleted}/{roundTotal}
{deadline && (
{round.votingEndAt && ( ({formatDateOnly(round.votingEndAt)}) )}
)}
) })} )} {/* No active rounds */} {activeRounds.length === 0 && totalAssignments > 0 && (

No active voting rounds

Check back later when a voting window opens

)} {/* Completion Summary by Round */} {Object.keys(assignmentsByRound).length > 0 && (
Round Summary
{Object.values(assignmentsByRound).map(({ round, assignments: roundAssignments }) => { const done = roundAssignments.filter((a) => a.evaluation?.status === 'SUBMITTED').length const total = roundAssignments.length const pct = total > 0 ? Math.round((done / total) * 100) : 0 return (
{round.name}
{pct}% ({done}/{total})
) })} )}
{/* No assignments at all */} {totalAssignments === 0 && (

No assignments yet

You'll see your project assignments here once they're assigned to you by an administrator.

)} ) } 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 */} }>
) }