"use client"; import { useEffect, useMemo, useState } from "react"; import { useForm, useFieldArray, type UseFormRegister } from "react-hook-form"; import { useRouter, useSearchParams } from "next/navigation"; type Target = "settings_fiber" | "settings_co2gan" | "settings_co2gal" | "settings_uv"; type Opt = { id: string; label: string }; function useOptions(path: string) { const [opts, setOpts] = useState([]); const [loading, setLoading] = useState(false); const [q, setQ] = useState(""); useEffect(() => { let alive = true; setLoading(true); const url = `/api/options/${path}${path.includes("?") ? "&" : "?"}q=${encodeURIComponent(q)}`; fetch(url, { cache: "no-store" }) .then((r) => r.json()) .then((j) => { if (alive) setOpts((j?.data as Opt[]) ?? []); }) .finally(() => { if (alive) setLoading(false); }); return () => { alive = false; }; }, [path, q]); return { opts, loading, setQ }; } function FilterableSelect({ label, name, register, options, loading, onQuery, placeholder = "—", required = false, }: { label: string; name: string; register: UseFormRegister; options: Opt[]; loading?: boolean; onQuery?: (q: string) => void; placeholder?: string; required?: boolean; }) { const [filter, setFilter] = useState(""); useEffect(() => { onQuery?.(filter); }, [filter, onQuery]); const filtered = useMemo(() => { if (!filter) return options; const f = filter.toLowerCase(); return options.filter((o) => o.label.toLowerCase().includes(f)); }, [options, filter]); return (
setFilter(e.target.value)} />
); } function BoolBox({ label, name, register }:{ label: string; name: string; register: UseFormRegister; }) { return ( ); } export default function SettingsSubmit({ initialTarget }: { initialTarget?: Target }) { const router = useRouter(); const sp = useSearchParams(); const initialFromQuery = (sp.get("target") as Target) || initialTarget || "settings_fiber"; const [target, setTarget] = useState(initialFromQuery); // Image inputs (for preview + multipart submit) const [photoFile, setPhotoFile] = useState(null); const [screenFile, setScreenFile] = useState(null); const [photoPreview, setPhotoPreview] = useState(""); const [screenPreview, setScreenPreview] = useState(""); // Generic lists (alphabetical) const mats = useOptions("material"); const coats = useOptions("material_coating"); const colors = useOptions("material_color"); const opacs = useOptions("material_opacity"); const soft = useOptions("laser_software"); // only visible for fiber // Target-driven lists const srcs = useOptions(`laser_source?target=${target}`); // wavelength filter server-side const lens = useOptions(`lens?target=${target}`); // scan vs focus lens by target // Repeater select choices from Directus field config const fillType = useOptions(`repeater-choices?target=${target}&group=fill_settings&field=type`); const rasterType = useOptions(`repeater-choices?target=${target}&group=raster_settings&field=type`); const rasterDither = useOptions(`repeater-choices?target=${target}&group=raster_settings&field=dither`); const { register, handleSubmit, control, reset, formState: { isSubmitting } } = useForm({ defaultValues: { setting_title: "", uploader: "", setting_notes: "", mat: "", mat_coat: "", mat_color: "", mat_opacity: "", mat_thickness: "", source: "", lens: "", focus: "", laser_soft: "", repeat_all: "", fill_settings: [], line_settings: [], raster_settings: [], }, }); const fills = useFieldArray({ control, name: "fill_settings" }); const lines = useFieldArray({ control, name: "line_settings" }); const rasters = useFieldArray({ control, name: "raster_settings" }); const isGantry = target === "settings_co2gan"; const isFiber = target === "settings_fiber"; // const isUV = target === "settings_uv"; // const isCO2Gal = target === "settings_co2gal"; function num(v: any) { return (v === "" || v == null) ? null : Number(v); } const bool = (v: any) => !!v; async function onSubmit(values: any) { // Browser validation for required photo if (!photoFile) { // Let the native required attribute handle focus/UX, // but this is a safety net in case the browser skips it. (document.querySelector('input[type="file"][data-role="photo"]') as HTMLInputElement | null)?.focus(); return; } const payload: any = { target, setting_title: values.setting_title, uploader: values.uploader, setting_notes: values.setting_notes || "", mat: values.mat || null, mat_coat: values.mat_coat || null, mat_color: values.mat_color || null, mat_opacity: values.mat_opacity || null, mat_thickness: num(values.mat_thickness), source: values.source || null, lens: values.lens || null, focus: num(values.focus), fill_settings: (values.fill_settings || []).map((r: any) => ({ name: r.name || "", power: num(r.power), speed: num(r.speed), interval: num(r.interval), pass: num(r.pass), type: r.type || "", // present on fiber/uv/co2gal frequency: num(r.frequency), pulse: num(r.pulse), angle: num(r.angle), auto: bool(r.auto), increment: num(r.increment), cross: bool(r.cross), // present on all flood: bool(r.flood), air: bool(r.air), })), line_settings: (values.line_settings || []).map((r: any) => ({ name: r.name || "", power: num(r.power), speed: num(r.speed), perf: bool(r.perf), cut: r.cut || "", skip: r.skip || "", pass: num(r.pass), air: bool(r.air), // extra on fiber/uv/co2gal frequency: num(r.frequency), pulse: num(r.pulse), wobble: bool(r.wobble), step: num(r.step), size: num(r.size), })), raster_settings: (values.raster_settings || []).map((r: any) => ({ name: r.name || "", power: num(r.power), speed: num(r.speed), type: r.type || "", dither: r.dither || "", halftone_cell: num(r.halftone_cell), halftone_angle: num(r.halftone_angle), inversion: bool(r.inversion), interval: num(r.interval), dot: num(r.dot), pass: num(r.pass), air: bool(r.air), // extras on fiber/uv/co2gal frequency: num(r.frequency), pulse: num(r.pulse), cross: bool(r.cross), })), }; if (isFiber) { payload.laser_soft = values.laser_soft || null; payload.repeat_all = num(values.repeat_all); } // Decide JSON vs multipart (include images when present) let res: Response; if (photoFile || screenFile) { const form = new FormData(); form.set("payload", JSON.stringify(payload)); if (photoFile) form.set("photo", photoFile, photoFile.name || "photo"); if (screenFile) form.set("screen", screenFile, screenFile.name || "screen"); res = await fetch("/api/submit/settings", { method: "POST", body: form }); } else { res = await fetch("/api/submit/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); } const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data?.error || "Submission failed"); // Clear form + file previews reset(); setPhotoFile(null); setScreenFile(null); setPhotoPreview(""); setScreenPreview(""); // Redirect to success page (don’t show alert) const id = data?.id ? String(data.id) : ""; router.push(`/submit/settings/success?target=${encodeURIComponent(target)}&id=${encodeURIComponent(id)}`); } // File input helpers function onPick( file: File | null, setFile: (f: File | null) => void, setPreview: (s: string) => void ) { setFile(file); if (!file) { setPreview(""); return; } const reader = new FileReader(); reader.onload = () => setPreview(String(reader.result || "")); reader.readAsDataURL(file); } return (
{/* Target + (fiber) software */}
{isFiber && (
)}
{/* Title / Uploader */}
{/* Images */}
onPick(e.target.files?.[0] ?? null, setPhotoFile, setPhotoPreview)} />

{photoFile ? <>Selected: {photoFile.name} : "Max 25 MB. JPG/PNG/WebP recommended."}

{photoPreview ? ( Result preview ) : null}
onPick(e.target.files?.[0] ?? null, setScreenFile, setScreenPreview)} />

{screenFile ? <>Selected: {screenFile.name} : "Max 25 MB. JPG/PNG/WebP recommended."}

{screenPreview ? ( Settings preview ) : null}
{/* Notes */}