UI/UX audit fixes: clickable pipelines, broken links, isActive locking

- Make pipeline cards clickable on list page (navigate to detail view)
- Fix broken nav link: applicant /messages → /mentor
- Fix broken nav link: mentor /messages → /projects
- Add isActive field locking to all 7 wizard sections (intake, main-track,
  filtering, assignment, awards, live-finals, notifications)
- Add minLoad ≤ maxLoad cross-field validation in assignment section
- Add duplicate stage slug detection in main track section
- Add active pipeline warning banners in intake and main track sections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 20:50:22 +01:00
parent 31225b099e
commit 7d1c87e938
11 changed files with 149 additions and 79 deletions

View File

@@ -309,6 +309,7 @@ export default function EditPipelinePage() {
onChange={(c) =>
updateStageConfig('INTAKE', c as unknown as Record<string, unknown>)
}
isActive={isActive}
/>
</WizardSection>
@@ -323,6 +324,7 @@ export default function EditPipelinePage() {
<MainTrackSection
stages={mainTrack?.stages ?? []}
onChange={updateMainTrackStages}
isActive={isActive}
/>
</WizardSection>
@@ -339,6 +341,7 @@ export default function EditPipelinePage() {
onChange={(c) =>
updateStageConfig('FILTER', c as unknown as Record<string, unknown>)
}
isActive={isActive}
/>
</WizardSection>
@@ -355,6 +358,7 @@ export default function EditPipelinePage() {
onChange={(c) =>
updateStageConfig('EVALUATION', c as unknown as Record<string, unknown>)
}
isActive={isActive}
/>
</WizardSection>
@@ -369,6 +373,7 @@ export default function EditPipelinePage() {
<AwardsSection
tracks={state.tracks}
onChange={(tracks) => updateState({ tracks })}
isActive={isActive}
/>
</WizardSection>
@@ -385,6 +390,7 @@ export default function EditPipelinePage() {
onChange={(c) =>
updateStageConfig('LIVE_FINAL', c as unknown as Record<string, unknown>)
}
isActive={isActive}
/>
</WizardSection>
@@ -403,6 +409,7 @@ export default function EditPipelinePage() {
onOverridePolicyChange={(overridePolicy) =>
updateState({ overridePolicy })
}
isActive={isActive}
/>
</WizardSection>

View File

@@ -133,71 +133,78 @@ export default function PipelineListPage() {
{pipelines && pipelines.length > 0 && (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{pipelines.map((pipeline) => (
<Card key={pipeline.id} className="group hover:shadow-md transition-shadow">
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">
<CardTitle className="text-base truncate">
{pipeline.name}
</CardTitle>
<CardDescription className="font-mono text-xs">
{pipeline.slug}
</CardDescription>
<Link
key={pipeline.id}
href={`/admin/rounds/pipeline/${pipeline.id}` as Route}
className="block"
>
<Card className="group hover:shadow-md transition-shadow cursor-pointer">
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="min-w-0 flex-1">
<CardTitle className="text-base truncate">
{pipeline.name}
</CardTitle>
<CardDescription className="font-mono text-xs">
{pipeline.slug}
</CardDescription>
</div>
<div className="flex items-center gap-2">
<Badge
variant="secondary"
className={cn(
'text-[10px] shrink-0',
statusColors[pipeline.status] ?? ''
)}
>
{pipeline.status}
</Badge>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => e.preventDefault()}
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild>
<Link href={`/admin/rounds/pipeline/${pipeline.id}` as Route}>
<Eye className="h-4 w-4 mr-2" />
View
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/admin/rounds/pipeline/${pipeline.id}/edit` as Route}>
<Edit className="h-4 w-4 mr-2" />
Edit
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="flex items-center gap-2">
<Badge
variant="secondary"
className={cn(
'text-[10px] shrink-0',
statusColors[pipeline.status] ?? ''
)}
>
{pipeline.status}
</Badge>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 opacity-0 group-hover:opacity-100 transition-opacity"
>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild>
<Link href={`/admin/rounds/pipeline/${pipeline.id}` as Route}>
<Eye className="h-4 w-4 mr-2" />
View
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/admin/rounds/pipeline/${pipeline.id}/edit` as Route}>
<Edit className="h-4 w-4 mr-2" />
Edit
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</CardHeader>
<CardContent>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Layers className="h-3.5 w-3.5" />
<span>{pipeline._count.tracks} tracks</span>
</div>
<div className="flex items-center gap-1">
<GitBranch className="h-3.5 w-3.5" />
<span>{pipeline._count.routingRules} rules</span>
</div>
</div>
</div>
</CardHeader>
<CardContent>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Layers className="h-3.5 w-3.5" />
<span>{pipeline._count.tracks} tracks</span>
</div>
<div className="flex items-center gap-1">
<GitBranch className="h-3.5 w-3.5" />
<span>{pipeline._count.routingRules} rules</span>
</div>
</div>
<p className="text-xs text-muted-foreground mt-2">
Created {format(new Date(pipeline.createdAt), 'MMM d, yyyy')}
</p>
</CardContent>
</Card>
<p className="text-xs text-muted-foreground mt-2">
Created {format(new Date(pipeline.createdAt), 'MMM d, yyyy')}
</p>
</CardContent>
</Card>
</Link>
))}
</div>
)}

View File

@@ -250,7 +250,7 @@ export default function ApplicantPipelinePage() {
</div>
</Link>
<Link
href={"/applicant/messages" as Route}
href={"/applicant/mentor" as Route}
className="group flex items-center gap-3 rounded-xl border p-4 transition-all hover:border-amber-500/30 hover:bg-amber-50/50 hover:-translate-y-0.5 hover:shadow-md"
>
<div className="rounded-lg bg-amber-50 p-2 dark:bg-amber-950/40">

View File

@@ -189,7 +189,7 @@ export default function MentorDashboard() {
{/* Quick Actions */}
<div className="flex flex-wrap gap-2">
<Button variant="outline" size="sm" asChild>
<Link href={'/mentor/messages' as Route}>
<Link href={'/mentor/projects' as Route}>
<Mail className="mr-2 h-4 w-4" />
Messages
</Link>