gpt-engineer-app[bot] commited on
Commit
999d380
·
1 Parent(s): 859d450
Files changed (1) hide show
  1. src/components/status-timeline.tsx +80 -0
src/components/status-timeline.tsx ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Check, Loader2, AlertTriangle, Clock } from "lucide-react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ type Stage = "queued" | "analyzing" | "completed" | "failed";
5
+
6
+ const STAGES: { key: Stage; label: string }[] = [
7
+ { key: "queued", label: "Queued" },
8
+ { key: "analyzing", label: "Analyzing" },
9
+ { key: "completed", label: "Completed" },
10
+ ];
11
+
12
+ function mapStatus(status: string): Stage {
13
+ if (status === "complete" || status === "completed") return "completed";
14
+ if (status === "analyzing" || status === "processing") return "analyzing";
15
+ if (status === "failed") return "failed";
16
+ return "queued";
17
+ }
18
+
19
+ export function StatusTimeline({ status }: { status: string }) {
20
+ const current = mapStatus(status);
21
+ const failed = current === "failed";
22
+ const currentIdx = failed ? 1 : STAGES.findIndex((s) => s.key === current);
23
+
24
+ return (
25
+ <div className="rounded-xl border border-border bg-surface/60 p-4">
26
+ <div className="text-[10px] uppercase tracking-widest text-muted-foreground font-mono mb-3">
27
+ Pipeline status
28
+ </div>
29
+ <div className="flex items-center gap-2">
30
+ {STAGES.map((stage, i) => {
31
+ const done = i < currentIdx;
32
+ const active = i === currentIdx && !failed;
33
+ const isFailed = failed && i === 1;
34
+ const Icon = isFailed
35
+ ? AlertTriangle
36
+ : done
37
+ ? Check
38
+ : active
39
+ ? Loader2
40
+ : Clock;
41
+ return (
42
+ <div key={stage.key} className="flex items-center gap-2 flex-1 min-w-0">
43
+ <div
44
+ className={cn(
45
+ "h-7 w-7 rounded-full flex items-center justify-center shrink-0 border",
46
+ isFailed && "bg-destructive/15 border-destructive text-destructive",
47
+ done && "bg-severity-low/20 border-severity-low text-severity-low",
48
+ active && "bg-primary/15 border-primary text-primary",
49
+ !done && !active && !isFailed && "bg-surface border-border text-muted-foreground"
50
+ )}
51
+ >
52
+ <Icon className={cn("h-3.5 w-3.5", active && "animate-spin")} />
53
+ </div>
54
+ <div className="min-w-0 flex-1">
55
+ <div
56
+ className={cn(
57
+ "text-xs font-medium truncate",
58
+ isFailed && "text-destructive",
59
+ active && "text-foreground",
60
+ !active && !isFailed && "text-muted-foreground"
61
+ )}
62
+ >
63
+ {isFailed ? "Failed" : stage.label}
64
+ </div>
65
+ </div>
66
+ {i < STAGES.length - 1 && (
67
+ <div
68
+ className={cn(
69
+ "h-px flex-1 min-w-4",
70
+ done ? "bg-severity-low/60" : isFailed ? "bg-destructive/40" : "bg-border"
71
+ )}
72
+ />
73
+ )}
74
+ </div>
75
+ );
76
+ })}
77
+ </div>
78
+ </div>
79
+ );
80
+ }