diff --git a/components/details/CO2GalvoDetail.tsx b/components/details/CO2GalvoDetail.tsx index d8fdd1d9..976542fa 100644 --- a/components/details/CO2GalvoDetail.tsx +++ b/components/details/CO2GalvoDetail.tsx @@ -14,15 +14,25 @@ type Rec = { photo?: { id?: string } | string | null; screen?: { id?: string } | string | null; - // ids & readable fields 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; - 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; + 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; laser_soft?: any; @@ -40,7 +50,11 @@ type Rec = { async function readJson(res: Response) { const text = await res.text(); - try { return text ? JSON.parse(text) : null; } catch { throw new Error(`Unexpected response (HTTP ${res.status})`); } + try { + return text ? JSON.parse(text) : null; + } catch { + throw new Error(`Unexpected response (HTTP ${res.status})`); + } } function ownerLabel(o: Rec["owner"]) { @@ -49,25 +63,15 @@ function ownerLabel(o: Rec["owner"]) { return o.username || String(o.id ?? "—"); } -// Use public Directus base for sources so images actually load +// Public Directus base for sources const API_BASE = (process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); function fileUrl(id?: string) { if (!id) return ""; - if (API_BASE) return `${API_BASE}/assets/${id}`; - return `/api/dx/assets/${id}`; // fallback if you proxy assets + return API_BASE ? `${API_BASE}/assets/${id}` : `/api/dx/assets/${id}`; } -/** Small helper to render a square-cropped thumbnail that opens a lightbox on click */ -function ZoomableSquareImage({ - src, - alt, - onOpen, -}: { - src: string; - alt: string; - onOpen: () => void; -}) { - // Use CSS aspect-ratio for a consistent 1:1 crop (works without Tailwind plugin) +function ZoomableSquareImage(props: { src: string; alt: string; onOpen: () => void }) { + const { src, alt, onOpen } = props; return (
{ e.currentTarget.style.display = "none"; }} + onError={(e) => { + (e.currentTarget as HTMLImageElement).style.display = "none"; + }} />
); } -export default function CO2GalvoDetail({ - id, - mode, - onSaved, - onBack, - showOwnerEdit = true, -}: { +export default function CO2GalvoDetail(props: { id: string | number; mode?: "view" | "edit"; onSaved?: (submission_id: string | number) => void; onBack?: () => void; showOwnerEdit?: boolean; }) { + const { id, mode, onBack, showOwnerEdit = true } = props; + const sp = useSearchParams(); const router = useRouter(); const editParam = sp.get("edit") === "1"; @@ -104,19 +106,17 @@ export default function CO2GalvoDetail({ const [loading, setLoading] = useState(true); const [err, setErr] = useState(null); - // lightbox state + // lightbox const [viewerSrc, setViewerSrc] = useState(null); - - // close lightbox on Escape useEffect(() => { - function onKey(e: KeyboardEvent) { + const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setViewerSrc(null); - } - if (viewerSrc) window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); + }; + if (viewerSrc) window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); }, [viewerSrc]); - // load record (with human-readable fields) + // load record useEffect(() => { if (!id) return; let dead = false; @@ -132,7 +132,6 @@ export default function CO2GalvoDetail({ "setting_notes", "photo.id", "screen.id", - "mat.id", "mat.name", "mat_coat.id", @@ -142,38 +141,33 @@ export default function CO2GalvoDetail({ "mat_opacity.id", "mat_opacity.opacity", "mat_thickness", - "source.submission_id", "source.make", "source.model", "source.nm", - "lens.id", "lens.field_size", "lens.focal_length", - "focus", "laser_soft", "repeat_all", - "fill_settings", "line_settings", "raster_settings", - "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 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" }); if (!r.ok) { const j = await readJson(r).catch(() => null); - throw new Error(j?.errors?.[0]?.message || `HTTP ${r.status}`); + throw new Error((j as any)?.errors?.[0]?.message || `HTTP ${r.status}`); } const j = await readJson(r); const row: Rec | null = Array.isArray(j?.data) ? j.data[0] || null : null; @@ -186,38 +180,51 @@ export default function CO2GalvoDetail({ } })(); - return () => { dead = true; }; + return () => { + dead = true; + }; }, [id]); const initialValues = useMemo(() => { if (!rec) return null; - const toId = (v: any) => (v == null ? null : typeof v === "object" ? v.id ?? v.submission_id ?? null : v); - const photoId = typeof rec.photo === "string" || typeof rec.photo === "number" ? String(rec.photo) : rec.photo?.id ?? null; - const screenId = typeof rec.screen === "string" || typeof rec.screen === "number" ? String(rec.screen) : rec.screen?.id ?? null; + const toId = (v: any) => + v == null ? null : typeof v === "object" ? v.id ?? v.submission_id ?? null : v; - const matId = toId(rec.mat); - const coatId = toId(rec.mat_coat); - const colorId = toId(rec.mat_color); + const photoId = + typeof rec.photo === "string" || typeof rec.photo === "number" + ? String(rec.photo) + : rec.photo?.id ?? null; + const screenId = + typeof rec.screen === "string" || typeof rec.screen === "number" + ? String(rec.screen) + : rec.screen?.id ?? null; + + const matId = toId(rec.mat); + const coatId = toId(rec.mat_coat); + const colorId = toId(rec.mat_color); const opacityId = toId(rec.mat_opacity); - const lensId = toId(rec.lens); - const sourceId = rec.source && typeof rec.source === "object" ? rec.source.submission_id ?? null : (rec.source as any) ?? null; + const lensId = toId(rec.lens); + const sourceId = + rec.source && typeof rec.source === "object" + ? rec.source.submission_id ?? null + : (rec.source as any) ?? null; return { setting_title: rec.setting_title ?? "", setting_notes: rec.setting_notes ?? "", photo: photoId, screen: screenId, - mat: matId ? String(matId) : null, - mat_coat: coatId ? String(coatId) : null, - mat_color: colorId ? String(colorId) : null, - mat_opacity: opacityId ? String(opacityId) : null, + mat: matId ? String(matId) : null, + mat_coat: coatId ? String(coatId) : null, + mat_color: colorId ? String(colorId) : null, + mat_opacity: opacityId ? String(opacityId) : null, mat_thickness: rec.mat_thickness ?? null, - source: sourceId != null ? String(sourceId) : null, - lens: lensId != null ? String(lensId) : null, - focus: rec.focus ?? null, - laser_soft: rec.laser_soft ?? null, - repeat_all: rec.repeat_all ?? null, + source: sourceId != null ? String(sourceId) : null, + lens: lensId != null ? String(lensId) : null, + focus: rec.focus ?? null, + laser_soft: rec.laser_soft ?? null, + repeat_all: rec.repeat_all ?? null, fill_settings: rec.fill_settings ?? [], line_settings: rec.line_settings ?? [], raster_settings: rec.raster_settings ?? [], @@ -231,7 +238,14 @@ export default function CO2GalvoDetail({ } if (loading) return

Loading setting…

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

Setting not found.

; // EDIT @@ -240,14 +254,24 @@ export default function CO2GalvoDetail({

Edit CO₂ Galvo Setting

- +
- +
); } - // VIEW (readable) + // VIEW const ownerDisplay = ownerLabel(rec.owner); 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); @@ -258,25 +282,59 @@ export default function CO2GalvoDetail({ return (
-

{rec.setting_title || "Untitled"}

-
Last modified: {rec.last_modified_date || "—"}
+

+ {rec.setting_title || "Untitled"} +

+
+ Last modified: {rec.last_modified_date || "—"} +
-
Owner: {ownerDisplay}
-
Uploader: {rec.uploader || "—"}
-
Material: {rec.mat?.name || "—"}
-
Coating: {rec.mat_coat?.name || "—"}
-
Color: {rec.mat_color?.name || "—"}
-
Opacity: {rec.mat_opacity?.opacity ?? "—"}
-
Thickness (mm): {rec.mat_thickness ?? "—"}
-
Laser Source: {[rec.source?.make, rec.source?.model].filter(Boolean).join(" ") || "—"}{rec.source?.nm ? ` (${rec.source.nm})` : ""}
-
Scan Lens: {rec.lens?.field_size || "—"}{rec.lens?.focal_length ? ` / ${rec.lens.focal_length} mm` : ""}
-
Focus (mm): {rec.focus ?? "—"}
-
Software: {rec.laser_soft ?? "—"}
-
Repeat All: {rec.repeat_all ?? "—"}
+
+ Owner: {ownerDisplay} +
+
+ Uploader: {rec.uploader || "—"} +
+
+ Material: {rec.mat?.name || "—"} +
+
+ Coating: {rec.mat_coat?.name || "—"} +
+
+ Color: {rec.mat_color?.name || "—"} +
+
+ Opacity:{" "} + {rec.mat_opacity?.opacity ?? "—"} +
+
+ Thickness (mm):{" "} + {rec.mat_thickness ?? "—"} +
+
+ Laser Source:{" "} + {[rec.source?.make, rec.source?.model].filter(Boolean).join(" ") || "—"} + {rec.source?.nm ? ` (${rec.source.nm})` : ""} +
+
+ Scan Lens:{" "} + {rec.lens?.field_size || "—"} + {rec.lens?.focal_length ? ` / ${rec.lens.focal_length} mm` : ""} +
+
+ Focus (mm): {rec.focus ?? "—"} +
+
+ Software: {rec.laser_soft ?? "—"} +
+
+ Repeat All: {rec.repeat_all ?? "—"} +
{rec.setting_notes ? ( @@ -289,31 +347,41 @@ export default function CO2GalvoDetail({ {showOwnerEdit && (
+ Edit this setting +
)}
- {/* Thumbnails: square crop with lightbox on click */}
{photoSrc ? (
- setViewerSrc(photoSrc)} /> + setViewerSrc(photoSrc)} + />
Result
) : null} {screenSrc ? (
- setViewerSrc(screenSrc)} /> -
Settings Screenshot
+ setViewerSrc(screenSrc)} + /> +
+ Settings Screenshot +
) : null}
- {/* Repeaters */} {(rec.fill_settings?.length ?? 0) > 0 && (

Fill Settings

@@ -420,7 +488,6 @@ export default function CO2GalvoDetail({
)} - {/* Lightbox */} {viewerSrc && (