"use client"; import React, { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { useForm, Controller } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; // UI bits (shadcn-style) import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Separator } from "@/components/ui/separator"; import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; import { toast } from "@/components/ui/use-toast"; import { Loader2, Plus, Save, Settings2, X } from "lucide-react"; import { SignOutButton } from "@/components/SignOutButton"; // simple fetch helper async function jfetch(url: string, init?: RequestInit): Promise { const res = await fetch(url, { ...init, headers: { "Content-Type": "application/json", ...(init?.headers || {}), }, cache: "no-store", }); if (!res.ok) { const text = await res.text(); throw new Error(text || res.statusText); } return (await res.json()) as T; } /** ---------- Types for option endpoints ---------- */ type Opt = { id: string; label: string }; // Finder for options with free-text filter function useOptions(endpoint: string) { const [opts, setOpts] = useState([]); const [loading, setLoading] = useState(false); const [q, setQ] = useState(""); useEffect(() => { let alive = true; setLoading(true); const url = `/api/options/${endpoint}${endpoint.includes("?") ? "&" : "?"}q=${encodeURIComponent(q)}`; fetch(url, { cache: "no-store" }) .then((r) => r.json()) .then((j) => { if (!alive) return; setOpts((j?.data as Opt[]) ?? []); }) .catch(() => { if (!alive) return; setOpts([]); }) .finally(() => alive && setLoading(false)); return () => { alive = false; }; }, [endpoint, q]); return { opts, loading, setQ }; } /** ---------- Form schema ---------- */ const RigSchema = z.object({ name: z.string().min(1, "Rig name required"), notes: z.string().optional(), // Relations (IDs) laser_source: z.string().min(1, "Laser source required"), laser_software: z.string().min(1, "Software required"), // One of scan lens (with optional aperture & expander) OR focus lens laser_scan_lens: z.string().optional(), laser_scan_lens_apt: z.string().optional(), laser_scan_lens_exp: z.string().optional(), laser_focus_lens: z.string().optional(), }); type RigForm = z.infer; /** ---------- Little select with filter ---------- */ function FilterableSelect({ label, value, onValueChange, placeholder = "Select…", endpoint, // e.g. "laser_source?target=settings_fiber" }: { label: string; value?: string; onValueChange: (v: string) => void; placeholder?: string; endpoint: string; }) { const { opts, loading, setQ } = useOptions(endpoint); return (
setQ(e.target.value)} className="mb-1" />
); } /** ---------- Page ---------- */ export default function MyRigsPage() { const router = useRouter(); const searchParams = useSearchParams(); const [saving, setSaving] = useState(false); const [list, setList] = useState([]); const [loadingList, setLoadingList] = useState(true); // Which lens type UI is active: "scan" vs "focus" const [lensMode, setLensMode] = useState<"scan" | "focus">("scan"); const form = useForm({ resolver: zodResolver(RigSchema), defaultValues: { name: "", notes: "", laser_source: "", laser_software: "", laser_scan_lens: "", laser_scan_lens_apt: "", laser_scan_lens_exp: "", laser_focus_lens: "", }, }); const sourceTarget = "settings_fiber"; // for now default fiber; could be dynamic later // Options hooks const srcs = useOptions(`laser_source?target=${sourceTarget}`); const soft = useOptions("laser_software"); const scanLens = useOptions(`lens?target=${sourceTarget}`); // F-theta const apertures = useOptions("laser_scan_lens_apt"); const expanders = useOptions("laser_scan_lens_exp"); const focusLens = useOptions("lens?target=settings_co2gan"); // focus lens for gantry // Load my rigs useEffect(() => { let alive = true; (async () => { try { setLoadingList(true); const res = await jfetch<{ data: any[] }>("/api/rigs/list"); if (!alive) return; setList(res.data || []); } catch (e: any) { console.warn("[my/rigs] list error", e?.message); } finally { alive = false ? undefined : setLoadingList(false); } })(); return () => { alive = false; }; }, []); async function onSave(data: RigForm) { try { setSaving(true); // If in focus mode, ensure scan-lens fields are cleared and vice versa. const payload: any = { ...data }; if (lensMode === "focus") { payload.laser_scan_lens = null; payload.laser_scan_lens_apt = null; payload.laser_scan_lens_exp = null; } else { payload.laser_focus_lens = null; } const res = await jfetch<{ ok: boolean; id: string }>("/api/rigs/save", { method: "POST", body: JSON.stringify(payload), }); toast({ title: "Rig saved", description: "Your rig has been saved to your account.", }); // refresh list const updated = await jfetch<{ data: any[] }>("/api/rigs/list"); setList(updated.data || []); // clear form (optional) form.reset({ name: "", notes: "", laser_source: "", laser_software: "", laser_scan_lens: "", laser_scan_lens_apt: "", laser_scan_lens_exp: "", laser_focus_lens: lensMode === "focus" ? "" : "", }); } catch (e: any) { toast({ title: "Save failed", description: e?.message || "Unknown error", variant: "destructive", }); } finally { setSaving(false); } } function LensModeToggle() { return (
setLensMode("scan")} > Scan Lens setLensMode("focus")} > Focus Lens
); } return (

My Rigs

Build a Rig
{form.formState.errors.name && (

{form.formState.errors.name.message}

)}
form.setValue("laser_source", v)} /> form.setValue("laser_software", v)} /> {lensMode === "scan" ? ( <> form.setValue("laser_scan_lens", v)} /> form.setValue("laser_scan_lens_apt", v)} /> form.setValue("laser_scan_lens_exp", v)} /> ) : ( <> form.setValue("laser_focus_lens", v)} />
Focus lenses don’t use F-numbers; just pick the lens by name.
)}