'use client' import { Suspense, use, useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { trpc } from '@/lib/trpc/client' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { Progress } from '@/components/ui/progress' import { Separator } from '@/components/ui/separator' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { ArrowLeft, Edit, Users, FileText, Calendar, CheckCircle2, Clock, AlertCircle, Archive, Play, Pause, BarChart3, Upload, Filter, Trash2, Loader2, Plus, ArrowRightCircle, Minus, XCircle, AlertTriangle, ListChecks, ClipboardCheck, } from 'lucide-react' import { toast } from 'sonner' import { AssignProjectsDialog } from '@/components/admin/assign-projects-dialog' import { AdvanceProjectsDialog } from '@/components/admin/advance-projects-dialog' import { RemoveProjectsDialog } from '@/components/admin/remove-projects-dialog' import { format, formatDistanceToNow, isPast, isFuture } from 'date-fns' interface PageProps { params: Promise<{ id: string }> } function RoundDetailContent({ roundId }: { roundId: string }) { const router = useRouter() const [assignOpen, setAssignOpen] = useState(false) const [advanceOpen, setAdvanceOpen] = useState(false) const [removeOpen, setRemoveOpen] = useState(false) const { data: round, isLoading, refetch: refetchRound } = trpc.round.get.useQuery({ id: roundId }) const { data: progress } = trpc.round.getProgress.useQuery({ id: roundId }) // Filtering queries (only fetch for FILTERING rounds) const roundType = (round?.settingsJson as { roundType?: string } | null)?.roundType const isFilteringRound = roundType === 'FILTERING' const { data: filteringStats, refetch: refetchFilteringStats } = trpc.filtering.getResultStats.useQuery( { roundId }, { enabled: isFilteringRound } ) const { data: filteringRules } = trpc.filtering.getRules.useQuery( { roundId }, { enabled: isFilteringRound } ) const utils = trpc.useUtils() const updateStatus = trpc.round.updateStatus.useMutation({ onSuccess: () => { utils.round.get.invalidate({ id: roundId }) }, }) const deleteRound = trpc.round.delete.useMutation({ onSuccess: () => { toast.success('Round deleted') utils.program.list.invalidate() utils.round.list.invalidate() router.push('/admin/rounds') }, onError: () => { toast.error('Failed to delete round') }, }) // Filtering mutations const executeRules = trpc.filtering.executeRules.useMutation() const finalizeResults = trpc.filtering.finalizeResults.useMutation() const handleExecuteFiltering = async () => { try { const result = await executeRules.mutateAsync({ roundId }) toast.success( `Filtering complete: ${result.passed} passed, ${result.filteredOut} filtered out, ${result.flagged} flagged` ) refetchFilteringStats() } catch (error) { toast.error( error instanceof Error ? error.message : 'Failed to execute filtering' ) } } const handleFinalizeFiltering = async () => { try { const result = await finalizeResults.mutateAsync({ roundId }) toast.success( `Finalized: ${result.passed} passed, ${result.filteredOut} filtered out` ) refetchFilteringStats() refetchRound() utils.project.list.invalidate() } catch (error) { toast.error( error instanceof Error ? error.message : 'Failed to finalize' ) } } if (isLoading) { return } if (!round) { return (

Round Not Found

) } const now = new Date() const isVotingOpen = round.status === 'ACTIVE' && round.votingStartAt && round.votingEndAt && new Date(round.votingStartAt) <= now && new Date(round.votingEndAt) >= now const getStatusBadge = () => { if (round.status === 'ACTIVE' && isVotingOpen) { return ( Voting Open ) } switch (round.status) { case 'DRAFT': return Draft case 'ACTIVE': return ( Active ) case 'CLOSED': return Closed case 'ARCHIVED': return ( Archived ) default: return {round.status} } } return (
{/* Header */}
{round.program.year} Edition

{round.name}

{getStatusBadge()}
{round.status === 'DRAFT' && ( )} {round.status === 'ACTIVE' && ( )} {round.status === 'DRAFT' && ( Delete Round This will permanently delete “{round.name}” and all associated projects, assignments, and evaluations. This action cannot be undone. Cancel deleteRound.mutate({ id: round.id })} disabled={deleteRound.isPending} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > {deleteRound.isPending ? ( <> Deleting... ) : ( 'Delete Round' )} )}
{/* Stats Grid */}
Projects
{round._count.roundProjects}
Assignments
{round._count.assignments}
Required Reviews
{round.requiredReviews}

per project

Completion
{progress?.completionPercentage || 0}%

{progress?.completedAssignments || 0} of {progress?.totalAssignments || 0}

{/* Progress */} {progress && progress.totalAssignments > 0 && ( Evaluation Progress
Overall Completion {progress.completionPercentage}%
{Object.entries(progress.evaluationsByStatus).map(([status, count]) => (

{count}

{status.toLowerCase().replace('_', ' ')}

))}
)} {/* Voting Window */} Voting Window

Start Date

{round.votingStartAt ? (

{format(new Date(round.votingStartAt), 'PPP')}

{format(new Date(round.votingStartAt), 'p')}

) : (

Not set

)}

End Date

{round.votingEndAt ? (

{format(new Date(round.votingEndAt), 'PPP')}

{format(new Date(round.votingEndAt), 'p')}

{isFuture(new Date(round.votingEndAt)) && (

Ends {formatDistanceToNow(new Date(round.votingEndAt), { addSuffix: true })}

)}
) : (

Not set

)}
{/* Voting status */} {round.votingStartAt && round.votingEndAt && (
{isVotingOpen ? (
Voting is currently open
) : isFuture(new Date(round.votingStartAt)) ? (
Voting opens {formatDistanceToNow(new Date(round.votingStartAt), { addSuffix: true })}
) : (
Voting period has ended
)}
)}
{/* Filtering Section (for FILTERING rounds) */} {isFilteringRound && (
Project Filtering Run automated screening rules on projects in this round
{/* Stats */} {filteringStats && filteringStats.total > 0 ? (

{filteringStats.total}

Total

{filteringStats.passed}

Passed

{filteringStats.filteredOut}

Filtered Out

{filteringStats.flagged}

Flagged

) : (

No filtering results yet

Configure rules and run filtering to screen projects

)} {/* Quick links */}
{filteringStats && filteringStats.total > 0 && ( )}
)} {/* Quick Actions */} Quick Actions
{!isFilteringRound && ( )}
{/* Dialogs */} utils.round.get.invalidate({ id: roundId })} /> utils.round.get.invalidate({ id: roundId })} /> utils.round.get.invalidate({ id: roundId })} />
) } function RoundDetailSkeleton() { return (
{[1, 2, 3, 4].map((i) => ( ))}
) } export default function RoundDetailPage({ params }: PageProps) { const { id } = use(params) return ( }> ) }