'use client' import { Suspense, use, useState } from 'react' import Link from 'next/link' 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 { Checkbox } from '@/components/ui/checkbox' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { ArrowLeft, Users, FileText, CheckCircle2, Clock, AlertCircle, Sparkles, Loader2, Plus, Trash2, RefreshCw, } from 'lucide-react' interface PageProps { params: Promise<{ id: string }> } function AssignmentManagementContent({ roundId }: { roundId: string }) { const [selectedSuggestions, setSelectedSuggestions] = useState>(new Set()) const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery({ id: roundId }) const { data: assignments, isLoading: loadingAssignments } = trpc.assignment.listByRound.useQuery({ roundId }) const { data: stats, isLoading: loadingStats } = trpc.assignment.getStats.useQuery({ roundId }) const { data: suggestions, isLoading: loadingSuggestions, refetch: refetchSuggestions } = trpc.assignment.getSuggestions.useQuery( { roundId, maxPerJuror: 10, minPerProject: 3 }, { enabled: !!round } ) const utils = trpc.useUtils() const deleteAssignment = trpc.assignment.delete.useMutation({ onSuccess: () => { utils.assignment.listByRound.invalidate({ roundId }) utils.assignment.getStats.invalidate({ roundId }) }, }) const applySuggestions = trpc.assignment.applySuggestions.useMutation({ onSuccess: () => { utils.assignment.listByRound.invalidate({ roundId }) utils.assignment.getStats.invalidate({ roundId }) utils.assignment.getSuggestions.invalidate({ roundId }) setSelectedSuggestions(new Set()) }, }) if (loadingRound || loadingAssignments) { return } if (!round) { return (

Round Not Found

) } const handleToggleSuggestion = (key: string) => { setSelectedSuggestions((prev) => { const newSet = new Set(prev) if (newSet.has(key)) { newSet.delete(key) } else { newSet.add(key) } return newSet }) } const handleSelectAllSuggestions = () => { if (suggestions) { if (selectedSuggestions.size === suggestions.length) { setSelectedSuggestions(new Set()) } else { setSelectedSuggestions( new Set(suggestions.map((s) => `${s.userId}-${s.projectId}`)) ) } } } const handleApplySelected = async () => { if (!suggestions) return const selected = suggestions.filter((s) => selectedSuggestions.has(`${s.userId}-${s.projectId}`) ) await applySuggestions.mutateAsync({ roundId, assignments: selected.map((s) => ({ userId: s.userId, projectId: s.projectId, reasoning: s.reasoning.join('; '), })), }) } // Group assignments by project const assignmentsByProject = assignments?.reduce((acc, assignment) => { const projectId = assignment.project.id if (!acc[projectId]) { acc[projectId] = { project: assignment.project, assignments: [], } } acc[projectId].assignments.push(assignment) return acc }, {} as Record) || {} return (
{/* Header */}
{round.program.name} / {round.name}

Manage Assignments

{/* Stats */} {stats && (
Total Assignments
{stats.totalAssignments}
Completed
{stats.completedAssignments}

{stats.completionPercentage}% complete

Projects Covered
{stats.projectsWithFullCoverage}/{stats.totalProjects}

{stats.coveragePercentage}% have {round.requiredReviews}+ reviews

Jury Members
{stats.juryMembersAssigned}

assigned to projects

)} {/* Coverage Progress */} {stats && ( Project Coverage {stats.projectsWithFullCoverage} of {stats.totalProjects} projects have at least {round.requiredReviews} reviewers assigned )} {/* Smart Suggestions */}
Smart Assignment Suggestions AI-powered recommendations based on expertise matching and workload balance
{loadingSuggestions ? (
) : suggestions && suggestions.length > 0 ? (
{selectedSuggestions.size} of {suggestions.length} selected
Juror Project Score Reasoning {suggestions.map((suggestion) => { const key = `${suggestion.userId}-${suggestion.projectId}` const isSelected = selectedSuggestions.has(key) return ( handleToggleSuggestion(key)} /> {suggestion.userId.slice(0, 8)}... {suggestion.projectId.slice(0, 8)}... = 60 ? 'default' : suggestion.score >= 40 ? 'secondary' : 'outline' } > {suggestion.score.toFixed(0)}
    {suggestion.reasoning.map((r, i) => (
  • {r}
  • ))}
) })}
) : (

All projects are covered!

No additional assignments are needed at this time

)}
{/* Current Assignments */} Current Assignments View and manage existing project assignments {Object.keys(assignmentsByProject).length > 0 ? (
{Object.entries(assignmentsByProject).map( ([projectId, { project, assignments: projectAssignments }]) => (

{project.title}

{projectAssignments.length} reviewer {projectAssignments.length !== 1 ? 's' : ''} {projectAssignments.length >= round.requiredReviews && ( Full coverage )}
{projectAssignments.map((assignment) => (
{assignment.user.name || assignment.user.email} {assignment.evaluation?.status === 'SUBMITTED' ? ( Submitted ) : assignment.evaluation?.status === 'DRAFT' ? ( In Progress ) : ( Pending )}
Remove Assignment? This will remove {assignment.user.name || assignment.user.email} from evaluating this project. This action cannot be undone. Cancel deleteAssignment.mutate({ id: assignment.id }) } className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > Remove
))}
) )}
) : (

No Assignments Yet

Use the smart suggestions above or manually assign jury members to projects

)}
) } function AssignmentsSkeleton() { return (
{[1, 2, 3, 4].map((i) => ( ))}
) } export default function AssignmentManagementPage({ params }: PageProps) { const { id } = use(params) return ( }> ) }