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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user