// components/details/CO2GalvoDetail.tsx "use client"; import { useEffect, useMemo, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import SettingsSubmit from "@/components/forms/SettingsSubmit"; type Rec = { submission_id: string | number; setting_title?: string | null; setting_notes?: string | null; photo?: { id?: string } | string | null; screen?: { id?: string } | string | null; // Material mat?: { id?: string | number; name?: string | null } | null; mat_coat?: { id?: string | number; name?: string | null } | null; mat_color?: { id?: string | number; name?: string | null } | null; mat_opacity?: { id?: string | number; opacity?: string | number | null } | null; mat_thickness?: number | null; // Rig & Optics laser_soft?: { id?: string | number; name?: string | null } | string | number | null; source?: { submission_id?: string | number; make?: string | null; model?: string | null; nm?: string | null } | null; lens?: { id?: string | number; field_size?: string | number | null; focal_length?: string | number | null } | null; focus?: number | null; // CO₂ Galvo fixed lists lens_conf?: { id?: string | number; name?: string | null } | null; lens_apt?: { id?: string | number; name?: string | null } | string | number | null; lens_exp?: { id?: string | number; name?: string | null } | string | number | null; repeat_all?: number | null; // Repeaters fill_settings?: any[] | null; line_settings?: any[] | null; raster_settings?: any[] | null; owner?: { id?: string | number; username?: string | null } | string | number | null; uploader?: string | null; last_modified_date?: string | null; }; export default function CO2GalvoDetail({ id, editable }: { id: string | number; editable?: boolean }) { const router = useRouter(); const sp = useSearchParams(); const editMode = sp.get("edit") === "1"; const [rec, setRec] = useState(null); const [loading, setLoading] = useState(true); const [err, setErr] = useState(null); // me id for owner-only edit const [meId, setMeId] = useState(null); // Lightbox const [viewerSrc, setViewerSrc] = useState(null); useEffect(() => { const onKey = (e: KeyboardEvent) => e.key === "Escape" && setViewerSrc(null); if (viewerSrc) window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [viewerSrc]); const API_BASE = (process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); const fileUrl = (assetId?: string) => (assetId ? (API_BASE ? `${API_BASE}/assets/${assetId}` : `/api/dx/assets/${assetId}`) : ""); function ownerLabel(o: Rec["owner"]) { if (!o) return "—"; if (typeof o === "string" || typeof o === "number") return String(o); return o.username || String(o.id ?? "—"); } const yesNo = (v: any) => (v ? "Yes" : "No"); // stringify possibly-object options for display const optLabel = (v: any): string => { if (v == null) return "—"; if (typeof v === "string" || typeof v === "number") return String(v); if (typeof v === "object") return v.name ?? (v.id != null ? String(v.id) : "—"); return "—"; }; // fetch me id useEffect(() => { let alive = true; (async () => { try { const r = await fetch(`/api/dx/users/me?fields=id`, { cache: "no-store", credentials: "include" }); const t = await r.text(); const j = t ? JSON.parse(t) : null; const idVal = j?.data?.id ?? j?.id ?? null; if (alive) setMeId(idVal ? String(idVal) : null); } catch { /* ignore */ } })(); return () => { alive = false; }; }, []); useEffect(() => { if (!id) return; let dead = false; (async () => { try { setLoading(true); setErr(null); const fields = [ "submission_id", "setting_title", "setting_notes", "photo.id", "screen.id", // Material "mat.id", "mat.name", "mat_coat.id", "mat_coat.name", "mat_color.id", "mat_color.name", "mat_opacity.id", "mat_opacity.opacity", "mat_thickness", // Rig & Optics "laser_soft.id", "laser_soft.name", "source.submission_id", "source.make", "source.model", "source.nm", "lens.id", "lens.field_size", "lens.focal_length", "lens_conf.id", "lens_conf.name", "lens_apt.id", "lens_apt.name", "lens_exp.id", "lens_exp.name", "focus", "repeat_all", // Repeaters "fill_settings", "line_settings", "raster_settings", // Meta "owner.id", "owner.username", "uploader", "last_modified_date", ].join(","); const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent(fields)}&filter[submission_id][_eq]=${encodeURIComponent( String(id) )}&limit=1`; const r = await fetch(url, { cache: "no-store", credentials: "include" }); const text = await r.text(); const j = text ? JSON.parse(text) : null; if (!r.ok) throw new Error(j?.errors?.[0]?.message || `HTTP ${r.status}`); const row: Rec | null = Array.isArray(j?.data) ? j.data[0] || null : null; if (!row) throw new Error("Setting not found."); if (!dead) setRec(row); } catch (e: any) { if (!dead) setErr(e?.message || String(e)); } finally { if (!dead) setLoading(false); } })(); return () => { dead = true; }; }, [id]); if (loading) return

Loading setting…

; if (err) return (
{err}
); if (!rec) return

Setting not found.

