"use client"; import { use, useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { PipelineStepper } from "@/components/progress/pipeline-stepper"; import { usePlacementProgress } from "@/hooks/use-placement-progress"; import { cancelPlacementPipeline, fetchPlacementPlan, fetchProject, startPlacementPipeline, } from "@/lib/api"; import type { PlacementPlan } from "@/lib/types"; import { ArrowLeft, CheckCircle2, Loader2, OctagonX, Ban, LayoutGrid, } from "lucide-react"; export default function PlacementPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = use(params); const router = useRouter(); const [projectName, setProjectName] = useState(""); const [placementStatus, setPlacementStatus] = useState("draft"); const [plan, setPlan] = useState(null); const [cancelling, setCancelling] = useState(false); const [starting, setStarting] = useState(false); const [statusLoaded, setStatusLoaded] = useState(false); const active = placementStatus === "queued" || placementStatus === "running"; const alreadyDone = placementStatus === "complete"; const { steps, done, cancelled, error, summary, started } = usePlacementProgress( id, statusLoaded && active, ); useEffect(() => { fetchProject(id) .then((p) => { setProjectName(p.name); setPlacementStatus(p.placementStatus ?? "draft"); setStatusLoaded(true); }) .catch(() => setStatusLoaded(true)); }, [id]); useEffect(() => { if (done) setPlacementStatus("complete"); }, [done]); useEffect(() => { if (!statusLoaded) return; if (!alreadyDone && !done && placementStatus !== "draft" && placementStatus !== "error") { return; } fetchPlacementPlan(id) .then(setPlan) .catch(() => setPlan(null)); }, [id, alreadyDone, done, placementStatus, statusLoaded]); const handleCancel = async () => { setCancelling(true); try { await cancelPlacementPipeline(id); } catch { // may already be finished } finally { setCancelling(false); } }; const handleStart = async () => { setStarting(true); try { await startPlacementPipeline(id); setPlacementStatus("queued"); window.location.reload(); } catch (e) { setStarting(false); alert(e instanceof Error ? e.message : "Failed to start placement"); } }; const finished = alreadyDone || done; const isRunning = statusLoaded && active && !done && !cancelled && !error; const isQueued = isRunning && !started; return (

Placement plan

{projectName ? `${projectName} · ` : ""} Routing-first topology (no millimetres)

{!statusLoaded && (
Checking placement status…
)} {isQueued && (

Queued — starting placement worker…

)} {isRunning && !isQueued && ( Progress
)} {cancelled && (
Placement cancelled
)} {error && (

Placement failed

{error}

)} {statusLoaded && !active && !error && !cancelled && (
{finished || plan ? (
Placement plan ready · {summary?.domains ?? plan?.domains.length ?? "?"} domains,{" "} {summary?.groups ?? plan?.groups.length ?? "?"} IC groups
) : (

No placement plan yet. Build one (free, no LLM) or open Domains if analysis already wrote functional groups.

)} {plan && ( Topology {plan.domains.map((d) => (

{d.domain_id}

Power: {d.power_nets.join(", ") || "—"}

Assemble: {d.assemble_order.join(" → ") || "—"}

))} {plan.groups.length > 0 && (

IC groups

{plan.groups.map((g) => (
{g.ref} {g.mpn ? ` · ${g.mpn}` : ""} {g.component_subtype ? ` · ${g.component_subtype}` : ""} {g.satellites.length > 0 && ( {" "} — satellites:{" "} {g.satellites .map((s) => `${s.ref}(${s.role_hint ?? "other"})`) .join(", ")} )}
))}
)}
)}
)}
); }