From fc380a49fb5361739f572a4b1461f6b107a606fc Mon Sep 17 00:00:00 2001 From: makearmy Date: Fri, 3 Oct 2025 19:11:14 -0400 Subject: [PATCH] edit details page non-owner fix --- components/details/CO2GalvoDetail.tsx | 283 +++++++++----------------- 1 file changed, 94 insertions(+), 189 deletions(-) diff --git a/components/details/CO2GalvoDetail.tsx b/components/details/CO2GalvoDetail.tsx index 976542fa..8be8f18a 100644 --- a/components/details/CO2GalvoDetail.tsx +++ b/components/details/CO2GalvoDetail.tsx @@ -3,7 +3,6 @@ import { useEffect, useMemo, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; -import Link from "next/link"; import SettingsSubmit from "@/components/forms/SettingsSubmit"; type Rec = { @@ -14,25 +13,15 @@ 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; @@ -50,11 +39,7 @@ 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"]) { @@ -63,40 +48,29 @@ function ownerLabel(o: Rec["owner"]) { return o.username || String(o.id ?? "—"); } -// Public Directus base for sources -const API_BASE = (process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, ""); +function isMine(owner: Rec["owner"], meId: string | null) { + if (!meId || !owner) return false; + if (typeof owner === "string" || typeof owner === "number") return String(owner) === meId; + return owner.id != null && String(owner.id) === meId; +} + function fileUrl(id?: string) { - if (!id) return ""; - return API_BASE ? `${API_BASE}/assets/${id}` : `/api/dx/assets/${id}`; + return id ? `/api/dx/assets/${id}` : ""; } -function ZoomableSquareImage(props: { src: string; alt: string; onOpen: () => void }) { - const { src, alt, onOpen } = props; - return ( -
- {alt} { - (e.currentTarget as HTMLImageElement).style.display = "none"; - }} - /> -
- ); -} - -export default function CO2GalvoDetail(props: { +export default function CO2GalvoDetail({ + id, + mode, + onSaved, + onBack, + showOwnerEdit = true, +}: { 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"; @@ -106,17 +80,23 @@ export default function CO2GalvoDetail(props: { const [loading, setLoading] = useState(true); const [err, setErr] = useState(null); - // lightbox - const [viewerSrc, setViewerSrc] = useState(null); + // current user id (for owner-only edit button) + const [meId, setMeId] = useState(null); useEffect(() => { - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape") setViewerSrc(null); - }; - if (viewerSrc) window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, [viewerSrc]); + let dead = false; + (async () => { + try { + const r = await fetch(`/api/dx/users/me?fields=id`, { cache: "no-store", credentials: "include" }); + if (!r.ok) return; + const j = await readJson(r); + const id = j?.data?.id ?? j?.id ?? null; + if (!dead) setMeId(id ? String(id) : null); + } catch { /* ignore */ } + })(); + return () => { dead = true; }; + }, []); - // load record + // load record (with human-readable fields) useEffect(() => { if (!id) return; let dead = false; @@ -132,6 +112,7 @@ export default function CO2GalvoDetail(props: { "setting_notes", "photo.id", "screen.id", + "mat.id", "mat.name", "mat_coat.id", @@ -141,33 +122,38 @@ export default function CO2GalvoDetail(props: { "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 as any)?.errors?.[0]?.message || `HTTP ${r.status}`); + throw new Error(j?.errors?.[0]?.message || `HTTP ${r.status}`); } const j = await readJson(r); const row: Rec | null = Array.isArray(j?.data) ? j.data[0] || null : null; @@ -180,49 +166,36 @@ export default function CO2GalvoDetail(props: { } })(); - 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 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 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 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, + 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 ?? [], @@ -238,14 +211,7 @@ export default function CO2GalvoDetail(props: { } if (loading) return

Loading setting…

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

Setting not found.

; // EDIT @@ -254,87 +220,46 @@ export default function CO2GalvoDetail(props: {

Edit CO₂ Galvo Setting

-
- +
); } - // VIEW + // VIEW (readable) const ownerDisplay = ownerLabel(rec.owner); + const canEdit = showOwnerEdit && isMine(rec.owner, meId); + 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 = photoId ? fileUrl(String(photoId)) : ""; const screenSrc = screenId ? fileUrl(String(screenId)) : ""; 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 ? ( @@ -344,44 +269,38 @@ export default function CO2GalvoDetail(props: {
) : null} - {showOwnerEdit && ( + {canEdit && (
- + router.push(`/portal/laser-settings?t=co2-galvo&view=detail&id=${rec.submission_id}&edit=1`) + } + className="inline-flex items-center rounded border px-2 py-1 text-xs hover:bg-muted" > - Edit this setting - + Edit +
)}
{photoSrc ? ( -
- setViewerSrc(photoSrc)} - /> -
Result
+
+ Result +
Result
) : null} {screenSrc ? ( -
- setViewerSrc(screenSrc)} - /> -
- Settings Screenshot -
+
+ Settings screenshot +
Settings Screenshot
) : null}
+ {/* Repeaters */} {(rec.fill_settings?.length ?? 0) > 0 && (

Fill Settings

@@ -487,20 +406,6 @@ export default function CO2GalvoDetail(props: {
)} - - {viewerSrc && ( -
setViewerSrc(null)} - > - e.stopPropagation()} - /> -
- )} ); }