2026-02-14 15:26:42 +01:00
|
|
|
'use client'
|
|
|
|
|
|
|
|
|
|
import { Suspense, use } from 'react'
|
|
|
|
|
import Link from 'next/link'
|
|
|
|
|
import type { Route } from 'next'
|
|
|
|
|
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 { Separator } from '@/components/ui/separator'
|
|
|
|
|
import {
|
|
|
|
|
Table,
|
|
|
|
|
TableBody,
|
|
|
|
|
TableCell,
|
|
|
|
|
TableHead,
|
|
|
|
|
TableHeader,
|
|
|
|
|
TableRow,
|
|
|
|
|
} from '@/components/ui/table'
|
|
|
|
|
import { FileViewer } from '@/components/shared/file-viewer'
|
|
|
|
|
import { FileUpload } from '@/components/shared/file-upload'
|
|
|
|
|
import { ProjectLogoWithUrl } from '@/components/shared/project-logo-with-url'
|
|
|
|
|
import { UserAvatar } from '@/components/shared/user-avatar'
|
|
|
|
|
import { EvaluationSummaryCard } from '@/components/admin/evaluation-summary-card'
|
|
|
|
|
import { AnimatedCard } from '@/components/shared/animated-container'
|
|
|
|
|
import {
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
Edit,
|
|
|
|
|
AlertCircle,
|
|
|
|
|
Users,
|
|
|
|
|
FileText,
|
|
|
|
|
Calendar,
|
|
|
|
|
CheckCircle2,
|
|
|
|
|
XCircle,
|
|
|
|
|
Circle,
|
|
|
|
|
Clock,
|
|
|
|
|
BarChart3,
|
|
|
|
|
ThumbsUp,
|
|
|
|
|
ThumbsDown,
|
|
|
|
|
MapPin,
|
|
|
|
|
Waves,
|
|
|
|
|
GraduationCap,
|
|
|
|
|
Heart,
|
|
|
|
|
Crown,
|
|
|
|
|
UserPlus,
|
2026-02-17 10:08:04 +01:00
|
|
|
Loader2,
|
|
|
|
|
ScanSearch,
|
2026-02-14 15:26:42 +01:00
|
|
|
} from 'lucide-react'
|
2026-02-17 10:08:04 +01:00
|
|
|
import { toast } from 'sonner'
|
2026-02-14 15:26:42 +01:00
|
|
|
import { formatDate, formatDateOnly } from '@/lib/utils'
|
|
|
|
|
|
|
|
|
|
interface PageProps {
|
|
|
|
|
params: Promise<{ id: string }>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Status badge colors
|
|
|
|
|
const statusColors: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
|
|
|
|
|
SUBMITTED: 'secondary',
|
|
|
|
|
ELIGIBLE: 'default',
|
|
|
|
|
ASSIGNED: 'default',
|
|
|
|
|
SEMIFINALIST: 'default',
|
|
|
|
|
FINALIST: 'default',
|
|
|
|
|
REJECTED: 'destructive',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Evaluation status colors
|
|
|
|
|
const evalStatusColors: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
|
|
|
|
|
NOT_STARTED: 'outline',
|
|
|
|
|
DRAFT: 'secondary',
|
|
|
|
|
SUBMITTED: 'default',
|
|
|
|
|
LOCKED: 'default',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ProjectDetailContent({ projectId }: { projectId: string }) {
|
|
|
|
|
// Fetch project + assignments + stats in a single combined query
|
|
|
|
|
const { data: fullDetail, isLoading } = trpc.project.getFullDetail.useQuery({
|
|
|
|
|
id: projectId,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const project = fullDetail?.project
|
|
|
|
|
const assignments = fullDetail?.assignments
|
|
|
|
|
const stats = fullDetail?.stats
|
|
|
|
|
|
|
|
|
|
// Fetch files (flat list for backward compatibility)
|
|
|
|
|
const { data: files } = trpc.file.listByProject.useQuery({ projectId })
|
|
|
|
|
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
// Fetch competitions for this project's program to get rounds
|
|
|
|
|
const { data: competitions } = trpc.competition.list.useQuery(
|
|
|
|
|
{ programId: project?.programId || '' },
|
2026-02-14 15:26:42 +01:00
|
|
|
{ enabled: !!project?.programId }
|
|
|
|
|
)
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
|
|
|
|
|
// Get first competition ID to fetch full details with rounds
|
|
|
|
|
const competitionId = competitions?.[0]?.id
|
|
|
|
|
|
|
|
|
|
// Fetch full competition details including rounds
|
|
|
|
|
const { data: competition } = trpc.competition.getById.useQuery(
|
|
|
|
|
{ id: competitionId || '' },
|
|
|
|
|
{ enabled: !!competitionId }
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Extract all rounds from the competition
|
|
|
|
|
const competitionRounds = competition?.rounds || []
|
|
|
|
|
|
2026-02-16 15:30:44 +01:00
|
|
|
// Fetch requirements for all rounds in a single query (avoids dynamic hook violation)
|
|
|
|
|
const roundIds = competitionRounds.map((r: { id: string }) => r.id)
|
|
|
|
|
const { data: allRequirements = [] } = trpc.file.listRequirementsByRounds.useQuery(
|
|
|
|
|
{ roundIds },
|
|
|
|
|
{ enabled: roundIds.length > 0 }
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
)
|
|
|
|
|
|
2026-02-14 15:26:42 +01:00
|
|
|
const utils = trpc.useUtils()
|
|
|
|
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
return <ProjectDetailSkeleton />
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!project) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<Button variant="ghost" asChild className="-ml-4">
|
|
|
|
|
<Link href="/admin/projects">
|
|
|
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
|
|
|
Back to Projects
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
|
|
|
<AlertCircle className="h-12 w-12 text-destructive/50" />
|
|
|
|
|
<p className="mt-2 font-medium">Project Not Found</p>
|
|
|
|
|
<Button asChild className="mt-4">
|
|
|
|
|
<Link href="/admin/projects">Back to Projects</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* Header */}
|
|
|
|
|
<div className="flex items-center gap-4">
|
|
|
|
|
<Button variant="ghost" asChild className="-ml-4">
|
|
|
|
|
<Link href="/admin/projects">
|
|
|
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
|
|
|
Back to Projects
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
|
|
|
<div className="flex items-start gap-4">
|
|
|
|
|
<ProjectLogoWithUrl
|
|
|
|
|
project={project}
|
|
|
|
|
size="lg"
|
|
|
|
|
fallback="initials"
|
|
|
|
|
/>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<div className="flex flex-wrap items-center gap-1 text-sm text-muted-foreground">
|
|
|
|
|
{project.programId ? (
|
|
|
|
|
<Link
|
|
|
|
|
href={`/admin/programs/${project.programId}`}
|
|
|
|
|
className="hover:underline"
|
|
|
|
|
>
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
Program
|
2026-02-14 15:26:42 +01:00
|
|
|
</Link>
|
|
|
|
|
) : (
|
|
|
|
|
<span>No program</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<h1 className="text-2xl font-semibold tracking-tight">
|
|
|
|
|
{project.title}
|
|
|
|
|
</h1>
|
|
|
|
|
<Badge variant={statusColors[project.status ?? 'SUBMITTED'] || 'secondary'}>
|
|
|
|
|
{(project.status ?? 'SUBMITTED').replace('_', ' ')}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
{project.teamName && (
|
|
|
|
|
<p className="text-muted-foreground">{project.teamName}</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Button variant="outline" asChild>
|
|
|
|
|
<Link href={`/admin/projects/${projectId}/edit`}>
|
|
|
|
|
<Edit className="mr-2 h-4 w-4" />
|
|
|
|
|
Edit
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Separator />
|
|
|
|
|
|
|
|
|
|
{/* Stats Grid */}
|
|
|
|
|
{stats && (
|
|
|
|
|
<AnimatedCard index={0}>
|
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
|
|
|
<Card className="transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md">
|
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
|
<CardTitle className="text-sm font-medium">
|
|
|
|
|
Average Score
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<div className="rounded-lg bg-brand-teal/10 p-1.5">
|
|
|
|
|
<BarChart3 className="h-4 w-4 text-brand-teal" />
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<div className="text-2xl font-bold">
|
|
|
|
|
{stats.averageGlobalScore?.toFixed(1) || '-'}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
Range: {stats.minScore || '-'} - {stats.maxScore || '-'}
|
|
|
|
|
</p>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
<Card className="transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md">
|
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
|
<CardTitle className="text-sm font-medium">
|
|
|
|
|
Recommendations
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<div className="rounded-lg bg-emerald-500/10 p-1.5">
|
|
|
|
|
<ThumbsUp className="h-4 w-4 text-emerald-500" />
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<div className="text-2xl font-bold">
|
|
|
|
|
{stats.yesPercentage?.toFixed(0) || 0}%
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
{stats.yesVotes} yes / {stats.noVotes} no
|
|
|
|
|
</p>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
</AnimatedCard>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Project Info */}
|
|
|
|
|
<AnimatedCard index={1}>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center gap-2.5 text-lg">
|
|
|
|
|
<div className="rounded-lg bg-emerald-500/10 p-1.5">
|
|
|
|
|
<FileText className="h-4 w-4 text-emerald-500" />
|
|
|
|
|
</div>
|
|
|
|
|
Project Information
|
|
|
|
|
</CardTitle>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
{/* Category & Ocean Issue badges */}
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{project.competitionCategory && (
|
|
|
|
|
<Badge variant="outline" className="gap-1">
|
|
|
|
|
<GraduationCap className="h-3 w-3" />
|
|
|
|
|
{project.competitionCategory === 'STARTUP' ? 'Start-up' : 'Business Concept'}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
{project.oceanIssue && (
|
|
|
|
|
<Badge variant="outline" className="gap-1">
|
|
|
|
|
<Waves className="h-3 w-3" />
|
|
|
|
|
{project.oceanIssue.replace(/_/g, ' ')}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
{project.wantsMentorship && (
|
|
|
|
|
<Badge variant="outline" className="gap-1 text-pink-600 border-pink-200 bg-pink-50">
|
|
|
|
|
<Heart className="h-3 w-3" />
|
|
|
|
|
Wants Mentorship
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{project.description && (
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground mb-1">
|
|
|
|
|
Description
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-sm whitespace-pre-wrap">{project.description}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Location & Institution */}
|
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
|
|
|
{(project.country || project.geographicZone) && (
|
|
|
|
|
<div className="flex items-start gap-2">
|
|
|
|
|
<MapPin className="h-4 w-4 text-muted-foreground mt-0.5" />
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground">Location</p>
|
|
|
|
|
<p className="text-sm">{project.geographicZone || project.country}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{project.institution && (
|
|
|
|
|
<div className="flex items-start gap-2">
|
|
|
|
|
<GraduationCap className="h-4 w-4 text-muted-foreground mt-0.5" />
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground">Institution</p>
|
|
|
|
|
<p className="text-sm">{project.institution}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{project.foundedAt && (
|
|
|
|
|
<div className="flex items-start gap-2">
|
|
|
|
|
<Calendar className="h-4 w-4 text-muted-foreground mt-0.5" />
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground">Founded</p>
|
|
|
|
|
<p className="text-sm">{formatDateOnly(project.foundedAt)}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Submission URLs */}
|
|
|
|
|
{(project.phase1SubmissionUrl || project.phase2SubmissionUrl) && (
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground">Submission Links</p>
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{project.phase1SubmissionUrl && (
|
|
|
|
|
<Button variant="outline" size="sm" asChild>
|
|
|
|
|
<a href={project.phase1SubmissionUrl} target="_blank" rel="noopener noreferrer">
|
|
|
|
|
Phase 1 Submission
|
|
|
|
|
</a>
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
{project.phase2SubmissionUrl && (
|
|
|
|
|
<Button variant="outline" size="sm" asChild>
|
|
|
|
|
<a href={project.phase2SubmissionUrl} target="_blank" rel="noopener noreferrer">
|
|
|
|
|
Phase 2 Submission
|
|
|
|
|
</a>
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* AI-Assigned Expertise Tags */}
|
|
|
|
|
{project.projectTags && project.projectTags.length > 0 && (
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground mb-2">
|
|
|
|
|
Expertise Tags
|
|
|
|
|
</p>
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{project.projectTags.map((pt) => (
|
|
|
|
|
<Badge
|
|
|
|
|
key={pt.tag.id}
|
|
|
|
|
variant="secondary"
|
|
|
|
|
className="flex items-center gap-1"
|
|
|
|
|
style={pt.tag.color ? { backgroundColor: `${pt.tag.color}20`, borderColor: pt.tag.color } : undefined}
|
|
|
|
|
>
|
|
|
|
|
{pt.tag.name}
|
|
|
|
|
{pt.confidence < 1 && (
|
|
|
|
|
<span className="text-xs opacity-60">
|
|
|
|
|
{Math.round(pt.confidence * 100)}%
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</Badge>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Simple Tags (legacy) */}
|
|
|
|
|
{project.tags && project.tags.length > 0 && (
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground mb-2">
|
|
|
|
|
Tags
|
|
|
|
|
</p>
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{project.tags.map((tag) => (
|
|
|
|
|
<Badge key={tag} variant="secondary">
|
|
|
|
|
{tag}
|
|
|
|
|
</Badge>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Internal Info */}
|
|
|
|
|
{(project.internalComments || project.applicationStatus || project.referralSource) && (
|
|
|
|
|
<div className="border-t pt-4 mt-4">
|
|
|
|
|
<p className="text-sm font-medium text-muted-foreground mb-3">Internal Notes</p>
|
|
|
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
|
|
|
{project.applicationStatus && (
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-xs text-muted-foreground">Application Status</p>
|
|
|
|
|
<p className="text-sm">{project.applicationStatus}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{project.referralSource && (
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-xs text-muted-foreground">Referral Source</p>
|
|
|
|
|
<p className="text-sm">{project.referralSource}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{project.internalComments && (
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
<p className="text-xs text-muted-foreground">Comments</p>
|
|
|
|
|
<p className="text-sm whitespace-pre-wrap">{project.internalComments}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-wrap gap-6 text-sm pt-2">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-muted-foreground">Created:</span>{' '}
|
|
|
|
|
{formatDateOnly(project.createdAt)}
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-muted-foreground">Updated:</span>{' '}
|
|
|
|
|
{formatDateOnly(project.updatedAt)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</AnimatedCard>
|
|
|
|
|
|
|
|
|
|
{/* Team Members Section */}
|
|
|
|
|
{project.teamMembers && project.teamMembers.length > 0 && (
|
|
|
|
|
<AnimatedCard index={2}>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<CardTitle className="flex items-center gap-2.5 text-lg">
|
|
|
|
|
<div className="rounded-lg bg-violet-500/10 p-1.5">
|
|
|
|
|
<Users className="h-4 w-4 text-violet-500" />
|
|
|
|
|
</div>
|
|
|
|
|
Team Members ({project.teamMembers.length})
|
|
|
|
|
</CardTitle>
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
|
|
|
{project.teamMembers.map((member: { id: string; role: string; title: string | null; user: { id: string; name: string | null; email: string; avatarUrl?: string | null } }) => (
|
|
|
|
|
<div key={member.id} className="flex items-center gap-3 p-3 rounded-lg border">
|
|
|
|
|
{member.role === 'LEAD' ? (
|
|
|
|
|
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
|
|
|
|
|
<Crown className="h-5 w-5 text-yellow-500" />
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<UserAvatar user={member.user} avatarUrl={member.user.avatarUrl} size="md" />
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<p className="font-medium text-sm truncate">
|
|
|
|
|
{member.user.name || 'Unnamed'}
|
|
|
|
|
</p>
|
|
|
|
|
<Badge variant="outline" className="text-xs">
|
|
|
|
|
{member.role === 'LEAD' ? 'Lead' : member.role === 'ADVISOR' ? 'Advisor' : 'Member'}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-muted-foreground truncate">
|
|
|
|
|
{member.user.email}
|
|
|
|
|
</p>
|
|
|
|
|
{member.title && (
|
|
|
|
|
<p className="text-xs text-muted-foreground">{member.title}</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</AnimatedCard>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Mentor Assignment Section */}
|
|
|
|
|
{project.wantsMentorship && (
|
|
|
|
|
<AnimatedCard index={3}>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<CardTitle className="flex items-center gap-2.5 text-lg">
|
|
|
|
|
<div className="rounded-lg bg-rose-500/10 p-1.5">
|
|
|
|
|
<Heart className="h-4 w-4 text-rose-500" />
|
|
|
|
|
</div>
|
|
|
|
|
Mentor Assignment
|
|
|
|
|
</CardTitle>
|
|
|
|
|
{!project.mentorAssignment && (
|
|
|
|
|
<Button variant="outline" size="sm" asChild>
|
|
|
|
|
<Link href={`/admin/projects/${projectId}/mentor` as Route}>
|
|
|
|
|
<UserPlus className="mr-2 h-4 w-4" />
|
|
|
|
|
Assign Mentor
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
{project.mentorAssignment ? (
|
|
|
|
|
<div className="flex items-center justify-between p-3 rounded-lg border">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<UserAvatar
|
|
|
|
|
user={project.mentorAssignment.mentor}
|
|
|
|
|
avatarUrl={project.mentorAssignment.mentor.avatarUrl}
|
|
|
|
|
size="md"
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">
|
|
|
|
|
{project.mentorAssignment.mentor.name || 'Unnamed'}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
{project.mentorAssignment.mentor.email}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Badge variant="outline">
|
|
|
|
|
{project.mentorAssignment.method.replace('_', ' ')}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
No mentor assigned yet. The applicant has requested mentorship support.
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</AnimatedCard>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Files Section */}
|
|
|
|
|
<AnimatedCard index={4}>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
2026-02-17 10:08:04 +01:00
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<CardTitle className="flex items-center gap-2.5 text-lg">
|
|
|
|
|
<div className="rounded-lg bg-rose-500/10 p-1.5">
|
|
|
|
|
<FileText className="h-4 w-4 text-rose-500" />
|
|
|
|
|
</div>
|
|
|
|
|
Files
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
Project documents and materials organized by competition round
|
|
|
|
|
</CardDescription>
|
2026-02-14 15:26:42 +01:00
|
|
|
</div>
|
2026-02-17 10:08:04 +01:00
|
|
|
<AnalyzeDocumentsButton projectId={projectId} onComplete={() => utils.file.listByProject.invalidate({ projectId })} />
|
|
|
|
|
</div>
|
2026-02-14 15:26:42 +01:00
|
|
|
</CardHeader>
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
<CardContent className="space-y-6">
|
|
|
|
|
{/* Requirements organized by round */}
|
|
|
|
|
{competitionRounds.length > 0 && allRequirements.length > 0 ? (
|
2026-02-14 15:26:42 +01:00
|
|
|
<>
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
{competitionRounds.map((round: { id: string; name: string }) => {
|
|
|
|
|
const roundRequirements = allRequirements.filter((req: any) => req.roundId === round.id)
|
|
|
|
|
if (roundRequirements.length === 0) return null
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div key={round.id} className="space-y-3">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<h3 className="text-sm font-semibold">{round.name}</h3>
|
|
|
|
|
<Badge variant="outline" className="text-xs">
|
|
|
|
|
{roundRequirements.length} requirement{roundRequirements.length !== 1 ? 's' : ''}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="grid gap-2">
|
|
|
|
|
{roundRequirements.map((req: any) => {
|
|
|
|
|
// Find file that fulfills this requirement
|
|
|
|
|
const fulfilledFile = files?.find((f: any) => f.requirementId === req.id)
|
|
|
|
|
const isFulfilled = !!fulfilledFile
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={req.id}
|
|
|
|
|
className={`flex items-center justify-between rounded-lg border p-3 ${
|
|
|
|
|
isFulfilled
|
|
|
|
|
? 'border-green-200 bg-green-50/50 dark:border-green-900 dark:bg-green-950/20'
|
|
|
|
|
: 'border-muted'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center gap-3 min-w-0">
|
|
|
|
|
{isFulfilled ? (
|
|
|
|
|
<CheckCircle2 className="h-5 w-5 shrink-0 text-green-600" />
|
|
|
|
|
) : (
|
|
|
|
|
<Circle className="h-5 w-5 shrink-0 text-muted-foreground" />
|
2026-02-14 15:26:42 +01:00
|
|
|
)}
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
<div className="min-w-0">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<p className="text-sm font-medium truncate">{req.name}</p>
|
|
|
|
|
{req.isRequired && (
|
|
|
|
|
<Badge variant="destructive" className="text-xs shrink-0">
|
|
|
|
|
Required
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{req.description && (
|
|
|
|
|
<p className="text-xs text-muted-foreground truncate">
|
|
|
|
|
{req.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex items-center gap-2 text-xs text-muted-foreground mt-0.5">
|
2026-02-16 15:30:44 +01:00
|
|
|
{req.acceptedMimeTypes?.length > 0 && (
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
<span>
|
|
|
|
|
{req.acceptedMimeTypes.map((mime: string) => {
|
|
|
|
|
if (mime === 'application/pdf') return 'PDF'
|
|
|
|
|
if (mime === 'image/*') return 'Images'
|
|
|
|
|
if (mime === 'video/*') return 'Video'
|
|
|
|
|
if (mime.includes('wordprocessing')) return 'Word'
|
|
|
|
|
if (mime.includes('spreadsheet')) return 'Excel'
|
|
|
|
|
if (mime.includes('presentation')) return 'PowerPoint'
|
|
|
|
|
return mime.split('/')[1] || mime
|
|
|
|
|
}).join(', ')}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{req.maxSizeMB && (
|
|
|
|
|
<span className="shrink-0">• Max {req.maxSizeMB}MB</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{isFulfilled && fulfilledFile && (
|
|
|
|
|
<p className="text-xs text-green-700 dark:text-green-400 mt-1 font-medium">
|
|
|
|
|
✓ {fulfilledFile.fileName}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-02-14 15:26:42 +01:00
|
|
|
</div>
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
{!isFulfilled && (
|
|
|
|
|
<span className="text-xs text-amber-600 dark:text-amber-400 shrink-0 ml-2 font-medium">
|
|
|
|
|
Missing
|
|
|
|
|
</span>
|
2026-02-14 15:26:42 +01:00
|
|
|
)}
|
|
|
|
|
</div>
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
})}
|
2026-02-14 15:26:42 +01:00
|
|
|
<Separator />
|
|
|
|
|
</>
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
) : null}
|
2026-02-14 15:26:42 +01:00
|
|
|
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
{/* General file upload section */}
|
2026-02-14 15:26:42 +01:00
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-semibold mb-3">
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
{allRequirements.length > 0 ? 'Additional Documents' : 'Upload Files'}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground mb-3">
|
|
|
|
|
Upload files not tied to specific requirements
|
2026-02-14 15:26:42 +01:00
|
|
|
</p>
|
|
|
|
|
<FileUpload
|
|
|
|
|
projectId={projectId}
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
availableRounds={competitionRounds?.map((r: any) => ({ id: r.id, name: r.name }))}
|
2026-02-14 15:26:42 +01:00
|
|
|
onUploadComplete={() => {
|
|
|
|
|
utils.file.listByProject.invalidate({ projectId })
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* All Files list */}
|
|
|
|
|
{files && files.length > 0 && (
|
|
|
|
|
<>
|
|
|
|
|
<Separator />
|
|
|
|
|
<div>
|
Admin system overhaul: full round config UI, flattened navigation, juries, awards integration, evaluation rewrite
- Phase 1: 7 round config sub-components covering all ~65 Zod schema fields across INTAKE, FILTERING, EVALUATION, SUBMISSION, MENTORING, LIVE_FINAL, DELIBERATION
- Phase 2: Replace Competitions nav with Rounds + add Juries; new /admin/rounds and /admin/rounds/[roundId] pages with tabbed detail (Config, Projects, Windows, Documents, Awards)
- Phase 3: Top-level /admin/juries with list + detail pages (members table, settings panel, self-service review)
- Phase 4: File requirements editor in round config; project detail per-requirement upload slots replacing generic drop zone
- Phase 5: Awards edit page with source round dropdown, eligibility mode, auto-tag rules builder; round detail Awards tab; specialAward router enhanced with evaluationRoundId/eligibilityMode fields
- Phase 6: Evaluation page rewrite supporting all 3 scoring modes (criteria/global/binary) with config-driven behavior; live voting UI polish
- Phase 7: UI design polish across admin pages — consistent headers, cards, hover transitions, empty states, brand colors
- Bulk upload page for admin project imports
- File router enhanced with admin upload and submission window procedures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 01:16:55 +01:00
|
|
|
<p className="text-sm font-semibold mb-3">All Uploaded Files</p>
|
2026-02-14 15:26:42 +01:00
|
|
|
<FileViewer
|
|
|
|
|
projectId={projectId}
|
|
|
|
|
files={files.map((f) => ({
|
|
|
|
|
id: f.id,
|
|
|
|
|
fileName: f.fileName,
|
|
|
|
|
fileType: f.fileType,
|
|
|
|
|
mimeType: f.mimeType,
|
|
|
|
|
size: f.size,
|
|
|
|
|
bucket: f.bucket,
|
|
|
|
|
objectKey: f.objectKey,
|
2026-02-17 10:08:04 +01:00
|
|
|
pageCount: f.pageCount,
|
|
|
|
|
textPreview: f.textPreview,
|
|
|
|
|
detectedLang: f.detectedLang,
|
|
|
|
|
langConfidence: f.langConfidence,
|
|
|
|
|
analyzedAt: f.analyzedAt ? String(f.analyzedAt) : null,
|
2026-02-17 16:43:47 +01:00
|
|
|
requirementId: f.requirementId,
|
|
|
|
|
requirement: f.requirement ? {
|
|
|
|
|
id: f.requirement.id,
|
|
|
|
|
name: f.requirement.name,
|
|
|
|
|
description: f.requirement.description,
|
|
|
|
|
isRequired: f.requirement.isRequired,
|
|
|
|
|
} : null,
|
2026-02-14 15:26:42 +01:00
|
|
|
}))}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</AnimatedCard>
|
|
|
|
|
|
|
|
|
|
{/* Assignments Section */}
|
|
|
|
|
{assignments && assignments.length > 0 && (
|
|
|
|
|
<AnimatedCard index={5}>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<CardTitle className="flex items-center gap-2.5 text-lg">
|
|
|
|
|
<div className="rounded-lg bg-violet-500/10 p-1.5">
|
|
|
|
|
<Users className="h-4 w-4 text-violet-500" />
|
|
|
|
|
</div>
|
|
|
|
|
Jury Assignments
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
{assignments.filter((a) => a.evaluation?.status === 'SUBMITTED')
|
|
|
|
|
.length}{' '}
|
|
|
|
|
of {assignments.length} evaluations completed
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</div>
|
|
|
|
|
<Button variant="outline" size="sm" asChild>
|
|
|
|
|
<Link href={`/admin/members`}>
|
|
|
|
|
Manage
|
|
|
|
|
</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<Table>
|
|
|
|
|
<TableHeader>
|
|
|
|
|
<TableRow>
|
|
|
|
|
<TableHead>Juror</TableHead>
|
|
|
|
|
<TableHead>Expertise</TableHead>
|
|
|
|
|
<TableHead>Status</TableHead>
|
|
|
|
|
<TableHead>Score</TableHead>
|
|
|
|
|
<TableHead>Decision</TableHead>
|
|
|
|
|
</TableRow>
|
|
|
|
|
</TableHeader>
|
|
|
|
|
<TableBody>
|
|
|
|
|
{assignments.map((assignment) => (
|
|
|
|
|
<TableRow key={assignment.id}>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<UserAvatar
|
|
|
|
|
user={assignment.user}
|
|
|
|
|
avatarUrl={assignment.user.avatarUrl}
|
|
|
|
|
size="sm"
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium text-sm">
|
|
|
|
|
{assignment.user.name || 'Unnamed'}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
{assignment.user.email}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<div className="flex flex-wrap gap-1">
|
|
|
|
|
{assignment.user.expertiseTags?.slice(0, 2).map((tag) => (
|
|
|
|
|
<Badge key={tag} variant="outline" className="text-xs">
|
|
|
|
|
{tag}
|
|
|
|
|
</Badge>
|
|
|
|
|
))}
|
|
|
|
|
{(assignment.user.expertiseTags?.length || 0) > 2 && (
|
|
|
|
|
<Badge variant="outline" className="text-xs">
|
|
|
|
|
+{(assignment.user.expertiseTags?.length || 0) - 2}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
<Badge
|
|
|
|
|
variant={
|
|
|
|
|
evalStatusColors[
|
|
|
|
|
assignment.evaluation?.status || 'NOT_STARTED'
|
|
|
|
|
] || 'secondary'
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
{(assignment.evaluation?.status || 'NOT_STARTED').replace(
|
|
|
|
|
'_',
|
|
|
|
|
' '
|
|
|
|
|
)}
|
|
|
|
|
</Badge>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
{assignment.evaluation?.globalScore !== null &&
|
|
|
|
|
assignment.evaluation?.globalScore !== undefined ? (
|
|
|
|
|
<span className="font-medium">
|
|
|
|
|
{assignment.evaluation.globalScore}/10
|
|
|
|
|
</span>
|
|
|
|
|
) : (
|
|
|
|
|
<span className="text-muted-foreground">-</span>
|
|
|
|
|
)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell>
|
|
|
|
|
{assignment.evaluation?.binaryDecision !== null &&
|
|
|
|
|
assignment.evaluation?.binaryDecision !== undefined ? (
|
|
|
|
|
assignment.evaluation.binaryDecision ? (
|
|
|
|
|
<div className="flex items-center gap-1 text-green-600">
|
|
|
|
|
<ThumbsUp className="h-4 w-4" />
|
|
|
|
|
<span className="text-sm">Yes</span>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex items-center gap-1 text-red-600">
|
|
|
|
|
<ThumbsDown className="h-4 w-4" />
|
|
|
|
|
<span className="text-sm">No</span>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
) : (
|
|
|
|
|
<span className="text-muted-foreground">-</span>
|
|
|
|
|
)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
))}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</AnimatedCard>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* AI Evaluation Summary */}
|
|
|
|
|
{assignments && assignments.length > 0 && stats && stats.totalEvaluations > 0 && (
|
|
|
|
|
<EvaluationSummaryCard
|
|
|
|
|
projectId={projectId}
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture.
New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy,
ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow.
New services: round-engine, round-assignment, deliberation, result-lock,
submission-manager, competition-context, ai-prompt-guard.
Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with
structured prompts, retry logic, and injection detection. All legacy
pipeline/stage code removed. 4 new migrations + seed aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 23:04:15 +01:00
|
|
|
roundId={assignments[0].roundId}
|
2026-02-14 15:26:42 +01:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ProjectDetailSkeleton() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
<Skeleton className="h-9 w-36" />
|
|
|
|
|
|
|
|
|
|
<div className="flex items-start justify-between">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Skeleton className="h-4 w-32" />
|
|
|
|
|
<Skeleton className="h-8 w-64" />
|
|
|
|
|
<Skeleton className="h-4 w-40" />
|
|
|
|
|
</div>
|
|
|
|
|
<Skeleton className="h-10 w-24" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Skeleton className="h-px w-full" />
|
|
|
|
|
|
|
|
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
|
|
|
{[1, 2, 3, 4].map((i) => (
|
|
|
|
|
<Card key={i}>
|
|
|
|
|
<CardHeader className="pb-2">
|
|
|
|
|
<Skeleton className="h-4 w-24" />
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<Skeleton className="h-8 w-16" />
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<Skeleton className="h-5 w-40" />
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<Skeleton className="h-24 w-full" />
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 10:08:04 +01:00
|
|
|
function AnalyzeDocumentsButton({ projectId, onComplete }: { projectId: string; onComplete: () => void }) {
|
|
|
|
|
const analyzeMutation = trpc.file.analyzeProjectFiles.useMutation({
|
|
|
|
|
onSuccess: (result) => {
|
|
|
|
|
toast.success(
|
|
|
|
|
`Analyzed ${result.analyzed} file${result.analyzed !== 1 ? 's' : ''}${result.failed > 0 ? ` (${result.failed} failed)` : ''}`
|
|
|
|
|
)
|
|
|
|
|
onComplete()
|
|
|
|
|
},
|
|
|
|
|
onError: (error) => {
|
|
|
|
|
toast.error(error.message || 'Analysis failed')
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => analyzeMutation.mutate({ projectId })}
|
|
|
|
|
disabled={analyzeMutation.isPending}
|
|
|
|
|
>
|
|
|
|
|
{analyzeMutation.isPending ? (
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<ScanSearch className="mr-2 h-4 w-4" />
|
|
|
|
|
)}
|
|
|
|
|
{analyzeMutation.isPending ? 'Analyzing...' : 'Analyze Documents'}
|
|
|
|
|
</Button>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 15:26:42 +01:00
|
|
|
export default function ProjectDetailPage({ params }: PageProps) {
|
|
|
|
|
const { id } = use(params)
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Suspense fallback={<ProjectDetailSkeleton />}>
|
|
|
|
|
<ProjectDetailContent projectId={id} />
|
|
|
|
|
</Suspense>
|
|
|
|
|
)
|
|
|
|
|
}
|