list and details cleanup
This commit is contained in:
parent
6829f2840c
commit
2e8297d426
2 changed files with 125 additions and 135 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import SettingsSubmit from "@/components/forms/SettingsSubmit";
|
||||||
|
|
||||||
type Rec = {
|
type Rec = {
|
||||||
submission_id: string | number;
|
submission_id: string | number;
|
||||||
|
|
@ -46,12 +47,13 @@ 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 router = useRouter();
|
||||||
const sp = useSearchParams();
|
const sp = useSearchParams();
|
||||||
|
const editMode = sp.get("edit") === "1";
|
||||||
|
|
||||||
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
|
// me id for owner-only edit
|
||||||
const [meId, setMeId] = useState<string | null>(null);
|
const [meId, setMeId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Lightbox
|
// Lightbox
|
||||||
|
|
@ -78,18 +80,13 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`/api/dx/users/me?fields=id`, { cache: "no-store", credentials: "include" });
|
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 t = await r.text();
|
||||||
const j = t ? JSON.parse(t) : null;
|
const j = t ? JSON.parse(t) : null;
|
||||||
const idVal = j?.data?.id ?? j?.id ?? null;
|
const idVal = j?.data?.id ?? j?.id ?? null;
|
||||||
if (alive) setMeId(idVal ? String(idVal) : null);
|
if (alive) setMeId(idVal ? String(idVal) : null);
|
||||||
} catch {
|
} catch { /* ignore */ }
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
return () => {
|
return () => { alive = false; };
|
||||||
alive = false;
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -148,9 +145,9 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
"last_modified_date",
|
"last_modified_date",
|
||||||
].join(",");
|
].join(",");
|
||||||
|
|
||||||
const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent(
|
const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent(fields)}&filter[submission_id][_eq]=${encodeURIComponent(
|
||||||
fields
|
String(id)
|
||||||
)}&filter[submission_id][_eq]=${encodeURIComponent(String(id))}&limit=1`;
|
)}&limit=1`;
|
||||||
|
|
||||||
const r = await fetch(url, { cache: "no-store", credentials: "include" });
|
const r = await fetch(url, { cache: "no-store", credentials: "include" });
|
||||||
const text = await r.text();
|
const text = await r.text();
|
||||||
|
|
@ -165,9 +162,7 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
if (!dead) setLoading(false);
|
if (!dead) setLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
return () => {
|
return () => { dead = true; };
|
||||||
dead = true;
|
|
||||||
};
|
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
if (loading) return <p className="p-6">Loading setting…</p>;
|
if (loading) return <p className="p-6">Loading setting…</p>;
|
||||||
|
|
@ -186,42 +181,49 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
|
|
||||||
const softName = typeof rec.laser_soft === "object" ? rec.laser_soft?.name ?? "—" : "—";
|
const softName = typeof rec.laser_soft === "object" ? rec.laser_soft?.name ?? "—" : "—";
|
||||||
const sourceText =
|
const sourceText =
|
||||||
[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})` : "");
|
|
||||||
|
|
||||||
const ownerId =
|
const ownerId =
|
||||||
typeof rec.owner === "object"
|
typeof rec.owner === "object" ? (rec.owner?.id != null ? String(rec.owner.id) : null) : rec.owner != null ? String(rec.owner) : null;
|
||||||
? rec.owner?.id != null
|
|
||||||
? String(rec.owner.id)
|
|
||||||
: null
|
|
||||||
: rec.owner != null
|
|
||||||
? String(rec.owner)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const isMine = meId && ownerId ? meId === ownerId : false;
|
const isMine = meId && ownerId ? meId === ownerId : false;
|
||||||
|
|
||||||
// Small field renderer: label on top, value below (+ optional suffix)
|
// Small field renderer (label on top, value below). Accepts React nodes.
|
||||||
const Field = ({ label, value, suffix }: { label: string; value: any; suffix?: string }) => (
|
const Field = ({ label, value, suffix }: { label: string; value: React.ReactNode | string | number | null | undefined; suffix?: string }) => {
|
||||||
<div className="space-y-1">
|
const primitive =
|
||||||
<div className="text-xs uppercase tracking-wide text-muted-foreground">{label}</div>
|
typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
||||||
<div className="text-sm break-words">
|
const isEmpty = value == null || value === "" || (typeof value === "number" && isNaN(value as number));
|
||||||
{value != null && value !== "" ? (
|
|
||||||
<>
|
return (
|
||||||
{String(value)}
|
<div className="space-y-1">
|
||||||
{suffix ? <span className="opacity-70"> {suffix}</span> : null}
|
<div className="text-xs uppercase tracking-wide text-muted-foreground">{label}</div>
|
||||||
</>
|
<div className="text-sm break-words">
|
||||||
) : (
|
{isEmpty ? (
|
||||||
"—"
|
"—"
|
||||||
)}
|
) : primitive ? (
|
||||||
</div>
|
<>
|
||||||
</div>
|
{String(value)}
|
||||||
);
|
{suffix ? <span className="opacity-70"> {suffix}</span> : null}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
// render React node directly (e.g., Notes paragraph)
|
||||||
|
value
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const openEdit = () => {
|
const openEdit = () => {
|
||||||
const q = new URLSearchParams(sp.toString());
|
const q = new URLSearchParams(sp.toString());
|
||||||
q.set("edit", "1");
|
q.set("edit", "1");
|
||||||
router.replace(`?${q.toString()}`, { scroll: false });
|
router.replace(`?${q.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
const closeEdit = () => {
|
||||||
|
const q = new URLSearchParams(sp.toString());
|
||||||
|
q.delete("edit");
|
||||||
|
router.replace(`?${q.toString()}`, { scroll: false });
|
||||||
|
};
|
||||||
|
|
||||||
// Pretty labels
|
// Pretty labels
|
||||||
const TYPE_LABEL: Record<string, string> = {
|
const TYPE_LABEL: Record<string, string> = {
|
||||||
|
|
@ -231,6 +233,52 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
};
|
};
|
||||||
const DITHER_LABEL = (v: string | undefined) => (v ? v.charAt(0).toUpperCase() + v.slice(1) : "—");
|
const DITHER_LABEL = (v: string | undefined) => (v ? v.charAt(0).toUpperCase() + v.slice(1) : "—");
|
||||||
|
|
||||||
|
// ----- EDIT MODE (restore working behavior) -----
|
||||||
|
if (editMode && rec) {
|
||||||
|
const toId = (v: any) =>
|
||||||
|
v == null ? "" : typeof v === "object" ? (v.id ?? v.submission_id ?? "") : String(v);
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
submission_id: rec.submission_id,
|
||||||
|
setting_title: rec.setting_title ?? "",
|
||||||
|
setting_notes: rec.setting_notes ?? "",
|
||||||
|
photo: photoId ? String(photoId) : null,
|
||||||
|
screen: screenId ? String(screenId) : null,
|
||||||
|
// Material
|
||||||
|
mat: toId(rec.mat) || "",
|
||||||
|
mat_coat: toId(rec.mat_coat) || "",
|
||||||
|
mat_color: toId(rec.mat_color) || "",
|
||||||
|
mat_opacity: toId(rec.mat_opacity) || "",
|
||||||
|
mat_thickness: rec.mat_thickness ?? null,
|
||||||
|
// Rig & Optics
|
||||||
|
laser_soft: typeof rec.laser_soft === "object" ? String(rec.laser_soft?.id ?? "") : String(rec.laser_soft ?? "") || "",
|
||||||
|
source: rec.source && typeof rec.source === "object" ? String(rec.source.submission_id ?? "") : String(rec.source ?? "") || "",
|
||||||
|
lens: toId(rec.lens) || "",
|
||||||
|
focus: rec.focus ?? null,
|
||||||
|
// CO2 triplet
|
||||||
|
lens_conf: toId(rec.lens_conf) || "",
|
||||||
|
lens_apt: toId(rec.lens_apt) || "",
|
||||||
|
lens_exp: toId(rec.lens_exp) || "",
|
||||||
|
repeat_all: rec.repeat_all ?? null,
|
||||||
|
// Repeaters
|
||||||
|
fill_settings: rec.fill_settings ?? [],
|
||||||
|
line_settings: rec.line_settings ?? [],
|
||||||
|
raster_settings: rec.raster_settings ?? [],
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-xl lg:text-2xl font-semibold">Edit CO₂ Galvo Setting</h1>
|
||||||
|
<button className="px-2 py-1 border rounded" onClick={closeEdit}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<SettingsSubmit mode="edit" submissionId={rec.submission_id} initialValues={initialValues} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -246,16 +294,20 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
<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>
|
||||||
|
|
||||||
{/* Info (full width) */}
|
{/* Top row: Info (left) + Images (right) */}
|
||||||
<section className="grid gap-3">
|
<section className="grid md:grid-cols-2 gap-6 items-start">
|
||||||
|
{/* Info */}
|
||||||
|
<div className="grid gap-3">
|
||||||
<Field label="Owner" value={ownerLabel(rec.owner)} />
|
<Field label="Owner" value={ownerLabel(rec.owner)} />
|
||||||
<Field label="Uploader" value={rec.uploader || "—"} />
|
<Field label="Uploader" value={rec.uploader || "—"} />
|
||||||
{rec.setting_notes ? <Field label="Notes" value={<p className="whitespace-pre-wrap">{rec.setting_notes}</p>} /> : null}
|
{rec.setting_notes ? (
|
||||||
</section>
|
<Field label="Notes" value={<p className="whitespace-pre-wrap">{rec.setting_notes}</p>} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Images (full width, click to expand, ~75% smaller) */}
|
{/* Images (side-by-side thumbnails) */}
|
||||||
{(photoSrc || screenSrc) && (
|
{(photoSrc || screenSrc) && (
|
||||||
<section className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
{photoSrc ? (
|
{photoSrc ? (
|
||||||
<figure className="space-y-1 justify-self-start">
|
<figure className="space-y-1 justify-self-start">
|
||||||
<div
|
<div
|
||||||
|
|
@ -280,10 +332,11 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
<figcaption className="text-xs text-muted-foreground text-center">Settings Screenshot</figcaption>
|
<figcaption className="text-xs text-muted-foreground text-center">Settings Screenshot</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
) : null}
|
) : null}
|
||||||
</section>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Two columns: left Rig & Optics, right Material */}
|
{/* Two columns below: left Rig & Optics, right Material */}
|
||||||
<section className="grid md:grid-cols-2 gap-6">
|
<section className="grid md:grid-cols-2 gap-6">
|
||||||
{/* Rig & Optics */}
|
{/* Rig & Optics */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
|
@ -334,7 +387,7 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
<Field label="Type" value={TYPE_LABEL[r.type] || "—"} />
|
<Field label="Type" value={TYPE_LABEL[r.type] || "—"} />
|
||||||
<Field label="Power" value={r.power ?? "—"} suffix="%" />
|
<Field label="Power" value={r.power ?? "—"} suffix="%" />
|
||||||
<Field label="Speed" value={r.speed ?? "—"} suffix="mm/s" />
|
<Field label="Speed" value={r.speed ?? "—"} suffix="mm/s" />
|
||||||
<Field label="Interval" value={r.interval ?? "—"} />
|
<Field label="Interval" value={r.interval ?? "—"} suffix="mm" />
|
||||||
<Field label="Angle" value={r.angle ?? "—"} suffix="°" />
|
<Field label="Angle" value={r.angle ?? "—"} suffix="°" />
|
||||||
<Field label="Pass" value={r.pass ?? "—"} />
|
<Field label="Pass" value={r.pass ?? "—"} />
|
||||||
<Field label="Frequency" value={r.frequency ?? "—"} suffix="kHz" />
|
<Field label="Frequency" value={r.frequency ?? "—"} suffix="kHz" />
|
||||||
|
|
@ -391,7 +444,7 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
<Field label="Dither" value={DITHER_LABEL(r.dither)} />
|
<Field label="Dither" value={DITHER_LABEL(r.dither)} />
|
||||||
<Field label="Power" value={r.power ?? "—"} suffix="%" />
|
<Field label="Power" value={r.power ?? "—"} suffix="%" />
|
||||||
<Field label="Speed" value={r.speed ?? "—"} suffix="mm/s" />
|
<Field label="Speed" value={r.speed ?? "—"} suffix="mm/s" />
|
||||||
<Field label="Interval" value={r.interval ?? "—"} />
|
<Field label="Interval" value={r.interval ?? "—"} suffix="mm" />
|
||||||
<Field label="Pass" value={r.pass ?? "—"} />
|
<Field label="Pass" value={r.pass ?? "—"} />
|
||||||
<Field label="Frequency" value={r.frequency ?? "—"} suffix="kHz" />
|
<Field label="Frequency" value={r.frequency ?? "—"} suffix="kHz" />
|
||||||
<Field label="Pulse" value={r.pulse ?? "—"} suffix="ns" />
|
<Field label="Pulse" value={r.pulse ?? "—"} suffix="ns" />
|
||||||
|
|
@ -410,16 +463,8 @@ export default function CO2GalvoDetail({ id, editable }: { id: string | number;
|
||||||
|
|
||||||
{/* Lightbox */}
|
{/* Lightbox */}
|
||||||
{viewerSrc && (
|
{viewerSrc && (
|
||||||
<div
|
<div className="fixed inset-0 z-50 bg-black/80 p-4 flex items-center justify-center" onClick={() => setViewerSrc(null)}>
|
||||||
className="fixed inset-0 z-50 bg-black/80 p-4 flex items-center justify-center"
|
<img src={viewerSrc} alt="" className="max-w-full max-h-full cursor-zoom-out" onClick={(e) => e.stopPropagation()} />
|
||||||
onClick={() => setViewerSrc(null)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={viewerSrc}
|
|
||||||
alt=""
|
|
||||||
className="max-w-full max-h-full cursor-zoom-out"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -51,32 +51,27 @@ export default function CO2GalvoList({
|
||||||
const [rows, setRows] = useState<Row[]>([]);
|
const [rows, setRows] = useState<Row[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [localQuery, setLocalQuery] = useState(queryText ?? "");
|
const [localQuery, setLocalQuery] = useState(queryText ?? "");
|
||||||
|
|
||||||
// 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);
|
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
|
// who am I?
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let alive = true;
|
let live = true;
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`${API}/users/me?fields=id`, { credentials: "include", cache: "no-store" });
|
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 j = await r.json().catch(() => null);
|
const idVal = j?.data?.id ?? j?.id ?? null;
|
||||||
const id = j?.data?.id ?? j?.id ?? null;
|
if (live) setMeId(idVal ? String(idVal) : null);
|
||||||
if (alive) setMeId(id ? String(id) : null);
|
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
if (live) setMeId(null);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
return () => {
|
return () => {
|
||||||
alive = false;
|
live = false;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -113,62 +108,9 @@ export default function CO2GalvoList({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Resolve owner usernames when only id/UUID is present
|
|
||||||
useEffect(() => {
|
|
||||||
const ids = new Set<string>();
|
|
||||||
for (const r of rows) {
|
|
||||||
const o = r.owner;
|
|
||||||
if (!o) continue;
|
|
||||||
if (typeof o === "string" || typeof o === "number") {
|
|
||||||
const id = String(o);
|
|
||||||
if (!ownerMap[id]) ids.add(id);
|
|
||||||
} else {
|
|
||||||
const id = o.id != null ? String(o.id) : "";
|
|
||||||
const hasUsername = !!o.username;
|
|
||||||
if (id && !hasUsername && !ownerMap[id]) ids.add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!ids.size) return;
|
|
||||||
|
|
||||||
let cancelled = false;
|
|
||||||
(async () => {
|
|
||||||
const all = Array.from(ids);
|
|
||||||
const updates: Record<string, string> = {};
|
|
||||||
const chunkSize = 100;
|
|
||||||
for (let i = 0; i < all.length; i += chunkSize) {
|
|
||||||
const slice = all.slice(i, i + chunkSize);
|
|
||||||
const qs = new URLSearchParams();
|
|
||||||
qs.set("fields", "id,username");
|
|
||||||
qs.set("limit", String(slice.length));
|
|
||||||
qs.set("filter[id][_in]", slice.join(","));
|
|
||||||
const url = `${API}/users?${qs.toString()}`;
|
|
||||||
try {
|
|
||||||
const r = await fetch(url, { credentials: "include", cache: "no-store" });
|
|
||||||
if (!r.ok) continue;
|
|
||||||
const j = await r.json().catch(() => null);
|
|
||||||
const arr: Array<{ id: string | number; username?: string | null }> = j?.data || [];
|
|
||||||
for (const u of arr) {
|
|
||||||
updates[String(u.id)] = u.username || String(u.id);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!cancelled && Object.keys(updates).length) {
|
|
||||||
setOwnerMap((prev) => ({ ...prev, ...updates }));
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, [rows, ownerMap]);
|
|
||||||
|
|
||||||
const ownerLabel = (o: Owner) => {
|
const ownerLabel = (o: Owner) => {
|
||||||
if (!o) return "—";
|
if (!o) return "—";
|
||||||
if (typeof o === "string" || typeof o === "number") {
|
if (typeof o === "string" || typeof o === "number") return String(o);
|
||||||
const id = String(o);
|
|
||||||
return ownerMap[id] || id;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
o.username ||
|
o.username ||
|
||||||
[o.first_name, o.last_name].filter(Boolean).join(" ").trim() ||
|
[o.first_name, o.last_name].filter(Boolean).join(" ").trim() ||
|
||||||
|
|
@ -177,15 +119,13 @@ export default function CO2GalvoList({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isMine = (o: Owner) => {
|
const isMine = (o: Owner): boolean => {
|
||||||
if (!meId || !o) return false;
|
if (!meId || !o) return false;
|
||||||
if (typeof o === "string" || typeof o === "number") return String(o) === meId;
|
if (typeof o === "string" || typeof o === "number") return String(o) === meId;
|
||||||
if (o.id != null) return String(o.id) === meId;
|
if (o.id != null) return String(o.id) === meId;
|
||||||
return false;
|
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;
|
||||||
|
|
@ -194,7 +134,9 @@ export default function CO2GalvoList({
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.some((v) => String(v).toLowerCase().includes(q))
|
.some((v) => String(v).toLowerCase().includes(q))
|
||||||
);
|
);
|
||||||
}, [rows, localQuery, ownerMap]);
|
}, [rows, localQuery]);
|
||||||
|
|
||||||
|
const addEditParam = (href: string) => (href.includes("?") ? `${href}&edit=1` : `${href}?edit=1`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
|
@ -226,22 +168,25 @@ export default function CO2GalvoList({
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y">
|
<tbody className="divide-y">
|
||||||
{filtered.map((r) => {
|
{filtered.map((r) => {
|
||||||
const href = linkFor(r.submission_id);
|
const mine = isMine(r.owner);
|
||||||
|
const ownerText = ownerLabel(r.owner) + (mine ? " (you)" : "");
|
||||||
|
const viewHref = linkFor(r.submission_id);
|
||||||
|
const editHref = addEditParam(viewHref);
|
||||||
return (
|
return (
|
||||||
<tr key={r.submission_id} className="hover:bg-muted/40">
|
<tr key={r.submission_id} className="hover:bg-muted/40">
|
||||||
<td className="px-2 py-2 truncate">
|
<td className="px-2 py-2 truncate">
|
||||||
<Link href={href} className="underline">
|
<Link href={viewHref} className="underline">
|
||||||
{r.setting_title || "Untitled"}
|
{r.setting_title || "Untitled"}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-2 truncate">{ownerLabel(r.owner)}</td>
|
<td className="px-2 py-2 truncate">{ownerText}</td>
|
||||||
<td className="px-2 py-2 truncate">{r.mat?.name || "—"}</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.mat_coat?.name || "—"}</td>
|
||||||
<td className="px-2 py-2 truncate">{r.source?.model || "—"}</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 truncate">{r.lens?.field_size || "—"}</td>
|
||||||
<td className="px-2 py-2">
|
<td className="px-2 py-2">
|
||||||
{isMine(r.owner) ? (
|
{mine ? (
|
||||||
<Link href={withEditParam(href)} className="underline">
|
<Link href={editHref} className="underline">
|
||||||
Edit
|
Edit
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue