feat: Implement analysis job tracking with progress timeline and enhanced data source status management.
This commit is contained in:
82
components/analysis-timeline.tsx
Normal file
82
components/analysis-timeline.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client"
|
||||
|
||||
import { CheckCircle2, AlertTriangle, Loader2 } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
type TimelineItem = {
|
||||
key: string
|
||||
label: string
|
||||
status: "pending" | "running" | "completed" | "failed"
|
||||
detail?: string
|
||||
}
|
||||
|
||||
function StatusIcon({ status }: { status: TimelineItem["status"] }) {
|
||||
if (status === "completed") {
|
||||
return <CheckCircle2 className="h-4 w-4 text-foreground" />
|
||||
}
|
||||
if (status === "failed") {
|
||||
return <AlertTriangle className="h-4 w-4 text-destructive" />
|
||||
}
|
||||
if (status === "running") {
|
||||
return <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
}
|
||||
return <span className="h-2.5 w-2.5 rounded-full border border-muted-foreground/60" />
|
||||
}
|
||||
|
||||
export function AnalysisTimeline({ items }: { items: TimelineItem[] }) {
|
||||
if (!items.length) return null
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-border/60 bg-muted/20 p-4">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Analysis timeline
|
||||
</div>
|
||||
<div className="mt-3 space-y-3">
|
||||
{items.map((item, index) => {
|
||||
const isPending = item.status === "pending"
|
||||
const nextStatus = items[index + 1]?.status
|
||||
const isStrongLine =
|
||||
nextStatus &&
|
||||
(item.status === "completed" || item.status === "running") &&
|
||||
(nextStatus === "completed" || nextStatus === "running")
|
||||
return (
|
||||
<div key={item.key} className="relative pl-6">
|
||||
<span
|
||||
className={cn(
|
||||
"absolute left-[6px] top-3 bottom-[-12px] w-px",
|
||||
index === items.length - 1 ? "hidden" : "bg-border/40",
|
||||
isStrongLine && "bg-foreground/60"
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-start gap-3 text-sm transition",
|
||||
isPending && "scale-[0.96] opacity-60"
|
||||
)}
|
||||
>
|
||||
<div className="mt-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-background shadow-sm">
|
||||
<StatusIcon status={item.status} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div
|
||||
className={cn(
|
||||
"font-medium",
|
||||
item.status === "failed" && "text-destructive"
|
||||
)}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
{item.detail && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{item.detail}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user