"use client"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; type Opt = { id: string | number; label: string }; const API = (process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); /** * Note: we are ONLY using this hook for kinds OTHER THAN "user_rig_type". * Rig types now arrive from the server via props. */ function useOptions( kind: "laser_software" | "laser_source" | "lens", targetKey?: string ) { const [opts, setOpts] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { let alive = true; setLoading(true); (async () => { let url = ""; let normalize = (rows: any[]): Opt[] => rows.map((r) => ({ id: String(r.id ?? r.submission_id), label: String( r.name ?? r.label ?? r.title ?? r.model ?? r.id ), })); if (kind === "laser_software") { url = `${API}/items/laser_software?fields=id,name&limit=1000&sort=name`; } else if (kind === "laser_source") { // fetch all sources; client filter by nm band from targetKey url = `${API}/items/laser_source?fields=submission_id,make,model,nm&limit=2000&sort=make,model`; const parseNum = (v: any) => { if (v == null) return null; const m = String(v).match(/(\d+(\.\d+)?)/); return m ? Number(m[1]) : null; }; const nmRange = (t?: string | null): [number, number] | null => { if (!t) return null; const s = t.toLowerCase(); if (s.includes("fiber")) return [1000, 9000]; if (s.includes("uv")) return [300, 400]; if (s.includes("gantry") || s.includes("co2 gantry") || s.includes("co₂ gantry")) return [10000, 11000]; if (s.includes("galvo") || s.includes("co2 galvo") || s.includes("co₂ galvo")) return [10000, 11000]; return null; }; const range = nmRange(targetKey); normalize = (rows) => { const filtered = range ? rows.filter((r: any) => { const nm = parseNum(r.nm); return nm != null && nm >= range[0] && nm <= range[1]; }) : rows; return filtered.map((r: any) => ({ id: String(r.submission_id), label: [r.make, r.model].filter(Boolean).join(" ") || String(r.submission_id), })); }; } else if (kind === "lens") { if (targetKey && targetKey.toLowerCase().includes("gantry")) { url = `${API}/items/laser_focus_lens?fields=id,name&limit=1000&sort=name`; } else { url = `${API}/items/laser_scan_lens?fields=id,field_size,focal_length&limit=1000`; normalize = (rows) => { const toNum = (v: any) => { const m = String(v ?? "").match(/-?\d+(\.\d+)?/); return m ? parseFloat(m[0]) : Number.POSITIVE_INFINITY; }; return [...rows] .sort( (a, b) => toNum(a.focal_length) - toNum(b.focal_length) ) .map((r) => ({ id: String(r.id), label: [ r.field_size && `${r.field_size} mm`, r.focal_length && `${r.focal_length} mm`, ] .filter(Boolean) .join(" — ") || String(r.id), })); }; } } const res = await fetch(url, { credentials: "include", cache: "no-store", }); const json = await res.json(); const rows = json?.data ?? []; const mapped = normalize(rows); if (alive) setOpts(mapped); })() .catch(() => alive && setOpts([])) .finally(() => alive && setLoading(false)); return () => { alive = false; }; }, [kind, targetKey]); return { opts, loading }; } export default function RigBuilderClient({ rigTypes }: { rigTypes: Opt[] }) { const router = useRouter(); const [name, setName] = useState(""); const [notes, setNotes] = useState(""); const [rigType, setRigType] = useState(""); const [laserSource, setLaserSource] = useState(""); const [scanLens, setScanLens] = useState(""); const [focusLens, setFocusLens] = useState(""); const [software, setSoftware] = useState(""); // rigTypes now come from the SERVER, authenticated, as props. const targetKey = useMemo(() => { const rt = rigTypes.find((o) => String(o.id) === String(rigType))?.label || ""; return rt; }, [rigTypes, rigType]); const sources = useOptions("laser_source", targetKey); const lens = useOptions("lens", targetKey); const softwares = useOptions("laser_software"); const isGantry = (targetKey || "").toLowerCase().includes("gantry"); async function onSubmit(e: React.FormEvent) { e.preventDefault(); const body: any = { name, rig_type: rigType ? Number(rigType) : null, laser_source: laserSource ? Number(laserSource) : null, notes: notes || "", laser_software: software ? Number(software) : null, }; if (isGantry) { body.laser_focus_lens = focusLens ? Number(focusLens) : null; body.laser_scan_lens = null; } else { body.laser_scan_lens = scanLens ? Number(scanLens) : null; body.laser_focus_lens = null; } const res = await fetch("/api/rigs", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(body), }); const j = await res.json().catch(() => ({})); if (!res.ok) { alert(j?.error || "Failed to create rig"); return; } // Go back to list tab router.replace("/portal/rigs?t=my", { scroll: false }); router.refresh(); } return (
setName(e.target.value)} required />
{/* Lens (focus for gantry, scan for others) */} {isGantry ? (
) : (
)}