'use client' import { use, useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { trpc } from '@/lib/trpc/client' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Skeleton } from '@/components/ui/skeleton' import { Switch } from '@/components/ui/switch' import { Label } from '@/components/ui/label' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Input } from '@/components/ui/input' import { UserAvatar } from '@/components/shared/user-avatar' import { Pagination } from '@/components/shared/pagination' import { toast } from 'sonner' import { ArrowLeft, Trophy, Users, CheckCircle2, Brain, BarChart3, Loader2, Crown, UserPlus, X, Play, Lock, Pencil, Trash2, Plus, Search, } from 'lucide-react' const STATUS_COLORS: Record = { DRAFT: 'secondary', NOMINATIONS_OPEN: 'default', VOTING_OPEN: 'default', CLOSED: 'outline', ARCHIVED: 'secondary', } export default function AwardDetailPage({ params, }: { params: Promise<{ id: string }> }) { const { id: awardId } = use(params) const router = useRouter() const { data: award, isLoading, refetch } = trpc.specialAward.get.useQuery({ id: awardId }) const { data: eligibilityData, refetch: refetchEligibility } = trpc.specialAward.listEligible.useQuery({ awardId, page: 1, perPage: 500, }) const { data: jurors, refetch: refetchJurors } = trpc.specialAward.listJurors.useQuery({ awardId }) const { data: voteResults } = trpc.specialAward.getVoteResults.useQuery({ awardId }) const { data: allUsers } = trpc.user.list.useQuery({ role: 'JURY_MEMBER', page: 1, perPage: 100 }) // Fetch all projects in the program for manual eligibility addition const { data: allProjects } = trpc.project.list.useQuery( { programId: award?.programId ?? '', perPage: 500 }, { enabled: !!award?.programId } ) const updateStatus = trpc.specialAward.updateStatus.useMutation() const runEligibility = trpc.specialAward.runEligibility.useMutation() const setEligibility = trpc.specialAward.setEligibility.useMutation() const addJuror = trpc.specialAward.addJuror.useMutation() const removeJuror = trpc.specialAward.removeJuror.useMutation() const setWinner = trpc.specialAward.setWinner.useMutation() const deleteAward = trpc.specialAward.delete.useMutation() const [selectedJurorId, setSelectedJurorId] = useState('') const [includeSubmitted, setIncludeSubmitted] = useState(true) const [addProjectDialogOpen, setAddProjectDialogOpen] = useState(false) const [projectSearchQuery, setProjectSearchQuery] = useState('') const handleStatusChange = async ( status: 'DRAFT' | 'NOMINATIONS_OPEN' | 'VOTING_OPEN' | 'CLOSED' | 'ARCHIVED' ) => { try { await updateStatus.mutateAsync({ id: awardId, status }) toast.success(`Status updated to ${status.replace('_', ' ')}`) refetch() } catch (error) { toast.error( error instanceof Error ? error.message : 'Failed to update status' ) } } const handleRunEligibility = async () => { try { const result = await runEligibility.mutateAsync({ awardId, includeSubmitted }) toast.success( `Eligibility run: ${result.eligible} eligible, ${result.ineligible} ineligible` ) refetchEligibility() refetch() } catch (error) { toast.error( error instanceof Error ? error.message : 'Failed to run eligibility' ) } } const handleToggleEligibility = async ( projectId: string, eligible: boolean ) => { try { await setEligibility.mutateAsync({ awardId, projectId, eligible }) refetchEligibility() } catch { toast.error('Failed to update eligibility') } } const handleAddJuror = async () => { if (!selectedJurorId) return try { await addJuror.mutateAsync({ awardId, userId: selectedJurorId }) toast.success('Juror added') setSelectedJurorId('') refetchJurors() } catch { toast.error('Failed to add juror') } } const handleRemoveJuror = async (userId: string) => { try { await removeJuror.mutateAsync({ awardId, userId }) refetchJurors() } catch { toast.error('Failed to remove juror') } } const handleSetWinner = async (projectId: string) => { try { await setWinner.mutateAsync({ awardId, projectId, overridden: true, }) toast.success('Winner set') refetch() } catch { toast.error('Failed to set winner') } } const handleDeleteAward = async () => { try { await deleteAward.mutateAsync({ id: awardId }) toast.success('Award deleted') router.push('/admin/awards') } catch (error) { toast.error( error instanceof Error ? error.message : 'Failed to delete award' ) } } const handleAddProjectToEligibility = async (projectId: string) => { try { await setEligibility.mutateAsync({ awardId, projectId, eligible: true }) toast.success('Project added to eligibility list') refetchEligibility() refetch() } catch { toast.error('Failed to add project') } } const handleRemoveFromEligibility = async (projectId: string) => { try { await setEligibility.mutateAsync({ awardId, projectId, eligible: false }) toast.success('Project removed from eligibility') refetchEligibility() refetch() } catch { toast.error('Failed to remove project') } } // Get projects that aren't already in the eligibility list const eligibleProjectIds = new Set( eligibilityData?.eligibilities.map((e) => e.projectId) || [] ) const availableProjects = allProjects?.projects.filter( (p) => !eligibleProjectIds.has(p.id) ) || [] const filteredAvailableProjects = availableProjects.filter( (p) => p.title.toLowerCase().includes(projectSearchQuery.toLowerCase()) || p.teamName?.toLowerCase().includes(projectSearchQuery.toLowerCase()) ) if (isLoading) { return (
) } if (!award) return null const jurorUserIds = new Set(jurors?.map((j) => j.userId) || []) const availableUsers = allUsers?.users.filter((u) => !jurorUserIds.has(u.id)) || [] return (
{/* Header */}

{award.name}

{award.status.replace('_', ' ')} {award.program.year} Edition {award.votingStartAt && ( Voting: {new Date(award.votingStartAt).toLocaleDateString()} - {award.votingEndAt ? new Date(award.votingEndAt).toLocaleDateString() : 'No end date'} )}
{award.status === 'DRAFT' && ( )} {award.status === 'NOMINATIONS_OPEN' && ( )} {award.status === 'VOTING_OPEN' && ( )} Delete Award? This will permanently delete "{award.name}" and all associated eligibility data, juror assignments, and votes. This action cannot be undone. Cancel {deleteAward.isPending ? ( ) : ( )} Delete Award
{/* Description */} {award.description && (

{award.description}

)} {/* Tabs */} Eligibility ({award.eligibleCount}) Jurors ({award._count.jurors}) Results {/* Eligibility Tab */}

{award.eligibleCount} of {award._count.eligibilities} projects eligible

{award.useAiEligibility ? ( ) : ( )} Add Project to Eligibility List Manually add a project that wasn't included by AI or rule-based filtering
setProjectSearchQuery(e.target.value)} className="pl-9" />
{filteredAvailableProjects.length > 0 ? ( Project Category Country Action {filteredAvailableProjects.slice(0, 50).map((project) => (

{project.title}

{project.teamName}

{project.competitionCategory ? ( {project.competitionCategory.replace('_', ' ')} ) : ( '-' )} {project.country || '-'}
))}
) : (

{projectSearchQuery ? 'No projects match your search' : 'All projects are already in the eligibility list'}

)}
{filteredAvailableProjects.length > 50 && (

Showing first 50 of {filteredAvailableProjects.length} projects. Use search to filter.

)}
{!award.useAiEligibility && (

AI eligibility is off for this award. Projects are loaded for manual selection.

)} {eligibilityData && eligibilityData.eligibilities.length > 0 ? ( Project Category Country Method Eligible Actions {eligibilityData.eligibilities.map((e) => (

{e.project.title}

{e.project.teamName}

{e.project.competitionCategory ? ( {e.project.competitionCategory.replace('_', ' ')} ) : ( '-' )} {e.project.country || '-'} {e.method === 'MANUAL' ? 'Manual' : 'Auto'} handleToggleEligibility(e.projectId, checked) } />
))}
) : (

No eligibility data

Run AI eligibility to evaluate projects or manually add projects

)}
{/* Jurors Tab */}
{jurors && jurors.length > 0 ? ( Member Role Actions {jurors.map((j) => (

{j.user.name || 'Unnamed'}

{j.user.email}

{j.user.role.replace('_', ' ')}
))}
) : (

No jurors assigned

Add members as jurors for this award

)}
{/* Results Tab */} {voteResults && voteResults.results.length > 0 ? ( <>
{voteResults.votedJurorCount} of {voteResults.jurorCount}{' '} jurors voted {voteResults.scoringMode.replace('_', ' ')}
# Project Votes Points Actions {voteResults.results.map((r, i) => ( {i + 1}
{r.project.id === voteResults.winnerId && ( )}

{r.project.title}

{r.project.teamName}

{r.votes} {r.points} {r.project.id !== voteResults.winnerId && ( )}
))}
) : (

No votes yet

Votes will appear here once jurors submit their selections

)}
) }