list and details cleanup
This commit is contained in:
parent
c59ef98fd9
commit
8fc0989b17
2 changed files with 199 additions and 141 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
type Rec = {
|
type Rec = {
|
||||||
submission_id: string | number;
|
submission_id: string | number;
|
||||||
|
|
@ -43,10 +44,16 @@ type Rec = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CO2GalvoDetail({ id, editable }: { id: string | number; editable?: boolean }) {
|
export default function CO2GalvoDetail({ id, editable }: { id: string | number; editable?: boolean }) {
|
||||||
|
const router = useRouter();
|
||||||
|
const sp = useSearchParams();
|
||||||
|
|
||||||
const [rec, setRec] = useState<Rec | null>(null);
|
const [rec, setRec] = useState<Rec | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [err, setErr] = useState<string | null>(null);
|
const [err, setErr] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// current user id to show "Edit" for owners
|
||||||
|
const [meId, setMeId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Lightbox
|
// Lightbox
|
||||||
const [viewerSrc, setViewerSrc] = useState<string | null>(null);
|
const [viewerSrc, setViewerSrc] = useState<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -65,6 +72,26 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
}
|
}
|
||||||
const yesNo = (v: any) => (v ? "Yes" : "No");
|
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(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
let dead = false;
|
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?.make, rec.source?.model].filter(Boolean).join(" ") +
|
||||||
(rec.source?.nm ? ` (${rec.source.nm})` : "");
|
(rec.source?.nm ? ` (${rec.source.nm})` : "");
|
||||||
|
|
||||||
// Small field renderer: label on top, value below
|
const ownerId =
|
||||||
const Field = ({ label, value }: { label: string; value: any }) => (
|
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 }) => (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-xs uppercase tracking-wide text-muted-foreground">{label}</div>
|
<div className="text-xs uppercase tracking-wide text-muted-foreground">{label}</div>
|
||||||
<div className="text-sm break-words">{value ?? "—"}</div>
|
<div className="text-sm break-words">
|
||||||
|
{value != null && value !== "" ? (
|
||||||
|
<>
|
||||||
|
{String(value)}
|
||||||
|
{suffix ? <span className="opacity-70"> {suffix}</span> : null}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"—"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const openEdit = () => {
|
||||||
|
const q = new URLSearchParams(sp.toString());
|
||||||
|
q.set("edit", "1");
|
||||||
|
router.replace(`?${q.toString()}`, { scroll: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pretty labels
|
||||||
|
const TYPE_LABEL: Record<string, string> = {
|
||||||
|
uni: "UniDirectional",
|
||||||
|
bi: "BiDirectional",
|
||||||
|
offset: "Offset Fill",
|
||||||
|
};
|
||||||
|
const DITHER_LABEL = (v: string | undefined) => (v ? v.charAt(0).toUpperCase() + v.slice(1) : "—");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="space-y-1">
|
<header className="space-y-1">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="text-2xl font-bold break-words">{rec.setting_title || "Untitled"}</h1>
|
<h1 className="text-2xl font-bold break-words">{rec.setting_title || "Untitled"}</h1>
|
||||||
|
{editable && isMine ? (
|
||||||
|
<button className="px-2 py-1 border rounded text-sm" onClick={openEdit}>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
<div className="text-sm text-muted-foreground">Last modified: {rec.last_modified_date || "—"}</div>
|
<div className="text-sm text-muted-foreground">Last modified: {rec.last_modified_date || "—"}</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
@ -224,17 +292,18 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
<Field label="Software" value={softName} />
|
<Field label="Software" value={softName} />
|
||||||
<Field label="Laser Source" value={sourceText || "—"} />
|
<Field label="Laser Source" value={sourceText || "—"} />
|
||||||
<Field label="Lens Configuration" value={rec.lens_conf?.name || "—"} />
|
<Field label="Lens Configuration" value={rec.lens_conf?.name || "—"} />
|
||||||
<Field label="Scan Head Aperture" value={rec.lens_apt?.name || "—"} />
|
<Field label="Scan Head Aperture" value={rec.lens_apt?.name || rec.lens_apt || null} suffix="mm" />
|
||||||
<Field label="Beam Expander" value={rec.lens_exp?.name || "—"} />
|
<Field label="Beam Expander" value={rec.lens_exp?.name || rec.lens_exp || null} suffix="x" />
|
||||||
<Field
|
<Field
|
||||||
label="Scan Lens"
|
label="Scan Lens"
|
||||||
|
// removed "mm" units per request
|
||||||
value={
|
value={
|
||||||
rec.lens
|
rec.lens
|
||||||
? `${rec.lens.field_size ?? "—"}${rec.lens.focal_length ? ` / ${rec.lens.focal_length} mm` : ""}`
|
? `${rec.lens.field_size ?? "—"}${rec.lens.focal_length ? ` / ${rec.lens.focal_length}` : ""}`
|
||||||
: "—"
|
: "—"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Field label="Focus (mm)" value={rec.focus ?? "—"} />
|
<Field label="Focus" value={rec.focus ?? "—"} suffix="mm" />
|
||||||
<Field label="Repeat All" value={rec.repeat_all ?? "—"} />
|
<Field label="Repeat All" value={rec.repeat_all ?? "—"} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -247,146 +316,92 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
<Field label="Coating" value={rec.mat_coat?.name || "—"} />
|
<Field label="Coating" value={rec.mat_coat?.name || "—"} />
|
||||||
<Field label="Color" value={rec.mat_color?.name || "—"} />
|
<Field label="Color" value={rec.mat_color?.name || "—"} />
|
||||||
<Field label="Opacity" value={rec.mat_opacity?.opacity ?? "—"} />
|
<Field label="Opacity" value={rec.mat_opacity?.opacity ?? "—"} />
|
||||||
<Field label="Thickness (mm)" value={rec.mat_thickness ?? "—"} />
|
<Field label="Thickness" value={rec.mat_thickness ?? "—"} suffix="mm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Repeaters (full width) */}
|
{/* Repeaters (cards, full width) */}
|
||||||
{(rec.fill_settings?.length ?? 0) > 0 && (
|
{(rec.fill_settings?.length ?? 0) > 0 && (
|
||||||
<section className="space-y-2">
|
<section className="space-y-3">
|
||||||
<h2 className="text-lg font-semibold">Fill Settings</h2>
|
<h2 className="text-lg font-semibold">Fill Settings</h2>
|
||||||
<div className="overflow-x-auto">
|
<div className="grid md:grid-cols-2 gap-3">
|
||||||
<table className="w-full text-sm">
|
|
||||||
<thead className="border-b">
|
|
||||||
<tr>
|
|
||||||
<th className="px-2 py-1 text-left">Name</th>
|
|
||||||
<th className="px-2 py-1 text-left">Type</th>
|
|
||||||
<th className="px-2 py-1 text-left">Power (%)</th>
|
|
||||||
<th className="px-2 py-1 text-left">Speed (mm/s)</th>
|
|
||||||
<th className="px-2 py-1 text-left">Interval</th>
|
|
||||||
<th className="px-2 py-1 text-left">Angle</th>
|
|
||||||
<th className="px-2 py-1 text-left">Pass</th>
|
|
||||||
<th className="px-2 py-1 text-left">Freq (kHz)</th>
|
|
||||||
<th className="px-2 py-1 text-left">Pulse (ns)</th>
|
|
||||||
<th className="px-2 py-1 text-left">Auto</th>
|
|
||||||
<th className="px-2 py-1 text-left">Cross</th>
|
|
||||||
<th className="px-2 py-1 text-left">Flood</th>
|
|
||||||
<th className="px-2 py-1 text-left">Air</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y">
|
|
||||||
{rec.fill_settings!.map((r: any, i: number) => (
|
{rec.fill_settings!.map((r: any, i: number) => (
|
||||||
<tr key={i}>
|
<div key={i} className="border rounded p-3 space-y-2">
|
||||||
<td className="px-2 py-1">{r.name || "—"}</td>
|
<div className="font-medium">{r.name || `Fill ${i + 1}`}</div>
|
||||||
<td className="px-2 py-1">{r.type || "—"}</td>
|
<div className="grid sm:grid-cols-2 gap-2">
|
||||||
<td className="px-2 py-1">{r.power ?? "—"}</td>
|
<Field label="Type" value={TYPE_LABEL[r.type] || "—"} />
|
||||||
<td className="px-2 py-1">{r.speed ?? "—"}</td>
|
<Field label="Power (%)" value={r.power ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.interval ?? "—"}</td>
|
<Field label="Speed (mm/s)" value={r.speed ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.angle ?? "—"}</td>
|
<Field label="Interval" value={r.interval ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.pass ?? "—"}</td>
|
<Field label="Angle" value={r.angle ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.frequency ?? "—"}</td>
|
<Field label="Pass" value={r.pass ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.pulse ?? "—"}</td>
|
<Field label="Freq (kHz)" value={r.frequency ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.auto)}</td>
|
<Field label="Pulse (ns)" value={r.pulse ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.cross)}</td>
|
<Field label="Increment" value={r.increment ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.flood)}</td>
|
<Field label="Auto Rotate" value={yesNo(r.auto)} />
|
||||||
<td className="px-2 py-1">{yesNo(r.air)}</td>
|
<Field label="Crosshatch" value={yesNo(r.cross)} />
|
||||||
</tr>
|
<Field label="Flood Fill" value={yesNo(r.flood)} />
|
||||||
|
<Field label="Air Assist" value={yesNo(r.air)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(rec.line_settings?.length ?? 0) > 0 && (
|
{(rec.line_settings?.length ?? 0) > 0 && (
|
||||||
<section className="space-y-2">
|
<section className="space-y-3">
|
||||||
<h2 className="text-lg font-semibold">Line Settings</h2>
|
<h2 className="text-lg font-semibold">Line Settings</h2>
|
||||||
<div className="overflow-x-auto">
|
<div className="grid md:grid-cols-2 gap-3">
|
||||||
<table className="w-full text-sm">
|
|
||||||
<thead className="border-b">
|
|
||||||
<tr>
|
|
||||||
<th className="px-2 py-1 text-left">Name</th>
|
|
||||||
<th className="px-2 py-1 text-left">Power</th>
|
|
||||||
<th className="px-2 py-1 text-left">Speed</th>
|
|
||||||
<th className="px-2 py-1 text-left">Freq</th>
|
|
||||||
<th className="px-2 py-1 text-left">Pulse</th>
|
|
||||||
<th className="px-2 py-1 text-left">Pass</th>
|
|
||||||
<th className="px-2 py-1 text-left">Perf</th>
|
|
||||||
<th className="px-2 py-1 text-left">Cut</th>
|
|
||||||
<th className="px-2 py-1 text-left">Skip</th>
|
|
||||||
<th className="px-2 py-1 text-left">Wobble</th>
|
|
||||||
<th className="px-2 py-1 text-left">Air</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y">
|
|
||||||
{rec.line_settings!.map((r: any, i: number) => (
|
{rec.line_settings!.map((r: any, i: number) => (
|
||||||
<tr key={i}>
|
<div key={i} className="border rounded p-3 space-y-2">
|
||||||
<td className="px-2 py-1">{r.name || "—"}</td>
|
<div className="font-medium">{r.name || `Line ${i + 1}`}</div>
|
||||||
<td className="px-2 py-1">{r.power ?? "—"}</td>
|
<div className="grid sm:grid-cols-2 gap-2">
|
||||||
<td className="px-2 py-1">{r.speed ?? "—"}</td>
|
<Field label="Power (%)" value={r.power ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.frequency ?? "—"}</td>
|
<Field label="Speed (mm/s)" value={r.speed ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.pulse ?? "—"}</td>
|
<Field label="Freq (kHz)" value={r.frequency ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.pass ?? "—"}</td>
|
<Field label="Pulse (ns)" value={r.pulse ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.perf)}</td>
|
<Field label="Pass" value={r.pass ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.cut)}</td>
|
<Field label="Step" value={r.step ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.skip)}</td>
|
<Field label="Size" value={r.size ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.wobble)}</td>
|
<Field label="Perf" value={yesNo(r.perf)} />
|
||||||
<td className="px-2 py-1">{yesNo(r.air)}</td>
|
<Field label="Cut" value={yesNo(r.cut)} />
|
||||||
</tr>
|
<Field label="Skip" value={yesNo(r.skip)} />
|
||||||
|
<Field label="Wobble" value={yesNo(r.wobble)} />
|
||||||
|
<Field label="Air Assist" value={yesNo(r.air)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(rec.raster_settings?.length ?? 0) > 0 && (
|
{(rec.raster_settings?.length ?? 0) > 0 && (
|
||||||
<section className="space-y-2">
|
<section className="space-y-3">
|
||||||
<h2 className="text-lg font-semibold">Raster Settings</h2>
|
<h2 className="text-lg font-semibold">Raster Settings</h2>
|
||||||
<div className="overflow-x-auto">
|
<div className="grid md:grid-cols-2 gap-3">
|
||||||
<table className="w-full text-sm">
|
|
||||||
<thead className="border-b">
|
|
||||||
<tr>
|
|
||||||
<th className="px-2 py-1 text-left">Name</th>
|
|
||||||
<th className="px-2 py-1 text-left">Type</th>
|
|
||||||
<th className="px-2 py-1 text-left">Dither</th>
|
|
||||||
<th className="px-2 py-1 text-left">Power</th>
|
|
||||||
<th className="px-2 py-1 text-left">Speed</th>
|
|
||||||
<th className="px-2 py-1 text-left">Interval</th>
|
|
||||||
<th className="px-2 py-1 text-left">Pass</th>
|
|
||||||
<th className="px-2 py-1 text-left">Cross</th>
|
|
||||||
<th className="px-2 py-1 text-left">Inversion</th>
|
|
||||||
<th className="px-2 py-1 text-left">Air</th>
|
|
||||||
<th className="px-2 py-1 text-left">Freq (kHz)</th>
|
|
||||||
<th className="px-2 py-1 text-left">Pulse (ns)</th>
|
|
||||||
<th className="px-2 py-1 text-left">Halftone Cell</th>
|
|
||||||
<th className="px-2 py-1 text-left">Halftone Angle</th>
|
|
||||||
<th className="px-2 py-1 text-left">Dot</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y">
|
|
||||||
{rec.raster_settings!.map((r: any, i: number) => (
|
{rec.raster_settings!.map((r: any, i: number) => (
|
||||||
<tr key={i}>
|
<div key={i} className="border rounded p-3 space-y-2">
|
||||||
<td className="px-2 py-1">{r.name || "—"}</td>
|
<div className="font-medium">{r.name || `Raster ${i + 1}`}</div>
|
||||||
<td className="px-2 py-1">{r.type || "—"}</td>
|
<div className="grid sm:grid-cols-2 gap-2">
|
||||||
<td className="px-2 py-1">{r.dither || "—"}</td>
|
<Field label="Type" value={TYPE_LABEL[r.type] || "—"} />
|
||||||
<td className="px-2 py-1">{r.power ?? "—"}</td>
|
<Field label="Dither" value={DITHER_LABEL(r.dither)} />
|
||||||
<td className="px-2 py-1">{r.speed ?? "—"}</td>
|
<Field label="Power (%)" value={r.power ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.interval ?? "—"}</td>
|
<Field label="Speed (mm/s)" value={r.speed ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.pass ?? "—"}</td>
|
<Field label="Interval" value={r.interval ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.cross)}</td>
|
<Field label="Pass" value={r.pass ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.inversion)}</td>
|
<Field label="Freq (kHz)" value={r.frequency ?? "—"} />
|
||||||
<td className="px-2 py-1">{yesNo(r.air)}</td>
|
<Field label="Pulse (ns)" value={r.pulse ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.frequency ?? "—"}</td>
|
{!!r.halftone_cell && <Field label="Halftone Cell" value={r.halftone_cell} />}
|
||||||
<td className="px-2 py-1">{r.pulse ?? "—"}</td>
|
{!!r.halftone_angle && <Field label="Halftone Angle" value={r.halftone_angle} />}
|
||||||
<td className="px-2 py-1">{r.halftone_cell ?? "—"}</td>
|
<Field label="Dot" value={r.dot ?? "—"} />
|
||||||
<td className="px-2 py-1">{r.halftone_angle ?? "—"}</td>
|
<Field label="Crosshatch" value={yesNo(r.cross)} />
|
||||||
<td className="px-2 py-1">{r.dot ?? "—"}</td>
|
<Field label="Inversion" value={yesNo(r.inversion)} />
|
||||||
</tr>
|
<Field label="Air Assist" value={yesNo(r.air)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,32 @@ export default function CO2GalvoList({
|
||||||
|
|
||||||
// id -> username map (fix showing UUIDs)
|
// id -> username map (fix showing UUIDs)
|
||||||
const [ownerMap, setOwnerMap] = useState<Record<string, string>>({});
|
const [ownerMap, setOwnerMap] = useState<Record<string, string>>({});
|
||||||
|
// current user id for "Edit" visibility
|
||||||
|
const [meId, setMeId] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryText !== undefined) setLocalQuery(queryText);
|
if (queryText !== undefined) setLocalQuery(queryText);
|
||||||
}, [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(() => {
|
useEffect(() => {
|
||||||
let live = true;
|
let live = true;
|
||||||
(async () => {
|
(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(() => {
|
useEffect(() => {
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
for (const r of rows) {
|
for (const r of rows) {
|
||||||
|
|
@ -130,7 +151,7 @@ export default function CO2GalvoList({
|
||||||
updates[String(u.id)] = u.username || String(u.id);
|
updates[String(u.id)] = u.username || String(u.id);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore batch errors */
|
/* ignore */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!cancelled && Object.keys(updates).length) {
|
if (!cancelled && Object.keys(updates).length) {
|
||||||
|
|
@ -146,7 +167,7 @@ export default function CO2GalvoList({
|
||||||
if (!o) return "—";
|
if (!o) return "—";
|
||||||
if (typeof o === "string" || typeof o === "number") {
|
if (typeof o === "string" || typeof o === "number") {
|
||||||
const id = String(o);
|
const id = String(o);
|
||||||
return ownerMap[id] || id; // prefer resolved username
|
return ownerMap[id] || id;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
o.username ||
|
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 filtered = useMemo(() => {
|
||||||
const q = (localQuery || "").toLowerCase();
|
const q = (localQuery || "").toLowerCase();
|
||||||
if (!q) return rows;
|
if (!q) return rows;
|
||||||
|
|
@ -185,29 +215,42 @@ export default function CO2GalvoList({
|
||||||
<table className="w-full table-fixed text-sm">
|
<table className="w-full table-fixed text-sm">
|
||||||
<thead className="border-b">
|
<thead className="border-b">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-2 py-2 text-left w-[30%]">Title</th>
|
<th className="px-2 py-2 text-left w-[28%]">Title</th>
|
||||||
<th className="px-2 py-2 text-left w-[16%]">Owner</th>
|
<th className="px-2 py-2 text-left w-[16%]">Owner</th>
|
||||||
<th className="px-2 py-2 text-left w-[14%]">Material</th>
|
<th className="px-2 py-2 text-left w-[14%]">Material</th>
|
||||||
<th className="px-2 py-2 text-left w-[14%]">Coating</th>
|
<th className="px-2 py-2 text-left w-[14%]">Coating</th>
|
||||||
<th className="px-2 py-2 text-left w-[14%]">Model</th>
|
<th className="px-2 py-2 text-left w-[14%]">Model</th>
|
||||||
<th className="px-2 py-2 text-left w-[10%]">Field</th>
|
<th className="px-2 py-2 text-left w-[10%]">Field</th>
|
||||||
|
<th className="px-2 py-2 text-left w-[4%]">Edit</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y">
|
<tbody className="divide-y">
|
||||||
{filtered.map((r) => (
|
{filtered.map((r) => {
|
||||||
<tr key={r.submission_id} className="hover:bg-muted/40">
|
const href = linkFor(r.submission_id);
|
||||||
<td className="px-2 py-2 truncate">
|
return (
|
||||||
<Link href={linkFor(r.submission_id)} className="underline">
|
<tr key={r.submission_id} className="hover:bg-muted/40">
|
||||||
{r.setting_title || "Untitled"}
|
<td className="px-2 py-2 truncate">
|
||||||
</Link>
|
<Link href={href} className="underline">
|
||||||
</td>
|
{r.setting_title || "Untitled"}
|
||||||
<td className="px-2 py-2 truncate">{ownerLabel(r.owner)}</td>
|
</Link>
|
||||||
<td className="px-2 py-2 truncate">{r.mat?.name || "—"}</td>
|
</td>
|
||||||
<td className="px-2 py-2 truncate">{r.mat_coat?.name || "—"}</td>
|
<td className="px-2 py-2 truncate">{ownerLabel(r.owner)}</td>
|
||||||
<td className="px-2 py-2 truncate">{r.source?.model || "—"}</td>
|
<td className="px-2 py-2 truncate">{r.mat?.name || "—"}</td>
|
||||||
<td className="px-2 py-2 truncate">{r.lens?.field_size || "—"}</td>
|
<td className="px-2 py-2 truncate">{r.mat_coat?.name || "—"}</td>
|
||||||
</tr>
|
<td className="px-2 py-2 truncate">{r.source?.model || "—"}</td>
|
||||||
))}
|
<td className="px-2 py-2 truncate">{r.lens?.field_size || "—"}</td>
|
||||||
|
<td className="px-2 py-2">
|
||||||
|
{isMine(r.owner) ? (
|
||||||
|
<Link href={withEditParam(href)} className="underline">
|
||||||
|
Edit
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="opacity-50">—</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue