feat: Replace floating Execute button with pipeline summary bar

The orphaned Execute button is now part of a summary bar that shows
pipeline stats (phase count, task count, running/completed indicators)
with the execute action anchored to the right. The bar is always visible,
providing context even when no phases are actionable.
This commit is contained in:
Lukas May
2026-03-04 13:12:12 +01:00
parent bf635375af
commit baca008447

View File

@@ -1,6 +1,7 @@
import { useEffect, useMemo } from "react";
import { Loader2, Play } from "lucide-react";
import { CheckCircle2, CircleDashed, Loader2, Play } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { trpc } from "@/lib/trpc";
import {
groupPhasesByDependencyLevel,
@@ -196,10 +197,49 @@ function PipelineTabInner({ initiativeId, phases, phasesLoading }: PipelineTabPr
);
}
const totalTasks = displayTasks.length;
const completedTasks = displayTasks.filter((t) => t.status === "completed").length;
const runningPhases = phases.filter((p) => p.status === "in_progress").length;
const completedPhases = phases.filter((p) => p.status === "completed").length;
const isMutating = approvePhase.isPending || queueAll.isPending;
return (
<div className="space-y-4">
{actionableCount > 0 && (
<div className="flex items-center gap-3">
<div className="space-y-3">
{/* Pipeline summary bar */}
<div className="flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-2.5">
{/* Stats */}
<div className="flex min-w-0 flex-1 items-center gap-4 text-xs text-muted-foreground">
<span className="tabular-nums">
<span className="font-medium text-foreground">{phases.length}</span> phase{phases.length === 1 ? "" : "s"}
</span>
<span className="tabular-nums">
<span className="font-medium text-foreground">{totalTasks}</span> task{totalTasks === 1 ? "" : "s"}
</span>
{runningPhases > 0 && (
<span className="flex items-center gap-1 text-status-active-fg">
<Loader2 className="h-3 w-3 animate-spin" />
{runningPhases} running
</span>
)}
{completedPhases > 0 && (
<span className={cn(
"flex items-center gap-1",
completedPhases === phases.length ? "text-status-success-fg" : "text-muted-foreground",
)}>
<CheckCircle2 className="h-3 w-3" />
{completedPhases}/{phases.length} done
</span>
)}
{completedPhases === 0 && runningPhases === 0 && actionableCount > 0 && (
<span className="flex items-center gap-1">
<CircleDashed className="h-3 w-3" />
Ready
</span>
)}
</div>
{/* Execute action */}
{actionableCount > 0 && (
<Button
size="sm"
onClick={async () => {
@@ -208,17 +248,18 @@ function PipelineTabInner({ initiativeId, phases, phasesLoading }: PipelineTabPr
}
queueAll.mutate({ initiativeId });
}}
disabled={approvePhase.isPending || queueAll.isPending}
disabled={isMutating}
>
{approvePhase.isPending || queueAll.isPending ? (
{isMutating ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<Play className="mr-1.5 h-3.5 w-3.5" />
)}
Execute {actionableCount === 1 ? "1 phase" : `${actionableCount} phases`}
Execute {actionableCount === 1 ? "1 phase" : `all ${actionableCount}`}
</Button>
</div>
)}
)}
</div>
<PipelineGraph
columns={columns}
tasksByPhase={tasksByPhase}