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
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:
@@ -20,6 +20,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { toast } from 'sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { Route } from 'next'
|
||||
import {
|
||||
ArrowLeft,
|
||||
MoreHorizontal,
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
Loader2,
|
||||
ChevronDown,
|
||||
Save,
|
||||
Wand2,
|
||||
} from 'lucide-react'
|
||||
|
||||
import { InlineEditableText } from '@/components/ui/inline-editable-text'
|
||||
@@ -41,8 +43,8 @@ import { AwardsSection } from '@/components/admin/pipeline/sections/awards-secti
|
||||
import { NotificationsSection } from '@/components/admin/pipeline/sections/notifications-section'
|
||||
import { RoutingRulesEditor } from '@/components/admin/pipeline/routing-rules-editor'
|
||||
import { AwardGovernanceEditor } from '@/components/admin/pipeline/award-governance-editor'
|
||||
import { normalizeStageConfig } from '@/lib/stage-config-schema'
|
||||
import { defaultNotificationConfig } from '@/lib/pipeline-defaults'
|
||||
import { toWizardTrackConfig } from '@/lib/pipeline-conversions'
|
||||
import type { WizardTrackConfig } from '@/types/pipeline-wizard'
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
@@ -52,71 +54,6 @@ const statusColors: Record<string, string> = {
|
||||
CLOSED: 'bg-blue-100 text-blue-700',
|
||||
}
|
||||
|
||||
function toWizardTrackConfig(
|
||||
track: {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
kind: 'MAIN' | 'AWARD' | 'SHOWCASE'
|
||||
sortOrder: number
|
||||
routingMode: 'PARALLEL' | 'EXCLUSIVE' | 'POST_MAIN' | null
|
||||
decisionMode:
|
||||
| 'JURY_VOTE'
|
||||
| 'AWARD_MASTER_DECISION'
|
||||
| 'ADMIN_DECISION'
|
||||
| null
|
||||
stages: Array<{
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
stageType:
|
||||
| 'INTAKE'
|
||||
| 'FILTER'
|
||||
| 'EVALUATION'
|
||||
| 'SELECTION'
|
||||
| 'LIVE_FINAL'
|
||||
| 'RESULTS'
|
||||
sortOrder: number
|
||||
configJson: unknown
|
||||
}>
|
||||
specialAward?: {
|
||||
name: string
|
||||
description: string | null
|
||||
scoringMode: 'PICK_WINNER' | 'RANKED' | 'SCORED'
|
||||
} | null
|
||||
}
|
||||
): WizardTrackConfig {
|
||||
return {
|
||||
id: track.id,
|
||||
name: track.name,
|
||||
slug: track.slug,
|
||||
kind: track.kind,
|
||||
sortOrder: track.sortOrder,
|
||||
routingModeDefault: track.routingMode ?? undefined,
|
||||
decisionMode: track.decisionMode ?? undefined,
|
||||
stages: track.stages
|
||||
.map((stage) => ({
|
||||
id: stage.id,
|
||||
name: stage.name,
|
||||
slug: stage.slug,
|
||||
stageType: stage.stageType,
|
||||
sortOrder: stage.sortOrder,
|
||||
configJson: normalizeStageConfig(
|
||||
stage.stageType,
|
||||
stage.configJson as Record<string, unknown> | null
|
||||
),
|
||||
}))
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder),
|
||||
awardConfig: track.specialAward
|
||||
? {
|
||||
name: track.specialAward.name,
|
||||
description: track.specialAward.description ?? undefined,
|
||||
scoringMode: track.specialAward.scoringMode,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export default function PipelineDetailPage() {
|
||||
const params = useParams()
|
||||
const pipelineId = params.id as string
|
||||
@@ -450,6 +387,13 @@ export default function PipelineDetailPage() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={`/admin/rounds/pipeline/${pipelineId}/wizard` as Route}>
|
||||
<Wand2 className="h-4 w-4 mr-2" />
|
||||
Edit in Wizard
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{pipeline.status === 'DRAFT' && (
|
||||
<DropdownMenuItem
|
||||
disabled={publishMutation.isPending}
|
||||
@@ -660,10 +604,6 @@ export default function PipelineDetailPage() {
|
||||
{/* Routing Rules (only if multiple tracks) */}
|
||||
{hasMultipleTracks && (
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold border-b pb-2 mb-4">Routing Rules</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Define conditions for routing projects between tracks.
|
||||
</p>
|
||||
<RoutingRulesEditor
|
||||
pipelineId={pipelineId}
|
||||
tracks={trackOptionsForEditors}
|
||||
|
||||
Reference in New Issue
Block a user