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";
|
||||
|
||||
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<Rec | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
||||
// current user id to show "Edit" for owners
|
||||
const [meId, setMeId] = useState<string | null>(null);
|
||||
|
||||
// Lightbox
|
||||
const [viewerSrc, setViewerSrc] = useState<string | null>(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 }) => (
|
||||
<div className="space-y-1">
|
||||
<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>
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<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>
|
||||
{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>
|
||||
</header>
|
||||
|
||||
|
|
@ -224,17 +292,18 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
|||
<Field label="Software" value={softName} />
|
||||
<Field label="Laser Source" value={sourceText || "—"} />
|
||||
<Field label="Lens Configuration" value={rec.lens_conf?.name || "—"} />
|
||||
<Field label="Scan Head Aperture" value={rec.lens_apt?.name || "—"} />
|
||||
<Field label="Beam Expander" value={rec.lens_exp?.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 || rec.lens_exp || null} suffix="x" />
|
||||
<Field
|
||||
label="Scan Lens"
|
||||
// removed "mm" units per request
|
||||
value={
|
||||
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 ?? "—"} />
|
||||
</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="Color" value={rec.mat_color?.name || "—"} />
|
||||
<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>
|
||||
</section>
|
||||
|
||||
{/* Repeaters (full width) */}
|
||||
{/* Repeaters (cards, full width) */}
|
||||
{(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>
|
||||
<div className="overflow-x-auto">
|
||||
<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">
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{rec.fill_settings!.map((r: any, i: number) => (
|
||||
<tr key={i}>
|
||||
<td className="px-2 py-1">{r.name || "—"}</td>
|
||||
<td className="px-2 py-1">{r.type || "—"}</td>
|
||||
<td className="px-2 py-1">{r.power ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.speed ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.interval ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.angle ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.pass ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.frequency ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.pulse ?? "—"}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.auto)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.cross)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.flood)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.air)}</td>
|
||||
</tr>
|
||||
<div key={i} className="border rounded p-3 space-y-2">
|
||||
<div className="font-medium">{r.name || `Fill ${i + 1}`}</div>
|
||||
<div className="grid sm:grid-cols-2 gap-2">
|
||||
<Field label="Type" value={TYPE_LABEL[r.type] || "—"} />
|
||||
<Field label="Power (%)" value={r.power ?? "—"} />
|
||||
<Field label="Speed (mm/s)" value={r.speed ?? "—"} />
|
||||
<Field label="Interval" value={r.interval ?? "—"} />
|
||||
<Field label="Angle" value={r.angle ?? "—"} />
|
||||
<Field label="Pass" value={r.pass ?? "—"} />
|
||||
<Field label="Freq (kHz)" value={r.frequency ?? "—"} />
|
||||
<Field label="Pulse (ns)" value={r.pulse ?? "—"} />
|
||||
<Field label="Increment" value={r.increment ?? "—"} />
|
||||
<Field label="Auto Rotate" value={yesNo(r.auto)} />
|
||||
<Field label="Crosshatch" value={yesNo(r.cross)} />
|
||||
<Field label="Flood Fill" value={yesNo(r.flood)} />
|
||||
<Field label="Air Assist" value={yesNo(r.air)} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{(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>
|
||||
<div className="overflow-x-auto">
|
||||
<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">
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{rec.line_settings!.map((r: any, i: number) => (
|
||||
<tr key={i}>
|
||||
<td className="px-2 py-1">{r.name || "—"}</td>
|
||||
<td className="px-2 py-1">{r.power ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.speed ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.frequency ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.pulse ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.pass ?? "—"}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.perf)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.cut)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.skip)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.wobble)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.air)}</td>
|
||||
</tr>
|
||||
<div key={i} className="border rounded p-3 space-y-2">
|
||||
<div className="font-medium">{r.name || `Line ${i + 1}`}</div>
|
||||
<div className="grid sm:grid-cols-2 gap-2">
|
||||
<Field label="Power (%)" value={r.power ?? "—"} />
|
||||
<Field label="Speed (mm/s)" value={r.speed ?? "—"} />
|
||||
<Field label="Freq (kHz)" value={r.frequency ?? "—"} />
|
||||
<Field label="Pulse (ns)" value={r.pulse ?? "—"} />
|
||||
<Field label="Pass" value={r.pass ?? "—"} />
|
||||
<Field label="Step" value={r.step ?? "—"} />
|
||||
<Field label="Size" value={r.size ?? "—"} />
|
||||
<Field label="Perf" value={yesNo(r.perf)} />
|
||||
<Field label="Cut" value={yesNo(r.cut)} />
|
||||
<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>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{(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>
|
||||
<div className="overflow-x-auto">
|
||||
<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">
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
{rec.raster_settings!.map((r: any, i: number) => (
|
||||
<tr key={i}>
|
||||
<td className="px-2 py-1">{r.name || "—"}</td>
|
||||
<td className="px-2 py-1">{r.type || "—"}</td>
|
||||
<td className="px-2 py-1">{r.dither || "—"}</td>
|
||||
<td className="px-2 py-1">{r.power ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.speed ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.interval ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.pass ?? "—"}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.cross)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.inversion)}</td>
|
||||
<td className="px-2 py-1">{yesNo(r.air)}</td>
|
||||
<td className="px-2 py-1">{r.frequency ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.pulse ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.halftone_cell ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.halftone_angle ?? "—"}</td>
|
||||
<td className="px-2 py-1">{r.dot ?? "—"}</td>
|
||||
</tr>
|
||||
<div key={i} className="border rounded p-3 space-y-2">
|
||||
<div className="font-medium">{r.name || `Raster ${i + 1}`}</div>
|
||||
<div className="grid sm:grid-cols-2 gap-2">
|
||||
<Field label="Type" value={TYPE_LABEL[r.type] || "—"} />
|
||||
<Field label="Dither" value={DITHER_LABEL(r.dither)} />
|
||||
<Field label="Power (%)" value={r.power ?? "—"} />
|
||||
<Field label="Speed (mm/s)" value={r.speed ?? "—"} />
|
||||
<Field label="Interval" value={r.interval ?? "—"} />
|
||||
<Field label="Pass" value={r.pass ?? "—"} />
|
||||
<Field label="Freq (kHz)" value={r.frequency ?? "—"} />
|
||||
<Field label="Pulse (ns)" value={r.pulse ?? "—"} />
|
||||
{!!r.halftone_cell && <Field label="Halftone Cell" value={r.halftone_cell} />}
|
||||
{!!r.halftone_angle && <Field label="Halftone Angle" value={r.halftone_angle} />}
|
||||
<Field label="Dot" value={r.dot ?? "—"} />
|
||||
<Field label="Crosshatch" value={yesNo(r.cross)} />
|
||||
<Field label="Inversion" value={yesNo(r.inversion)} />
|
||||
<Field label="Air Assist" value={yesNo(r.air)} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -54,11 +54,32 @@ export default function CO2GalvoList({
|
|||
|
||||
// id -> username map (fix showing UUIDs)
|
||||
const [ownerMap, setOwnerMap] = useState<Record<string, string>>({});
|
||||
// current user id for "Edit" visibility
|
||||
const [meId, setMeId] = useState<string | null>(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<string>();
|
||||
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({
|
|||
<table className="w-full table-fixed text-sm">
|
||||
<thead className="border-b">
|
||||
<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-[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%]">Model</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>
|
||||
</thead>
|
||||
<tbody className="divide-y">
|
||||
{filtered.map((r) => (
|
||||
<tr key={r.submission_id} className="hover:bg-muted/40">
|
||||
<td className="px-2 py-2 truncate">
|
||||
<Link href={linkFor(r.submission_id)} className="underline">
|
||||
{r.setting_title || "Untitled"}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-2 py-2 truncate">{ownerLabel(r.owner)}</td>
|
||||
<td className="px-2 py-2 truncate">{r.mat?.name || "—"}</td>
|
||||
<td className="px-2 py-2 truncate">{r.mat_coat?.name || "—"}</td>
|
||||
<td className="px-2 py-2 truncate">{r.source?.model || "—"}</td>
|
||||
<td className="px-2 py-2 truncate">{r.lens?.field_size || "—"}</td>
|
||||
</tr>
|
||||
))}
|
||||
{filtered.map((r) => {
|
||||
const href = linkFor(r.submission_id);
|
||||
return (
|
||||
<tr key={r.submission_id} className="hover:bg-muted/40">
|
||||
<td className="px-2 py-2 truncate">
|
||||
<Link href={href} className="underline">
|
||||
{r.setting_title || "Untitled"}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-2 py-2 truncate">{ownerLabel(r.owner)}</td>
|
||||
<td className="px-2 py-2 truncate">{r.mat?.name || "—"}</td>
|
||||
<td className="px-2 py-2 truncate">{r.mat_coat?.name || "—"}</td>
|
||||
<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>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue