Remodel/packages/web/app/scenarios/[id]/page.tsx

108 lines
3.1 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { getScenario, scenarioEventsUrl, type ProgressEvent } from "@/lib/api";
import { Button } from "@/components/ui/button";
function ProgressBar({ pct }: { pct: number }) {
return (
<div className="w-full bg-muted rounded-full h-3 overflow-hidden">
<div
className="bg-primary h-3 rounded-full transition-all duration-500"
style={{ width: `${pct}%` }}
/>
</div>
);
}
export default function ScenarioPage() {
const params = useParams<{ id: string }>();
const router = useRouter();
const id = params.id;
const [progress, setProgress] = useState<ProgressEvent | null>(null);
const [done, setDone] = useState(false);
const { data: scenario, refetch } = useQuery({
queryKey: ["scenario", id],
queryFn: () => getScenario(id),
refetchInterval: done ? false : 3000,
});
useEffect(() => {
if (!id) return;
const es = new EventSource(scenarioEventsUrl(id));
es.onmessage = (event: MessageEvent<string>) => {
const data = JSON.parse(event.data) as ProgressEvent;
setProgress(data);
if (data.stage === "done") {
setDone(true);
es.close();
void refetch();
}
};
es.onerror = () => es.close();
return () => es.close();
}, [id, refetch]);
const statusColor =
scenario?.status === "success"
? "text-green-600"
: scenario?.status === "failed"
? "text-red-600"
: scenario?.status === "running"
? "text-blue-600"
: "text-yellow-600";
const kpis = scenario?.kpis_json
? (JSON.parse(scenario.kpis_json) as Record<string, unknown>)
: null;
return (
<main className="flex-1 container mx-auto px-4 py-8 max-w-3xl">
<div className="mb-6">
<Button variant="ghost" size="sm" onClick={() => router.push("/")}>
&larr; Back
</Button>
</div>
<h1 className="text-xl font-bold mb-1">
{scenario?.name ?? "Loading…"}
</h1>
<p className={`text-sm font-medium capitalize mb-6 ${statusColor}`}>
{scenario?.status ?? "—"}
</p>
{(scenario?.status === "queued" || scenario?.status === "running") && (
<div className="mb-6">
<div className="flex justify-between text-sm text-muted-foreground mb-2">
<span>{progress?.stage ?? "waiting…"}</span>
<span>{progress?.pct ?? 0}%</span>
</div>
<ProgressBar pct={progress?.pct ?? 0} />
</div>
)}
{scenario?.status === "success" && kpis && (
<div className="border rounded-lg p-6">
<h2 className="font-semibold mb-4">Result</h2>
<pre className="text-sm text-muted-foreground bg-muted/50 p-4 rounded overflow-auto">
{JSON.stringify(kpis, null, 2)}
</pre>
</div>
)}
{scenario?.status === "failed" && (
<div className="border border-red-200 rounded-lg p-6 text-red-600">
Scenario failed. Check worker logs.
</div>
)}
</main>
);
}