Redesign admin dashboard: pipeline view, round-specific stats, smart actions
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:
Matt
2026-02-17 11:12:28 +01:00
parent 842e79e319
commit 1a0525c108
17 changed files with 2367 additions and 1025 deletions

View File

@@ -0,0 +1,78 @@
'use client'
import { Card, CardContent, CardHeader } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
export function DashboardSkeleton() {
return (
<>
{/* Header skeleton */}
<Skeleton className="h-32 w-full rounded-2xl" />
{/* Stats row skeleton */}
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
{[...Array(5)].map((_, i) => (
<Card key={i} className="border-l-4 border-l-muted">
<CardContent className="p-4">
<Skeleton className="h-4 w-16" />
<Skeleton className="mt-2 h-8 w-12" />
<Skeleton className="mt-1 h-3 w-20" />
</CardContent>
</Card>
))}
</div>
{/* Two-column content skeleton */}
<div className="grid gap-6 lg:grid-cols-12">
<div className="space-y-6 lg:col-span-8">
<Card>
<CardHeader>
<Skeleton className="h-5 w-32" />
<Skeleton className="h-3 w-48" />
</CardHeader>
<CardContent>
<div className="space-y-3">
{[...Array(3)].map((_, i) => (
<Skeleton key={i} className="h-24 w-full rounded-xl" />
))}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<Skeleton className="h-5 w-40" />
<Skeleton className="h-3 w-32" />
</CardHeader>
<CardContent>
<div className="space-y-2">
{[...Array(5)].map((_, i) => (
<Skeleton key={i} className="h-14 w-full" />
))}
</div>
</CardContent>
</Card>
</div>
<div className="space-y-6 lg:col-span-4">
{[...Array(4)].map((_, i) => (
<Card key={i}>
<CardHeader><Skeleton className="h-5 w-32" /></CardHeader>
<CardContent>
<div className="space-y-3">
{[...Array(3)].map((_, j) => (
<Skeleton key={j} className="h-10 w-full" />
))}
</div>
</CardContent>
</Card>
))}
</div>
</div>
{/* Bottom skeleton */}
<div className="grid gap-6 lg:grid-cols-12">
<Skeleton className="h-[400px] w-full rounded-lg lg:col-span-8" />
<Skeleton className="h-[400px] w-full rounded-lg lg:col-span-4" />
</div>
</>
)
}