import { Link } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { Badge } from "~/shared/components/ui/badge"; import { api } from "~/shared/lib/api"; import type { PipelineJobItem } from "~/shared/lib/types"; import { ColumnShell } from "./ColumnShell"; interface ProcessingColumnProps { items: PipelineJobItem[]; progress?: { id: number; seconds: number; total: number } | null; queueStatus?: { status: string; until?: string; seconds?: number } | null; onMutate: () => void; } export function ProcessingColumn({ items, progress, queueStatus, onMutate }: ProcessingColumnProps) { const job = items[0]; // at most one running job // Wall-clock elapsed since the job started — re-renders every second. const [now, setNow] = useState(() => Date.now()); useEffect(() => { if (!job) return; const t = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(t); }, [job]); // Only trust progress if it belongs to the current job — stale events from // a previous job would otherwise show wrong numbers until the new job emits. const liveProgress = job && progress && progress.id === job.id ? progress : null; const startedAt = job?.started_at ? new Date(`${job.started_at}Z`).getTime() : null; const elapsedMs = startedAt ? Math.max(0, now - startedAt) : 0; const percent = liveProgress && liveProgress.total > 0 ? (liveProgress.seconds / liveProgress.total) * 100 : null; // ETA = elapsed × (1 - p) / p — only meaningful once we have a few percent // of progress and the rate has stabilised. const etaMs = percent != null && percent >= 1 && elapsedMs > 0 ? (elapsedMs * (100 - percent)) / percent : null; const formatDuration = (ms: number) => { const total = Math.floor(ms / 1000); const h = Math.floor(total / 3600); const m = Math.floor((total % 3600) / 60); const s = total % 60; if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`; return `${m}:${String(s).padStart(2, "0")}`; }; const stop = async () => { if (!confirm(`Stop the running job "${job?.name}"? It will be marked as error.`)) return; await api.post("/api/execute/stop"); onMutate(); }; return ( {queueStatus && queueStatus.status !== "running" && (
{queueStatus.status === "paused" && <>Paused until {queueStatus.until}} {queueStatus.status === "sleeping" && <>Sleeping {queueStatus.seconds}s between jobs} {queueStatus.status === "idle" && <>Idle}
)} {job ? (
{job.name}
running {job.job_type}
elapsed {formatDuration(elapsedMs)} {percent != null ? `${Math.round(percent)}%` : "starting…"} {etaMs != null ? `~${formatDuration(etaMs)} left` : "—"}
) : (

No active job

)} ); }