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
'use client'
Fix filtering config save, auto-save, streamed results, improved AI prompt
- Add missing fields to FilteringConfigSchema (aiParseFiles, startupAdvanceCount,
conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) — Zod was silently
stripping them on save
- Restore auto-save with 800ms debounce on config changes
- Add staggered animations for filtering results (stream in one-by-one)
- Improve AI screening prompt: file type label mappings, soft cap handling,
missing documents = fail, better user prompt structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:18:04 +01:00
import { useState , useMemo , useCallback , useRef , useEffect } from 'react'
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
import { useParams } from 'next/navigation'
import Link from 'next/link'
import type { Route } from 'next'
import { trpc } from '@/lib/trpc/client'
import { toast } from 'sonner'
2026-02-16 09:20:02 +01:00
import { cn } from '@/lib/utils'
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
import { Button } from '@/components/ui/button'
2026-02-16 09:20:02 +01:00
import { Card , CardContent , CardDescription , CardHeader , CardTitle } from '@/components/ui/card'
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
import { Tabs , TabsContent , TabsList , TabsTrigger } from '@/components/ui/tabs'
import { Skeleton } from '@/components/ui/skeleton'
import { Badge } from '@/components/ui/badge'
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
import { Checkbox } from '@/components/ui/checkbox'
Rounds overhaul: full CRUD submission windows, scheduling UI, analytics, design refresh
- Fix special award FK crash: replace 4x raw auditLog.create with logAudit() helper
- Add updateSubmissionWindow + deleteSubmissionWindow mutations to round router
- Add per-round analytics (_count, juryGroup) to competition.getById
- Remove redundant acceptedCategories from intake config
- Rewrite submission window manager with full CRUD, all fields, date pickers
- Add round scheduling card (open/close dates) to round detail page
- Add project count, assignment count, jury group to round list cards
- Visual redesign: pipeline view, brand colors, progress bars, enhanced cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:07:09 +01:00
import { Input } from '@/components/ui/input'
2026-02-16 09:20:02 +01:00
import { Switch } from '@/components/ui/switch'
import { Label } from '@/components/ui/label'
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
import {
DropdownMenu ,
DropdownMenuContent ,
DropdownMenuItem ,
DropdownMenuSeparator ,
DropdownMenuTrigger ,
} from '@/components/ui/dropdown-menu'
2026-02-16 09:20:02 +01:00
import {
AlertDialog ,
AlertDialogAction ,
AlertDialogCancel ,
AlertDialogContent ,
AlertDialogDescription ,
AlertDialogFooter ,
AlertDialogHeader ,
AlertDialogTitle ,
AlertDialogTrigger ,
} from '@/components/ui/alert-dialog'
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
import {
Dialog ,
DialogContent ,
DialogDescription ,
DialogFooter ,
DialogHeader ,
DialogTitle ,
} from '@/components/ui/dialog'
2026-02-16 09:20:02 +01:00
import {
Select ,
SelectContent ,
SelectItem ,
SelectTrigger ,
SelectValue ,
} from '@/components/ui/select'
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
import {
ArrowLeft ,
Save ,
Loader2 ,
ChevronDown ,
Play ,
Square ,
Archive ,
2026-02-16 09:20:02 +01:00
Layers ,
Rounds overhaul: full CRUD submission windows, scheduling UI, analytics, design refresh
- Fix special award FK crash: replace 4x raw auditLog.create with logAudit() helper
- Add updateSubmissionWindow + deleteSubmissionWindow mutations to round router
- Add per-round analytics (_count, juryGroup) to competition.getById
- Remove redundant acceptedCategories from intake config
- Rewrite submission window manager with full CRUD, all fields, date pickers
- Add round scheduling card (open/close dates) to round detail page
- Add project count, assignment count, jury group to round list cards
- Visual redesign: pipeline view, brand colors, progress bars, enhanced cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:07:09 +01:00
Users ,
2026-02-16 09:20:02 +01:00
CalendarDays ,
BarChart3 ,
ClipboardList ,
Settings ,
Zap ,
Shield ,
UserPlus ,
CheckCircle2 ,
AlertTriangle ,
FileText ,
Trophy ,
Clock ,
Send ,
Download ,
Plus ,
Trash2 ,
ArrowRight ,
2026-02-16 12:06:07 +01:00
RotateCcw ,
2026-02-16 09:20:02 +01:00
X ,
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
Check ,
ChevronsUpDown ,
Search ,
2026-02-18 14:03:38 +01:00
MoreHorizontal ,
2026-02-18 14:34:27 +01:00
ShieldAlert ,
Eye ,
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
} from 'lucide-react'
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
import {
Command ,
CommandEmpty ,
CommandGroup ,
CommandInput ,
CommandItem ,
CommandList ,
} from '@/components/ui/command'
import {
Popover ,
PopoverContent ,
PopoverTrigger ,
} from '@/components/ui/popover'
import { ScrollArea } from '@/components/ui/scroll-area'
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
import { RoundConfigForm } from '@/components/admin/competition/round-config-form'
import { ProjectStatesTable } from '@/components/admin/round/project-states-table'
2026-02-16 16:43:23 +01:00
// SubmissionWindowManager removed — round dates + file requirements in Config are sufficient
2026-02-16 09:20:02 +01:00
import { FileRequirementsEditor } from '@/components/admin/round/file-requirements-editor'
import { FilteringDashboard } from '@/components/admin/round/filtering-dashboard'
import { CoverageReport } from '@/components/admin/assignment/coverage-report'
import { AssignmentPreviewSheet } from '@/components/admin/assignment/assignment-preview-sheet'
import { CsvExportDialog } from '@/components/shared/csv-export-dialog'
2026-02-16 12:38:28 +01:00
import { AnimatedCard } from '@/components/shared/animated-container'
2026-02-16 16:43:23 +01:00
import { DateTimePicker } from '@/components/ui/datetime-picker'
2026-02-16 12:46:01 +01:00
import { AddMemberDialog } from '@/components/admin/jury/add-member-dialog'
2026-02-16 12:38:28 +01:00
import { motion } from 'motion/react'
2026-02-18 12:43:28 +01:00
import { EvaluationFormBuilder } from '@/components/forms/evaluation-form-builder'
import type { Criterion } from '@/components/forms/evaluation-form-builder'
2026-02-16 09:20:02 +01:00
// ── Status & type config maps ──────────────────────────────────────────────
const roundStatusConfig = {
ROUND_DRAFT : {
label : 'Draft' ,
bgClass : 'bg-gray-100 text-gray-700' ,
dotClass : 'bg-gray-500' ,
description : 'Not yet active. Configure before launching.' ,
} ,
ROUND_ACTIVE : {
label : 'Active' ,
bgClass : 'bg-emerald-100 text-emerald-700' ,
dotClass : 'bg-emerald-500 animate-pulse' ,
description : 'Round is live. Projects can be processed.' ,
} ,
ROUND_CLOSED : {
label : 'Closed' ,
bgClass : 'bg-blue-100 text-blue-700' ,
dotClass : 'bg-blue-500' ,
description : 'No longer accepting changes. Results are final.' ,
} ,
ROUND_ARCHIVED : {
label : 'Archived' ,
bgClass : 'bg-muted text-muted-foreground' ,
dotClass : 'bg-muted-foreground' ,
description : 'Historical record only.' ,
} ,
} as const
const roundTypeConfig : Record < string , { label : string ; color : string ; description : string } > = {
INTAKE : { label : 'Intake' , color : 'bg-gray-100 text-gray-700' , description : 'Collecting applications' } ,
FILTERING : { label : 'Filtering' , color : 'bg-amber-100 text-amber-700' , description : 'AI + manual screening' } ,
EVALUATION : { label : 'Evaluation' , color : 'bg-blue-100 text-blue-700' , description : 'Jury evaluation & scoring' } ,
SUBMISSION : { label : 'Submission' , color : 'bg-purple-100 text-purple-700' , description : 'Document submission' } ,
MENTORING : { label : 'Mentoring' , color : 'bg-teal-100 text-teal-700' , description : 'Mentor-guided development' } ,
LIVE_FINAL : { label : 'Live Final' , color : 'bg-red-100 text-red-700' , description : 'Live presentations & voting' } ,
DELIBERATION : { label : 'Deliberation' , color : 'bg-indigo-100 text-indigo-700' , description : 'Final jury deliberation' } ,
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-16 09:20:02 +01:00
const stateColors : Record < string , string > = {
PENDING : 'bg-gray-400' ,
IN_PROGRESS : 'bg-blue-500' ,
PASSED : 'bg-green-500' ,
REJECTED : 'bg-red-500' ,
COMPLETED : 'bg-emerald-500' ,
WITHDRAWN : 'bg-orange-400' ,
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-16 09:20:02 +01:00
// ═══════════════════════════════════════════════════════════════════════════
// Main Page Component
// ═══════════════════════════════════════════════════════════════════════════
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
export default function RoundDetailPage() {
const params = useParams ( )
const roundId = params . roundId as string
const [ config , setConfig ] = useState < Record < string , unknown > > ( { } )
2026-02-17 12:33:20 +01:00
const [ autosaveStatus , setAutosaveStatus ] = useState < 'idle' | 'saving' | 'saved' | 'error' > ( 'idle' )
2026-02-16 09:20:02 +01:00
const [ activeTab , setActiveTab ] = useState ( 'overview' )
const [ previewSheetOpen , setPreviewSheetOpen ] = useState ( false )
2026-02-17 14:45:57 +01:00
// AI assignment generation (lifted to page level so it persists when sheet closes)
const aiAssignmentMutation = trpc . roundAssignment . aiPreview . useMutation ( {
onSuccess : ( ) = > {
toast . success ( 'AI assignments ready!' , {
action : {
label : 'Review' ,
onClick : ( ) = > setPreviewSheetOpen ( true ) ,
} ,
duration : 10000 ,
} )
} ,
onError : ( err ) = > {
toast . error ( ` AI generation failed: ${ err . message } ` )
} ,
} )
2026-02-16 09:20:02 +01:00
const [ exportOpen , setExportOpen ] = useState ( false )
const [ advanceDialogOpen , setAdvanceDialogOpen ] = useState ( false )
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
const [ aiRecommendations , setAiRecommendations ] = useState < {
STARTUP : Array < { projectId : string ; rank : number ; score : number ; category : string ; strengths : string [ ] ; concerns : string [ ] ; recommendation : string } >
BUSINESS_CONCEPT : Array < { projectId : string ; rank : number ; score : number ; category : string ; strengths : string [ ] ; concerns : string [ ] ; recommendation : string } >
} | null > ( null )
const [ shortlistDialogOpen , setShortlistDialogOpen ] = useState ( false )
2026-02-16 12:46:01 +01:00
const [ createJuryOpen , setCreateJuryOpen ] = useState ( false )
const [ newJuryName , setNewJuryName ] = useState ( '' )
const [ addMemberOpen , setAddMemberOpen ] = useState ( false )
2026-02-16 16:43:23 +01:00
const [ closeAndAdvance , setCloseAndAdvance ] = useState ( false )
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
const utils = trpc . useUtils ( )
2026-02-16 09:20:02 +01:00
// ── Core data queries ──────────────────────────────────────────────────
2026-02-16 09:30:19 +01:00
const { data : round , isLoading } = trpc . round . getById . useQuery (
{ id : roundId } ,
2026-02-16 12:06:07 +01:00
{ refetchInterval : 15_000 , refetchOnWindowFocus : true } ,
2026-02-16 09:30:19 +01:00
)
const { data : projectStates } = trpc . roundEngine . getProjectStates . useQuery (
{ roundId } ,
2026-02-16 12:06:07 +01:00
{ refetchInterval : 10_000 , refetchOnWindowFocus : true } ,
2026-02-16 09:30:19 +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
2026-02-16 09:20:02 +01:00
const competitionId = round ? . competitionId ? ? ''
const { data : juryGroups } = trpc . juryGroup . list . useQuery (
{ competitionId } ,
2026-02-16 12:06:07 +01:00
{ enabled : ! ! competitionId , refetchInterval : 30_000 , refetchOnWindowFocus : true } ,
2026-02-16 09:30:19 +01:00
)
const { data : fileRequirements } = trpc . file . listRequirements . useQuery (
{ roundId } ,
2026-02-16 12:06:07 +01:00
{ refetchInterval : 15_000 , refetchOnWindowFocus : true } ,
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 awards linked to this round
2026-02-16 09:20:02 +01:00
const { data : competition } = trpc . competition . getById . useQuery (
{ id : competitionId } ,
2026-02-16 09:30:19 +01:00
{ enabled : ! ! competitionId , refetchInterval : 60_000 } ,
2026-02-16 09:20:02 +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
const programId = competition ? . programId
const { data : awards } = trpc . specialAward . list . useQuery (
{ programId : programId ! } ,
2026-02-16 09:30:19 +01:00
{ enabled : ! ! programId , refetchInterval : 60_000 } ,
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-16 09:20:02 +01:00
const roundAwards = awards ? . filter ( ( a ) = > a . evaluationRoundId === roundId ) ? ? [ ]
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-17 16:25:59 +01:00
// Filtering results stats (only for FILTERING rounds)
const { data : filteringStats } = trpc . filtering . getResultStats . useQuery (
{ roundId } ,
{ enabled : round?.roundType === 'FILTERING' , refetchInterval : 5_000 } ,
)
2026-02-17 18:53:51 +01:00
// Initialize config from server on load; re-sync after saves
2026-02-17 16:43:47 +01:00
const serverConfig = useMemo ( ( ) = > ( round ? . configJson as Record < string , unknown > ) ? ? { } , [ round ? . configJson ] )
const configInitialized = useRef ( false )
2026-02-17 18:53:51 +01:00
const savingRef = useRef ( false )
// Sync local config with server: on initial load AND whenever serverConfig
// changes after a save completes (so Zod-applied defaults get picked up)
useEffect ( ( ) = > {
if ( ! round ) return
if ( ! configInitialized . current ) {
configInitialized . current = true
setConfig ( serverConfig )
} else if ( ! savingRef . current ) {
// Server changed (e.g. after save invalidation) — re-sync
setConfig ( serverConfig )
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-17 18:53:51 +01:00
} , [ serverConfig , round ] )
2026-02-17 16:43:47 +01:00
const hasUnsavedConfig = useMemo (
( ) = > configInitialized . current && JSON . stringify ( config ) !== JSON . stringify ( serverConfig ) ,
[ config , serverConfig ] ,
)
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-16 09:20:02 +01:00
// ── Mutations ──────────────────────────────────────────────────────────
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
const updateMutation = trpc . round . update . useMutation ( {
onSuccess : ( ) = > {
2026-02-17 18:53:51 +01:00
savingRef . current = false
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
utils . round . getById . invalidate ( { id : roundId } )
2026-02-17 12:33:20 +01:00
setAutosaveStatus ( 'saved' )
setTimeout ( ( ) = > setAutosaveStatus ( 'idle' ) , 2000 )
} ,
onError : ( err ) = > {
2026-02-17 18:53:51 +01:00
savingRef . current = false
2026-02-17 12:33:20 +01:00
setAutosaveStatus ( 'error' )
toast . error ( err . message )
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
} ,
} )
const activateMutation = trpc . roundEngine . activate . useMutation ( {
onSuccess : ( ) = > {
utils . round . getById . invalidate ( { id : roundId } )
toast . success ( 'Round activated' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
const closeMutation = trpc . roundEngine . close . useMutation ( {
onSuccess : ( ) = > {
utils . round . getById . invalidate ( { id : roundId } )
toast . success ( 'Round closed' )
2026-02-16 16:43:23 +01:00
if ( closeAndAdvance ) {
setCloseAndAdvance ( false )
// Small delay to let cache invalidation complete before opening dialog
setTimeout ( ( ) = > setAdvanceDialogOpen ( true ) , 300 )
}
} ,
onError : ( err ) = > {
setCloseAndAdvance ( false )
toast . error ( err . message )
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-16 12:06:07 +01:00
const reopenMutation = trpc . roundEngine . reopen . useMutation ( {
onSuccess : ( data ) = > {
utils . round . getById . invalidate ( { id : roundId } )
utils . roundEngine . getProjectStates . invalidate ( { roundId } )
const msg = data . pausedRounds ? . length
? ` Round reopened. Paused: ${ data . pausedRounds . join ( ', ' ) } `
: 'Round reopened'
toast . success ( msg )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
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
const archiveMutation = trpc . roundEngine . archive . useMutation ( {
onSuccess : ( ) = > {
utils . round . getById . invalidate ( { id : roundId } )
toast . success ( 'Round archived' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
2026-02-16 09:20:02 +01:00
const assignJuryMutation = trpc . round . update . useMutation ( {
onSuccess : ( ) = > {
utils . round . getById . invalidate ( { id : roundId } )
2026-02-16 12:46:01 +01:00
utils . juryGroup . list . invalidate ( { competitionId } )
2026-02-16 09:20:02 +01:00
toast . success ( 'Jury group updated' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
2026-02-16 12:46:01 +01:00
// Jury group detail query (for the assigned group)
const juryGroupId = round ? . juryGroupId ? ? ''
const { data : juryGroupDetail } = trpc . juryGroup . getById . useQuery (
{ id : juryGroupId } ,
{ enabled : ! ! juryGroupId , refetchInterval : 10_000 } ,
)
const createJuryMutation = trpc . juryGroup . create . useMutation ( {
onSuccess : ( newGroup ) = > {
utils . juryGroup . list . invalidate ( { competitionId } )
// Auto-assign the new jury group to this round
assignJuryMutation . mutate ( { id : roundId , juryGroupId : newGroup.id } )
toast . success ( ` Jury " ${ newGroup . name } " created and assigned ` )
setCreateJuryOpen ( false )
setNewJuryName ( '' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
const deleteJuryMutation = trpc . juryGroup . delete . useMutation ( {
onSuccess : ( result ) = > {
utils . juryGroup . list . invalidate ( { competitionId } )
utils . round . getById . invalidate ( { id : roundId } )
toast . success ( ` Jury " ${ result . name } " deleted ` )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
const removeJuryMemberMutation = trpc . juryGroup . removeMember . useMutation ( {
onSuccess : ( ) = > {
if ( juryGroupId ) utils . juryGroup . getById . invalidate ( { id : juryGroupId } )
toast . success ( 'Member removed' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
2026-02-16 09:20:02 +01:00
const advanceMutation = trpc . round . advanceProjects . useMutation ( {
onSuccess : ( data ) = > {
utils . round . getById . invalidate ( { id : roundId } )
utils . roundEngine . getProjectStates . invalidate ( { roundId } )
2026-02-16 19:09:23 +01:00
const msg = data . autoPassedCount
? ` Passed ${ data . autoPassedCount } and advanced ${ data . advancedCount } project(s) to ${ data . targetRoundName } `
: ` Advanced ${ data . advancedCount } project(s) to ${ data . targetRoundName } `
toast . success ( msg )
2026-02-16 09:20:02 +01:00
setAdvanceDialogOpen ( false )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
const shortlistMutation = trpc . round . generateAIRecommendations . useMutation ( {
onSuccess : ( data ) = > {
if ( data . success ) {
setAiRecommendations ( data . recommendations )
toast . success (
` AI recommendations generated: ${ data . recommendations . STARTUP . length } startups, ${ data . recommendations . BUSINESS_CONCEPT . length } concepts ` +
( data . tokensUsed ? ` ( ${ data . tokensUsed } tokens) ` : '' ) ,
)
} else {
toast . error ( data . errors ? . join ( '; ' ) || 'AI shortlist failed' )
}
setShortlistDialogOpen ( false )
} ,
onError : ( err ) = > {
toast . error ( err . message )
setShortlistDialogOpen ( false )
} ,
} )
2026-02-16 12:06:07 +01:00
const isTransitioning = activateMutation . isPending || closeMutation . isPending || reopenMutation . isPending || archiveMutation . isPending
2026-02-16 09:20:02 +01:00
const handleConfigChange = useCallback ( ( newConfig : Record < string , unknown > ) = > {
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
setConfig ( newConfig )
2026-02-17 16:43:47 +01:00
} , [ ] )
const saveConfig = useCallback ( ( ) = > {
2026-02-17 18:53:51 +01:00
savingRef . current = true
2026-02-17 12:33:20 +01:00
setAutosaveStatus ( 'saving' )
2026-02-17 16:43:47 +01:00
updateMutation . mutate ( { id : roundId , configJson : config } )
} , [ config , roundId , updateMutation ] )
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
Fix filtering config save, auto-save, streamed results, improved AI prompt
- Add missing fields to FilteringConfigSchema (aiParseFiles, startupAdvanceCount,
conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) — Zod was silently
stripping them on save
- Restore auto-save with 800ms debounce on config changes
- Add staggered animations for filtering results (stream in one-by-one)
- Improve AI screening prompt: file type label mappings, soft cap handling,
missing documents = fail, better user prompt structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:18:04 +01:00
// ── Auto-save: debounce config changes and save automatically ────────
2026-02-17 18:53:51 +01:00
const configJson = JSON . stringify ( config )
const serverJson = JSON . stringify ( serverConfig )
Fix filtering config save, auto-save, streamed results, improved AI prompt
- Add missing fields to FilteringConfigSchema (aiParseFiles, startupAdvanceCount,
conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) — Zod was silently
stripping them on save
- Restore auto-save with 800ms debounce on config changes
- Add staggered animations for filtering results (stream in one-by-one)
- Improve AI screening prompt: file type label mappings, soft cap handling,
missing documents = fail, better user prompt structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:18:04 +01:00
useEffect ( ( ) = > {
if ( ! configInitialized . current ) return
2026-02-17 18:53:51 +01:00
if ( configJson === serverJson ) return
Fix filtering config save, auto-save, streamed results, improved AI prompt
- Add missing fields to FilteringConfigSchema (aiParseFiles, startupAdvanceCount,
conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) — Zod was silently
stripping them on save
- Restore auto-save with 800ms debounce on config changes
- Add staggered animations for filtering results (stream in one-by-one)
- Improve AI screening prompt: file type label mappings, soft cap handling,
missing documents = fail, better user prompt structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:18:04 +01:00
const timer = setTimeout ( ( ) = > {
2026-02-17 18:53:51 +01:00
savingRef . current = true
Fix filtering config save, auto-save, streamed results, improved AI prompt
- Add missing fields to FilteringConfigSchema (aiParseFiles, startupAdvanceCount,
conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) — Zod was silently
stripping them on save
- Restore auto-save with 800ms debounce on config changes
- Add staggered animations for filtering results (stream in one-by-one)
- Improve AI screening prompt: file type label mappings, soft cap handling,
missing documents = fail, better user prompt structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:18:04 +01:00
setAutosaveStatus ( 'saving' )
updateMutation . mutate ( { id : roundId , configJson : config } )
} , 800 )
return ( ) = > clearTimeout ( timer )
// eslint-disable-next-line react-hooks/exhaustive-deps
2026-02-17 18:53:51 +01:00
} , [ configJson ] )
Fix filtering config save, auto-save, streamed results, improved AI prompt
- Add missing fields to FilteringConfigSchema (aiParseFiles, startupAdvanceCount,
conceptAdvanceCount, notifyOnEntry, notifyOnAdvance) — Zod was silently
stripping them on save
- Restore auto-save with 800ms debounce on config changes
- Add staggered animations for filtering results (stream in one-by-one)
- Improve AI screening prompt: file type label mappings, soft cap handling,
missing documents = fail, better user prompt structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:18:04 +01:00
2026-02-16 09:20:02 +01:00
// ── Computed values ────────────────────────────────────────────────────
const projectCount = round ? . _count ? . projectRoundStates ? ? 0
const stateCounts = useMemo ( ( ) = >
projectStates ? . reduce ( ( acc : Record < string , number > , ps : any ) = > {
acc [ ps . state ] = ( acc [ ps . state ] || 0 ) + 1
return acc
} , { } as Record < string , number > ) ? ? { } ,
[ projectStates ] )
const passedCount = stateCounts [ 'PASSED' ] ? ? 0
const juryGroup = round ? . juryGroup
const juryMemberCount = juryGroup ? . members ? . 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-16 09:20:02 +01:00
const isFiltering = round ? . roundType === 'FILTERING'
const isEvaluation = round ? . roundType === 'EVALUATION'
2026-02-16 16:43:23 +01:00
const hasJury = [ 'EVALUATION' , 'LIVE_FINAL' , 'DELIBERATION' ] . includes ( round ? . roundType ? ? '' )
const hasAwards = hasJury
2026-02-16 19:09:23 +01:00
const isSimpleAdvance = [ 'INTAKE' , 'SUBMISSION' , 'MENTORING' ] . includes ( round ? . roundType ? ? '' )
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-16 09:20:02 +01:00
const poolLink = ` /admin/projects/pool?roundId= ${ roundId } &competitionId= ${ competitionId } ` as Route
// ── Loading state ──────────────────────────────────────────────────────
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
if ( isLoading ) {
return (
< div className = "space-y-6" >
2026-02-16 12:38:28 +01:00
{ /* Header skeleton — dark gradient placeholder */ }
< div className = "rounded-xl bg-gradient-to-r from-[#053d57]/20 to-[#0a5a7c]/20 p-6 animate-pulse" >
< div className = "flex items-center gap-3" >
< Skeleton className = "h-8 w-8 rounded bg-white/20" / >
< div className = "space-y-2" >
< Skeleton className = "h-7 w-64 bg-white/20" / >
< Skeleton className = "h-4 w-40 bg-white/20" / >
< / 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-16 09:20:02 +01:00
< div className = "grid gap-3 grid-cols-2 sm:grid-cols-4" >
2026-02-16 12:38:28 +01:00
{ [ 1 , 2 , 3 , 4 ] . map ( ( i ) = > < Skeleton key = { i } className = "h-28 rounded-lg" / > ) }
2026-02-16 09:20:02 +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
< Skeleton className = "h-10 w-full" / >
2026-02-16 12:38:28 +01:00
< Skeleton className = "h-96 w-full rounded-lg" / >
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 >
)
}
if ( ! round ) {
return (
< div className = "space-y-6" >
< div className = "flex items-center gap-3" >
2026-02-16 09:20:02 +01:00
< Link href = { '/admin/rounds' as Route } >
< Button variant = "ghost" size = "icon" className = "h-8 w-8" aria-label = "Back" >
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
< ArrowLeft className = "h-4 w-4" / >
< / Button >
< / Link >
< div >
< h1 className = "text-xl font-bold" > Round Not Found < / h1 >
2026-02-16 09:20:02 +01:00
< p className = "text-sm text-muted-foreground" > This round does not exist . < / p >
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 >
< / div >
)
}
2026-02-16 09:20:02 +01:00
const status = round . status as keyof typeof roundStatusConfig
const statusCfg = roundStatusConfig [ status ] || roundStatusConfig . ROUND_DRAFT
const typeCfg = roundTypeConfig [ round . roundType ] || roundTypeConfig . INTAKE
// ── Readiness checklist ────────────────────────────────────────────────
const readinessItems = [
{
label : 'Projects assigned' ,
ready : projectCount > 0 ,
detail : projectCount > 0 ? ` ${ projectCount } projects ` : 'No projects yet' ,
action : projectCount === 0 ? poolLink : undefined ,
actionLabel : 'Assign Projects' ,
} ,
2026-02-16 16:43:23 +01:00
. . . ( hasJury
2026-02-16 09:20:02 +01:00
? [ {
label : 'Jury group set' ,
ready : ! ! juryGroup ,
detail : juryGroup ? ` ${ juryGroup . name } ( ${ juryMemberCount } members) ` : 'No jury group assigned' ,
action : undefined as Route | undefined ,
actionLabel : undefined as string | undefined ,
} ]
: [ ] ) ,
{
label : 'Dates configured' ,
ready : ! ! round . windowOpenAt && ! ! round . windowCloseAt ,
detail :
round . windowOpenAt && round . windowCloseAt
? ` ${ new Date ( round . windowOpenAt ) . toLocaleDateString ( ) } \ u2014 ${ new Date ( round . windowCloseAt ) . toLocaleDateString ( ) } `
: 'No dates set \u2014 configure in Config tab' ,
action : undefined as Route | undefined ,
actionLabel : undefined as string | undefined ,
} ,
2026-02-17 14:13:25 +01:00
. . . ( ( isEvaluation && ! ( config . requireDocumentUpload as boolean ) )
? [ ]
: [ {
label : 'File requirements set' ,
ready : ( fileRequirements ? . length ? ? 0 ) > 0 ,
detail :
( fileRequirements ? . length ? ? 0 ) > 0
? ` ${ fileRequirements ? . length } requirement(s) `
: 'No file requirements \u2014 configure in Config tab' ,
action : undefined as Route | undefined ,
actionLabel : undefined as string | undefined ,
} ] ) ,
2026-02-16 09:20:02 +01:00
]
const readyCount = readinessItems . filter ( ( i ) = > i . ready ) . length
// ═════════════════════════════════════════════════════════════════════════
// Render
// ═════════════════════════════════════════════════════════════════════════
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
return (
< div className = "space-y-6" >
2026-02-16 12:38:28 +01:00
{ /* ===== HEADER — Dark Blue gradient banner ===== */ }
< motion.div
initial = { { opacity : 0 , y : - 8 } }
animate = { { opacity : 1 , y : 0 } }
transition = { { duration : 0.4 , ease : 'easeOut' } }
className = "rounded-xl bg-gradient-to-r from-[#053d57] to-[#0a5a7c] p-5 sm:p-6 text-white shadow-lg"
>
< div className = "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between" >
< div className = "flex items-start gap-3 min-w-0" >
2026-02-17 19:53:20 +01:00
< Link href = { ( round . specialAwardId ? ` /admin/awards/ ${ round . specialAwardId } ` : '/admin/rounds' ) as Route } className = "mt-0.5 shrink-0" >
< Button variant = "ghost" size = "icon" className = "h-8 w-8 text-white/80 hover:text-white hover:bg-white/10" aria-label = { round . specialAwardId ? 'Back to Award' : 'Back to rounds' } >
2026-02-16 12:38:28 +01:00
< ArrowLeft className = "h-4 w-4" / >
< / Button >
< / Link >
< div className = "min-w-0" >
< div className = "flex flex-wrap items-center gap-2.5" >
< h1 className = "text-xl font-bold tracking-tight truncate" > { round . name } < / h1 >
< Badge variant = "secondary" className = "text-xs shrink-0 bg-white/15 text-white border-white/20 hover:bg-white/20" >
{ typeCfg . label }
< / Badge >
{ /* Status dropdown */ }
< DropdownMenu >
< DropdownMenuTrigger asChild >
< button
className = { cn (
'inline-flex items-center gap-1.5 text-[11px] font-medium px-2.5 py-1 rounded-full transition-colors shrink-0' ,
'bg-white/15 text-white hover:bg-white/25' ,
) }
2026-02-16 09:20:02 +01:00
>
2026-02-16 12:38:28 +01:00
< span className = { cn ( 'h-1.5 w-1.5 rounded-full' , statusCfg . dotClass ) } / >
{ statusCfg . label }
< ChevronDown className = "h-3 w-3" / >
< / button >
< / DropdownMenuTrigger >
< DropdownMenuContent align = "start" >
{ status === 'ROUND_DRAFT' && (
2026-02-16 09:20:02 +01:00
< DropdownMenuItem
2026-02-16 12:38:28 +01:00
onClick = { ( ) = > activateMutation . mutate ( { roundId } ) }
2026-02-16 09:20:02 +01:00
disabled = { isTransitioning }
>
< Play className = "h-4 w-4 mr-2 text-emerald-600" / >
2026-02-16 12:38:28 +01:00
Activate Round
2026-02-16 09:20:02 +01:00
< / DropdownMenuItem >
2026-02-16 12:38:28 +01:00
) }
{ status === 'ROUND_ACTIVE' && (
2026-02-16 09:20:02 +01:00
< DropdownMenuItem
2026-02-16 12:38:28 +01:00
onClick = { ( ) = > closeMutation . mutate ( { roundId } ) }
2026-02-16 09:20:02 +01:00
disabled = { isTransitioning }
>
2026-02-16 12:38:28 +01:00
< Square className = "h-4 w-4 mr-2 text-blue-600" / >
Close Round
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
< / DropdownMenuItem >
2026-02-16 12:38:28 +01:00
) }
{ status === 'ROUND_CLOSED' && (
< >
< DropdownMenuItem
onClick = { ( ) = > reopenMutation . mutate ( { roundId } ) }
disabled = { isTransitioning }
>
< Play className = "h-4 w-4 mr-2 text-emerald-600" / >
Reopen Round
< / DropdownMenuItem >
< DropdownMenuSeparator / >
< DropdownMenuItem
onClick = { ( ) = > archiveMutation . mutate ( { roundId } ) }
disabled = { isTransitioning }
>
< Archive className = "h-4 w-4 mr-2" / >
Archive Round
< / DropdownMenuItem >
< / >
) }
{ isTransitioning && (
< div className = "flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground" >
< Loader2 className = "h-3 w-3 animate-spin" / >
Updating . . .
< / div >
) }
< / DropdownMenuContent >
< / DropdownMenu >
< / div >
< p className = "text-sm text-white/60 mt-1" > { typeCfg . description } < / p >
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-16 12:38:28 +01:00
{ /* Action buttons */ }
< div className = "flex items-center gap-2 shrink-0 flex-wrap" >
2026-02-17 12:33:20 +01:00
{ autosaveStatus === 'saved' && (
< span className = "flex items-center gap-1.5 text-xs text-emerald-300" >
< CheckCircle2 className = "h-3.5 w-3.5" / >
Saved
< / span >
) }
2026-02-16 12:38:28 +01:00
< Link href = { poolLink } >
2026-02-16 13:21:35 +01:00
< Button variant = "outline" size = "sm" className = "border-white/40 bg-white/15 text-white hover:bg-white/30 hover:text-white" >
2026-02-16 12:38:28 +01:00
< Layers className = "h-4 w-4 mr-1.5" / >
Project Pool
< / Button >
< / Link >
< / 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 >
2026-02-16 12:38:28 +01:00
< / motion.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
2026-02-16 12:38:28 +01:00
{ /* ===== STATS BAR — Accent-bordered cards ===== */ }
2026-02-16 18:33:08 +01:00
< div className = { cn ( "grid gap-3 grid-cols-2" , hasJury ? "sm:grid-cols-4" : "sm:grid-cols-3" ) } >
2026-02-16 09:20:02 +01:00
{ /* Projects */ }
2026-02-16 12:38:28 +01:00
< AnimatedCard index = { 0 } >
< Card className = "border-l-4 border-l-[#557f8c] hover:shadow-md transition-shadow" >
< CardContent className = "pt-4 pb-3" >
< div className = "flex items-center gap-2.5" >
< div className = "rounded-full bg-[#557f8c]/10 p-1.5" >
< Layers className = "h-4 w-4 text-[#557f8c]" / >
< / div >
< span className = "text-sm font-medium text-muted-foreground" > Projects < / span >
Rounds overhaul: full CRUD submission windows, scheduling UI, analytics, design refresh
- Fix special award FK crash: replace 4x raw auditLog.create with logAudit() helper
- Add updateSubmissionWindow + deleteSubmissionWindow mutations to round router
- Add per-round analytics (_count, juryGroup) to competition.getById
- Remove redundant acceptedCategories from intake config
- Rewrite submission window manager with full CRUD, all fields, date pickers
- Add round scheduling card (open/close dates) to round detail page
- Add project count, assignment count, jury group to round list cards
- Visual redesign: pipeline view, brand colors, progress bars, enhanced cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:07:09 +01:00
< / div >
2026-02-16 12:38:28 +01:00
< p className = "text-3xl font-bold mt-2" > { projectCount } < / p >
< div className = "flex flex-wrap gap-1.5 mt-1.5" >
{ Object . entries ( stateCounts ) . map ( ( [ state , count ] ) = > (
< span key = { state } className = "text-[10px] text-muted-foreground" >
{ String ( count ) } { state . toLowerCase ( ) . replace ( '_' , ' ' ) }
< / span >
) ) }
< / div >
< / CardContent >
< / Card >
< / AnimatedCard >
Rounds overhaul: full CRUD submission windows, scheduling UI, analytics, design refresh
- Fix special award FK crash: replace 4x raw auditLog.create with logAudit() helper
- Add updateSubmissionWindow + deleteSubmissionWindow mutations to round router
- Add per-round analytics (_count, juryGroup) to competition.getById
- Remove redundant acceptedCategories from intake config
- Rewrite submission window manager with full CRUD, all fields, date pickers
- Add round scheduling card (open/close dates) to round detail page
- Add project count, assignment count, jury group to round list cards
- Visual redesign: pipeline view, brand colors, progress bars, enhanced cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:07:09 +01:00
2026-02-16 18:33:08 +01:00
{ /* Jury (with inline group selector) — only for jury-relevant rounds */ }
{ hasJury && (
< AnimatedCard index = { 1 } >
< Card className = "border-l-4 border-l-purple-500 hover:shadow-md transition-shadow" >
< CardContent className = "pt-4 pb-3" >
< div className = "flex items-center gap-2.5 mb-1" data - jury - select >
< div className = "rounded-full bg-purple-50 p-1.5" >
< Users className = "h-4 w-4 text-purple-500" / >
< / div >
< span className = "text-sm font-medium text-muted-foreground" > Jury < / span >
2026-02-16 12:38:28 +01:00
< / div >
2026-02-16 18:33:08 +01:00
{ juryGroups && juryGroups . length > 0 ? (
< Select
value = { round . juryGroupId ? ? '__none__' }
onValueChange = { ( value ) = > {
assignJuryMutation . mutate ( {
id : roundId ,
juryGroupId : value === '__none__' ? null : value ,
} )
} }
disabled = { assignJuryMutation . isPending }
>
< SelectTrigger className = "h-8 text-xs mt-1" >
< SelectValue placeholder = "Select jury group..." / >
< / SelectTrigger >
< SelectContent >
< SelectItem value = "__none__" > No jury assigned < / SelectItem >
{ juryGroups . map ( ( jg : any ) = > (
< SelectItem key = { jg . id } value = { jg . id } >
{ jg . name } ( { jg . _count ? . members ? ? 0 } members )
< / SelectItem >
) ) }
< / SelectContent >
< / Select >
) : juryGroup ? (
< >
< p className = "text-3xl font-bold mt-2" > { juryMemberCount } < / p >
< p className = "text-xs text-muted-foreground truncate" > { juryGroup . name } < / p >
< / >
) : (
< >
< p className = "text-3xl font-bold mt-2 text-muted-foreground" > & mdash ; < / p >
< p className = "text-xs text-muted-foreground" > No jury groups yet < / p >
< / >
) }
< / CardContent >
< / Card >
< / AnimatedCard >
) }
Rounds overhaul: full CRUD submission windows, scheduling UI, analytics, design refresh
- Fix special award FK crash: replace 4x raw auditLog.create with logAudit() helper
- Add updateSubmissionWindow + deleteSubmissionWindow mutations to round router
- Add per-round analytics (_count, juryGroup) to competition.getById
- Remove redundant acceptedCategories from intake config
- Rewrite submission window manager with full CRUD, all fields, date pickers
- Add round scheduling card (open/close dates) to round detail page
- Add project count, assignment count, jury group to round list cards
- Visual redesign: pipeline view, brand colors, progress bars, enhanced cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:07:09 +01:00
2026-02-16 09:20:02 +01:00
{ /* Window */ }
2026-02-16 18:33:08 +01:00
< AnimatedCard index = { hasJury ? 2 : 1 } >
2026-02-16 12:38:28 +01:00
< Card className = "border-l-4 border-l-emerald-500 hover:shadow-md transition-shadow" >
< CardContent className = "pt-4 pb-3" >
< div className = "flex items-center gap-2.5" >
< div className = "rounded-full bg-emerald-50 p-1.5" >
< CalendarDays className = "h-4 w-4 text-emerald-500" / >
< / div >
< span className = "text-sm font-medium text-muted-foreground" > Window < / span >
< / div >
{ round . windowOpenAt || round . windowCloseAt ? (
< >
< p className = "text-sm font-bold mt-2" >
{ round . windowOpenAt
? new Date ( round . windowOpenAt ) . toLocaleDateString ( )
: 'No start' }
< / p >
< p className = "text-xs text-muted-foreground" >
{ round . windowCloseAt
? ` Closes ${ new Date ( round . windowCloseAt ) . toLocaleDateString ( ) } `
: 'No deadline' }
< / p >
< / >
) : (
< >
< p className = "text-3xl font-bold mt-2 text-muted-foreground" > & mdash ; < / p >
< p className = "text-xs text-muted-foreground" > No dates set < / p >
< / >
) }
< / CardContent >
< / Card >
< / AnimatedCard >
Rounds overhaul: full CRUD submission windows, scheduling UI, analytics, design refresh
- Fix special award FK crash: replace 4x raw auditLog.create with logAudit() helper
- Add updateSubmissionWindow + deleteSubmissionWindow mutations to round router
- Add per-round analytics (_count, juryGroup) to competition.getById
- Remove redundant acceptedCategories from intake config
- Rewrite submission window manager with full CRUD, all fields, date pickers
- Add round scheduling card (open/close dates) to round detail page
- Add project count, assignment count, jury group to round list cards
- Visual redesign: pipeline view, brand colors, progress bars, enhanced cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:07:09 +01:00
2026-02-16 09:20:02 +01:00
{ /* Advancement */ }
2026-02-16 18:33:08 +01:00
< AnimatedCard index = { hasJury ? 3 : 2 } >
2026-02-16 12:38:28 +01:00
< Card className = "border-l-4 border-l-amber-500 hover:shadow-md transition-shadow" >
< CardContent className = "pt-4 pb-3" >
< div className = "flex items-center gap-2.5" >
< div className = "rounded-full bg-amber-50 p-1.5" >
< BarChart3 className = "h-4 w-4 text-amber-500" / >
< / div >
< span className = "text-sm font-medium text-muted-foreground" > Advancement < / span >
< / div >
{ round . advancementRules && round . advancementRules . length > 0 ? (
< >
< p className = "text-3xl font-bold mt-2" > { round . advancementRules . length } < / p >
< p className = "text-xs text-muted-foreground" >
{ round . advancementRules . map ( ( r : any ) = > r . ruleType . replace ( '_' , ' ' ) . toLowerCase ( ) ) . join ( ', ' ) }
< / p >
< / >
) : (
< >
< p className = "text-3xl font-bold mt-2 text-muted-foreground" > & mdash ; < / p >
< p className = "text-xs text-muted-foreground" > Admin selection < / p >
< / >
) }
< / CardContent >
< / Card >
< / AnimatedCard >
Rounds overhaul: full CRUD submission windows, scheduling UI, analytics, design refresh
- Fix special award FK crash: replace 4x raw auditLog.create with logAudit() helper
- Add updateSubmissionWindow + deleteSubmissionWindow mutations to round router
- Add per-round analytics (_count, juryGroup) to competition.getById
- Remove redundant acceptedCategories from intake config
- Rewrite submission window manager with full CRUD, all fields, date pickers
- Add round scheduling card (open/close dates) to round detail page
- Add project count, assignment count, jury group to round list cards
- Visual redesign: pipeline view, brand colors, progress bars, enhanced cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:07:09 +01:00
< / div >
2026-02-16 12:38:28 +01:00
{ /* ===== TABS — Underline style ===== */ }
2026-02-16 09:20:02 +01:00
< Tabs value = { activeTab } onValueChange = { setActiveTab } className = "space-y-4" >
2026-02-16 12:38:28 +01:00
< div className = "border-b overflow-x-auto" >
< TabsList className = "bg-transparent h-auto p-0 gap-0 w-full sm:w-auto" >
{ [
{ value : 'overview' , label : 'Overview' , icon : Zap } ,
{ value : 'projects' , label : 'Projects' , icon : Layers } ,
. . . ( isFiltering ? [ { value : 'filtering' , label : 'Filtering' , icon : Shield } ] : [ ] ) ,
. . . ( isEvaluation ? [ { value : 'assignments' , label : 'Assignments' , icon : ClipboardList } ] : [ ] ) ,
2026-02-16 16:43:23 +01:00
. . . ( hasJury ? [ { value : 'jury' , label : 'Jury' , icon : Users } ] : [ ] ) ,
2026-02-16 12:38:28 +01:00
{ value : 'config' , label : 'Config' , icon : Settings } ,
2026-02-16 16:43:23 +01:00
. . . ( hasAwards ? [ { value : 'awards' , label : 'Awards' , icon : Trophy } ] : [ ] ) ,
2026-02-16 12:38:28 +01:00
] . map ( ( tab ) = > (
< TabsTrigger
key = { tab . value }
value = { tab . value }
className = { cn (
'relative rounded-none border-b-2 border-transparent px-4 py-2.5 text-sm font-medium transition-all' ,
'data-[state=active]:border-b-[#de0f1e] data-[state=active]:text-[#053d57] data-[state=active]:font-semibold data-[state=active]:shadow-none' ,
'text-muted-foreground hover:text-foreground' ,
'bg-transparent data-[state=active]:bg-transparent' ,
) }
>
< tab.icon className = { cn ( 'h-3.5 w-3.5 mr-1.5' , activeTab === tab . value ? 'text-[#557f8c]' : '' ) } / >
{ tab . label }
{ tab . value === 'awards' && roundAwards . length > 0 && (
< Badge variant = "secondary" className = "ml-1.5 h-5 min-w-5 text-[10px] px-1.5 bg-[#de0f1e] text-white" >
{ roundAwards . length }
< / Badge >
) }
< / TabsTrigger >
) ) }
< / TabsList >
< / 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
2026-02-16 09:20:02 +01:00
{ /* ═══════════ OVERVIEW TAB ═══════════ */ }
< TabsContent value = "overview" className = "space-y-6" >
2026-02-16 12:38:28 +01:00
{ /* Readiness Checklist with Progress Ring */ }
< AnimatedCard index = { 0 } >
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-4" >
{ /* SVG Progress Ring */ }
< div className = "relative h-14 w-14 shrink-0" >
< svg className = "h-14 w-14 -rotate-90" viewBox = "0 0 56 56" >
< circle cx = "28" cy = "28" r = "24" fill = "none" stroke = "currentColor" strokeWidth = "3" className = "text-muted/30" / >
< circle
cx = "28" cy = "28" r = "24" fill = "none"
strokeWidth = "3" strokeLinecap = "round"
stroke = { readyCount === readinessItems . length ? '#10b981' : '#de0f1e' }
strokeDasharray = { ` ${ ( readyCount / readinessItems . length ) * 150.8 } 150.8 ` }
className = "transition-all duration-700"
/ >
< / svg >
< span className = "absolute inset-0 flex items-center justify-center text-xs font-bold" >
{ readyCount } / { readinessItems . length }
< / span >
2026-02-16 09:20:02 +01:00
< / div >
2026-02-16 12:38:28 +01:00
< div >
< CardTitle className = "text-base" > Launch Readiness < / CardTitle >
< CardDescription >
{ readyCount === readinessItems . length
? 'All checks passed — ready to go'
: ` ${ readinessItems . length - readyCount } item(s) remaining ` }
< / CardDescription >
< / div >
< / div >
< Badge
variant = { readyCount === readinessItems . length ? 'default' : 'secondary' }
className = { cn (
'text-xs' ,
readyCount === readinessItems . length
? 'bg-emerald-100 text-emerald-700'
: 'bg-amber-100 text-amber-700' ,
2026-02-16 09:20:02 +01:00
) }
2026-02-16 12:38:28 +01:00
>
{ readyCount === readinessItems . length ? 'Ready' : 'Incomplete' }
< / Badge >
< / div >
< / CardHeader >
< CardContent >
< div className = "space-y-3" >
{ readinessItems . map ( ( item ) = > (
< div key = { item . label } className = "flex items-start gap-3" >
{ item . ready ? (
< CheckCircle2 className = "h-4 w-4 text-emerald-500 mt-0.5 shrink-0" / >
) : (
< AlertTriangle className = "h-4 w-4 text-amber-500 mt-0.5 shrink-0" / >
) }
< div className = "flex-1 min-w-0" >
< p className = { cn ( 'text-sm font-medium' , item . ready && 'text-muted-foreground line-through opacity-60' ) } >
{ item . label }
< / p >
< p className = "text-xs text-muted-foreground" > { item . detail } < / p >
< / div >
{ item . action && (
< Link href = { item . action } >
< Button size = "sm" className = "shrink-0 text-xs bg-[#de0f1e] hover:bg-[#c00d1a] text-white" >
{ item . actionLabel }
< / Button >
< / Link >
) }
< / div >
) ) }
< / div >
< / CardContent >
< / Card >
< / AnimatedCard >
2026-02-17 16:25:59 +01:00
{ /* Filtering Results Summary — only for FILTERING rounds with results */ }
{ isFiltering && filteringStats && filteringStats . total > 0 && (
< AnimatedCard index = { 1 } >
< Card className = "border-l-4 border-l-purple-500" >
< CardHeader className = "pb-3" >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-3" >
< div className = "rounded-full bg-purple-50 p-2" >
< Shield className = "h-5 w-5 text-purple-600" / >
< / div >
< div >
< CardTitle className = "text-base" > Filtering Results < / CardTitle >
< CardDescription >
{ filteringStats . total } projects evaluated
< / CardDescription >
< / div >
< / div >
< Button
size = "sm"
variant = "outline"
onClick = { ( ) = > setActiveTab ( 'filtering' ) }
>
View Details
< ArrowRight className = "h-3.5 w-3.5 ml-1.5" / >
< / Button >
< / div >
< / CardHeader >
< CardContent >
< div className = "grid grid-cols-3 gap-4 mb-4" >
< div className = "text-center p-3 rounded-lg bg-emerald-50" >
< p className = "text-2xl font-bold text-emerald-700" > { filteringStats . passed } < / p >
< p className = "text-xs text-emerald-600 font-medium" > Passed < / p >
< / div >
< div className = "text-center p-3 rounded-lg bg-red-50" >
< p className = "text-2xl font-bold text-red-700" > { filteringStats . filteredOut } < / p >
< p className = "text-xs text-red-600 font-medium" > Filtered Out < / p >
< / div >
< div className = "text-center p-3 rounded-lg bg-amber-50" >
< p className = "text-2xl font-bold text-amber-700" > { filteringStats . flagged } < / p >
< p className = "text-xs text-amber-600 font-medium" > Flagged < / p >
< / div >
< / div >
{ /* Progress bar showing pass rate */ }
< div className = "space-y-1.5" >
< div className = "flex justify-between text-xs text-muted-foreground" >
< span > Pass rate < / span >
< span > { Math . round ( ( filteringStats . passed / filteringStats . total ) * 100 ) } % < / span >
< / div >
< div className = "h-2.5 rounded-full bg-muted overflow-hidden flex" >
< div
className = "bg-emerald-500 transition-all duration-500"
style = { { width : ` ${ ( filteringStats . passed / filteringStats . total ) * 100 } % ` } }
/ >
< div
className = "bg-red-400 transition-all duration-500"
style = { { width : ` ${ ( filteringStats . filteredOut / filteringStats . total ) * 100 } % ` } }
/ >
< div
className = "bg-amber-400 transition-all duration-500"
style = { { width : ` ${ ( filteringStats . flagged / filteringStats . total ) * 100 } % ` } }
/ >
< / div >
{ filteringStats . overridden > 0 && (
< p className = "text-xs text-muted-foreground" >
{ filteringStats . overridden } result ( s ) manually overridden
< / p >
) }
< / div >
< / CardContent >
< / Card >
< / AnimatedCard >
) }
2026-02-16 12:38:28 +01:00
{ /* Quick Actions — Grouped & styled */ }
2026-02-17 16:25:59 +01:00
< AnimatedCard index = { 2 } >
2026-02-16 12:38:28 +01:00
< Card >
< CardHeader >
< CardTitle className = "text-base" > Quick Actions < / CardTitle >
< CardDescription > Common operations for this round < / CardDescription >
< / CardHeader >
< CardContent className = "space-y-4" >
{ /* Round Control Group */ }
{ ( status === 'ROUND_DRAFT' || status === 'ROUND_ACTIVE' || status === 'ROUND_CLOSED' ) && (
< div >
< p className = "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-2" > Round Control < / p >
< div className = "grid gap-3 sm:grid-cols-2 lg:grid-cols-3" >
{ status === 'ROUND_DRAFT' && (
< AlertDialog >
< AlertDialogTrigger asChild >
< button className = "flex items-start gap-3 p-4 rounded-lg border border-l-4 border-l-emerald-500 hover:-translate-y-0.5 hover:shadow-md transition-all text-left" >
< Play className = "h-5 w-5 text-emerald-600 mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" > Activate Round < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Start this round and allow project processing
< / p >
< / div >
< / button >
< / AlertDialogTrigger >
< AlertDialogContent >
< AlertDialogHeader >
< AlertDialogTitle > Activate this round ? < / AlertDialogTitle >
< AlertDialogDescription >
The round will go live . Projects can be processed and jury members will be able to see their assignments .
< / AlertDialogDescription >
< / AlertDialogHeader >
< AlertDialogFooter >
< AlertDialogCancel > Cancel < / AlertDialogCancel >
< AlertDialogAction onClick = { ( ) = > activateMutation . mutate ( { roundId } ) } >
Activate
< / AlertDialogAction >
< / AlertDialogFooter >
< / AlertDialogContent >
< / AlertDialog >
) }
{ status === 'ROUND_ACTIVE' && (
< AlertDialog >
< AlertDialogTrigger asChild >
< button className = "flex items-start gap-3 p-4 rounded-lg border border-l-4 border-l-blue-500 hover:-translate-y-0.5 hover:shadow-md transition-all text-left" >
< Square className = "h-5 w-5 text-blue-600 mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" > Close Round < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Stop accepting changes and finalize results
< / p >
< / div >
< / button >
< / AlertDialogTrigger >
< AlertDialogContent >
< AlertDialogHeader >
< AlertDialogTitle > Close this round ? < / AlertDialogTitle >
< AlertDialogDescription >
No further changes will be accepted . You can reactivate later if needed .
{ projectCount > 0 && (
< span className = "block mt-2" >
{ projectCount } projects are currently in this round .
< / span >
) }
< / AlertDialogDescription >
< / AlertDialogHeader >
< AlertDialogFooter >
< AlertDialogCancel > Cancel < / AlertDialogCancel >
< AlertDialogAction onClick = { ( ) = > closeMutation . mutate ( { roundId } ) } >
Close Round
< / AlertDialogAction >
< / AlertDialogFooter >
< / AlertDialogContent >
< / AlertDialog >
) }
{ status === 'ROUND_CLOSED' && (
< AlertDialog >
< AlertDialogTrigger asChild >
< button className = "flex items-start gap-3 p-4 rounded-lg border border-l-4 border-l-amber-500 bg-amber-50/30 hover:-translate-y-0.5 hover:shadow-md transition-all text-left" >
< RotateCcw className = "h-5 w-5 text-amber-600 mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" > Reopen Round < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Reactivate this round . Any subsequent active rounds will be paused .
< / p >
< / div >
< / button >
< / AlertDialogTrigger >
< AlertDialogContent >
< AlertDialogHeader >
< AlertDialogTitle > Reopen this round ? < / AlertDialogTitle >
< AlertDialogDescription >
The round will become active again . Any rounds after this one that are currently active will be paused ( closed ) automatically .
< / AlertDialogDescription >
< / AlertDialogHeader >
< AlertDialogFooter >
< AlertDialogCancel > Cancel < / AlertDialogCancel >
< AlertDialogAction onClick = { ( ) = > reopenMutation . mutate ( { roundId } ) } >
Reopen
< / AlertDialogAction >
< / AlertDialogFooter >
< / AlertDialogContent >
< / AlertDialog >
) }
< / div >
2026-02-16 09:20:02 +01:00
< / div >
2026-02-16 12:38:28 +01:00
) }
2026-02-16 09:20:02 +01:00
2026-02-16 12:38:28 +01:00
{ /* Project Management Group */ }
< div >
< p className = "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-2" > Project Management < / p >
< div className = "grid gap-3 sm:grid-cols-2 lg:grid-cols-3" >
< Link href = { poolLink } >
< button className = "flex items-start gap-3 p-4 rounded-lg border hover:-translate-y-0.5 hover:shadow-md transition-all text-left w-full" >
< Layers className = "h-5 w-5 text-[#557f8c] mt-0.5 shrink-0" / >
2026-02-16 09:20:02 +01:00
< div >
2026-02-16 12:38:28 +01:00
< p className = "text-sm font-medium" > Assign Projects < / p >
2026-02-16 09:20:02 +01:00
< p className = "text-xs text-muted-foreground mt-0.5" >
2026-02-16 12:38:28 +01:00
Add projects from the pool to this round
2026-02-16 09:20:02 +01:00
< / p >
< / div >
< / button >
2026-02-16 12:38:28 +01:00
< / Link >
2026-02-16 09:20:02 +01:00
2026-02-16 12:38:28 +01:00
< button
onClick = { ( ) = > setActiveTab ( 'projects' ) }
className = "flex items-start gap-3 p-4 rounded-lg border hover:-translate-y-0.5 hover:shadow-md transition-all text-left"
>
< BarChart3 className = "h-5 w-5 text-[#557f8c] mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" > Manage Projects < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
View , filter , and transition project states
< / p >
< / div >
< / button >
2026-02-16 16:43:23 +01:00
{ /* Advance projects (always visible when projects exist) */ }
{ projectCount > 0 && (
2026-02-16 12:38:28 +01:00
< button
2026-02-16 19:09:23 +01:00
onClick = { ( ) = > ( isSimpleAdvance || passedCount > 0 )
2026-02-16 16:43:23 +01:00
? setAdvanceDialogOpen ( true )
: toast . info ( 'Mark projects as "Passed" first in the Projects tab' ) }
className = { cn (
'flex items-start gap-3 p-4 rounded-lg border hover:-translate-y-0.5 hover:shadow-md transition-all text-left' ,
2026-02-16 19:09:23 +01:00
( isSimpleAdvance || passedCount > 0 )
2026-02-16 16:43:23 +01:00
? 'border-l-4 border-l-emerald-500 bg-emerald-50/30'
: 'border-dashed opacity-60' ,
) }
2026-02-16 12:38:28 +01:00
>
2026-02-16 19:09:23 +01:00
< ArrowRight className = { cn ( 'h-5 w-5 mt-0.5 shrink-0' , ( isSimpleAdvance || passedCount > 0 ) ? 'text-emerald-600' : 'text-muted-foreground' ) } / >
2026-02-16 09:20:02 +01:00
< div >
2026-02-16 12:38:28 +01:00
< p className = "text-sm font-medium" > Advance Projects < / p >
2026-02-16 09:20:02 +01:00
< p className = "text-xs text-muted-foreground mt-0.5" >
2026-02-16 19:09:23 +01:00
{ isSimpleAdvance
? ` Advance all ${ projectCount } project(s) to the next round `
: passedCount > 0
? ` Move ${ passedCount } passed project(s) to the next round `
: 'Mark projects as "Passed" first, then advance' }
2026-02-16 09:20:02 +01:00
< / p >
< / div >
2026-02-16 19:09:23 +01:00
< Badge className = "ml-auto shrink-0 bg-emerald-100 text-emerald-700 text-[10px]" > { isSimpleAdvance ? projectCount : passedCount } < / Badge >
2026-02-16 09:20:02 +01:00
< / button >
2026-02-16 12:38:28 +01:00
) }
2026-02-16 09:20:02 +01:00
2026-02-16 16:43:23 +01:00
{ /* Close & Advance (active rounds with passed projects) */ }
{ status === 'ROUND_ACTIVE' && passedCount > 0 && (
< button
onClick = { ( ) = > {
setCloseAndAdvance ( true )
closeMutation . mutate ( { roundId } )
} }
disabled = { isTransitioning }
className = "flex items-start gap-3 p-4 rounded-lg border border-l-4 border-l-purple-500 bg-purple-50/30 hover:-translate-y-0.5 hover:shadow-md transition-all text-left"
>
< Square className = "h-5 w-5 text-purple-600 mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" > Close & Advance < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Close this round and advance { passedCount } passed project ( s ) to the next round
< / p >
< / div >
< / button >
) }
{ /* Jury assignment for rounds that use jury */ }
{ hasJury && ! juryGroup && (
2026-02-16 12:38:28 +01:00
< button
onClick = { ( ) = > {
const el = document . querySelector ( '[data-jury-select]' )
if ( el ) el . scrollIntoView ( { behavior : 'smooth' , block : 'center' } )
} }
className = "flex items-start gap-3 p-4 rounded-lg border border-l-4 border-l-amber-500 bg-amber-50/30 hover:-translate-y-0.5 hover:shadow-md transition-all text-left"
>
< UserPlus className = "h-5 w-5 text-amber-600 mt-0.5 shrink-0" / >
2026-02-16 12:06:07 +01:00
< div >
2026-02-16 12:38:28 +01:00
< p className = "text-sm font-medium" > Assign Jury Group < / p >
2026-02-16 12:06:07 +01:00
< p className = "text-xs text-muted-foreground mt-0.5" >
2026-02-16 12:38:28 +01:00
No jury group assigned . Select one in the Jury card above .
2026-02-16 12:06:07 +01:00
< / p >
< / div >
< / button >
2026-02-16 12:38:28 +01:00
) }
2026-02-16 09:20:02 +01:00
2026-02-16 12:38:28 +01:00
{ /* Evaluation: manage assignments */ }
{ isEvaluation && (
< button
onClick = { ( ) = > setActiveTab ( 'assignments' ) }
className = "flex items-start gap-3 p-4 rounded-lg border hover:-translate-y-0.5 hover:shadow-md transition-all text-left"
>
< ClipboardList className = "h-5 w-5 text-[#557f8c] mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" > Manage Assignments < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Generate and review jury - project assignments
< / p >
< / div >
< / button >
) }
< / div >
< / div >
2026-02-16 09:20:02 +01:00
2026-02-16 12:38:28 +01:00
{ /* AI Tools Group */ }
{ ( ( isFiltering || isEvaluation ) && projectCount > 0 ) && (
2026-02-16 09:20:02 +01:00
< div >
2026-02-16 12:38:28 +01:00
< p className = "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-2" > AI Tools < / p >
< div className = "grid gap-3 sm:grid-cols-2 lg:grid-cols-3" >
{ isFiltering && (
< button
onClick = { ( ) = > setActiveTab ( 'filtering' ) }
className = "flex items-start gap-3 p-4 rounded-lg border bg-gradient-to-br from-purple-50/50 to-blue-50/50 hover:-translate-y-0.5 hover:shadow-md transition-all text-left"
>
< Shield className = "h-5 w-5 text-purple-600 mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" > Run AI Filtering < / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Screen projects with AI and manual review
< / p >
< / div >
< / button >
) }
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
2026-02-16 12:38:28 +01:00
< button
onClick = { ( ) = > setShortlistDialogOpen ( true ) }
className = "flex items-start gap-3 p-4 rounded-lg border bg-gradient-to-br from-purple-50/50 to-blue-50/50 hover:-translate-y-0.5 hover:shadow-md transition-all text-left"
disabled = { shortlistMutation . isPending }
>
< Zap className = "h-5 w-5 text-purple-600 mt-0.5 shrink-0" / >
< div >
< p className = "text-sm font-medium" >
{ shortlistMutation . isPending ? 'Generating...' : 'AI Recommendations' }
< / p >
< p className = "text-xs text-muted-foreground mt-0.5" >
Generate ranked shortlist per category using AI analysis
< / p >
< / div >
< / button >
2026-02-16 09:20:02 +01:00
< / div >
2026-02-16 12:38:28 +01:00
< / div >
2026-02-16 09:20:02 +01:00
) }
2026-02-16 12:38:28 +01:00
< / CardContent >
< / Card >
< / AnimatedCard >
2026-02-16 09:20:02 +01:00
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
{ /* Advance Projects Dialog */ }
< AdvanceProjectsDialog
open = { advanceDialogOpen }
onOpenChange = { setAdvanceDialogOpen }
roundId = { roundId }
2026-02-16 19:09:23 +01:00
roundType = { round ? . roundType }
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
projectStates = { projectStates }
config = { config }
advanceMutation = { advanceMutation }
2026-02-16 16:43:23 +01:00
competitionRounds = { competition ? . rounds ? . map ( ( r : any ) = > ( {
id : r.id ,
name : r.name ,
sortOrder : r.sortOrder ,
roundType : r.roundType ,
} ) ) }
currentSortOrder = { round ? . sortOrder }
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
/ >
{ /* AI Shortlist Confirmation Dialog */ }
< AlertDialog open = { shortlistDialogOpen } onOpenChange = { setShortlistDialogOpen } >
2026-02-16 09:20:02 +01:00
< AlertDialogContent >
< AlertDialogHeader >
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
< AlertDialogTitle > Generate AI Recommendations ? < / AlertDialogTitle >
2026-02-16 09:20:02 +01:00
< AlertDialogDescription >
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
The AI will analyze all project evaluations and generate a ranked shortlist
for each category independently .
{ config . startupAdvanceCount ? (
< span className = "block mt-1" >
Startup target : top { String ( config . startupAdvanceCount ) }
< / span >
) : null }
{ config . conceptAdvanceCount ? (
< span className = "block" >
Business Concept target : top { String ( config . conceptAdvanceCount ) }
< / span >
) : null }
{ config . aiParseFiles ? (
< span className = "block mt-1 text-amber-600" >
Document parsing is enabled — the AI will read uploaded file contents .
< / span >
) : null }
2026-02-16 09:20:02 +01:00
< / AlertDialogDescription >
< / AlertDialogHeader >
< AlertDialogFooter >
< AlertDialogCancel > Cancel < / AlertDialogCancel >
< AlertDialogAction
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
onClick = { ( ) = > shortlistMutation . mutate ( { roundId } ) }
disabled = { shortlistMutation . isPending }
2026-02-16 09:20:02 +01:00
>
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
{ shortlistMutation . isPending && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
Generate
2026-02-16 09:20:02 +01:00
< / AlertDialogAction >
< / AlertDialogFooter >
< / AlertDialogContent >
< / AlertDialog >
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
{ /* AI Recommendations Display */ }
{ aiRecommendations && (
< AIRecommendationsDisplay
recommendations = { aiRecommendations }
onClear = { ( ) = > setAiRecommendations ( null ) }
/ >
) }
2026-02-16 09:20:02 +01:00
{ /* Round Info + Project Breakdown */ }
< div className = "grid gap-4 sm:grid-cols-2" >
2026-02-16 12:38:28 +01:00
< AnimatedCard index = { 2 } >
< Card >
< CardHeader >
< CardTitle className = "text-sm" > Round Details < / CardTitle >
< / CardHeader >
< CardContent className = "space-y-0 text-sm" >
{ [
{ label : 'Type' , value : < Badge variant = "secondary" className = { cn ( 'text-xs' , typeCfg . color ) } > { typeCfg . label } < / Badge > } ,
{ label : 'Status' , value : < span className = "font-medium" > { statusCfg . label } < / span > } ,
{ label : 'Sort Order' , value : < span className = "font-medium font-mono" > { round . sortOrder } < / span > } ,
. . . ( round . purposeKey ? [ { label : 'Purpose' , value : < span className = "font-medium" > { round . purposeKey } < / span > } ] : [ ] ) ,
{ label : 'Jury Group' , value : < span className = "font-medium" > { juryGroup ? juryGroup . name : '\u2014' } < / span > } ,
{ label : 'Opens' , value : < span className = "font-medium" > { round . windowOpenAt ? new Date ( round . windowOpenAt ) . toLocaleString ( ) : '\u2014' } < / span > } ,
{ label : 'Closes' , value : < span className = "font-medium" > { round . windowCloseAt ? new Date ( round . windowCloseAt ) . toLocaleString ( ) : '\u2014' } < / span > } ,
] . map ( ( row , i ) = > (
< div key = { row . label } className = { cn ( 'flex justify-between items-center py-2.5' , i > 0 && 'border-t border-dotted border-muted' ) } >
< span className = "text-muted-foreground" > { row . label } < / span >
{ row . value }
< / div >
) ) }
< / CardContent >
< / Card >
< / AnimatedCard >
< AnimatedCard index = { 3 } >
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< CardTitle className = "text-sm" > Project Breakdown < / CardTitle >
{ projectCount > 0 && (
< span className = "text-xs font-mono text-muted-foreground" > { projectCount } total < / span >
) }
2026-02-16 09:20:02 +01:00
< / div >
2026-02-16 12:38:28 +01:00
< / CardHeader >
< CardContent >
{ projectCount === 0 ? (
< p className = "text-sm text-muted-foreground py-4 text-center" >
No projects assigned yet
< / p >
) : (
< div className = "space-y-3" >
{ [ 'PENDING' , 'IN_PROGRESS' , 'PASSED' , 'REJECTED' , 'COMPLETED' , 'WITHDRAWN' ] . map ( ( state ) = > {
const count = stateCounts [ state ] || 0
if ( count === 0 ) return null
const pct = ( ( count / projectCount ) * 100 ) . toFixed ( 0 )
return (
< div key = { state } >
< div className = "flex justify-between text-xs mb-1.5" >
< span className = "text-muted-foreground capitalize font-medium" > { state . toLowerCase ( ) . replace ( '_' , ' ' ) } < / span >
< span className = "font-bold tabular-nums" > { count } < span className = "font-normal text-muted-foreground" > ( { pct } % ) < / span > < / span >
< / div >
< div className = "h-2 bg-muted rounded-full overflow-hidden" >
< div
className = { cn ( 'h-full rounded-full transition-all duration-500' , stateColors [ state ] ) }
style = { { width : ` ${ pct } % ` } }
/ >
< / div >
2026-02-16 09:20:02 +01:00
< / div >
2026-02-16 12:38:28 +01:00
)
} ) }
< / div >
) }
< / CardContent >
< / Card >
< / AnimatedCard >
2026-02-16 09:20:02 +01:00
< / div >
< / TabsContent >
{ /* ═══════════ PROJECTS TAB ═══════════ */ }
< TabsContent value = "projects" className = "space-y-4" >
< ProjectStatesTable competitionId = { competitionId } roundId = { roundId } / >
< / TabsContent >
{ /* ═══════════ FILTERING TAB ═══════════ */ }
{ isFiltering && (
< TabsContent value = "filtering" className = "space-y-4" >
< FilteringDashboard competitionId = { competitionId } roundId = { roundId } / >
< / TabsContent >
) }
2026-02-16 12:46:01 +01:00
{ /* ═══════════ JURY TAB ═══════════ */ }
2026-02-16 16:43:23 +01:00
{ hasJury && (
2026-02-16 12:46:01 +01:00
< TabsContent value = "jury" className = "space-y-6" >
{ /* Jury Group Selector + Create */ }
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< div >
< CardTitle className = "text-base" > Jury Group < / CardTitle >
< CardDescription >
Select or create a jury group for this round
< / CardDescription >
< / div >
< div className = "flex items-center gap-2" >
< Button size = "sm" variant = "outline" onClick = { ( ) = > setCreateJuryOpen ( true ) } >
< Plus className = "h-4 w-4 mr-1.5" / >
New Jury
< / Button >
< / div >
< / div >
< / CardHeader >
< CardContent >
{ juryGroups && juryGroups . length > 0 ? (
< div className = "space-y-4" >
< Select
value = { round . juryGroupId ? ? '__none__' }
onValueChange = { ( value ) = > {
assignJuryMutation . mutate ( {
id : roundId ,
juryGroupId : value === '__none__' ? null : value ,
} )
} }
disabled = { assignJuryMutation . isPending }
>
< SelectTrigger className = "w-full sm:w-80" >
< SelectValue placeholder = "Select jury group..." / >
< / SelectTrigger >
< SelectContent >
< SelectItem value = "__none__" > No jury assigned < / SelectItem >
{ juryGroups . map ( ( jg : any ) = > (
< SelectItem key = { jg . id } value = { jg . id } >
{ jg . name } ( { jg . _count ? . members ? ? 0 } members )
< / SelectItem >
) ) }
< / SelectContent >
< / Select >
{ /* Delete button for currently selected jury group */ }
{ round . juryGroupId && (
< AlertDialog >
< AlertDialogTrigger asChild >
< Button size = "sm" variant = "ghost" className = "text-destructive hover:text-destructive" >
< Trash2 className = "h-4 w-4 mr-1.5" / >
Delete & quot ; { juryGroup ? . name } & quot ;
< / Button >
< / AlertDialogTrigger >
< AlertDialogContent >
< AlertDialogHeader >
< AlertDialogTitle > Delete jury group ? < / AlertDialogTitle >
< AlertDialogDescription >
This will permanently delete & quot ; { juryGroup ? . name } & quot ; and remove all its members .
Rounds using this jury group will be unlinked . This action cannot be undone .
< / AlertDialogDescription >
< / AlertDialogHeader >
< AlertDialogFooter >
< AlertDialogCancel > Cancel < / AlertDialogCancel >
< AlertDialogAction
onClick = { ( ) = > deleteJuryMutation . mutate ( { id : round.juryGroupId ! } ) }
className = "bg-destructive text-destructive-foreground hover:bg-destructive/90"
disabled = { deleteJuryMutation . isPending }
>
{ deleteJuryMutation . isPending && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
Delete Jury
< / AlertDialogAction >
< / AlertDialogFooter >
< / AlertDialogContent >
< / AlertDialog >
) }
< / div >
) : (
< div className = "flex flex-col items-center justify-center py-10 text-center" >
< div className = "rounded-full bg-purple-50 p-4 mb-4" >
< Users className = "h-8 w-8 text-purple-400" / >
< / div >
< p className = "text-sm font-medium" > No Jury Groups < / p >
< p className = "text-xs text-muted-foreground mt-1 max-w-sm" >
Create a jury group to assign members who will evaluate projects in this round .
< / p >
< Button size = "sm" className = "mt-4" onClick = { ( ) = > setCreateJuryOpen ( true ) } >
< Plus className = "h-4 w-4 mr-1.5" / >
Create First Jury
< / Button >
< / div >
) }
< / CardContent >
< / Card >
{ /* Members list (only if a jury group is assigned) */ }
{ juryGroupDetail && (
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< div >
< CardTitle className = "text-base" >
Members & mdash ; { juryGroupDetail . name }
< / CardTitle >
< CardDescription >
{ juryGroupDetail . members . length } member { juryGroupDetail . members . length !== 1 ? 's' : '' }
< / CardDescription >
< / div >
< Button size = "sm" onClick = { ( ) = > setAddMemberOpen ( true ) } >
< UserPlus className = "h-4 w-4 mr-1.5" / >
Add Member
< / Button >
< / div >
< / CardHeader >
< CardContent >
{ juryGroupDetail . members . length === 0 ? (
< div className = "flex flex-col items-center justify-center py-10 text-center" >
< div className = "rounded-full bg-muted p-4 mb-4" >
< UserPlus className = "h-8 w-8 text-muted-foreground" / >
< / div >
< p className = "text-sm font-medium" > No Members Yet < / p >
< p className = "text-xs text-muted-foreground mt-1" >
Add jury members to start assigning projects for evaluation .
< / p >
< Button size = "sm" variant = "outline" className = "mt-4" onClick = { ( ) = > setAddMemberOpen ( true ) } >
< UserPlus className = "h-4 w-4 mr-1.5" / >
Add First Member
< / Button >
< / div >
) : (
< div className = "space-y-1" >
{ juryGroupDetail . members . map ( ( member : any , idx : number ) = > (
< div
key = { member . id }
className = { cn (
'flex items-center justify-between py-2.5 px-3 rounded-md transition-colors' ,
idx % 2 === 1 && 'bg-muted/30' ,
) }
>
< div className = "min-w-0 flex-1" >
< p className = "text-sm font-medium truncate" >
{ member . user . name || 'Unnamed User' }
< / p >
< p className = "text-xs text-muted-foreground truncate" > { member . user . email } < / p >
< / div >
< AlertDialog >
< AlertDialogTrigger asChild >
< Button
variant = "ghost"
size = "icon"
className = "h-7 w-7 text-destructive hover:text-destructive shrink-0"
>
< Trash2 className = "h-3.5 w-3.5" / >
< / Button >
< / AlertDialogTrigger >
< AlertDialogContent >
< AlertDialogHeader >
< AlertDialogTitle > Remove member ? < / AlertDialogTitle >
< AlertDialogDescription >
Remove { member . user . name || member . user . email } from { juryGroupDetail . name } ?
< / AlertDialogDescription >
< / AlertDialogHeader >
< AlertDialogFooter >
< AlertDialogCancel > Cancel < / AlertDialogCancel >
< AlertDialogAction
onClick = { ( ) = > removeJuryMemberMutation . mutate ( { id : member.id } ) }
className = "bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Remove
< / AlertDialogAction >
< / AlertDialogFooter >
< / AlertDialogContent >
< / AlertDialog >
< / div >
) ) }
< / div >
) }
< / CardContent >
< / Card >
) }
{ /* Create Jury Dialog */ }
< Dialog open = { createJuryOpen } onOpenChange = { setCreateJuryOpen } >
< DialogContent >
< DialogHeader >
< DialogTitle > Create Jury Group < / DialogTitle >
< DialogDescription >
Create a new jury group for this competition . It will be automatically assigned to this round .
< / DialogDescription >
< / DialogHeader >
< div className = "space-y-4" >
< div className = "space-y-2" >
< label className = "text-sm font-medium" > Name < / label >
< Input
placeholder = "e.g. Round 1 Jury, Expert Panel, Final Jury"
value = { newJuryName }
onChange = { ( e ) = > setNewJuryName ( e . target . value ) }
onKeyDown = { ( e ) = > {
if ( e . key === 'Enter' && newJuryName . trim ( ) ) {
createJuryMutation . mutate ( {
competitionId ,
name : newJuryName.trim ( ) ,
slug : newJuryName.trim ( ) . toLowerCase ( ) . replace ( /[^a-z0-9]+/g , '-' ) . replace ( /(^-|-$)/g , '' ) ,
} )
}
} }
/ >
< / div >
< / div >
< DialogFooter >
< Button variant = "outline" onClick = { ( ) = > setCreateJuryOpen ( false ) } > Cancel < / Button >
< Button
onClick = { ( ) = > {
createJuryMutation . mutate ( {
competitionId ,
name : newJuryName.trim ( ) ,
slug : newJuryName.trim ( ) . toLowerCase ( ) . replace ( /[^a-z0-9]+/g , '-' ) . replace ( /(^-|-$)/g , '' ) ,
} )
} }
disabled = { createJuryMutation . isPending || ! newJuryName . trim ( ) }
>
{ createJuryMutation . isPending && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
Create
< / Button >
< / DialogFooter >
< / DialogContent >
< / Dialog >
{ /* Add Member Dialog */ }
{ juryGroupId && (
< AddMemberDialog
juryGroupId = { juryGroupId }
open = { addMemberOpen }
onOpenChange = { ( open ) = > {
setAddMemberOpen ( open )
if ( ! open ) utils . juryGroup . getById . invalidate ( { id : juryGroupId } )
} }
/ >
) }
< / TabsContent >
2026-02-16 16:43:23 +01:00
) }
2026-02-16 12:46:01 +01:00
2026-02-16 09:20:02 +01:00
{ /* ═══════════ ASSIGNMENTS TAB (Evaluation rounds) ═══════════ */ }
{ isEvaluation && (
< TabsContent value = "assignments" className = "space-y-6" >
{ /* Coverage Report */ }
2026-02-17 12:33:20 +01:00
< CoverageReport roundId = { roundId } requiredReviews = { ( config . requiredReviewsPerProject as number ) || 3 } / >
2026-02-16 09:20:02 +01:00
{ /* Generate Assignments */ }
2026-02-17 14:45:57 +01:00
< Card className = { cn ( aiAssignmentMutation . isPending && 'border-violet-300 shadow-violet-100 shadow-sm' ) } >
2026-02-16 09:20:02 +01:00
< CardHeader >
< div className = "flex items-center justify-between" >
< div >
2026-02-17 14:45:57 +01:00
< CardTitle className = "text-base flex items-center gap-2" >
Assignment Generation
{ aiAssignmentMutation . isPending && (
< Badge variant = "outline" className = "gap-1.5 text-violet-600 border-violet-300 animate-pulse" >
< Loader2 className = "h-3 w-3 animate-spin" / >
AI generating . . .
< / Badge >
) }
{ aiAssignmentMutation . data && ! aiAssignmentMutation . isPending && (
< Badge variant = "outline" className = "gap-1 text-emerald-600 border-emerald-300" >
< CheckCircle2 className = "h-3 w-3" / >
{ aiAssignmentMutation . data . stats . assignmentsGenerated } ready
< / Badge >
) }
< / CardTitle >
2026-02-16 09:20:02 +01:00
< CardDescription >
AI - suggested jury - to - project assignments based on expertise and workload
< / CardDescription >
< / div >
2026-02-17 14:45:57 +01:00
< div className = "flex items-center gap-2" >
{ aiAssignmentMutation . data && ! aiAssignmentMutation . isPending && (
< Button
size = "sm"
variant = "outline"
onClick = { ( ) = > setPreviewSheetOpen ( true ) }
>
Review Assignments
< / Button >
) }
< Button
size = "sm"
onClick = { ( ) = > {
aiAssignmentMutation . mutate ( {
roundId ,
requiredReviews : ( config . requiredReviewsPerProject as number ) || 3 ,
} )
} }
disabled = { projectCount === 0 || ! juryGroup || aiAssignmentMutation . isPending }
>
{ aiAssignmentMutation . isPending ? (
< >
< Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / >
Generating . . .
< / >
) : (
< >
< Zap className = "h-4 w-4 mr-1.5" / >
{ aiAssignmentMutation . data ? 'Regenerate' : 'Generate with AI' }
< / >
) }
< / Button >
< / div >
2026-02-16 09:20:02 +01:00
< / div >
< / CardHeader >
< CardContent >
{ ! juryGroup && (
< div className = "flex items-center gap-2 p-3 rounded-lg bg-amber-50 border border-amber-200 text-sm text-amber-800" >
< AlertTriangle className = "h-4 w-4 shrink-0" / >
Assign a jury group first before generating assignments .
< / div >
) }
{ projectCount === 0 && (
< div className = "flex items-center gap-2 p-3 rounded-lg bg-amber-50 border border-amber-200 text-sm text-amber-800" >
< AlertTriangle className = "h-4 w-4 shrink-0" / >
Add projects to this round first .
< / div >
) }
2026-02-17 14:45:57 +01:00
{ juryGroup && projectCount > 0 && ! aiAssignmentMutation . isPending && ! aiAssignmentMutation . data && (
2026-02-16 09:20:02 +01:00
< p className = "text-sm text-muted-foreground" >
2026-02-17 14:45:57 +01:00
Click & quot ; Generate with AI & quot ; to create assignments using GPT analysis of juror expertise , project descriptions , and documents . Or open the preview to use the algorithm instead .
2026-02-16 09:20:02 +01:00
< / p >
) }
2026-02-17 14:45:57 +01:00
{ aiAssignmentMutation . isPending && (
< div className = "flex items-center gap-3 p-3 rounded-lg bg-violet-50 border border-violet-200 dark:bg-violet-950/20 dark:border-violet-800" >
< div className = "relative" >
< div className = "h-8 w-8 rounded-full border-2 border-violet-300 border-t-violet-600 animate-spin" / >
< / div >
< div >
< p className = "text-sm font-medium text-violet-800 dark:text-violet-200" > AI is analyzing projects and jurors . . . < / p >
< p className = "text-xs text-violet-600 dark:text-violet-400" >
Matching expertise , reviewing bios , and balancing workloads
< / p >
< / div >
< / div >
) }
{ aiAssignmentMutation . data && ! aiAssignmentMutation . isPending && (
< div className = "flex items-center gap-3 p-3 rounded-lg bg-emerald-50 border border-emerald-200 dark:bg-emerald-950/20 dark:border-emerald-800" >
< CheckCircle2 className = "h-5 w-5 text-emerald-600 shrink-0" / >
< div className = "flex-1" >
< p className = "text-sm font-medium text-emerald-800 dark:text-emerald-200" >
{ aiAssignmentMutation . data . stats . assignmentsGenerated } assignments generated
< / p >
< p className = "text-xs text-emerald-600 dark:text-emerald-400" >
{ aiAssignmentMutation . data . stats . totalJurors } jurors , { aiAssignmentMutation . data . stats . totalProjects } projects
{ aiAssignmentMutation . data . fallbackUsed && ' (algorithm fallback)' }
< / p >
< / div >
< Button size = "sm" variant = "outline" onClick = { ( ) = > setPreviewSheetOpen ( true ) } >
Review & amp ; Execute
< / Button >
< / div >
) }
2026-02-16 09:20:02 +01:00
< / CardContent >
< / Card >
{ /* Jury Progress + Score Distribution */ }
< div className = "grid gap-4 lg:grid-cols-2" >
< JuryProgressTable roundId = { roundId } / >
< ScoreDistribution roundId = { roundId } / >
< / div >
{ /* Actions: Send Reminders + Export */ }
< div className = "flex flex-wrap items-center gap-3" >
< SendRemindersButton roundId = { roundId } / >
< Button variant = "outline" size = "sm" onClick = { ( ) = > setExportOpen ( true ) } >
< Download className = "h-4 w-4 mr-1.5" / >
Export Evaluations
< / Button >
< / div >
{ /* Individual Assignments Table */ }
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
< IndividualAssignmentsTable roundId = { roundId } projectStates = { projectStates } / >
2026-02-16 09:20:02 +01:00
2026-02-18 14:34:27 +01:00
{ /* Conflict of Interest Declarations */ }
< COIReviewSection roundId = { roundId } / >
2026-02-16 09:20:02 +01:00
{ /* Unassigned Queue */ }
2026-02-17 12:33:20 +01:00
< RoundUnassignedQueue roundId = { roundId } requiredReviews = { ( config . requiredReviewsPerProject as number ) || 3 } / >
2026-02-16 09:20:02 +01:00
{ /* Assignment Preview Sheet */ }
< AssignmentPreviewSheet
roundId = { roundId }
open = { previewSheetOpen }
onOpenChange = { setPreviewSheetOpen }
2026-02-17 12:33:20 +01:00
requiredReviews = { ( config . requiredReviewsPerProject as number ) || 3 }
2026-02-17 14:45:57 +01:00
aiResult = { aiAssignmentMutation . data ? ? null }
isAIGenerating = { aiAssignmentMutation . isPending }
onGenerateAI = { ( ) = > aiAssignmentMutation . mutate ( {
roundId ,
requiredReviews : ( config . requiredReviewsPerProject as number ) || 3 ,
} ) }
onResetAI = { ( ) = > aiAssignmentMutation . reset ( ) }
2026-02-16 09:20:02 +01:00
/ >
{ /* CSV Export Dialog */ }
< ExportEvaluationsDialog roundId = { roundId } open = { exportOpen } onOpenChange = { setExportOpen } / >
< / TabsContent >
) }
{ /* ═══════════ CONFIG TAB ═══════════ */ }
< TabsContent value = "config" className = "space-y-6" >
2026-02-16 16:43:23 +01:00
{ /* Round Dates */ }
< Card >
< CardHeader className = "border-b" >
< CardTitle className = "text-base" > Round Dates < / CardTitle >
< CardDescription >
When this round starts and ends . Defines the active period for document uploads and evaluations .
< / CardDescription >
< / CardHeader >
< CardContent className = "pt-4" >
< div className = "grid gap-4 sm:grid-cols-2" >
< div className = "space-y-2" >
< Label > Start Date < / Label >
< DateTimePicker
value = { round . windowOpenAt ? new Date ( round . windowOpenAt ) : null }
onChange = { ( date ) = > updateMutation . mutate ( { id : roundId , windowOpenAt : date } ) }
placeholder = "Select start date & time"
clearable
/ >
< / div >
< div className = "space-y-2" >
< Label > End Date < / Label >
< DateTimePicker
value = { round . windowCloseAt ? new Date ( round . windowCloseAt ) : null }
onChange = { ( date ) = > updateMutation . mutate ( { id : roundId , windowCloseAt : date } ) }
placeholder = "Select end date & time"
clearable
/ >
< / div >
< / div >
< / CardContent >
< / Card >
2026-02-16 09:20:02 +01:00
{ /* General Round Settings */ }
< Card >
2026-02-16 12:38:28 +01:00
< CardHeader className = "border-b" >
2026-02-16 09:20:02 +01:00
< CardTitle className = "text-base" > General Settings < / CardTitle >
< CardDescription > Settings that apply to this round regardless of type < / CardDescription >
< / CardHeader >
2026-02-16 12:38:28 +01:00
< CardContent className = "space-y-0 pt-0" >
< div className = "flex items-center justify-between p-4 rounded-md" >
2026-02-16 09:20:02 +01:00
< div className = "space-y-0.5" >
< Label htmlFor = "notify-on-entry" className = "text-sm font-medium" >
Notify on round entry
< / Label >
< p className = "text-xs text-muted-foreground" >
Send an automated email to project applicants when their project enters this round
< / p >
< / div >
< Switch
id = "notify-on-entry"
checked = { ! ! config . notifyOnEntry }
onCheckedChange = { ( checked ) = > {
handleConfigChange ( { . . . config , notifyOnEntry : checked } )
} }
/ >
< / div >
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
2026-02-16 12:38:28 +01:00
< div className = "flex items-center justify-between p-4 rounded-md bg-muted/30" >
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
< div className = "space-y-0.5" >
< Label htmlFor = "notify-on-advance" className = "text-sm font-medium" >
Notify on advance
< / Label >
< p className = "text-xs text-muted-foreground" >
Send an email to project applicants when their project advances from this round to the next
< / p >
< / div >
< Switch
id = "notify-on-advance"
checked = { ! ! config . notifyOnAdvance }
onCheckedChange = { ( checked ) = > {
handleConfigChange ( { . . . config , notifyOnAdvance : checked } )
} }
/ >
< / div >
2026-02-16 12:38:28 +01:00
< div className = "border-t mt-2 pt-4 px-4 pb-2 bg-[#053d57]/[0.03] rounded-b-lg -mx-6 -mb-6 p-6" >
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
< Label className = "text-sm font-medium" > Advancement Targets < / Label >
< p className = "text-xs text-muted-foreground mb-3" >
Target number of projects per category to advance from this round
< / p >
< div className = "grid grid-cols-2 gap-4" >
< div className = "space-y-1.5" >
< Label htmlFor = "startup-advance-count" className = "text-xs text-muted-foreground" >
Startup Projects
< / Label >
< Input
id = "startup-advance-count"
type = "number"
min = { 0 }
className = "h-9"
placeholder = "No limit"
value = { ( config . startupAdvanceCount as number ) ? ? '' }
onChange = { ( e ) = > {
const val = e . target . value ? parseInt ( e . target . value , 10 ) : undefined
handleConfigChange ( { . . . config , startupAdvanceCount : val } )
} }
/ >
< / div >
< div className = "space-y-1.5" >
< Label htmlFor = "concept-advance-count" className = "text-xs text-muted-foreground" >
Concept Projects
< / Label >
< Input
id = "concept-advance-count"
type = "number"
min = { 0 }
className = "h-9"
placeholder = "No limit"
value = { ( config . conceptAdvanceCount as number ) ? ? '' }
onChange = { ( e ) = > {
const val = e . target . value ? parseInt ( e . target . value , 10 ) : undefined
handleConfigChange ( { . . . config , conceptAdvanceCount : val } )
} }
/ >
< / div >
< / div >
< / div >
2026-02-16 09:20:02 +01:00
< / CardContent >
< / Card >
{ /* Round-type-specific config */ }
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
< RoundConfigForm
roundType = { round . roundType }
config = { config }
onChange = { handleConfigChange }
2026-02-16 09:20:02 +01:00
juryGroups = { juryGroups ? . map ( ( jg : any ) = > ( { id : jg.id , name : jg.name } ) ) }
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-16 09:20:02 +01:00
{ /* Evaluation Criteria Editor (EVALUATION rounds only) */ }
{ isEvaluation && < EvaluationCriteriaEditor roundId = { roundId } / > }
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-17 14:13:25 +01:00
{ /* Document Requirements — hidden for EVALUATION rounds unless requireDocumentUpload is on */ }
{ ( ! isEvaluation || ! ! ( config . requireDocumentUpload as boolean ) ) && (
< Card >
< CardHeader >
< CardTitle className = "text-base" > Document Requirements < / CardTitle >
< CardDescription >
Files applicants must submit for this round
{ round . windowCloseAt && (
< > & mdash ; due by { new Date ( round . windowCloseAt ) . toLocaleDateString ( ) } < / >
) }
< / CardDescription >
< / CardHeader >
< CardContent >
< FileRequirementsEditor
roundId = { roundId }
windowOpenAt = { round . windowOpenAt }
windowCloseAt = { round . windowCloseAt }
/ >
< / CardContent >
< / Card >
) }
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
< / TabsContent >
2026-02-16 09:20:02 +01:00
{ /* ═══════════ AWARDS TAB ═══════════ */ }
2026-02-16 16:43:23 +01:00
{ hasAwards && (
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
< TabsContent value = "awards" className = "space-y-4" >
< Card >
< CardContent className = "p-6" >
{ roundAwards . length === 0 ? (
2026-02-16 12:38:28 +01:00
< div className = "text-center py-12 text-muted-foreground" >
< div className = "rounded-full bg-[#de0f1e]/10 p-4 w-fit mx-auto mb-4" >
< Trophy className = "h-8 w-8 text-[#de0f1e]/60" / >
< / div >
< p className = "text-sm font-medium text-foreground" > No Awards Linked < / p >
< p className = "text-xs mt-1 max-w-sm mx-auto" >
2026-02-16 09:20:02 +01:00
Create an award and set this round as its evaluation round to see it here
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 >
< / div >
) : (
< div className = "space-y-3" >
{ roundAwards . map ( ( award ) = > {
const eligibleCount = award . _count ? . eligibilities || 0
return (
< Link
key = { award . id }
href = { ` /admin/awards/ ${ award . id } ` as Route }
className = "block"
>
2026-02-16 12:38:28 +01:00
< div className = "flex items-start justify-between gap-4 rounded-lg border border-l-4 border-l-[#de0f1e] p-4 transition-all hover:shadow-md hover:-translate-y-0.5" >
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 = "flex-1 min-w-0" >
< div className = "flex items-center gap-2 mb-1" >
< h3 className = "font-medium truncate" > { award . name } < / h3 >
< Badge
variant = {
award . eligibilityMode === 'SEPARATE_POOL'
? 'default'
: 'secondary'
}
className = "shrink-0"
>
{ award . eligibilityMode === 'SEPARATE_POOL'
? 'Separate Pool'
: 'Stay in Main' }
< / Badge >
< / div >
{ award . description && (
< p className = "text-sm text-muted-foreground line-clamp-1" >
{ award . description }
< / p >
) }
< / div >
< div className = "flex items-center gap-4 text-sm text-muted-foreground shrink-0" >
< div className = "text-right" >
2026-02-16 09:20:02 +01:00
< div className = "font-medium text-foreground" > { eligibleCount } < / 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 className = "text-xs" > eligible < / div >
< / div >
< / div >
< / div >
< / Link >
)
} ) }
< / div >
) }
< / CardContent >
< / Card >
< / TabsContent >
2026-02-16 16:43:23 +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
< / Tabs >
2026-02-17 16:43:47 +01:00
{ /* Floating save bar — appears when config has unsaved changes */ }
{ hasUnsavedConfig && (
< div className = "fixed bottom-0 left-0 right-0 z-50 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 shadow-[0_-4px_12px_rgba(0,0,0,0.1)]" >
< div className = "container flex items-center justify-between py-3 px-4 max-w-5xl mx-auto" >
< div className = "flex items-center gap-2 text-sm" >
< div className = "h-2 w-2 rounded-full bg-amber-500 animate-pulse" / >
< span className = "text-muted-foreground" > You have unsaved changes < / span >
< / div >
< div className = "flex items-center gap-2" >
{ autosaveStatus === 'error' && (
< span className = "text-xs text-red-500 mr-2" > Save failed — try again < / span >
) }
< Button
size = "sm"
variant = "outline"
onClick = { ( ) = > {
setConfig ( serverConfig )
} }
>
Discard
< / Button >
< Button
size = "sm"
onClick = { saveConfig }
disabled = { updateMutation . isPending }
className = "bg-[#de0f1e] hover:bg-[#c00d1a] text-white"
>
{ updateMutation . isPending ? (
< > < Loader2 className = "h-3.5 w-3.5 mr-1.5 animate-spin" / > Saving . . . < / >
) : (
< > < Save className = "h-3.5 w-3.5 mr-1.5" / > Save Changes < / >
) }
< / Button >
< / div >
< / div >
< / div >
) }
2026-02-16 09:20:02 +01:00
< / div >
)
}
// ═══════════════════════════════════════════════════════════════════════════
// Sub-components
// ═══════════════════════════════════════════════════════════════════════════
// ── Unassigned projects queue ────────────────────────────────────────────
2026-02-17 12:33:20 +01:00
function RoundUnassignedQueue ( { roundId , requiredReviews = 3 } : { roundId : string ; requiredReviews? : number } ) {
2026-02-16 09:20:02 +01:00
const { data : unassigned , isLoading } = trpc . roundAssignment . unassignedQueue . useQuery (
2026-02-17 12:33:20 +01:00
{ roundId , requiredReviews } ,
2026-02-16 09:30:19 +01:00
{ refetchInterval : 15_000 } ,
2026-02-16 09:20:02 +01:00
)
return (
< Card >
< CardHeader >
< CardTitle className = "text-base" > Unassigned Projects < / CardTitle >
2026-02-17 12:33:20 +01:00
< CardDescription > Projects with fewer than { requiredReviews } jury assignments < / CardDescription >
2026-02-16 09:20:02 +01:00
< / CardHeader >
< CardContent >
{ isLoading ? (
< div className = "space-y-2" >
{ [ 1 , 2 , 3 ] . map ( ( i ) = > < Skeleton key = { i } className = "h-14 w-full" / > ) }
< / div >
) : unassigned && unassigned . length > 0 ? (
< div className = "space-y-2 max-h-[400px] overflow-y-auto" >
{ unassigned . map ( ( project : any ) = > (
< div
key = { project . id }
2026-02-16 12:38:28 +01:00
className = { cn (
'flex justify-between items-center p-3 border rounded-md hover:bg-muted/30 transition-colors' ,
( project . assignmentCount || 0 ) === 0 && 'border-l-4 border-l-red-500' ,
) }
2026-02-16 09:20:02 +01:00
>
< div className = "min-w-0" >
< p className = "text-sm font-medium truncate" > { project . title } < / p >
< p className = "text-xs text-muted-foreground" >
{ project . competitionCategory || 'No category' }
{ project . teamName && ` \ u00b7 ${ project . teamName } ` }
< / p >
< / div >
< Badge variant = "outline" className = { cn (
'text-xs shrink-0 ml-3' ,
( project . assignmentCount || 0 ) === 0
? 'bg-red-50 text-red-700 border-red-200'
: 'bg-amber-50 text-amber-700 border-amber-200' ,
) } >
{ project . assignmentCount || 0 } / 3
< / Badge >
< / div >
) ) }
< / div >
) : (
< p className = "text-sm text-muted-foreground text-center py-6" >
All projects have sufficient assignments
< / p >
) }
< / CardContent >
< / Card >
)
}
// ── Jury Progress Table ──────────────────────────────────────────────────
function JuryProgressTable ( { roundId } : { roundId : string } ) {
2026-02-16 09:30:19 +01:00
const { data : workload , isLoading } = trpc . analytics . getJurorWorkload . useQuery (
{ roundId } ,
{ refetchInterval : 15_000 } ,
)
2026-02-16 09:20:02 +01:00
return (
< Card >
< CardHeader >
< CardTitle className = "text-base" > Jury Progress < / CardTitle >
< CardDescription > Evaluation completion per juror < / CardDescription >
< / CardHeader >
< CardContent >
{ isLoading ? (
< div className = "space-y-3" >
{ [ 1 , 2 , 3 ] . map ( ( i ) = > < Skeleton key = { i } className = "h-10 w-full" / > ) }
< / div >
) : ! workload || workload . length === 0 ? (
< p className = "text-sm text-muted-foreground text-center py-6" >
No assignments yet
< / p >
) : (
< div className = "space-y-3 max-h-[350px] overflow-y-auto" >
{ workload . map ( ( juror ) = > {
const pct = juror . completionRate
2026-02-16 12:38:28 +01:00
const barGradient = pct === 100
? 'bg-gradient-to-r from-emerald-400 to-emerald-600'
2026-02-16 09:20:02 +01:00
: pct >= 50
2026-02-16 12:38:28 +01:00
? 'bg-gradient-to-r from-blue-400 to-blue-600'
2026-02-16 09:20:02 +01:00
: pct > 0
2026-02-16 12:38:28 +01:00
? 'bg-gradient-to-r from-amber-400 to-amber-600'
2026-02-16 09:20:02 +01:00
: 'bg-gray-300'
return (
2026-02-16 12:38:28 +01:00
< div key = { juror . id } className = "space-y-1 hover:bg-muted/20 rounded px-1 py-0.5 -mx-1 transition-colors" >
2026-02-16 09:20:02 +01:00
< div className = "flex justify-between text-xs" >
< span className = "font-medium truncate max-w-[60%]" > { juror . name } < / span >
2026-02-16 12:38:28 +01:00
< span className = "text-muted-foreground shrink-0 tabular-nums" >
2026-02-16 09:20:02 +01:00
{ juror . completed } / { juror . assigned } ( { pct } % )
< / span >
< / div >
2026-02-16 12:38:28 +01:00
< div className = "h-2 bg-muted rounded-full overflow-hidden" >
2026-02-16 09:20:02 +01:00
< div
2026-02-16 12:38:28 +01:00
className = { cn ( 'h-full rounded-full transition-all duration-500' , barGradient ) }
2026-02-16 09:20:02 +01:00
style = { { width : ` ${ pct } % ` } }
/ >
< / div >
< / div >
)
} ) }
< / div >
) }
< / CardContent >
< / Card >
)
}
// ── Score Distribution ───────────────────────────────────────────────────
function ScoreDistribution ( { roundId } : { roundId : string } ) {
2026-02-16 09:30:19 +01:00
const { data : dist , isLoading } = trpc . analytics . getRoundScoreDistribution . useQuery (
{ roundId } ,
{ refetchInterval : 15_000 } ,
)
2026-02-16 09:20:02 +01:00
const maxCount = useMemo ( ( ) = >
dist ? Math . max ( . . . dist . globalDistribution . map ( ( b ) = > b . count ) , 1 ) : 1 ,
[ dist ] )
return (
< Card >
< CardHeader >
< CardTitle className = "text-base" > Score Distribution < / CardTitle >
< CardDescription >
{ dist ? ` ${ dist . totalEvaluations } evaluations \ u2014 avg ${ dist . averageGlobalScore . toFixed ( 1 ) } ` : 'Loading...' }
< / CardDescription >
< / CardHeader >
< CardContent >
{ isLoading ? (
< div className = "flex items-end gap-1 h-32" >
{ Array . from ( { length : 10 } ) . map ( ( _ , i ) = > < Skeleton key = { i } className = "flex-1 h-full" / > ) }
< / div >
) : ! dist || dist . totalEvaluations === 0 ? (
< p className = "text-sm text-muted-foreground text-center py-6" >
No evaluations submitted yet
< / p >
) : (
< div className = "flex items-end gap-1 h-32" >
{ dist . globalDistribution . map ( ( bucket ) = > {
const heightPct = ( bucket . count / maxCount ) * 100
return (
< div key = { bucket . score } className = "flex-1 flex flex-col items-center gap-1" >
< span className = "text-[9px] text-muted-foreground" > { bucket . count || '' } < / span >
< div className = "w-full relative rounded-t" style = { { height : ` ${ Math . max ( heightPct , 2 ) } % ` } } >
< div className = { cn (
'absolute inset-0 rounded-t transition-all' ,
bucket . score <= 3 ? 'bg-red-400' :
bucket . score <= 6 ? 'bg-amber-400' :
'bg-emerald-400' ,
) } / >
< / div >
< span className = "text-[10px] text-muted-foreground" > { bucket . score } < / span >
< / div >
)
} ) }
< / div >
) }
< / CardContent >
< / Card >
)
}
// ── Send Reminders Button ────────────────────────────────────────────────
function SendRemindersButton ( { roundId } : { roundId : string } ) {
const [ open , setOpen ] = useState ( false )
const mutation = trpc . evaluation . triggerReminders . useMutation ( {
onSuccess : ( data ) = > {
toast . success ( ` Sent ${ data . sent } reminder(s) ` )
setOpen ( false )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
return (
< >
< Button variant = "outline" size = "sm" onClick = { ( ) = > setOpen ( true ) } >
< Send className = "h-4 w-4 mr-1.5" / >
Send Reminders
< / Button >
< AlertDialog open = { open } onOpenChange = { setOpen } >
< AlertDialogContent >
< AlertDialogHeader >
< AlertDialogTitle > Send evaluation reminders ? < / AlertDialogTitle >
< AlertDialogDescription >
This will send reminder emails to all jurors who have incomplete evaluations for this round .
< / AlertDialogDescription >
< / AlertDialogHeader >
< AlertDialogFooter >
< AlertDialogCancel > Cancel < / AlertDialogCancel >
< AlertDialogAction
onClick = { ( ) = > mutation . mutate ( { roundId } ) }
disabled = { mutation . isPending }
>
{ mutation . isPending && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
Send Reminders
< / AlertDialogAction >
< / AlertDialogFooter >
< / AlertDialogContent >
< / AlertDialog >
< / >
)
}
// ── Export Evaluations Dialog ─────────────────────────────────────────────
function ExportEvaluationsDialog ( {
roundId ,
open ,
onOpenChange ,
} : {
roundId : string
open : boolean
onOpenChange : ( open : boolean ) = > void
} ) {
const [ exportData , setExportData ] = useState < any > ( undefined )
const [ isLoadingExport , setIsLoadingExport ] = useState ( false )
const utils = trpc . useUtils ( )
const handleRequestData = async ( ) = > {
setIsLoadingExport ( true )
try {
const data = await utils . export . evaluations . fetch ( { roundId , includeDetails : true } )
setExportData ( data )
return data
} finally {
setIsLoadingExport ( false )
}
}
return (
< CsvExportDialog
open = { open }
onOpenChange = { onOpenChange }
exportData = { exportData }
isLoading = { isLoadingExport }
filename = { ` evaluations- ${ roundId } ` }
onRequestData = { handleRequestData }
/ >
)
}
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-16 09:20:02 +01:00
// ── Individual Assignments Table ─────────────────────────────────────────
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
function IndividualAssignmentsTable ( {
roundId ,
projectStates ,
} : {
roundId : string
projectStates : any [ ] | undefined
} ) {
2026-02-16 09:20:02 +01:00
const [ addDialogOpen , setAddDialogOpen ] = useState ( false )
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
const [ selectedJurorId , setSelectedJurorId ] = useState ( '' )
const [ selectedProjectIds , setSelectedProjectIds ] = useState < Set < string > > ( new Set ( ) )
const [ jurorPopoverOpen , setJurorPopoverOpen ] = useState ( false )
const [ projectSearch , setProjectSearch ] = useState ( '' )
2026-02-16 09:20:02 +01:00
const utils = trpc . useUtils ( )
2026-02-16 09:30:19 +01:00
const { data : assignments , isLoading } = trpc . assignment . listByStage . useQuery (
{ roundId } ,
{ refetchInterval : 15_000 } ,
)
2026-02-16 09:20:02 +01:00
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
const { data : juryMembers } = trpc . user . getJuryMembers . useQuery (
{ roundId } ,
{ enabled : addDialogOpen } ,
)
2026-02-16 09:20:02 +01:00
const deleteMutation = trpc . assignment . delete . useMutation ( {
onSuccess : ( ) = > {
utils . assignment . listByStage . invalidate ( { roundId } )
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
utils . roundEngine . getProjectStates . invalidate ( { roundId } )
2026-02-16 09:20:02 +01:00
toast . success ( 'Assignment removed' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
2026-02-18 14:03:38 +01:00
const resetEvalMutation = trpc . evaluation . resetEvaluation . useMutation ( {
onSuccess : ( ) = > {
utils . assignment . listByStage . invalidate ( { roundId } )
toast . success ( 'Evaluation reset — juror can now start over' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
2026-02-16 09:20:02 +01:00
const createMutation = trpc . assignment . create . useMutation ( {
onSuccess : ( ) = > {
utils . assignment . listByStage . invalidate ( { roundId } )
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
utils . roundEngine . getProjectStates . invalidate ( { roundId } )
utils . user . getJuryMembers . invalidate ( { roundId } )
2026-02-16 09:20:02 +01:00
toast . success ( 'Assignment created' )
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
resetDialog ( )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
const bulkCreateMutation = trpc . assignment . bulkCreate . useMutation ( {
onSuccess : ( result ) = > {
utils . assignment . listByStage . invalidate ( { roundId } )
utils . roundEngine . getProjectStates . invalidate ( { roundId } )
utils . user . getJuryMembers . invalidate ( { roundId } )
toast . success ( ` ${ result . created } assignment(s) created ` )
resetDialog ( )
2026-02-16 09:20:02 +01:00
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
const resetDialog = useCallback ( ( ) = > {
setAddDialogOpen ( false )
setSelectedJurorId ( '' )
setSelectedProjectIds ( new Set ( ) )
setProjectSearch ( '' )
} , [ ] )
const selectedJuror = useMemo (
( ) = > juryMembers ? . find ( ( j : any ) = > j . id === selectedJurorId ) ,
[ juryMembers , selectedJurorId ] ,
)
// Filter projects by search term
const filteredProjects = useMemo ( ( ) = > {
const items = projectStates ? ? [ ]
if ( ! projectSearch ) return items
const q = projectSearch . toLowerCase ( )
return items . filter ( ( ps : any ) = >
ps . project ? . title ? . toLowerCase ( ) . includes ( q ) ||
ps . project ? . teamName ? . toLowerCase ( ) . includes ( q ) ||
ps . project ? . competitionCategory ? . toLowerCase ( ) . includes ( q )
)
} , [ projectStates , projectSearch ] )
// Existing assignments for the selected juror (to grey out already-assigned projects)
const jurorExistingProjectIds = useMemo ( ( ) = > {
if ( ! selectedJurorId || ! assignments ) return new Set < string > ( )
return new Set (
assignments
. filter ( ( a : any ) = > a . userId === selectedJurorId )
. map ( ( a : any ) = > a . projectId )
)
} , [ selectedJurorId , assignments ] )
const toggleProject = useCallback ( ( projectId : string ) = > {
setSelectedProjectIds ( prev = > {
const next = new Set ( prev )
if ( next . has ( projectId ) ) {
next . delete ( projectId )
} else {
next . add ( projectId )
}
return next
} )
} , [ ] )
const selectAllUnassigned = useCallback ( ( ) = > {
const unassigned = filteredProjects
. filter ( ( ps : any ) = > ! jurorExistingProjectIds . has ( ps . project ? . id ) )
. map ( ( ps : any ) = > ps . project ? . id )
. filter ( Boolean )
setSelectedProjectIds ( new Set ( unassigned ) )
} , [ filteredProjects , jurorExistingProjectIds ] )
const handleCreate = useCallback ( ( ) = > {
if ( ! selectedJurorId || selectedProjectIds . size === 0 ) return
const projectIds = Array . from ( selectedProjectIds )
if ( projectIds . length === 1 ) {
createMutation . mutate ( {
userId : selectedJurorId ,
projectId : projectIds [ 0 ] ,
roundId ,
} )
} else {
bulkCreateMutation . mutate ( {
roundId ,
assignments : projectIds.map ( projectId = > ( {
userId : selectedJurorId ,
projectId ,
} ) ) ,
} )
}
} , [ selectedJurorId , selectedProjectIds , roundId , createMutation , bulkCreateMutation ] )
const isMutating = createMutation . isPending || bulkCreateMutation . isPending
2026-02-16 09:20:02 +01:00
return (
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< div >
< CardTitle className = "text-base" > All Assignments < / CardTitle >
< CardDescription >
{ assignments ? . length ? ? 0 } individual jury - project assignments
< / CardDescription >
< / div >
< Button size = "sm" variant = "outline" onClick = { ( ) = > setAddDialogOpen ( true ) } >
< Plus className = "h-4 w-4 mr-1.5" / >
Add
< / Button >
< / div >
< / CardHeader >
< CardContent >
{ isLoading ? (
< div className = "space-y-2" >
{ [ 1 , 2 , 3 , 4 , 5 ] . map ( ( i ) = > < Skeleton key = { i } className = "h-12 w-full" / > ) }
< / div >
) : ! assignments || assignments . length === 0 ? (
< p className = "text-sm text-muted-foreground text-center py-6" >
No assignments yet . Generate assignments or add one manually .
< / p >
) : (
< div className = "space-y-1 max-h-[500px] overflow-y-auto" >
2026-02-18 14:03:38 +01:00
< div className = "grid grid-cols-[1fr_1fr_100px_70px] gap-2 text-xs text-muted-foreground font-medium px-3 py-2 sticky top-0 bg-background border-b" >
2026-02-16 09:20:02 +01:00
< span > Juror < / span >
< span > Project < / span >
< span > Status < / span >
2026-02-18 14:03:38 +01:00
< span > Actions < / span >
2026-02-16 09:20:02 +01:00
< / div >
2026-02-16 12:38:28 +01:00
{ assignments . map ( ( a : any , idx : number ) = > (
2026-02-16 09:20:02 +01:00
< div
key = { a . id }
2026-02-16 12:38:28 +01:00
className = { cn (
2026-02-18 14:03:38 +01:00
'grid grid-cols-[1fr_1fr_100px_70px] gap-2 items-center px-3 py-2 rounded-md text-sm transition-colors' ,
2026-02-16 12:38:28 +01:00
idx % 2 === 1 ? 'bg-muted/20' : 'hover:bg-muted/20' ,
) }
2026-02-16 09:20:02 +01:00
>
< span className = "truncate" > { a . user ? . name || a . user ? . email || 'Unknown' } < / span >
< span className = "truncate text-muted-foreground" > { a . project ? . title || 'Unknown' } < / span >
< Badge
variant = "outline"
className = { cn (
'text-[10px] justify-center' ,
a . evaluation ? . status === 'SUBMITTED'
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
2026-02-18 14:03:38 +01:00
: a . evaluation ? . status === 'DRAFT'
2026-02-16 09:20:02 +01:00
? 'bg-blue-50 text-blue-700 border-blue-200'
: 'bg-gray-50 text-gray-600 border-gray-200' ,
) }
>
{ a . evaluation ? . status || 'PENDING' }
< / Badge >
2026-02-18 14:03:38 +01:00
< DropdownMenu >
< DropdownMenuTrigger asChild >
< Button variant = "ghost" size = "icon" className = "h-7 w-7" >
< MoreHorizontal className = "h-3.5 w-3.5 text-muted-foreground" / >
< / Button >
< / DropdownMenuTrigger >
< DropdownMenuContent align = "end" >
{ a . evaluation && (
< >
< DropdownMenuItem
onClick = { ( ) = > {
if ( confirm ( ` Reset evaluation by ${ a . user ? . name || a . user ? . email } for " ${ a . project ? . title } "? This will erase all scores and feedback so they can start over. ` ) ) {
resetEvalMutation . mutate ( { assignmentId : a.id } )
}
} }
disabled = { resetEvalMutation . isPending }
>
< RotateCcw className = "h-3.5 w-3.5 mr-2" / >
Reset Evaluation
< / DropdownMenuItem >
< DropdownMenuSeparator / >
< / >
) }
< DropdownMenuItem
className = "text-destructive focus:text-destructive"
onClick = { ( ) = > {
if ( confirm ( ` Remove assignment for ${ a . user ? . name || a . user ? . email } on " ${ a . project ? . title } "? ` ) ) {
deleteMutation . mutate ( { id : a.id } )
}
} }
disabled = { deleteMutation . isPending }
>
< Trash2 className = "h-3.5 w-3.5 mr-2" / >
Delete Assignment
< / DropdownMenuItem >
< / DropdownMenuContent >
< / DropdownMenu >
2026-02-16 09:20:02 +01:00
< / div >
) ) }
< / div >
) }
< / CardContent >
{ /* Add Assignment Dialog */ }
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
< Dialog open = { addDialogOpen } onOpenChange = { ( open ) = > {
if ( ! open ) resetDialog ( )
else setAddDialogOpen ( true )
} } >
< DialogContent className = "sm:max-w-[540px]" >
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
< DialogHeader >
2026-02-16 09:20:02 +01:00
< DialogTitle > Add Assignment < / DialogTitle >
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
< DialogDescription >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
Select a juror and one or more projects to assign
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
< / DialogDescription >
< / DialogHeader >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
2026-02-16 09:20:02 +01:00
< div className = "space-y-4" >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
{ /* Juror Selector */ }
2026-02-16 09:20:02 +01:00
< div className = "space-y-2" >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
< Label className = "text-sm font-medium" > Juror < / Label >
< Popover open = { jurorPopoverOpen } onOpenChange = { setJurorPopoverOpen } >
< PopoverTrigger asChild >
< Button
variant = "outline"
role = "combobox"
aria - expanded = { jurorPopoverOpen }
className = "w-full justify-between font-normal"
>
{ selectedJuror
? (
< span className = "flex items-center gap-2 truncate" >
< span className = "truncate" > { selectedJuror . name || selectedJuror . email } < / span >
< Badge variant = "secondary" className = "text-[10px] shrink-0" >
{ selectedJuror . currentAssignments } / { selectedJuror . maxAssignments ? ? '\u221E' }
< / Badge >
< / span >
)
: < span className = "text-muted-foreground" > Select a jury member . . . < / span >
}
< ChevronsUpDown className = "ml-2 h-4 w-4 shrink-0 opacity-50" / >
< / Button >
< / PopoverTrigger >
< PopoverContent className = "w-[480px] p-0" align = "start" >
< Command >
< CommandInput placeholder = "Search by name or email..." / >
< CommandList >
< CommandEmpty > No jury members found . < / CommandEmpty >
< CommandGroup >
{ juryMembers ? . map ( ( juror : any ) = > {
const atCapacity = juror . maxAssignments !== null && juror . availableSlots === 0
return (
< CommandItem
key = { juror . id }
value = { ` ${ juror . name ? ? '' } ${ juror . email } ` }
disabled = { atCapacity }
onSelect = { ( ) = > {
setSelectedJurorId ( juror . id === selectedJurorId ? '' : juror . id )
setSelectedProjectIds ( new Set ( ) )
setJurorPopoverOpen ( false )
} }
>
< Check
className = { cn (
'mr-2 h-4 w-4' ,
selectedJurorId === juror . id ? 'opacity-100' : 'opacity-0' ,
) }
/ >
< div className = "flex flex-1 items-center justify-between min-w-0" >
< div className = "min-w-0" >
< p className = "text-sm font-medium truncate" >
{ juror . name || 'Unnamed' }
< / p >
< p className = "text-xs text-muted-foreground truncate" >
{ juror . email }
< / p >
< / div >
< Badge
variant = { atCapacity ? 'destructive' : 'secondary' }
className = "text-[10px] ml-2 shrink-0"
>
{ juror . currentAssignments } / { juror . maxAssignments ? ? '\u221E' }
{ atCapacity ? ' full' : '' }
< / Badge >
< / div >
< / CommandItem >
)
} ) }
< / CommandGroup >
< / CommandList >
< / Command >
< / PopoverContent >
< / Popover >
2026-02-16 09:20:02 +01:00
< / div >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
{ /* Project Multi-Select */ }
2026-02-16 09:20:02 +01:00
< div className = "space-y-2" >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
< div className = "flex items-center justify-between" >
< Label className = "text-sm font-medium" >
Projects
{ selectedProjectIds . size > 0 && (
< span className = "ml-1.5 text-muted-foreground font-normal" >
( { selectedProjectIds . size } selected )
< / span >
) }
< / Label >
{ selectedJurorId && (
< div className = "flex items-center gap-2" >
< Button
type = "button"
variant = "ghost"
size = "sm"
className = "h-7 text-xs"
onClick = { selectAllUnassigned }
>
Select all
< / Button >
{ selectedProjectIds . size > 0 && (
< Button
type = "button"
variant = "ghost"
size = "sm"
className = "h-7 text-xs"
onClick = { ( ) = > setSelectedProjectIds ( new Set ( ) ) }
>
Clear
< / Button >
) }
< / div >
) }
< / div >
{ /* Search input */ }
< div className = "relative" >
< Search className = "absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" / >
< Input
placeholder = "Filter projects..."
value = { projectSearch }
onChange = { ( e ) = > setProjectSearch ( e . target . value ) }
className = "pl-9 h-9"
/ >
< / div >
{ /* Project checklist */ }
< ScrollArea className = "h-[240px] rounded-md border" >
< div className = "p-2 space-y-0.5" >
{ ! selectedJurorId ? (
< p className = "text-sm text-muted-foreground text-center py-8" >
Select a juror first
< / p >
) : filteredProjects . length === 0 ? (
< p className = "text-sm text-muted-foreground text-center py-8" >
No projects found
< / p >
) : (
filteredProjects . map ( ( ps : any ) = > {
const project = ps . project
if ( ! project ) return null
const alreadyAssigned = jurorExistingProjectIds . has ( project . id )
const isSelected = selectedProjectIds . has ( project . id )
return (
< label
key = { project . id }
className = { cn (
'flex items-center gap-3 rounded-md px-2.5 py-2 text-sm cursor-pointer transition-colors' ,
alreadyAssigned
? 'opacity-50 cursor-not-allowed'
: isSelected
? 'bg-accent'
: 'hover:bg-muted/50' ,
) }
>
< Checkbox
checked = { isSelected }
disabled = { alreadyAssigned }
onCheckedChange = { ( ) = > toggleProject ( project . id ) }
/ >
< div className = "flex flex-1 items-center justify-between min-w-0" >
< span className = "truncate" > { project . title } < / span >
< div className = "flex items-center gap-1.5 shrink-0 ml-2" >
{ project . competitionCategory && (
< Badge variant = "outline" className = "text-[10px]" >
{ project . competitionCategory === 'STARTUP'
? 'Startup'
: project . competitionCategory === 'BUSINESS_CONCEPT'
? 'Concept'
: project . competitionCategory }
< / Badge >
) }
{ alreadyAssigned && (
< Badge variant = "secondary" className = "text-[10px]" >
Assigned
< / Badge >
) }
< / div >
< / div >
< / label >
)
} )
) }
< / div >
< / ScrollArea >
2026-02-16 09:20:02 +01:00
< / div >
< / div >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +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
< DialogFooter >
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
< Button variant = "outline" onClick = { resetDialog } >
Cancel
< / Button >
2026-02-16 09:20:02 +01:00
< Button
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
onClick = { handleCreate }
disabled = { ! selectedJurorId || selectedProjectIds . size === 0 || isMutating }
2026-02-16 09:20:02 +01:00
>
Platform-wide UX fixes: assignment dialog, invalidation, settings, dashboard
1. Assignment dialog overhaul: replace raw UUID inputs with searchable
juror Combobox (shows name, email, capacity) and multi-select project
checklist with bulk assignment support
2. Query invalidation sweep: fix missing invalidations in
assignment-preview-sheet (roundAssignment.execute) and
filtering-dashboard (filtering.finalizeResults) so data refreshes
without page reload
3. Rename Submissions tab to Document Windows with descriptive
header explaining upload window configuration
4. Connect 6 disconnected settings: storage_provider, local_storage_path,
avatar_max_size_mb, allowed_image_types, whatsapp_enabled,
whatsapp_provider - all now accessible in Settings UI
5. Admin dashboard redesign: branded Editorial Command Center with
Dark Blue gradient header, colored border-l-4 stat cards, staggered
animations, 2-column layout, action-required panel, activity timeline
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:05:25 +01:00
{ isMutating && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
{ selectedProjectIds . size <= 1
? 'Create Assignment'
: ` Create ${ selectedProjectIds . size } Assignments `
}
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
< / Button >
< / DialogFooter >
< / DialogContent >
< / Dialog >
2026-02-16 09:20:02 +01:00
< / Card >
)
}
// ── Evaluation Criteria Editor ───────────────────────────────────────────
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
// ── Advance Projects Dialog ─────────────────────────────────────────────
function AdvanceProjectsDialog ( {
open ,
onOpenChange ,
roundId ,
2026-02-16 19:09:23 +01:00
roundType ,
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
projectStates ,
config ,
advanceMutation ,
2026-02-16 16:43:23 +01:00
competitionRounds ,
currentSortOrder ,
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
} : {
open : boolean
onOpenChange : ( open : boolean ) = > void
roundId : string
2026-02-16 19:09:23 +01:00
roundType? : string
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
projectStates : any [ ] | undefined
config : Record < string , unknown >
2026-02-16 19:09:23 +01:00
advanceMutation : { mutate : ( input : { roundId : string ; projectIds? : string [ ] ; targetRoundId? : string ; autoPassPending? : boolean } ) = > void ; isPending : boolean }
2026-02-16 16:43:23 +01:00
competitionRounds? : Array < { id : string ; name : string ; sortOrder : number ; roundType : string } >
currentSortOrder? : number
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
} ) {
2026-02-16 19:09:23 +01:00
// For non-jury rounds (INTAKE, SUBMISSION, MENTORING), offer a simpler "advance all" flow
const isSimpleAdvance = [ 'INTAKE' , 'SUBMISSION' , 'MENTORING' ] . includes ( roundType ? ? '' )
2026-02-16 16:43:23 +01:00
// Target round selector
const availableTargets = useMemo ( ( ) = >
( competitionRounds ? ? [ ] )
. filter ( ( r ) = > r . sortOrder > ( currentSortOrder ? ? - 1 ) && r . id !== roundId )
. sort ( ( a , b ) = > a . sortOrder - b . sortOrder ) ,
[ competitionRounds , currentSortOrder , roundId ] )
const [ targetRoundId , setTargetRoundId ] = useState < string > ( '' )
// Default to first available target when dialog opens
if ( open && ! targetRoundId && availableTargets . length > 0 ) {
setTargetRoundId ( availableTargets [ 0 ] . id )
}
2026-02-16 19:09:23 +01:00
const allProjects = projectStates ? ? [ ]
const pendingCount = allProjects . filter ( ( ps : any ) = > ps . state === 'PENDING' ) . length
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
const passedProjects = useMemo ( ( ) = >
2026-02-16 19:09:23 +01:00
allProjects . filter ( ( ps : any ) = > ps . state === 'PASSED' ) ,
[ allProjects ] )
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
const startups = useMemo ( ( ) = >
passedProjects . filter ( ( ps : any ) = > ps . project ? . competitionCategory === 'STARTUP' ) ,
[ passedProjects ] )
const concepts = useMemo ( ( ) = >
passedProjects . filter ( ( ps : any ) = > ps . project ? . competitionCategory === 'BUSINESS_CONCEPT' ) ,
[ passedProjects ] )
const other = useMemo ( ( ) = >
passedProjects . filter ( ( ps : any ) = >
ps . project ? . competitionCategory !== 'STARTUP' && ps . project ? . competitionCategory !== 'BUSINESS_CONCEPT' ,
) ,
[ passedProjects ] )
const startupCap = ( config . startupAdvanceCount as number ) || 0
const conceptCap = ( config . conceptAdvanceCount as number ) || 0
const [ selected , setSelected ] = useState < Set < string > > ( new Set ( ) )
// Reset selection when dialog opens
if ( open && selected . size === 0 && passedProjects . length > 0 ) {
const initial = new Set < string > ( )
// Auto-select all (or up to cap if configured)
const startupSlice = startupCap > 0 ? startups . slice ( 0 , startupCap ) : startups
const conceptSlice = conceptCap > 0 ? concepts . slice ( 0 , conceptCap ) : concepts
for ( const ps of startupSlice ) initial . add ( ps . project ? . id )
for ( const ps of conceptSlice ) initial . add ( ps . project ? . id )
for ( const ps of other ) initial . add ( ps . project ? . id )
setSelected ( initial )
}
const toggleProject = ( projectId : string ) = > {
setSelected ( ( prev ) = > {
const next = new Set ( prev )
if ( next . has ( projectId ) ) next . delete ( projectId )
else next . add ( projectId )
return next
} )
}
const toggleAll = ( projects : any [ ] , on : boolean ) = > {
setSelected ( ( prev ) = > {
const next = new Set ( prev )
for ( const ps of projects ) {
if ( on ) next . add ( ps . project ? . id )
else next . delete ( ps . project ? . id )
}
return next
} )
}
2026-02-16 19:09:23 +01:00
const handleAdvance = ( autoPass? : boolean ) = > {
if ( autoPass ) {
// Auto-pass all pending then advance all
advanceMutation . mutate ( {
roundId ,
autoPassPending : true ,
. . . ( targetRoundId ? { targetRoundId } : { } ) ,
} )
} else {
const ids = Array . from ( selected )
if ( ids . length === 0 ) return
advanceMutation . mutate ( {
roundId ,
projectIds : ids ,
. . . ( targetRoundId ? { targetRoundId } : { } ) ,
} )
}
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
onOpenChange ( false )
setSelected ( new Set ( ) )
2026-02-16 16:43:23 +01:00
setTargetRoundId ( '' )
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
}
const handleClose = ( ) = > {
onOpenChange ( false )
setSelected ( new Set ( ) )
2026-02-16 16:43:23 +01:00
setTargetRoundId ( '' )
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
}
const renderCategorySection = (
label : string ,
projects : any [ ] ,
cap : number ,
badgeColor : string ,
) = > {
const selectedInCategory = projects . filter ( ( ps : any ) = > selected . has ( ps . project ? . id ) ) . length
const overCap = cap > 0 && selectedInCategory > cap
return (
< div className = "space-y-2" >
< div className = "flex items-center justify-between" >
< div className = "flex items-center gap-2" >
< Checkbox
checked = { projects . length > 0 && projects . every ( ( ps : any ) = > selected . has ( ps . project ? . id ) ) }
onCheckedChange = { ( checked ) = > toggleAll ( projects , ! ! checked ) }
/ >
< span className = "text-sm font-medium" > { label } < / span >
< Badge variant = "secondary" className = { cn ( 'text-[10px]' , badgeColor ) } >
{ selectedInCategory } / { projects . length }
< / Badge >
{ cap > 0 && (
< span className = { cn ( 'text-[10px]' , overCap ? 'text-red-500 font-medium' : 'text-muted-foreground' ) } >
( target : { cap } )
< / span >
) }
< / div >
< / div >
{ projects . length === 0 ? (
< p className = "text-xs text-muted-foreground pl-7" > No passed projects in this category < / p >
) : (
< div className = "space-y-1 pl-7" >
{ projects . map ( ( ps : any ) = > (
< label
key = { ps . project ? . id }
className = "flex items-center gap-2 p-2 rounded hover:bg-muted/30 cursor-pointer"
>
< Checkbox
checked = { selected . has ( ps . project ? . id ) }
onCheckedChange = { ( ) = > toggleProject ( ps . project ? . id ) }
/ >
< span className = "text-sm truncate flex-1" > { ps . project ? . title || 'Untitled' } < / span >
{ ps . project ? . teamName && (
< span className = "text-xs text-muted-foreground shrink-0" > { ps . project . teamName } < / span >
) }
< / label >
) ) }
< / div >
) }
< / div >
)
}
2026-02-16 19:09:23 +01:00
const totalProjectCount = allProjects . length
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
return (
< Dialog open = { open } onOpenChange = { handleClose } >
< DialogContent className = "max-w-lg max-h-[85vh] flex flex-col" >
< DialogHeader >
< DialogTitle > Advance Projects < / DialogTitle >
< DialogDescription >
2026-02-16 19:09:23 +01:00
{ isSimpleAdvance
? ` Move all ${ totalProjectCount } projects to the next round. `
: ` Select which passed projects to advance. ${ selected . size } of ${ passedProjects . length } selected. `
}
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
< / DialogDescription >
< / DialogHeader >
2026-02-16 16:43:23 +01:00
{ /* Target round selector */ }
{ availableTargets . length > 0 && (
< div className = "space-y-2 pb-2 border-b" >
< Label className = "text-sm" > Advance to < / Label >
< Select value = { targetRoundId } onValueChange = { setTargetRoundId } >
< SelectTrigger >
< SelectValue placeholder = "Select target round" / >
< / SelectTrigger >
< SelectContent >
{ availableTargets . map ( ( r ) = > (
< SelectItem key = { r . id } value = { r . id } >
{ r . name } ( { r . roundType . replace ( '_' , ' ' ) . toLowerCase ( ) } )
< / SelectItem >
) ) }
< / SelectContent >
< / Select >
< / div >
) }
{ availableTargets . length === 0 && (
< div className = "text-sm text-amber-600 bg-amber-50 rounded-md p-3" >
No subsequent rounds found . Projects will advance to the next round by sort order .
< / div >
) }
2026-02-16 19:09:23 +01:00
{ isSimpleAdvance ? (
/* Simple mode for INTAKE/SUBMISSION/MENTORING — no per-project selection needed */
< div className = "py-4 space-y-3" >
< div className = "rounded-lg border bg-muted/30 p-4 text-center space-y-1" >
< p className = "text-3xl font-bold" > { totalProjectCount } < / p >
< p className = "text-sm text-muted-foreground" > projects will be advanced < / p >
< / div >
{ pendingCount > 0 && (
< div className = "rounded-md border border-blue-200 bg-blue-50 px-3 py-2" >
< p className = "text-xs text-blue-700" >
{ pendingCount } pending project { pendingCount !== 1 ? 's' : '' } will be automatically marked as passed and advanced .
{ passedProjects . length > 0 && ` ${ passedProjects . length } already passed. ` }
< / p >
< / div >
) }
< / div >
) : (
/* Detailed mode for jury/evaluation rounds — per-project selection */
< div className = "flex-1 overflow-y-auto space-y-4 py-2" >
{ renderCategorySection ( 'Startup' , startups , startupCap , 'bg-blue-100 text-blue-700' ) }
{ renderCategorySection ( 'Business Concept' , concepts , conceptCap , 'bg-purple-100 text-purple-700' ) }
{ other . length > 0 && renderCategorySection ( 'Other / Uncategorized' , other , 0 , 'bg-gray-100 text-gray-700' ) }
< / div >
) }
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
< DialogFooter >
< Button variant = "outline" onClick = { handleClose } > Cancel < / Button >
2026-02-16 19:09:23 +01:00
{ isSimpleAdvance ? (
< Button
onClick = { ( ) = > handleAdvance ( true ) }
disabled = { totalProjectCount === 0 || advanceMutation . isPending }
>
{ advanceMutation . isPending && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
Advance All { totalProjectCount } Project { totalProjectCount !== 1 ? 's' : '' }
< / Button >
) : (
< Button
onClick = { ( ) = > handleAdvance ( ) }
disabled = { selected . size === 0 || advanceMutation . isPending }
>
{ advanceMutation . isPending && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
Advance { selected . size } Project { selected . size !== 1 ? 's' : '' }
< / Button >
) }
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
< / DialogFooter >
< / DialogContent >
< / Dialog >
)
}
// ── AI Recommendations Display ──────────────────────────────────────────
type RecommendationItem = {
projectId : string
rank : number
score : number
category : string
strengths : string [ ]
concerns : string [ ]
recommendation : string
}
function AIRecommendationsDisplay ( {
recommendations ,
onClear ,
} : {
recommendations : { STARTUP : RecommendationItem [ ] ; BUSINESS_CONCEPT : RecommendationItem [ ] }
onClear : ( ) = > void
} ) {
const [ expandedId , setExpandedId ] = useState < string | null > ( null )
const renderCategory = ( label : string , items : RecommendationItem [ ] , colorClass : string ) = > {
if ( items . length === 0 ) return (
< div className = "text-center py-4 text-muted-foreground text-sm" >
No { label . toLowerCase ( ) } projects evaluated
< / div >
)
return (
< div className = "space-y-2" >
{ items . map ( ( item ) = > {
const isExpanded = expandedId === ` ${ item . category } - ${ item . projectId } `
return (
< div
key = { item . projectId }
className = "border rounded-lg overflow-hidden"
>
< button
onClick = { ( ) = > setExpandedId ( isExpanded ? null : ` ${ item . category } - ${ item . projectId } ` ) }
className = "w-full flex items-center gap-3 p-3 text-left hover:bg-muted/30 transition-colors"
>
< span className = { cn (
2026-02-16 12:38:28 +01:00
'h-7 w-7 rounded-full flex items-center justify-center text-xs font-bold text-white shrink-0 shadow-sm' ,
colorClass === 'bg-blue-500' ? 'bg-gradient-to-br from-blue-400 to-blue-600' : 'bg-gradient-to-br from-purple-400 to-purple-600' ,
AI category-aware evaluation: per-round config, file parsing, shortlist, advance flow
- Per-juror cap mode (HARD/SOFT/NONE) in add-member dialog and members table
- Jury invite flow: create user + add to group + send invitation from dialog
- Per-round config: notifyOnAdvance, aiParseFiles, startupAdvanceCount, conceptAdvanceCount
- Moved notify-on-advance from competition-level to per-round setting
- AI filtering: round-tagged files with newest-first sorting, optional file content extraction
- File content extractor service (pdf-parse for PDF, utf-8 for text files)
- AI shortlist runs independently per category (STARTUP / BUSINESS_CONCEPT)
- generateAIRecommendations tRPC endpoint with per-round config integration
- AI recommendations UI: trigger button, confirmation dialog, per-category results display
- Category-aware advance dialog: select/deselect projects by category with target caps
- STAGE_ACTIVE bug fix in assignment router
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 10:09:52 +01:00
) } >
{ item . rank }
< / span >
< div className = "flex-1 min-w-0" >
< p className = "text-sm font-medium truncate" > { item . projectId } < / p >
< p className = "text-xs text-muted-foreground truncate" > { item . recommendation } < / p >
< / div >
< Badge variant = "outline" className = "shrink-0 text-xs font-mono" >
{ item . score } / 100
< / Badge >
< ChevronDown className = { cn (
'h-4 w-4 text-muted-foreground transition-transform shrink-0' ,
isExpanded && 'rotate-180' ,
) } / >
< / button >
{ isExpanded && (
< div className = "px-3 pb-3 pt-0 space-y-2 border-t bg-muted/10" >
< div className = "pt-2" >
< p className = "text-xs font-medium text-emerald-700 mb-1" > Strengths < / p >
< ul className = "text-xs text-muted-foreground space-y-0.5 pl-4 list-disc" >
{ item . strengths . map ( ( s , i ) = > < li key = { i } > { s } < / li > ) }
< / ul >
< / div >
{ item . concerns . length > 0 && (
< div >
< p className = "text-xs font-medium text-amber-700 mb-1" > Concerns < / p >
< ul className = "text-xs text-muted-foreground space-y-0.5 pl-4 list-disc" >
{ item . concerns . map ( ( c , i ) = > < li key = { i } > { c } < / li > ) }
< / ul >
< / div >
) }
< div >
< p className = "text-xs font-medium text-blue-700 mb-1" > Recommendation < / p >
< p className = "text-xs text-muted-foreground" > { item . recommendation } < / p >
< / div >
< / div >
) }
< / div >
)
} ) }
< / div >
)
}
return (
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< div >
< CardTitle className = "text-base" > AI Shortlist Recommendations < / CardTitle >
< CardDescription >
Ranked independently per category — { recommendations . STARTUP . length } startups , { recommendations . BUSINESS_CONCEPT . length } concepts
< / CardDescription >
< / div >
< Button variant = "ghost" size = "sm" onClick = { onClear } >
< X className = "h-4 w-4 mr-1" / >
Dismiss
< / Button >
< / div >
< / CardHeader >
< CardContent >
< div className = "grid gap-6 lg:grid-cols-2" >
< div >
< h4 className = "text-sm font-semibold mb-3 flex items-center gap-2" >
< div className = "h-2 w-2 rounded-full bg-blue-500" / >
Startup ( { recommendations . STARTUP . length } )
< / h4 >
{ renderCategory ( 'Startup' , recommendations . STARTUP , 'bg-blue-500' ) }
< / div >
< div >
< h4 className = "text-sm font-semibold mb-3 flex items-center gap-2" >
< div className = "h-2 w-2 rounded-full bg-purple-500" / >
Business Concept ( { recommendations . BUSINESS_CONCEPT . length } )
< / h4 >
{ renderCategory ( 'Business Concept' , recommendations . BUSINESS_CONCEPT , 'bg-purple-500' ) }
< / div >
< / div >
< / CardContent >
< / Card >
)
}
// ── Evaluation Criteria Editor ───────────────────────────────────────────
2026-02-16 09:20:02 +01:00
function EvaluationCriteriaEditor ( { roundId } : { roundId : string } ) {
2026-02-18 12:43:28 +01:00
const [ pendingCriteria , setPendingCriteria ] = useState < Criterion [ ] | null > ( null )
2026-02-16 09:20:02 +01:00
const utils = trpc . useUtils ( )
2026-02-18 12:43:28 +01:00
2026-02-16 09:30:19 +01:00
const { data : form , isLoading } = trpc . evaluation . getForm . useQuery (
{ roundId } ,
{ refetchInterval : 30_000 } ,
)
2026-02-16 09:20:02 +01:00
const upsertMutation = trpc . evaluation . upsertForm . useMutation ( {
onSuccess : ( ) = > {
utils . evaluation . getForm . invalidate ( { roundId } )
toast . success ( 'Evaluation criteria saved' )
2026-02-18 12:43:28 +01:00
setPendingCriteria ( null )
2026-02-16 09:20:02 +01:00
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
2026-02-18 12:43:28 +01:00
// Convert server criteriaJson to Criterion[] format
const serverCriteria : Criterion [ ] = useMemo ( ( ) = > {
if ( ! form ? . criteriaJson ) return [ ]
return ( form . criteriaJson as Criterion [ ] ) . map ( ( c ) = > {
// Handle legacy numeric-only format: convert "scale" string like "1-10" back to minScore/maxScore
const type = c . type || 'numeric'
if ( type === 'numeric' && typeof c . scale === 'string' ) {
const parts = ( c . scale as string ) . split ( '-' ) . map ( Number )
if ( parts . length === 2 && ! isNaN ( parts [ 0 ] ) && ! isNaN ( parts [ 1 ] ) ) {
return { . . . c , type : 'numeric' as const , scale : parts [ 1 ] , minScore : parts [ 0 ] , maxScore : parts [ 1 ] } as unknown as Criterion
}
}
return { . . . c , type } as Criterion
} )
} , [ form ? . criteriaJson ] )
2026-02-16 09:20:02 +01:00
2026-02-18 12:43:28 +01:00
const handleChange = useCallback ( ( criteria : Criterion [ ] ) = > {
setPendingCriteria ( criteria )
} , [ ] )
2026-02-16 09:20:02 +01:00
const handleSave = ( ) = > {
2026-02-18 12:43:28 +01:00
const criteria = pendingCriteria ? ? serverCriteria
2026-02-16 09:20:02 +01:00
const validCriteria = criteria . filter ( ( c ) = > c . label . trim ( ) )
if ( validCriteria . length === 0 ) {
toast . error ( 'Add at least one criterion' )
return
}
2026-02-18 12:43:28 +01:00
// Map to upsertForm format
upsertMutation . mutate ( {
roundId ,
criteria : validCriteria.map ( ( c ) = > ( {
id : c.id ,
label : c.label ,
description : c.description ,
type : c . type || 'numeric' ,
weight : c.weight ,
scale : typeof c . scale === 'number' ? c.scale : undefined ,
minScore : ( c as any ) . minScore ,
maxScore : ( c as any ) . maxScore ,
required : c.required ,
maxLength : c.maxLength ,
placeholder : c.placeholder ,
trueLabel : c.trueLabel ,
falseLabel : c.falseLabel ,
condition : c.condition ,
sectionId : c.sectionId ,
} ) ) ,
} )
2026-02-16 09:20:02 +01:00
}
return (
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< div >
< CardTitle className = "text-base" > Evaluation Criteria < / CardTitle >
< CardDescription >
2026-02-18 12:43:28 +01:00
{ form
? ` Version ${ form . version } \ u2014 ${ ( form . criteriaJson as Criterion [ ] ) . filter ( ( c ) = > ( c . type || 'numeric' ) !== 'section_header' ) . length } criteria `
: 'No criteria defined yet. Add numeric scores, yes/no questions, and text fields.' }
2026-02-16 09:20:02 +01:00
< / CardDescription >
< / div >
2026-02-18 12:43:28 +01:00
{ pendingCriteria && (
< div className = "flex items-center gap-2" >
< Button size = "sm" variant = "outline" onClick = { ( ) = > setPendingCriteria ( null ) } >
2026-02-16 09:20:02 +01:00
Cancel
< / Button >
< Button size = "sm" onClick = { handleSave } disabled = { upsertMutation . isPending } >
{ upsertMutation . isPending && < Loader2 className = "h-4 w-4 mr-1.5 animate-spin" / > }
Save Criteria
< / Button >
2026-02-18 12:43:28 +01:00
< / div >
) }
2026-02-16 09:20:02 +01:00
< / div >
< / CardHeader >
< CardContent >
{ isLoading ? (
< div className = "space-y-3" >
{ [ 1 , 2 , 3 ] . map ( ( i ) = > < Skeleton key = { i } className = "h-16 w-full" / > ) }
< / div >
) : (
2026-02-18 12:43:28 +01:00
< EvaluationFormBuilder
initialCriteria = { serverCriteria }
onChange = { handleChange }
/ >
2026-02-16 09:20:02 +01:00
) }
< / CardContent >
< / Card >
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-18 14:34:27 +01:00
// ── COI Review Section ────────────────────────────────────────────────────
function COIReviewSection ( { roundId } : { roundId : string } ) {
const utils = trpc . useUtils ( )
const { data : declarations , isLoading } = trpc . evaluation . listCOIByStage . useQuery (
{ roundId } ,
{ refetchInterval : 15_000 } ,
)
const reviewMutation = trpc . evaluation . reviewCOI . useMutation ( {
onSuccess : ( ) = > {
utils . evaluation . listCOIByStage . invalidate ( { roundId } )
toast . success ( 'COI review updated' )
} ,
onError : ( err ) = > toast . error ( err . message ) ,
} )
// Don't show section if no declarations
if ( ! isLoading && ( ! declarations || declarations . length === 0 ) ) {
return null
}
const conflictCount = declarations ? . filter ( ( d ) = > d . hasConflict ) . length ? ? 0
const unreviewedCount = declarations ? . filter ( ( d ) = > d . hasConflict && ! d . reviewedAt ) . length ? ? 0
return (
< Card >
< CardHeader >
< div className = "flex items-center justify-between" >
< div >
< CardTitle className = "text-base flex items-center gap-2" >
< ShieldAlert className = "h-4 w-4" / >
Conflict of Interest Declarations
< / CardTitle >
< CardDescription >
{ declarations ? . length ? ? 0 } declaration { ( declarations ? . length ? ? 0 ) !== 1 ? 's' : '' }
{ conflictCount > 0 && (
< > & mdash ; < span className = "text-amber-600 font-medium" > { conflictCount } conflict { conflictCount !== 1 ? 's' : '' } < / span > < / >
) }
{ unreviewedCount > 0 && (
< > ( { unreviewedCount } pending review ) < / >
) }
< / CardDescription >
< / div >
< / div >
< / CardHeader >
< CardContent >
{ isLoading ? (
< div className = "space-y-2" >
{ [ 1 , 2 , 3 ] . map ( ( i ) = > < Skeleton key = { i } className = "h-14 w-full" / > ) }
< / div >
) : (
< div className = "space-y-1 max-h-[400px] overflow-y-auto" >
< div className = "grid grid-cols-[1fr_1fr_80px_100px_100px] gap-2 text-xs text-muted-foreground font-medium px-3 py-2 sticky top-0 bg-background border-b" >
< span > Juror < / span >
< span > Project < / span >
< span > Conflict < / span >
< span > Type < / span >
< span > Action < / span >
< / div >
{ declarations ? . map ( ( coi : any , idx : number ) = > (
< div
key = { coi . id }
className = { cn (
'grid grid-cols-[1fr_1fr_80px_100px_100px] gap-2 items-center px-3 py-2 rounded-md text-sm transition-colors' ,
idx % 2 === 1 ? 'bg-muted/20' : 'hover:bg-muted/20' ,
coi . hasConflict && ! coi . reviewedAt && 'border-l-4 border-l-amber-500' ,
) }
>
< span className = "truncate" > { coi . user ? . name || coi . user ? . email || 'Unknown' } < / span >
< span className = "truncate text-muted-foreground" > { coi . assignment ? . project ? . title || 'Unknown' } < / span >
< Badge
variant = "outline"
className = { cn (
'text-[10px] justify-center' ,
coi . hasConflict
? 'bg-red-50 text-red-700 border-red-200'
: 'bg-emerald-50 text-emerald-700 border-emerald-200' ,
) }
>
{ coi . hasConflict ? 'Yes' : 'No' }
< / Badge >
< span className = "text-xs text-muted-foreground truncate" >
{ coi . hasConflict ? ( coi . conflictType || 'Unspecified' ) : '\u2014' }
< / span >
{ coi . hasConflict ? (
coi . reviewedAt ? (
< Badge
variant = "outline"
className = { cn (
'text-[10px] justify-center' ,
coi . reviewAction === 'cleared'
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
: coi . reviewAction === 'reassigned'
? 'bg-blue-50 text-blue-700 border-blue-200'
: 'bg-gray-50 text-gray-600 border-gray-200' ,
) }
>
{ coi . reviewAction === 'cleared' ? 'Cleared' : coi . reviewAction === 'reassigned' ? 'Reassigned' : 'Noted' }
< / Badge >
) : (
< DropdownMenu >
< DropdownMenuTrigger asChild >
< Button variant = "outline" size = "sm" className = "h-7 text-xs" >
< Eye className = "h-3 w-3 mr-1" / >
Review
< / Button >
< / DropdownMenuTrigger >
< DropdownMenuContent align = "end" >
< DropdownMenuItem
onClick = { ( ) = > reviewMutation . mutate ( { id : coi.id , reviewAction : 'cleared' } ) }
disabled = { reviewMutation . isPending }
>
< CheckCircle2 className = "h-3.5 w-3.5 mr-2 text-emerald-600" / >
Clear — no real conflict
< / DropdownMenuItem >
< DropdownMenuItem
onClick = { ( ) = > reviewMutation . mutate ( { id : coi.id , reviewAction : 'reassigned' } ) }
disabled = { reviewMutation . isPending }
>
< UserPlus className = "h-3.5 w-3.5 mr-2 text-blue-600" / >
Reassign to another juror
< / DropdownMenuItem >
< DropdownMenuItem
onClick = { ( ) = > reviewMutation . mutate ( { id : coi.id , reviewAction : 'noted' } ) }
disabled = { reviewMutation . isPending }
>
< FileText className = "h-3.5 w-3.5 mr-2 text-gray-600" / >
Note — keep as is
< / DropdownMenuItem >
< / DropdownMenuContent >
< / DropdownMenu >
)
) : (
< span className = "text-xs text-muted-foreground" > & mdash ; < / span >
) }
< / div >
) ) }
< / div >
) }
< / CardContent >
< / Card >
)
}