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>
106 lines
3.1 KiB
TypeScript
106 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import { motion } from 'motion/react'
|
|
import { formatEnumLabel } from '@/lib/utils'
|
|
|
|
type PipelineRound = {
|
|
id: string
|
|
name: string
|
|
roundType: string
|
|
status: string
|
|
projectStates: {
|
|
PENDING: number
|
|
IN_PROGRESS: number
|
|
PASSED: number
|
|
REJECTED: number
|
|
COMPLETED: number
|
|
WITHDRAWN: number
|
|
total: number
|
|
}
|
|
liveSessionStatus: string | null
|
|
assignmentCount: number
|
|
}
|
|
|
|
type RoundStatsLiveFinalProps = {
|
|
round: PipelineRound
|
|
}
|
|
|
|
export function RoundStatsLiveFinal({ round }: RoundStatsLiveFinalProps) {
|
|
const { projectStates, liveSessionStatus, assignmentCount } = round
|
|
const sessionLabel = liveSessionStatus
|
|
? formatEnumLabel(liveSessionStatus)
|
|
: 'Not started'
|
|
|
|
const stats = [
|
|
{
|
|
value: projectStates.total,
|
|
label: 'Presenting',
|
|
detail: 'Projects in finals',
|
|
accent: 'text-brand-blue',
|
|
},
|
|
{
|
|
value: sessionLabel,
|
|
label: 'Session',
|
|
detail: liveSessionStatus ? 'Live session active' : 'No session yet',
|
|
accent: liveSessionStatus ? 'text-emerald-600' : 'text-amber-600',
|
|
isText: true,
|
|
},
|
|
{
|
|
value: projectStates.COMPLETED,
|
|
label: 'Scored',
|
|
detail: `${projectStates.total - projectStates.COMPLETED} remaining`,
|
|
accent: 'text-emerald-600',
|
|
},
|
|
{
|
|
value: assignmentCount,
|
|
label: 'Jury votes',
|
|
detail: 'Jury assignments',
|
|
accent: 'text-brand-teal',
|
|
},
|
|
]
|
|
|
|
return (
|
|
<>
|
|
<p className="mb-2 text-[11px] font-semibold uppercase tracking-widest text-muted-foreground/70">
|
|
{round.name} — Live Finals
|
|
</p>
|
|
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ duration: 0.4 }}
|
|
className="flex items-baseline justify-between border-b border-t py-3 md:hidden"
|
|
>
|
|
{stats.map((s, i) => (
|
|
<div key={i} className={`flex-1 text-center ${i > 0 ? 'border-l border-border/50' : ''}`}>
|
|
<span className={`font-bold tabular-nums tracking-tight ${(s as any).isText ? 'text-sm' : 'text-xl'}`}>
|
|
{s.value}
|
|
</span>
|
|
<p className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground mt-0.5">{s.label}</p>
|
|
</div>
|
|
))}
|
|
</motion.div>
|
|
|
|
<div className="hidden md:block">
|
|
<div className="grid grid-cols-4 gap-px rounded-lg bg-border/40 overflow-hidden">
|
|
{stats.map((s, i) => (
|
|
<motion.div
|
|
key={i}
|
|
initial={{ opacity: 0, y: 8 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: i * 0.06 }}
|
|
className="bg-background px-5 py-4 group hover:bg-muted/30 transition-colors"
|
|
>
|
|
<span className={`font-bold tabular-nums tracking-tight ${(s as any).isText ? 'text-lg' : 'text-3xl'}`}>
|
|
{s.value}
|
|
</span>
|
|
<p className="text-xs font-medium text-muted-foreground mt-1">{s.label}</p>
|
|
<p className={`text-xs mt-0.5 ${s.accent}`}>{s.detail}</p>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|