From a76a6d14142292675ee3759d6d5651a0a7dd5563 Mon Sep 17 00:00:00 2001 From: makearmy Date: Fri, 3 Oct 2025 15:11:00 -0400 Subject: [PATCH] build fixes --- components/lists/CO2GalvoList.tsx | 344 ++++++++++++++++++++----- components/portal/SettingsSwitcher.tsx | 14 +- 2 files changed, 287 insertions(+), 71 deletions(-) diff --git a/components/lists/CO2GalvoList.tsx b/components/lists/CO2GalvoList.tsx index 72aeb5b5..d4a1c372 100644 --- a/components/lists/CO2GalvoList.tsx +++ b/components/lists/CO2GalvoList.tsx @@ -1,64 +1,286 @@ -return ( -
-
- { - setLocalQuery(e.currentTarget.value); - onQueryChange?.(e.currentTarget.value); - })} - placeholder="Search by title, owner, material, model…" - className="w-full border rounded px-3 py-2" - /> -
+// components/lists/CO2GalvoList.tsx +"use client"; - {loading ? ( -

Loading…

- ) : ( -
- - - - - - - - - - - - - - {filtered.map((s) => { - const mine = isMine(s.owner); - const ownerText = ownerLabel(s.owner) + (mine ? " (you)" : ""); +import { useEffect, useMemo, useState } from "react"; +import Link from "next/link"; + +type Owner = +| string +| number +| { + id?: string | number; + username?: string | null; + first_name?: string | null; + last_name?: string | null; + email?: string | null; +} +| null +| undefined; + +type SettingsRow = { + submission_id: string | number; + setting_title?: string | null; + uploader?: string | null; + owner?: Owner; + mat?: { name?: string | null } | null; + mat_coat?: { name?: string | null } | null; + source?: { model?: string | null } | null; + lens?: { field_size?: string | number | null } | null; +}; + +export type CO2GalvoListProps = { + /** Build the href for a record (portal vs standalone) */ + linkFor: (submission_id: string | number, opts?: { edit?: boolean }) => string; + /** Optional controlled search text (else internal input) */ + queryText?: string; + onQueryChange?: (q: string) => void; +}; + +async function readJson(res: Response) { + const text = await res.text(); + try { + return text ? JSON.parse(text) : null; + } catch { + throw new Error(`Unexpected response (status ${res.status})`); + } +} + +export default function CO2GalvoList({ linkFor, queryText, onQueryChange }: CO2GalvoListProps) { + const [settings, setSettings] = useState([]); + const [ownerMap, setOwnerMap] = useState>({}); + const [loading, setLoading] = useState(true); + const [resolvingOwners, setResolvingOwners] = useState(false); + const [meId, setMeId] = useState(null); + const [localQuery, setLocalQuery] = useState(queryText ?? ""); + + // keep local and external search text in sync + useEffect(() => { + if (queryText !== undefined) setLocalQuery(queryText); + }, [queryText]); + + // load current user id (for edit visibility) + useEffect(() => { + 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 list + useEffect(() => { + const fields = [ + "submission_id", + "setting_title", + "uploader", + "owner", + "owner.id", + "owner.username", + "photo.id", + "photo.title", + "mat.name", + "mat_coat.name", + "source.model", + "lens.field_size", + ].join(","); + + const url = `/api/dx/items/settings_co2gal?fields=${encodeURIComponent(fields)}&limit=-1`; + + fetch(url, { cache: "no-store", credentials: "include" }) + .then(async (res) => { + if (!res.ok) { + const j = await readJson(res).catch(() => null); + const msg = (j as any)?.errors?.[0]?.message || `HTTP ${res.status}`; + throw new Error(msg); + } + return readJson(res); + }) + .then((json: any) => setSettings(Array.isArray(json?.data) ? json.data : [])) + .catch((e) => { + console.error("CO2 Galvo list fetch failed:", e); + setSettings([]); + }) + .finally(() => setLoading(false)); + }, []); + + // resolve owner usernames if needed + useEffect(() => { + if (!settings.length) return; + + const ids = new Set(); + for (const s of settings) { + const o = s.owner; + if (!o) continue; + + if (typeof o === "string" || typeof o === "number") { + const k = String(o); + if (!ownerMap[k]) ids.add(k); + } 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; + + const all = Array.from(ids); + const chunk = 100; + setResolvingOwners(true); + + (async () => { + const updates: Record = {}; + for (let i = 0; i < all.length; i += chunk) { + const slice = all.slice(i, i + chunk); + const qs = new URLSearchParams(); + qs.set("fields", "id,username"); + qs.set("limit", String(slice.length)); + qs.set("filter[id][_in]", slice.join(",")); + + try { + const r = await fetch(`/api/dx/users?${qs.toString()}`, { + credentials: "include", + cache: "no-store", + }); + if (!r.ok) { + const j = await readJson(r).catch(() => null); + const msg = (j as any)?.errors?.[0]?.message || `HTTP ${r.status}`; + console.warn("Owner lookup failed:", msg); + if (r.status === 401 || r.status === 403) break; + continue; + } + const j = await readJson(r); + const rows: Array<{ id: string; username?: string | null }> = j?.data || []; + for (const row of rows) { + updates[String(row.id)] = row.username || String(row.id); + } + } catch (e) { + console.warn("Owner lookup error:", e); + break; + } + } + + if (Object.keys(updates).length) { + setOwnerMap((prev) => ({ ...prev, ...updates })); + } + setResolvingOwners(false); + })(); + }, [settings, ownerMap]); + + 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 ownerLabel = (o: Owner) => { + if (!o) return "—"; + if (typeof o === "string" || typeof o === "number") { + const id = String(o); + return ownerMap[id] || id; + } return ( - - - - - - - - - + o.username || + [o.first_name, o.last_name].filter(Boolean).join(" ").trim() || + o.email || + (o.id != null ? String(o.id) : "—") ); - })} - -
TitleOwnerMaterialCoatingModelFieldEdit
- - {s.setting_title || "Untitled"} - - {ownerText}{s.mat?.name || "—"}{s.mat_coat?.name || "—"}{s.source?.model || "—"}{s.lens?.field_size || "—"} - {mine ? ( - - Edit - - ) : ( - - )} -
-
- )} -
- ); + }; + + const filtered = useMemo(() => { + const nq = (localQuery || "").toLowerCase(); + if (!nq) return settings; + return settings.filter((entry) => { + const fields = [ + entry.setting_title, + ownerLabel(entry.owner), + entry.uploader, + entry.mat?.name, + entry.mat_coat?.name, + entry.source?.model, + entry.lens?.field_size as any, + ].filter(Boolean); + return fields.some((v: any) => String(v).toLowerCase().includes(nq)); + }); + }, [settings, localQuery, ownerMap]); + + return ( +
+
+ { + setLocalQuery(e.currentTarget.value); + onQueryChange?.(e.currentTarget.value); + }} + placeholder="Search by title, owner, material, model…" + className="w-full border rounded px-3 py-2" + /> +
+ + {loading ? ( +

Loading…

+ ) : ( +
+ + + + + + + + + + + + + + {filtered.map((s) => { + const mine = isMine(s.owner); + const ownerText = ownerLabel(s.owner) + (mine ? " (you)" : ""); + return ( + + + + + + + + + + ); + })} + +
Title + Owner {resolvingOwners ? "…resolving" : ""} + MaterialCoatingModelFieldEdit
+ + {s.setting_title || "Untitled"} + + {ownerText}{s.mat?.name || "—"}{s.mat_coat?.name || "—"}{s.source?.model || "—"}{s.lens?.field_size || "—"} + {mine ? ( + + Edit + + ) : ( + + )} +
+
+ )} +
+ ); +} diff --git a/components/portal/SettingsSwitcher.tsx b/components/portal/SettingsSwitcher.tsx index 96a8758e..34727878 100644 --- a/components/portal/SettingsSwitcher.tsx +++ b/components/portal/SettingsSwitcher.tsx @@ -30,10 +30,7 @@ const TABS: { key: Tab; label: string }[] = [ ]; const isDataTab = (k: string): k is DataTab => -k === "fiber" || k === "uv" || k === "co2-ganry" || k === "co2-gantry" || k === "co2-galvo" -// keep typo guard for "co2-ganry" if you want; remove if unnecessary -? true -: (k === "fiber" || k === "uv" || k === "co2-gantry" || k === "co2-galvo"); +k === "fiber" || k === "uv" || k === "co2-gantry" || k === "co2-galvo"; const tabToTarget: Record; case "add": return ( -
+

Add Setting

@@ -61,10 +58,8 @@ function Panel({ tab, lastDataTab }: { tab: Tab; lastDataTab: DataTab }) { // We navigate away for "My Settings", so this branch is not normally rendered. // Fallback content (just in case someone forces ?t=my on this page): return ( -
-

+

Opening My Settings… -

); default: @@ -120,9 +115,8 @@ export default function SettingsSwitcher() { ))}
-
+ {/* Removed the extra border/padding wrapper to avoid double-framing */}
-
); }