Pipeline UX: clickable cards, wizard edit, routing rules redesign, category quotas
All checks were successful
Build and Push Docker Image / build (push) Successful in 18s

- Simplify pipeline list cards: whole card is clickable, remove clutter
- Add wizard edit page for existing pipelines with full state pre-population
- Extract toWizardTrackConfig to shared utility for reuse
- Rewrite predicate builder with 3 modes: Simple (sentence-style), AI (NLP), Advanced (JSON)
- Fix routing operators to match backend (eq/neq/in/contains/gt/lt)
- Rewrite routing rules editor with collapsible cards and natural language summaries
- Add parseNaturalLanguageRule AI procedure for routing rules
- Add per-category quotas to SelectionConfig and EvaluationConfig
- Add category quota UI toggles to selection and assignment sections
- Add category breakdown display to selection panel
- Add category-aware scoring to smart assignment (penalty/bonus)
- Add category-aware filtering targets with excess demotion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-14 20:10:24 +01:00
parent c634982835
commit 382570cebd
17 changed files with 2577 additions and 1095 deletions

View File

@@ -12,14 +12,12 @@ import {
CardTitle,
} from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
import {
Plus,
Layers,
GitBranch,
Calendar,
Workflow,
Pencil,
} from 'lucide-react'
import {
Plus,
Layers,
Calendar,
Workflow,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { formatDistanceToNow } from 'date-fns'
import { useEdition } from '@/contexts/edition-context'
@@ -148,19 +146,15 @@ export default function PipelineListPage() {
const config = statusConfig[status] || statusConfig.DRAFT
const description = (pipeline.settingsJson as Record<string, unknown> | null)?.description as string | undefined
return (
<Card key={pipeline.id} className="group hover:shadow-md transition-shadow h-full flex flex-col">
<CardHeader className="pb-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<CardTitle className="text-base leading-tight mb-1">
<Link href={`/admin/rounds/pipeline/${pipeline.id}` as Route} className="hover:underline">
{pipeline.name}
</Link>
</CardTitle>
<p className="font-mono text-xs text-muted-foreground truncate">
{pipeline.slug}
</p>
return (
<Link key={pipeline.id} href={`/admin/rounds/pipeline/${pipeline.id}` as Route}>
<Card className="group cursor-pointer hover:shadow-md transition-shadow h-full flex flex-col">
<CardHeader className="pb-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<CardTitle className="text-base leading-tight">
{pipeline.name}
</CardTitle>
</div>
<Badge
variant="secondary"
@@ -174,7 +168,6 @@ export default function PipelineListPage() {
</Badge>
</div>
{/* Description */}
{description && (
<p className="text-xs text-muted-foreground line-clamp-2 mt-2">
{description}
@@ -182,10 +175,9 @@ export default function PipelineListPage() {
)}
</CardHeader>
<CardContent className="mt-auto">
{/* Track Indicator - Simplified visualization */}
<div className="mb-3 pb-3 border-b">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground mb-1.5">
<CardContent className="mt-auto">
<div className="flex items-center justify-between text-xs text-muted-foreground">
<div className="flex items-center gap-1.5">
<Layers className="h-3.5 w-3.5" />
<span className="font-medium">
{pipeline._count.tracks === 0
@@ -195,56 +187,15 @@ export default function PipelineListPage() {
: `${pipeline._count.tracks} tracks`}
</span>
</div>
{pipeline._count.tracks > 0 && (
<div className="flex items-center gap-1">
{Array.from({ length: Math.min(pipeline._count.tracks, 5) }).map((_, i) => (
<div
key={i}
className="h-6 flex-1 rounded border border-border bg-muted/30 flex items-center justify-center"
>
<div className="h-1 w-1 rounded-full bg-muted-foreground/40" />
</div>
))}
{pipeline._count.tracks > 5 && (
<span className="text-[10px] text-muted-foreground ml-1">
+{pipeline._count.tracks - 5}
</span>
)}
</div>
)}
<span>Updated {formatDistanceToNow(new Date(pipeline.updatedAt))} ago</span>
</div>
{/* Stats */}
<div className="space-y-1.5">
<div className="flex items-center justify-between text-xs">
<div className="flex items-center gap-1.5 text-muted-foreground">
<GitBranch className="h-3.5 w-3.5" />
<span>Routing rules</span>
</div>
<span className="font-medium text-foreground">
{pipeline._count.routingRules}
</span>
</div>
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>Updated {formatDistanceToNow(new Date(pipeline.updatedAt))} ago</span>
</div>
</div>
<div className="mt-3 flex items-center gap-2">
<Link href={`/admin/rounds/pipeline/${pipeline.id}/edit` as Route} className="w-full">
<Button size="sm" variant="outline" className="w-full">
<Pencil className="h-3.5 w-3.5 mr-1.5" />
Edit
</Button>
</Link>
</div>
</CardContent>
</Card>
)
})}
</div>
)}
</CardContent>
</Card>
</Link>
)
})}
</div>
)}
</div>
)
}