Admin dashboard & round management UX overhaul
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m43s
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:
@@ -34,8 +34,8 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { toast } from 'sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Plus, Scale, Users, Loader2 } from 'lucide-react'
|
||||
import { cn, formatEnumLabel } from '@/lib/utils'
|
||||
import { Plus, Scale, Users, Loader2, ArrowRight, CircleDot } from 'lucide-react'
|
||||
|
||||
const capModeLabels = {
|
||||
HARD: 'Hard Cap',
|
||||
@@ -267,33 +267,82 @@ function CompetitionJuriesSection({ competition }: CompetitionJuriesSectionProps
|
||||
No jury groups configured for this competition.
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="space-y-3">
|
||||
{juryGroups.map((group) => (
|
||||
<Link key={group.id} href={`/admin/juries/${group.id}` as Route}>
|
||||
<Card className="transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md cursor-pointer">
|
||||
<Card className="transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md cursor-pointer group">
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
{/* Header row */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="font-semibold text-sm line-clamp-1">{group.name}</h3>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn('text-[10px] shrink-0', capModeColors[group.defaultCapMode as keyof typeof capModeColors])}
|
||||
>
|
||||
{capModeLabels[group.defaultCapMode as keyof typeof capModeLabels]}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-1">
|
||||
<Users className="h-3 w-3" />
|
||||
<span>{group._count.members} members</span>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-brand-blue/10 shrink-0">
|
||||
<Scale className="h-4 w-4 text-brand-blue" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-semibold text-sm line-clamp-1 group-hover:text-brand-blue transition-colors">
|
||||
{group.name}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{group._count.members} member{group._count.members !== 1 ? 's' : ''}
|
||||
{' · '}
|
||||
{group._count.assignments} assignment{group._count.assignments !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{group._count.assignments} assignments
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn('text-[10px]', capModeColors[group.defaultCapMode as keyof typeof capModeColors])}
|
||||
>
|
||||
{capModeLabels[group.defaultCapMode as keyof typeof capModeLabels]}
|
||||
</Badge>
|
||||
<ArrowRight className="h-4 w-4 text-muted-foreground/40 group-hover:text-brand-blue transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Default max: {group.defaultMaxAssignments}
|
||||
</div>
|
||||
|
||||
{/* Round assignments */}
|
||||
{(group as any).rounds?.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{(group as any).rounds.map((r: any) => (
|
||||
<Badge
|
||||
key={r.id}
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'text-[10px] gap-1',
|
||||
r.status === 'ROUND_ACTIVE' && 'border-blue-300 bg-blue-50 text-blue-700',
|
||||
r.status === 'ROUND_CLOSED' && 'border-emerald-300 bg-emerald-50 text-emerald-700',
|
||||
r.status === 'ROUND_DRAFT' && 'border-slate-200 text-slate-500',
|
||||
)}
|
||||
>
|
||||
<CircleDot className="h-2.5 w-2.5" />
|
||||
{r.name}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Member preview */}
|
||||
{(group as any).members?.length > 0 && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="flex -space-x-1.5">
|
||||
{(group as any).members.slice(0, 5).map((m: any) => (
|
||||
<div
|
||||
key={m.id}
|
||||
className="h-6 w-6 rounded-full bg-brand-blue/10 border-2 border-white flex items-center justify-center text-[9px] font-semibold text-brand-blue"
|
||||
title={m.user?.name || m.user?.email}
|
||||
>
|
||||
{(m.user?.name || m.user?.email || '?').charAt(0).toUpperCase()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{group._count.members > 5 && (
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
+{group._count.members - 5} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user