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

264
src/lib/round-config.ts Normal file
View File

@@ -0,0 +1,264 @@
/**
* Shared round type, status, and project state configuration.
*
* Single source of truth for colors, labels, icons, and descriptions
* used across the admin dashboard, round detail, pipeline, and timeline.
*/
import type { RoundType, RoundStatus, ProjectRoundStateValue } from '@prisma/client'
import {
Upload,
Filter,
ClipboardCheck,
FileUp,
GraduationCap,
Radio,
Scale,
Inbox,
Users,
Circle,
Clock,
CheckCircle2,
} from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
// ── Round Type Configuration ─────────────────────────────────────────────────
export type RoundTypeConfig = {
label: string
description: string
/** Badge classes: bg + text color */
badgeClass: string
/** Hex dot color for pipeline nodes */
dotColor: string
/** Lighter variant: bg, text, border classes for cards/filters */
cardBg: string
cardText: string
cardBorder: string
/** Icon for pipeline nodes and round detail */
icon: LucideIcon
iconColor: string
iconBg: string
}
export const roundTypeConfig: Record<RoundType, RoundTypeConfig> = {
INTAKE: {
label: 'Intake',
description: 'Collecting applications',
badgeClass: 'bg-gray-100 text-gray-700',
dotColor: '#9ca3af',
cardBg: 'bg-gray-50',
cardText: 'text-gray-600',
cardBorder: 'border-gray-300',
icon: Inbox,
iconColor: 'text-sky-600',
iconBg: 'bg-sky-100',
},
FILTERING: {
label: 'Filtering',
description: 'AI + manual screening',
badgeClass: 'bg-amber-100 text-amber-700',
dotColor: '#f59e0b',
cardBg: 'bg-amber-50',
cardText: 'text-amber-700',
cardBorder: 'border-amber-300',
icon: Filter,
iconColor: 'text-amber-600',
iconBg: 'bg-amber-100',
},
EVALUATION: {
label: 'Evaluation',
description: 'Jury evaluation & scoring',
badgeClass: 'bg-blue-100 text-blue-700',
dotColor: '#3b82f6',
cardBg: 'bg-blue-50',
cardText: 'text-blue-700',
cardBorder: 'border-blue-300',
icon: ClipboardCheck,
iconColor: 'text-violet-600',
iconBg: 'bg-violet-100',
},
SUBMISSION: {
label: 'Submission',
description: 'Document submission',
badgeClass: 'bg-purple-100 text-purple-700',
dotColor: '#8b5cf6',
cardBg: 'bg-purple-50',
cardText: 'text-purple-700',
cardBorder: 'border-purple-300',
icon: FileUp,
iconColor: 'text-blue-600',
iconBg: 'bg-blue-100',
},
MENTORING: {
label: 'Mentoring',
description: 'Mentor-guided development',
badgeClass: 'bg-teal-100 text-teal-700',
dotColor: '#557f8c',
cardBg: 'bg-teal-50',
cardText: 'text-teal-700',
cardBorder: 'border-teal-300',
icon: GraduationCap,
iconColor: 'text-teal-600',
iconBg: 'bg-teal-100',
},
LIVE_FINAL: {
label: 'Live Final',
description: 'Live presentations & voting',
badgeClass: 'bg-red-100 text-red-700',
dotColor: '#de0f1e',
cardBg: 'bg-red-50',
cardText: 'text-red-700',
cardBorder: 'border-red-300',
icon: Radio,
iconColor: 'text-red-600',
iconBg: 'bg-red-100',
},
DELIBERATION: {
label: 'Deliberation',
description: 'Final jury deliberation',
badgeClass: 'bg-indigo-100 text-indigo-700',
dotColor: '#6366f1',
cardBg: 'bg-indigo-50',
cardText: 'text-indigo-700',
cardBorder: 'border-indigo-300',
icon: Scale,
iconColor: 'text-indigo-600',
iconBg: 'bg-indigo-100',
},
}
// ── Round Status Configuration ───────────────────────────────────────────────
export type RoundStatusConfig = {
label: string
description: string
/** Badge classes for status badges */
bgClass: string
/** Dot color class (with optional animation) */
dotClass: string
/** Hex color for pipeline dot */
dotColor: string
/** Whether the dot should pulse (active round) */
pulse: boolean
/** Icon for timeline displays */
timelineIcon: LucideIcon
timelineIconColor: string
/** Container classes for pipeline nodes */
pipelineContainer: string
}
export const roundStatusConfig: Record<RoundStatus, RoundStatusConfig> = {
ROUND_DRAFT: {
label: 'Draft',
description: 'Not yet active. Configure before launching.',
bgClass: 'bg-gray-100 text-gray-700',
dotClass: 'bg-gray-500',
dotColor: '#9ca3af',
pulse: false,
timelineIcon: Circle,
timelineIconColor: 'text-gray-400',
pipelineContainer: 'bg-slate-50 border-slate-200 text-slate-400 border-dashed',
},
ROUND_ACTIVE: {
label: 'Active',
description: 'Round is live. Projects can be processed.',
bgClass: 'bg-emerald-100 text-emerald-700',
dotClass: 'bg-emerald-500 animate-pulse',
dotColor: '#10b981',
pulse: true,
timelineIcon: Clock,
timelineIconColor: 'text-emerald-500',
pipelineContainer: 'bg-blue-50 border-blue-300 text-blue-700 ring-2 ring-blue-400/30 shadow-lg shadow-blue-500/10',
},
ROUND_CLOSED: {
label: 'Closed',
description: 'No longer accepting changes. Results are final.',
bgClass: 'bg-blue-100 text-blue-700',
dotClass: 'bg-blue-500',
dotColor: '#3b82f6',
pulse: false,
timelineIcon: CheckCircle2,
timelineIconColor: 'text-blue-500',
pipelineContainer: 'bg-emerald-50 border-emerald-200 text-emerald-600',
},
ROUND_ARCHIVED: {
label: 'Archived',
description: 'Historical record only.',
bgClass: 'bg-muted text-muted-foreground',
dotClass: 'bg-muted-foreground',
dotColor: '#6b7280',
pulse: false,
timelineIcon: CheckCircle2,
timelineIconColor: 'text-gray-400',
pipelineContainer: 'bg-gray-50 border-gray-200 text-gray-400 opacity-60',
},
}
// ── Project Round State Colors ───────────────────────────────────────────────
export type ProjectStateConfig = {
label: string
/** Background color class for bars and dots */
bg: string
/** Text color class */
text: string
/** Badge variant classes */
badgeClass: string
}
export const projectStateConfig: Record<ProjectRoundStateValue, ProjectStateConfig> = {
PENDING: {
label: 'Pending',
bg: 'bg-slate-300',
text: 'text-slate-700',
badgeClass: 'bg-gray-100 text-gray-700',
},
IN_PROGRESS: {
label: 'In Progress',
bg: 'bg-blue-400',
text: 'text-blue-700',
badgeClass: 'bg-blue-100 text-blue-700',
},
PASSED: {
label: 'Passed',
bg: 'bg-emerald-500',
text: 'text-emerald-700',
badgeClass: 'bg-emerald-100 text-emerald-700',
},
REJECTED: {
label: 'Rejected',
bg: 'bg-red-400',
text: 'text-red-700',
badgeClass: 'bg-red-100 text-red-700',
},
COMPLETED: {
label: 'Completed',
bg: 'bg-[#557f8c]',
text: 'text-teal-700',
badgeClass: 'bg-teal-100 text-teal-700',
},
WITHDRAWN: {
label: 'Withdrawn',
bg: 'bg-slate-400',
text: 'text-slate-600',
badgeClass: 'bg-orange-100 text-orange-700',
},
}
// ── Award Status Configuration ───────────────────────────────────────────────
export const awardStatusConfig = {
DRAFT: { label: 'Draft', color: 'text-gray-500', bgClass: 'bg-gray-100 text-gray-700' },
NOMINATIONS_OPEN: { label: 'Nominations Open', color: 'text-amber-600', bgClass: 'bg-amber-100 text-amber-700' },
VOTING_OPEN: { label: 'Voting Open', color: 'text-emerald-600', bgClass: 'bg-emerald-100 text-emerald-700' },
CLOSED: { label: 'Closed', color: 'text-blue-500', bgClass: 'bg-blue-100 text-blue-700' },
ARCHIVED: { label: 'Archived', color: 'text-gray-400', bgClass: 'bg-gray-100 text-gray-600' },
} as const
// ── Round type option list (for selects/filters) ────────────────────────────
export const ROUND_TYPE_OPTIONS = Object.entries(roundTypeConfig).map(([value, cfg]) => ({
value: value as RoundType,
label: cfg.label,
}))