'use client' import { useState, useEffect, useMemo } from 'react' import { useSearchParams } from 'next/navigation' import Link from 'next/link' import type { Route } from 'next' import { trpc } from '@/lib/trpc/client' import { useEdition } from '@/contexts/edition-context' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Switch } from '@/components/ui/switch' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from '@/components/ui/dialog' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { Card, CardContent } from '@/components/ui/card' import { Skeleton } from '@/components/ui/skeleton' import { getCountryName, getCountryFlag, normalizeCountryToCode } from '@/lib/countries' import { toast } from 'sonner' import { ArrowLeft, ChevronLeft, ChevronRight, Loader2, X, Layers, Info } from 'lucide-react' const roundTypeColors: Record = { INTAKE: 'bg-gray-100 text-gray-700', FILTERING: 'bg-amber-100 text-amber-700', EVALUATION: 'bg-blue-100 text-blue-700', SUBMISSION: 'bg-purple-100 text-purple-700', MENTORING: 'bg-teal-100 text-teal-700', LIVE_FINAL: 'bg-red-100 text-red-700', DELIBERATION: 'bg-indigo-100 text-indigo-700', } export default function ProjectPoolPage() { const searchParams = useSearchParams() const { currentEdition, isLoading: editionLoading } = useEdition() // URL params for deep-linking context const urlRoundId = searchParams.get('roundId') || '' const urlCompetitionId = searchParams.get('competitionId') || '' // Auto-select programId from edition const programId = currentEdition?.id || '' const [selectedProjects, setSelectedProjects] = useState([]) const [assignDialogOpen, setAssignDialogOpen] = useState(false) const [assignAllDialogOpen, setAssignAllDialogOpen] = useState(false) const [targetRoundId, setTargetRoundId] = useState(urlRoundId) const [searchQuery, setSearchQuery] = useState('') const [categoryFilter, setCategoryFilter] = useState<'STARTUP' | 'BUSINESS_CONCEPT' | 'all'>('all') const [showUnassignedOnly, setShowUnassignedOnly] = useState(false) const [currentPage, setCurrentPage] = useState(1) const perPage = 50 // Pre-select target round from URL param useEffect(() => { if (urlRoundId) setTargetRoundId(urlRoundId) }, [urlRoundId]) const { data: poolData, isLoading: isLoadingPool, refetch } = trpc.projectPool.listUnassigned.useQuery( { programId, competitionCategory: categoryFilter === 'all' ? undefined : categoryFilter, search: searchQuery || undefined, unassignedOnly: showUnassignedOnly, excludeRoundId: urlRoundId || undefined, page: currentPage, perPage, }, { enabled: !!programId } ) // Load rounds from program (flattened from all competitions, now with competitionId) const { data: programData, isLoading: isLoadingRounds } = trpc.program.get.useQuery( { id: programId }, { enabled: !!programId } ) // Get round name for context banner const allRounds = useMemo(() => { return (programData?.rounds || []) as Array<{ id: string name: string competitionId: string status: string _count: { projects: number; assignments: number } }> }, [programData]) // Filter rounds by competitionId if URL param is set const filteredRounds = useMemo(() => { if (urlCompetitionId) { return allRounds.filter((r) => r.competitionId === urlCompetitionId) } return allRounds }, [allRounds, urlCompetitionId]) const contextRound = urlRoundId ? allRounds.find((r) => r.id === urlRoundId) : null const utils = trpc.useUtils() const assignMutation = trpc.projectPool.assignToRound.useMutation({ onSuccess: (result) => { utils.project.list.invalidate() utils.projectPool.listUnassigned.invalidate() toast.success(`Assigned ${result.assignedCount} project${result.assignedCount !== 1 ? 's' : ''} to round`) setSelectedProjects([]) setAssignDialogOpen(false) setTargetRoundId(urlRoundId) refetch() }, onError: (error: unknown) => { toast.error((error as { message?: string }).message || 'Failed to assign projects') }, }) const assignAllMutation = trpc.projectPool.assignAllToRound.useMutation({ onSuccess: (result) => { utils.project.list.invalidate() utils.projectPool.listUnassigned.invalidate() toast.success(`Assigned all ${result.assignedCount} projects to round`) setSelectedProjects([]) setAssignAllDialogOpen(false) setTargetRoundId(urlRoundId) refetch() }, onError: (error: unknown) => { toast.error((error as { message?: string }).message || 'Failed to assign projects') }, }) const isPending = assignMutation.isPending || assignAllMutation.isPending const handleBulkAssign = () => { if (selectedProjects.length === 0 || !targetRoundId) return assignMutation.mutate({ projectIds: selectedProjects, roundId: targetRoundId, }) } const handleAssignAll = () => { if (!targetRoundId || !programId) return assignAllMutation.mutate({ programId, roundId: targetRoundId, competitionCategory: categoryFilter === 'all' ? undefined : categoryFilter, unassignedOnly: showUnassignedOnly, }) } const handleQuickAssign = (projectId: string, roundId: string) => { assignMutation.mutate({ projectIds: [projectId], roundId, }) } const toggleSelectAll = () => { if (!poolData?.projects) return if (selectedProjects.length === poolData.projects.length) { setSelectedProjects([]) } else { setSelectedProjects(poolData.projects.map((p) => p.id)) } } const toggleSelectProject = (projectId: string) => { if (selectedProjects.includes(projectId)) { setSelectedProjects(selectedProjects.filter((id) => id !== projectId)) } else { setSelectedProjects([...selectedProjects, projectId]) } } if (editionLoading) { return (
) } return (
{/* Header */}

Project Pool

{currentEdition ? `${currentEdition.name} ${currentEdition.year} \u2014 ${poolData?.total ?? '...'} projects` : 'No edition selected'}

{/* Context banner when coming from a round */} {contextRound && (

Assigning to {contextRound.name} {' \u2014 '} projects already in this round are hidden

)} {/* Filters */}
{ setSearchQuery(e.target.value) setCurrentPage(1) }} />
{ setShowUnassignedOnly(checked) setCurrentPage(1) }} />
{/* Action bar */} {programId && poolData && poolData.total > 0 && (

{poolData.total} project{poolData.total !== 1 ? 's' : ''} {showUnassignedOnly && ' (unassigned only)'}

{selectedProjects.length > 0 && ( )}
)} {/* Projects Table */} {programId ? ( <> {isLoadingPool ? (
{[...Array(5)].map((_, i) => ( ))}
) : poolData && poolData.total > 0 ? ( <>
{poolData.projects.map((project) => ( ))}
0 && selectedProjects.length === poolData.projects.length} onCheckedChange={toggleSelectAll} /> Project Category Rounds Country Submitted Quick Assign
toggleSelectProject(project.id)} />
{project.title}
{project.teamName}
{project.competitionCategory && ( {project.competitionCategory === 'STARTUP' ? 'Startup' : 'Business Concept'} )} {(project as any).projectRoundStates?.length > 0 ? (
{(project as any).projectRoundStates.map((prs: any) => ( {prs.round?.name || 'Round'} ))}
) : ( None )}
{project.country ? (() => { const code = normalizeCountryToCode(project.country) const flag = code ? getCountryFlag(code) : null const name = code ? getCountryName(code) : project.country return <>{flag && {flag} }{name} })() : '-'} {project.submittedAt ? new Date(project.submittedAt).toLocaleDateString() : '-'} {isLoadingRounds ? ( ) : ( )}
{/* Pagination */} {poolData.totalPages > 1 && (

Showing {((currentPage - 1) * perPage) + 1} to {Math.min(currentPage * perPage, poolData.total)} of {poolData.total}

)} ) : (

{showUnassignedOnly ? 'No unassigned projects found' : urlRoundId ? 'All projects are already assigned to this round' : 'No projects found for this program'}

)} ) : ( No edition selected. Please select an edition from the sidebar. )} {/* Bulk Assignment Dialog (selected projects) */} Assign Selected Projects Assign {selectedProjects.length} selected project{selectedProjects.length > 1 ? 's' : ''} to a round:
{/* Assign ALL Dialog */} Assign All Projects This will assign all {poolData?.total || 0}{categoryFilter !== 'all' ? ` ${categoryFilter === 'STARTUP' ? 'Startup' : 'Business Concept'}` : ''}{showUnassignedOnly ? ' unassigned' : ''} projects to a round in one operation.
) }