import { Suspense } from 'react' import Link from 'next/link' import { notFound, redirect } from 'next/navigation' import { auth } from '@/lib/auth' import { prisma } from '@/lib/prisma' 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 { Separator } from '@/components/ui/separator' import { Skeleton } from '@/components/ui/skeleton' import { FileViewer, FileViewerSkeleton } from '@/components/shared/file-viewer' import { ArrowLeft, ArrowRight, Users, Calendar, Clock, CheckCircle2, Edit3, Tag, FileText, AlertCircle, } from 'lucide-react' import { formatDistanceToNow, format, isPast, isFuture } from 'date-fns' interface PageProps { params: Promise<{ id: string }> } async function ProjectContent({ projectId }: { projectId: string }) { const session = await auth() const userId = session?.user?.id if (!userId) { redirect('/login') } // Get project with assignment info for this user const project = await prisma.project.findUnique({ where: { id: projectId }, include: { files: true, round: { include: { program: { select: { name: true, year: true }, }, evaluationForms: { where: { isActive: true }, take: 1, }, }, }, }, }) if (!project) { notFound() } // Check if user is assigned to this project const assignment = await prisma.assignment.findFirst({ where: { projectId, userId, }, include: { evaluation: true, }, }) if (!assignment) { // User is not assigned to this project return (

Access Denied

You are not assigned to evaluate this project

) } const evaluation = assignment.evaluation const round = project.round const now = new Date() // Check voting window const isVotingOpen = round.status === 'ACTIVE' && round.votingStartAt && round.votingEndAt && new Date(round.votingStartAt) <= now && new Date(round.votingEndAt) >= now const isVotingUpcoming = round.votingStartAt && isFuture(new Date(round.votingStartAt)) const isVotingClosed = round.votingEndAt && isPast(new Date(round.votingEndAt)) // Determine evaluation status const getEvaluationStatus = () => { if (!evaluation) return { label: 'Not Started', variant: 'outline' as const, icon: Clock } switch (evaluation.status) { case 'DRAFT': return { label: 'In Progress', variant: 'secondary' as const, icon: Edit3 } case 'SUBMITTED': return { label: 'Submitted', variant: 'default' as const, icon: CheckCircle2 } case 'LOCKED': return { label: 'Locked', variant: 'default' as const, icon: CheckCircle2 } default: return { label: 'Not Started', variant: 'outline' as const, icon: Clock } } } const status = getEvaluationStatus() const StatusIcon = status.icon const canEvaluate = isVotingOpen && evaluation?.status !== 'SUBMITTED' && evaluation?.status !== 'LOCKED' const canViewEvaluation = evaluation?.status === 'SUBMITTED' || evaluation?.status === 'LOCKED' return (
{/* Back button */} {/* Project Header */}
{round.program.year} Edition / {round.name}

{project.title}

{project.teamName && (
{project.teamName}
)}
{status.label} {round.votingEndAt && ( )}
{/* Tags */} {project.tags.length > 0 && (
{project.tags.map((tag) => ( {tag} ))}
)}
{/* Action buttons */}
{canEvaluate && ( )} {canViewEvaluation && ( )} {!isVotingOpen && !canViewEvaluation && ( )}
{/* Main content grid */}
{/* Description - takes 2 columns on large screens */}
{/* Description */} Project Description {project.description ? (

{project.description}

) : (

No description provided

)}
{/* Files */}
{/* Sidebar */}
{/* Round Info */} Round Details
Round {round.name}
Program {round.program.name}
{round.votingStartAt && (
Voting Opens {format(new Date(round.votingStartAt), 'PPp')}
)} {round.votingEndAt && (
Voting Closes {format(new Date(round.votingEndAt), 'PPp')}
)}
Status
{/* Evaluation Progress */} {evaluation && ( Your Evaluation
Status {status.label}
{evaluation.status === 'DRAFT' && (

Last saved{' '} {formatDistanceToNow(new Date(evaluation.updatedAt), { addSuffix: true, })}

)} {evaluation.status === 'SUBMITTED' && evaluation.submittedAt && (

Submitted{' '} {formatDistanceToNow(new Date(evaluation.submittedAt), { addSuffix: true, })}

)}
)}
) } function DeadlineDisplay({ votingStartAt, votingEndAt, }: { votingStartAt: Date | null votingEndAt: Date }) { const now = new Date() const endDate = new Date(votingEndAt) const startDate = votingStartAt ? new Date(votingStartAt) : null if (startDate && isFuture(startDate)) { return (
Opens {format(startDate, 'PPp')}
) } if (isPast(endDate)) { return (
Closed {formatDistanceToNow(endDate, { addSuffix: true })}
) } const daysRemaining = Math.ceil( (endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) ) const isUrgent = daysRemaining <= 3 return (
{daysRemaining <= 0 ? `Due ${formatDistanceToNow(endDate, { addSuffix: true })}` : `${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining`}
) } function RoundStatusBadge({ status, votingStartAt, votingEndAt, }: { status: string votingStartAt: Date | null votingEndAt: Date | null }) { const now = new Date() const isVotingOpen = status === 'ACTIVE' && votingStartAt && votingEndAt && new Date(votingStartAt) <= now && new Date(votingEndAt) >= now if (isVotingOpen) { return Voting Open } if (status === 'ACTIVE' && votingStartAt && isFuture(new Date(votingStartAt))) { return Upcoming } if (status === 'ACTIVE' && votingEndAt && isPast(new Date(votingEndAt))) { return Voting Closed } return {status} } function ProjectSkeleton() { return (
) } export default async function ProjectDetailPage({ params }: PageProps) { const { id } = await params return ( }> ) }