diff --git a/components/details/CO2GalvoDetail.tsx b/components/details/CO2GalvoDetail.tsx index 82cc1eb7..1d807843 100644 --- a/components/details/CO2GalvoDetail.tsx +++ b/components/details/CO2GalvoDetail.tsx @@ -2,6 +2,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; type Rec = { submission_id: string | number; @@ -43,10 +44,16 @@ type Rec = { }; export default function CO2GalvoDetail({ id, editable }: { id: string | number; editable?: boolean }) { + const router = useRouter(); + const sp = useSearchParams(); + const [rec, setRec] = useState(null); const [loading, setLoading] = useState(true); const [err, setErr] = useState(null); + // current user id to show "Edit" for owners + const [meId, setMeId] = useState(null); + // Lightbox const [viewerSrc, setViewerSrc] = useState(null); useEffect(() => { @@ -65,6 +72,26 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number; } const yesNo = (v: any) => (v ? "Yes" : "No"); + // fetch me id + useEffect(() => { + let alive = true; + (async () => { + try { + const r = await fetch(`/api/dx/users/me?fields=id`, { cache: "no-store", credentials: "include" }); + if (!r.ok) return; + 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; @@ -162,19 +189,60 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number; [rec.source?.make, rec.source?.model].filter(Boolean).join(" ") + (rec.source?.nm ? ` (${rec.source.nm})` : ""); - // Small field renderer: label on top, value below - const Field = ({ label, value }: { label: string; value: any }) => ( + 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 (+ optional suffix) + const Field = ({ label, value, suffix }: { label: string; value: any; suffix?: string }) => (
{label}
-
{value ?? "—"}
+
+ {value != null && value !== "" ? ( + <> + {String(value)} + {suffix ? {suffix} : null} + + ) : ( + "—" + )} +
); + const openEdit = () => { + const q = new URLSearchParams(sp.toString()); + q.set("edit", "1"); + 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) : "—"); + return (
{/* Header */}
+

{rec.setting_title || "Untitled"}

+ {editable && isMine ? ( + + ) : null} +
Last modified: {rec.last_modified_date || "—"}
@@ -224,17 +292,18 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number; - - + + - +
@@ -247,146 +316,92 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number; - + - {/* Repeaters (full width) */} + {/* Repeaters (cards, full width) */} {(rec.fill_settings?.length ?? 0) > 0 && ( -
+

Fill Settings

-
- - - - - - - - - - - - - - - - - - - +
{rec.fill_settings!.map((r: any, i: number) => ( -
- - - - - - - - - - - - - - +
+
{r.name || `Fill ${i + 1}`}
+
+ + + + + + + + + + + + + +
+
))} - -
NameTypePower (%)Speed (mm/s)IntervalAnglePassFreq (kHz)Pulse (ns)AutoCrossFloodAir
{r.name || "—"}{r.type || "—"}{r.power ?? "—"}{r.speed ?? "—"}{r.interval ?? "—"}{r.angle ?? "—"}{r.pass ?? "—"}{r.frequency ?? "—"}{r.pulse ?? "—"}{yesNo(r.auto)}{yesNo(r.cross)}{yesNo(r.flood)}{yesNo(r.air)}
)} {(rec.line_settings?.length ?? 0) > 0 && ( -
+

Line Settings

-
- - - - - - - - - - - - - - - - - +
{rec.line_settings!.map((r: any, i: number) => ( -
- - - - - - - - - - - - +
+
{r.name || `Line ${i + 1}`}
+
+ + + + + + + + + + + + +
+
))} - -
NamePowerSpeedFreqPulsePassPerfCutSkipWobbleAir
{r.name || "—"}{r.power ?? "—"}{r.speed ?? "—"}{r.frequency ?? "—"}{r.pulse ?? "—"}{r.pass ?? "—"}{yesNo(r.perf)}{yesNo(r.cut)}{yesNo(r.skip)}{yesNo(r.wobble)}{yesNo(r.air)}
)} {(rec.raster_settings?.length ?? 0) > 0 && ( -
+

Raster Settings

-
- - - - - - - - - - - - - - - - - - - - - +
{rec.raster_settings!.map((r: any, i: number) => ( -
- - - - - - - - - - - - - - - - +
+
{r.name || `Raster ${i + 1}`}
+
+ + + + + + + + + {!!r.halftone_cell && } + {!!r.halftone_angle && } + + + + +
+
))} - -
NameTypeDitherPowerSpeedIntervalPassCrossInversionAirFreq (kHz)Pulse (ns)Halftone CellHalftone AngleDot
{r.name || "—"}{r.type || "—"}{r.dither || "—"}{r.power ?? "—"}{r.speed ?? "—"}{r.interval ?? "—"}{r.pass ?? "—"}{yesNo(r.cross)}{yesNo(r.inversion)}{yesNo(r.air)}{r.frequency ?? "—"}{r.pulse ?? "—"}{r.halftone_cell ?? "—"}{r.halftone_angle ?? "—"}{r.dot ?? "—"}
)} diff --git a/components/lists/CO2GalvoList.tsx b/components/lists/CO2GalvoList.tsx index a1aac22a..5e76feea 100644 --- a/components/lists/CO2GalvoList.tsx +++ b/components/lists/CO2GalvoList.tsx @@ -54,11 +54,32 @@ export default function CO2GalvoList({ // id -> username map (fix showing UUIDs) const [ownerMap, setOwnerMap] = useState>({}); + // current user id for "Edit" visibility + const [meId, setMeId] = useState(null); useEffect(() => { if (queryText !== undefined) setLocalQuery(queryText); }, [queryText]); + // Load current user id + useEffect(() => { + let alive = true; + (async () => { + try { + const r = await fetch(`${API}/users/me?fields=id`, { credentials: "include", cache: "no-store" }); + if (!r.ok) return; + const j = await r.json().catch(() => null); + const id = j?.data?.id ?? j?.id ?? null; + if (alive) setMeId(id ? String(id) : null); + } catch { + /* ignore */ + } + })(); + return () => { + alive = false; + }; + }, []); + useEffect(() => { let live = true; (async () => { @@ -92,7 +113,7 @@ export default function CO2GalvoList({ }; }, []); - // Resolve owner usernames when we only have an id/UUID + // Resolve owner usernames when only id/UUID is present useEffect(() => { const ids = new Set(); for (const r of rows) { @@ -130,7 +151,7 @@ export default function CO2GalvoList({ updates[String(u.id)] = u.username || String(u.id); } } catch { - /* ignore batch errors */ + /* ignore */ } } if (!cancelled && Object.keys(updates).length) { @@ -146,7 +167,7 @@ export default function CO2GalvoList({ if (!o) return "—"; if (typeof o === "string" || typeof o === "number") { const id = String(o); - return ownerMap[id] || id; // prefer resolved username + return ownerMap[id] || id; } return ( o.username || @@ -156,6 +177,15 @@ export default function CO2GalvoList({ ); }; + const isMine = (o: Owner) => { + if (!meId || !o) return false; + if (typeof o === "string" || typeof o === "number") return String(o) === meId; + if (o.id != null) return String(o.id) === meId; + return false; + }; + + const withEditParam = (href: string) => (href.includes("?") ? `${href}&edit=1` : `${href}?edit=1`); + const filtered = useMemo(() => { const q = (localQuery || "").toLowerCase(); if (!q) return rows; @@ -185,29 +215,42 @@ export default function CO2GalvoList({ - + + - {filtered.map((r) => ( - - - - - - - - - ))} + {filtered.map((r) => { + const href = linkFor(r.submission_id); + return ( + + + + + + + + + + ); + })}
TitleTitle Owner Material Coating Model FieldEdit
- - {r.setting_title || "Untitled"} - - {ownerLabel(r.owner)}{r.mat?.name || "—"}{r.mat_coat?.name || "—"}{r.source?.model || "—"}{r.lens?.field_size || "—"}
+ + {r.setting_title || "Untitled"} + + {ownerLabel(r.owner)}{r.mat?.name || "—"}{r.mat_coat?.name || "—"}{r.source?.model || "—"}{r.lens?.field_size || "—"} + {isMine(r.owner) ? ( + + Edit + + ) : ( + + )} +