"use client"; import { useState, useTransition } from "react"; import { useRouter } from "next/navigation"; export type ScanOp = { id: string; sequence: number; name: string; status: string; qcRequired: boolean; instructions: string | null; materialNotes: string | null; settings: string | null; plannedMinutes: number | null; plannedUnits: number | null; claimedByUserId: string | null; claimedAt: string | null; machine: { id: string; name: string; kind: string } | null; part: { id: string; code: string; name: string; material: string | null; qty: number; assembly: { id: string; code: string; name: string; project: { id: string; code: string; name: string }; }; }; claimedBy: { id: string; name: string } | null; }; type Viewer = { id: string; role: "admin" | "operator"; claimedByMe: boolean; }; export default function ScanClient({ initialOp, viewer }: { initialOp: ScanOp; viewer: Viewer }) { const router = useRouter(); const [op, setOp] = useState(initialOp); const [claimedByMe, setClaimedByMe] = useState(viewer.claimedByMe); const [error, setError] = useState(null); const [isPending, startTransition] = useTransition(); // Inline form state for pause/done. We keep one set of fields and let the // active button decide which API to hit — the fields (units processed, note) // are identical between release and close. const [units, setUnits] = useState(""); const [note, setNote] = useState(""); const [qcPassed, setQcPassed] = useState(null); const [qcNotes, setQcNotes] = useState(""); const isOperator = viewer.role === "operator"; const active = op.status === "in_progress"; const completed = op.status === "completed"; async function call(path: string, body?: unknown) { setError(null); const res = await fetch(path, { method: "POST", headers: { "content-type": "application/json" }, body: body ? JSON.stringify(body) : undefined, }); const data = await res.json().catch(() => ({})); if (!res.ok) { setError(data.error ?? "Request failed"); return null; } return data; } function onClaim() { startTransition(async () => { const data = await call(`/api/v1/operations/${op.id}/claim`); if (data?.operation) { setOp({ ...op, ...data.operation }); setClaimedByMe(true); } }); } function onRelease() { startTransition(async () => { const data = await call(`/api/v1/operations/${op.id}/release`, { unitsProcessed: units ? Number(units) : undefined, note: note || undefined, }); if (data?.operation) { setOp({ ...op, ...data.operation }); setClaimedByMe(false); // Bounce back to /op so the operator sees their queue. router.push("/op"); } }); } function onClose() { if (op.qcRequired && qcPassed === null) { setError("This step requires QC — mark pass or fail before completing"); return; } startTransition(async () => { const data = await call(`/api/v1/operations/${op.id}/close`, { unitsProcessed: units ? Number(units) : undefined, note: note || undefined, qc: qcPassed === null ? undefined : { passed: qcPassed, notes: qcNotes || undefined, measurements: "" }, }); if (data?.operation) { setOp({ ...op, ...data.operation }); setClaimedByMe(false); router.push("/op"); } }); } return (
{op.part.assembly.project.code} · {op.part.assembly.code}

{op.part.name}

Part {op.part.code} {op.part.material ? ` · ${op.part.material}` : null} · qty {op.part.qty}
Step {op.sequence} {op.status === "in_progress" ? "in progress" : op.status} {op.qcRequired ? ( QC ) : null}
{op.name}
{op.machine ? (
Machine: {op.machine.name} ({op.machine.kind})
) : null} {op.plannedMinutes || op.plannedUnits ? (
Plan: {op.plannedMinutes ? ` ${op.plannedMinutes} min` : ""} {op.plannedMinutes && op.plannedUnits ? " ·" : ""} {op.plannedUnits ? ` ${op.plannedUnits} units` : ""}
) : null}
{op.instructions ? (

Instructions

{op.instructions}

) : null} {op.materialNotes ? (

Material notes

{op.materialNotes}

) : null} {op.settings ? (

Settings

            {op.settings}
          
) : null} {error ? (
{error}
) : null} {isOperator && !completed ? (
{!claimedByMe && op.claimedBy && op.claimedByUserId !== viewer.id ? (
{op.claimedBy.name} is currently on this step.
) : null} {!claimedByMe ? ( ) : ( <>