; const photoId = typeof rec.photo === "object" ? rec.photo?.id : (rec.photo as any); const screenId = typeof rec.screen === "object" ? rec.screen?.id : (rec.screen as any); const photoSrc = fileUrl(photoId ? String(photoId) : ""); const screenSrc = fileUrl(screenId ? String(screenId) : ""); const softName = typeof rec.laser_soft === "object" ? rec.laser_soft?.name ?? "—" : "—"; const sourceText = [rec.source?.make, rec.source?.model].filter(Boolean).join(" ") + (rec.source?.nm ? ` (${rec.source.nm})` : ""); const ownerId = typeof rec.owner === "object" ? (rec.owner?.id != null ? String(rec.owner.id) : null) : rec.owner != null ? String(rec.owner) : null; const isMine = meId && ownerId ? meId === ownerId : false; // Small field renderer (label on top, value below) const Field = ({ label, value, suffix }: { label: string; value: React.ReactNode | string | number | null | undefined; suffix?: string }) => { const primitive = typeof value === "string" || typeof value === "number" || typeof value === "boolean"; const isEmpty = value == null || value === "" || (typeof value === "number" && isNaN(value as number)); return (
{label}
{isEmpty ? ( "—" ) : primitive ? ( <> {String(value)} {suffix ? {suffix} : null} ) : ( value )}
); }; const openEdit = () => { const q = new URLSearchParams(sp.toString()); q.set("edit", "1"); router.replace(`?${q.toString()}`, { scroll: false }); }; const closeEdit = () => { const q = new URLSearchParams(sp.toString()); q.delete("edit"); router.replace(`?${q.toString()}`, { scroll: false }); }; // Pretty labels const TYPE_LABEL: Record = { uni: "UniDirectional", bi: "BiDirectional", offset: "Offset Fill", }; const DITHER_LABEL = (v: string | undefined) => (v ? v.charAt(0).toUpperCase() + v.slice(1) : "—"); // ----- EDIT MODE ----- if (editMode && rec) { const toId = (v: any) => v == null ? "" : typeof v === "object" ? (v.id ?? v.submission_id ?? "") : String(v); const initialValues = { submission_id: rec.submission_id, setting_title: rec.setting_title ?? "", setting_notes: rec.setting_notes ?? "", photo: photoId ? String(photoId) : null, screen: screenId ? String(screenId) : null, // Material mat: toId(rec.mat) || "", mat_coat: toId(rec.mat_coat) || "", mat_color: toId(rec.mat_color) || "", mat_opacity: toId(rec.mat_opacity) || "", mat_thickness: rec.mat_thickness ?? null, // Rig & Optics laser_soft: typeof rec.laser_soft === "object" ? String(rec.laser_soft?.id ?? "") : String(rec.laser_soft ?? "") || "", source: rec.source && typeof rec.source === "object" ? String(rec.source.submission_id ?? "") : String(rec.source ?? "") || "", lens: toId(rec.lens) || "", focus: rec.focus ?? null, // CO2 triplet lens_conf: toId(rec.lens_conf) || "", lens_apt: toId(rec.lens_apt) || "", lens_exp: toId(rec.lens_exp) || "", repeat_all: rec.repeat_all ?? null, // Repeaters fill_settings: rec.fill_settings ?? [], line_settings: rec.line_settings ?? [], raster_settings: rec.raster_settings ?? [], }; return (

Edit CO₂ Galvo Setting

); } return (
{/* Header */}

{rec.setting_title || "Untitled"}

{editable && isMine ? ( ) : null}
Last modified: {rec.last_modified_date || "—"}
{/* Top row: Info (left) + Images (right) */}
{/* Info */}
{rec.setting_notes ? ( {rec.setting_notes}

} /> ) : null}
{/* Images (side-by-side thumbnails) */} {(photoSrc || screenSrc) && (
{photoSrc ? (
setViewerSrc(photoSrc)} > Result
Result
) : null} {screenSrc ? (
setViewerSrc(screenSrc)} > Settings Screenshot
Settings Screenshot
) : null}
)}
{/* Two columns below: left Rig & Optics, right Material */}
{/* Rig & Optics */}

Rig & Optics

{/* Material */}

Material

{/* Repeaters (cards, full width) */} {(rec.fill_settings?.length ?? 0) > 0 && (

Fill Settings

{rec.fill_settings!.map((r: any, i: number) => { const showIncrement = !!r.auto; return (
{r.name || `Fill ${i + 1}`}
{showIncrement && }
); })}
)} {(rec.line_settings?.length ?? 0) > 0 && (

Line Settings

{rec.line_settings!.map((r: any, i: number) => { const perfEnabled = !!r.perf; const wobbleEnabled = !!r.wobble; return (
{r.name || `Line ${i + 1}`}
{/* Base fields – match form order */}
{/* Perforation row */}
{perfEnabled && } {perfEnabled && }
{/* Wobble row */}
{wobbleEnabled && } {wobbleEnabled && }
{/* Simple toggle */}
); })}
)} {(rec.raster_settings?.length ?? 0) > 0 && (

Raster Settings

{rec.raster_settings!.map((r: any, i: number) => { const isHalftone = r?.dither === "halftone"; return (
{r.name || `Raster ${i + 1}`}
{isHalftone && } {isHalftone && }
); })}
)} {/* Lightbox */} {viewerSrc && (
setViewerSrc(null)}> e.stopPropagation()} />
)}
); }