Redesign admin dashboard: pipeline view, round-specific stats, smart actions
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m18s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m18s
Complete rewrite of the admin dashboard replacing the 1056-line monolith with modular components. Key improvements: - Competition pipeline: horizontal visualization of all rounds in pipeline order (by sortOrder, not creation date) with type-specific icons and metrics - Round-specific stats: stat cards dynamically change based on active round type (INTAKE shows submissions/docs, FILTERING shows pass/fail/flagged, EVALUATION shows assignments/completion, fallback shows generic project/jury stats) - Smart actions: context-aware "Action Required" panel that only flags the next draft round (not all), only flags unassigned projects for EVALUATION rounds, includes deadline warnings with severity levels (critical/warning/info) - Active round panel: detailed view with project state bar, type-specific content, and deadline countdown - Extracted components: project list, activity feed, category breakdown, skeleton Backend enriched with 17 parallel queries building rich PipelineRound data including per-round project states, eval stats, filtering stats, live session status, and deliberation counts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
110
src/components/dashboard/competition-pipeline.tsx
Normal file
110
src/components/dashboard/competition-pipeline.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
'use client'
|
||||
|
||||
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 {
|
||||
PipelineRoundNode,
|
||||
type PipelineRound,
|
||||
} from '@/components/dashboard/pipeline-round-node'
|
||||
|
||||
function Connector({
|
||||
prevStatus,
|
||||
index,
|
||||
}: {
|
||||
prevStatus: string
|
||||
index: number
|
||||
}) {
|
||||
const isCompleted =
|
||||
prevStatus === 'ROUND_CLOSED' || prevStatus === 'ROUND_ARCHIVED'
|
||||
|
||||
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"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'h-0.5 w-6',
|
||||
isCompleted ? 'bg-emerald-300' : 'bg-slate-200'
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CompetitionPipeline({
|
||||
rounds,
|
||||
}: {
|
||||
rounds: PipelineRound[]
|
||||
}) {
|
||||
if (rounds.length === 0) {
|
||||
return (
|
||||
<Card className="border-dashed">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-slate-500/10">
|
||||
<Workflow className="h-4 w-4 text-slate-500" />
|
||||
</div>
|
||||
<CardTitle className="text-base">Competition Pipeline</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col items-center justify-center py-10 text-center">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-muted">
|
||||
<Workflow className="h-7 w-7 text-muted-foreground/40" />
|
||||
</div>
|
||||
<p className="mt-3 text-sm font-medium text-muted-foreground">
|
||||
No rounds configured yet
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Create rounds to visualize the competition pipeline.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-brand-teal/10">
|
||||
<Workflow className="h-4 w-4 text-brand-teal" />
|
||||
</div>
|
||||
<CardTitle className="text-base">Competition Pipeline</CardTitle>
|
||||
</div>
|
||||
<Link
|
||||
href="/admin/rounds"
|
||||
className="flex items-center gap-1 text-xs font-semibold uppercase tracking-wider text-brand-teal hover:text-brand-teal-light transition-colors"
|
||||
>
|
||||
All rounds <ArrowRight className="h-3 w-3" />
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="overflow-x-auto pb-2">
|
||||
<div className="flex items-start gap-0 min-w-max">
|
||||
{rounds.map((round, index) => (
|
||||
<div key={round.id} className="flex items-start">
|
||||
<PipelineRoundNode round={round} index={index} />
|
||||
{index < rounds.length - 1 && (
|
||||
<Connector
|
||||
prevStatus={round.status}
|
||||
index={index}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user