From 59cbd8002afedc97def1a6cf934b64ef7acd5968 Mon Sep 17 00:00:00 2001 From: makearmy Date: Thu, 2 Oct 2025 19:26:39 -0400 Subject: [PATCH] co2-galvo owner test --- app/settings/co2-galvo/page.tsx | 134 +++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 18 deletions(-) diff --git a/app/settings/co2-galvo/page.tsx b/app/settings/co2-galvo/page.tsx index ed9651b6..2f3e2d5c 100644 --- a/app/settings/co2-galvo/page.tsx +++ b/app/settings/co2-galvo/page.tsx @@ -1,3 +1,4 @@ +// app/settings/co2-galvo/page.tsx "use client"; import { useEffect, useState, useMemo } from "react"; @@ -11,32 +12,54 @@ type Owner = | 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 default function CO2GalvoSettingsPage() { const searchParams = useSearchParams(); const initialQuery = searchParams.get("query") || ""; const [query, setQuery] = useState(initialQuery); const [debouncedQuery, setDebouncedQuery] = useState(initialQuery); - const [settings, setSettings] = useState([]); + const [settings, setSettings] = useState([]); + const [ownerMap, setOwnerMap] = useState>({}); // id -> username const [loading, setLoading] = useState(true); + const [resolvingOwners, setResolvingOwners] = useState(false); + // Debounce search box useEffect(() => { const t = setTimeout(() => setDebouncedQuery(query), 300); return () => clearTimeout(t); }, [query]); + // Safe JSON reader (so HTML 404s don't blow up) + 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})`); + } + } + + // Initial load of settings (using user token via /api/dx proxy) useEffect(() => { - // ✅ include parent field `owner` AND subfields; use auth proxy + setLoading(true); const fields = [ "submission_id", "setting_title", "uploader", - "owner", // parent → ensures raw id comes through if expansion blocked - "owner.id", - "owner.username", - "owner.first_name", - "owner.last_name", - "owner.email", + "owner", // may be id or object depending on role + "owner.id", // ask, but we won't depend on expansion + "owner.username", // ask, but we won't depend on expansion "photo.id", "photo.title", "mat.name", @@ -50,12 +73,13 @@ export default function CO2GalvoSettingsPage() { fetch(url, { cache: "no-store", credentials: "include" }) .then(async (res) => { if (!res.ok) { - const j = await res.json().catch(() => ({})); - throw new Error(j?.errors?.[0]?.message || `HTTP ${res.status}`); + const j = await readJson(res).catch(() => null); + const msg = (j as any)?.errors?.[0]?.message || `HTTP ${res.status}`; + throw new Error(msg); } - return res.json(); + return readJson(res); }) - .then((json) => setSettings(json?.data || [])) + .then((json: any) => setSettings(Array.isArray(json?.data) ? json.data : [])) .catch((e) => { console.error("CO2 Galvo list fetch failed:", e); setSettings([]); @@ -63,20 +87,94 @@ export default function CO2GalvoSettingsPage() { .finally(() => setLoading(false)); }, []); + // After settings load, resolve owner usernames directly via /api/dx/users using the user token + useEffect(() => { + if (!settings.length) return; + + // Collect IDs needing resolution (owner is raw id OR object without username) + 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 { + // object with maybe id/username + 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; // Directus handles big _in, but chunk to be safe + 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)); + // filter[id][_in]=a,b,c + 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 403/401, perms don't allow reading other users; stop trying further + 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 ownerLabel = (o: Owner) => { if (!o) return "—"; - if (typeof o === "string" || typeof o === "number") return String(o); + + if (typeof o === "string" || typeof o === "number") { + const id = String(o); + return ownerMap[id] || id; // prefer resolved username, fall back to id + } + + // object return ( o.username || [o.first_name, o.last_name].filter(Boolean).join(" ").trim() || o.email || - String(o.id || "—") + (o.id != null ? String(o.id) : "—") ); }; const highlight = (text?: string) => { if (!debouncedQuery) return text || ""; - const regex = new RegExp(`(${debouncedQuery.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi"); + const regex = new RegExp(`(${debouncedQuery.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})`, "gi"); return (text || "").replace(regex, "$1"); }; @@ -90,13 +188,13 @@ export default function CO2GalvoSettingsPage() { entry.mat?.name, entry.mat_coat?.name, entry.source?.model, - entry.lens?.field_size, + entry.lens?.field_size as any, ]; return fieldsToSearch .filter(Boolean) .some((field: string) => String(field).toLowerCase().includes(q)); }); - }, [settings, debouncedQuery]); + }, [settings, debouncedQuery, ownerMap]); return (
@@ -117,7 +215,7 @@ export default function CO2GalvoSettingsPage() { Title - Owner + Owner {resolvingOwners ? "…resolving" : ""} Material Coating Model