Admin dashboard & round management UX overhaul
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m43s

- Extract round detail monolith (2900→600 lines) into 13 standalone components
- Add shared round/status config (round-config.ts) replacing 4 local copies
- Delete 12 legacy competition-scoped pages, merge project pool into projects page
- Add round-type-specific dashboard stat panels (submission, mentoring, live final, deliberation, summary)
- Add contextual header quick actions based on active round type
- Improve pipeline visualization: progress bars, checkmarks, chevron connectors, overflow fix
- Add config tab completion dots (green/amber/red) and inline validation warnings
- Enhance juries page with round assignments, member avatars, and cap mode badges
- Add context-aware project list (recent submissions vs active evaluations)
- Move competition settings into Manage Editions page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 17:14:00 +01:00
parent f7bc3b4dd2
commit f26ee3f076
51 changed files with 4530 additions and 6276 deletions

View File

@@ -4,7 +4,7 @@ import Link from 'next/link'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import { motion } from 'motion/react'
import { Workflow, ArrowRight } from 'lucide-react'
import { Workflow, ArrowRight, ChevronRight } from 'lucide-react'
import {
PipelineRoundNode,
type PipelineRound,
@@ -12,27 +12,46 @@ import {
function Connector({
prevStatus,
nextStatus,
index,
}: {
prevStatus: string
nextStatus: string
index: number
}) {
const isCompleted =
prevStatus === 'ROUND_CLOSED' || prevStatus === 'ROUND_ARCHIVED'
const isNextActive = nextStatus === 'ROUND_ACTIVE'
return (
<motion.div
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.25, delay: 0.15 + index * 0.06 }}
className="flex items-center self-center origin-left"
initial={{ scaleX: 0, opacity: 0 }}
animate={{ scaleX: 1, opacity: 1 }}
transition={{ duration: 0.3, delay: 0.15 + index * 0.06 }}
className="flex items-center self-center origin-left px-0.5"
>
<div
className={cn(
'h-0.5 w-6',
isCompleted ? 'bg-emerald-300' : 'bg-slate-200'
)}
/>
<div className="flex items-center gap-0">
<div
className={cn(
'h-0.5 w-5 transition-colors',
isCompleted
? 'bg-emerald-400'
: isNextActive
? 'bg-blue-300'
: 'bg-slate-200'
)}
/>
<ChevronRight
className={cn(
'h-3.5 w-3.5 -ml-1',
isCompleted
? 'text-emerald-400'
: isNextActive
? 'text-blue-300'
: 'text-slate-200'
)}
/>
</div>
</motion.div>
)
}
@@ -88,15 +107,17 @@ export function CompetitionPipeline({
</Link>
</div>
</CardHeader>
<CardContent>
<div className="overflow-x-auto pb-2">
<div className="flex items-start gap-0 min-w-max">
<CardContent className="px-4 pb-4">
{/* Scrollable container with padding to prevent cutoff */}
<div className="overflow-x-auto -mx-1 px-1 pt-2 pb-3">
<div className="flex items-center gap-0 min-w-max">
{rounds.map((round, index) => (
<div key={round.id} className="flex items-start">
<div key={round.id} className="flex items-center">
<PipelineRoundNode round={round} index={index} />
{index < rounds.length - 1 && (
<Connector
prevStatus={round.status}
nextStatus={rounds[index + 1].status}
index={index}
/>
)}