co2-galvo owner test
This commit is contained in:
parent
f7a5f8c121
commit
59cbd8002a
1 changed files with 116 additions and 18 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
// app/settings/co2-galvo/page.tsx
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
|
|
@ -11,32 +12,54 @@ type Owner =
|
||||||
| null
|
| null
|
||||||
| undefined;
|
| 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() {
|
export default function CO2GalvoSettingsPage() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const initialQuery = searchParams.get("query") || "";
|
const initialQuery = searchParams.get("query") || "";
|
||||||
|
|
||||||
const [query, setQuery] = useState(initialQuery);
|
const [query, setQuery] = useState(initialQuery);
|
||||||
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery);
|
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery);
|
||||||
const [settings, setSettings] = useState<any[]>([]);
|
const [settings, setSettings] = useState<SettingsRow[]>([]);
|
||||||
|
const [ownerMap, setOwnerMap] = useState<Record<string, string>>({}); // id -> username
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [resolvingOwners, setResolvingOwners] = useState(false);
|
||||||
|
|
||||||
|
// Debounce search box
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const t = setTimeout(() => setDebouncedQuery(query), 300);
|
const t = setTimeout(() => setDebouncedQuery(query), 300);
|
||||||
return () => clearTimeout(t);
|
return () => clearTimeout(t);
|
||||||
}, [query]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
// ✅ include parent field `owner` AND subfields; use auth proxy
|
setLoading(true);
|
||||||
const fields = [
|
const fields = [
|
||||||
"submission_id",
|
"submission_id",
|
||||||
"setting_title",
|
"setting_title",
|
||||||
"uploader",
|
"uploader",
|
||||||
"owner", // parent → ensures raw id comes through if expansion blocked
|
"owner", // may be id or object depending on role
|
||||||
"owner.id",
|
"owner.id", // ask, but we won't depend on expansion
|
||||||
"owner.username",
|
"owner.username", // ask, but we won't depend on expansion
|
||||||
"owner.first_name",
|
|
||||||
"owner.last_name",
|
|
||||||
"owner.email",
|
|
||||||
"photo.id",
|
"photo.id",
|
||||||
"photo.title",
|
"photo.title",
|
||||||
"mat.name",
|
"mat.name",
|
||||||
|
|
@ -50,12 +73,13 @@ export default function CO2GalvoSettingsPage() {
|
||||||
fetch(url, { cache: "no-store", credentials: "include" })
|
fetch(url, { cache: "no-store", credentials: "include" })
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const j = await res.json().catch(() => ({}));
|
const j = await readJson(res).catch(() => null);
|
||||||
throw new Error(j?.errors?.[0]?.message || `HTTP ${res.status}`);
|
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) => {
|
.catch((e) => {
|
||||||
console.error("CO2 Galvo list fetch failed:", e);
|
console.error("CO2 Galvo list fetch failed:", e);
|
||||||
setSettings([]);
|
setSettings([]);
|
||||||
|
|
@ -63,20 +87,94 @@ export default function CO2GalvoSettingsPage() {
|
||||||
.finally(() => setLoading(false));
|
.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<string>();
|
||||||
|
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<string, string> = {};
|
||||||
|
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) => {
|
const ownerLabel = (o: Owner) => {
|
||||||
if (!o) return "—";
|
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 (
|
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() ||
|
||||||
o.email ||
|
o.email ||
|
||||||
String(o.id || "—")
|
(o.id != null ? String(o.id) : "—")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const highlight = (text?: string) => {
|
const highlight = (text?: string) => {
|
||||||
if (!debouncedQuery) return text || "";
|
if (!debouncedQuery) return text || "";
|
||||||
const regex = new RegExp(`(${debouncedQuery.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi");
|
const regex = new RegExp(`(${debouncedQuery.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")})`, "gi");
|
||||||
return (text || "").replace(regex, "<mark>$1</mark>");
|
return (text || "").replace(regex, "<mark>$1</mark>");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -90,13 +188,13 @@ export default function CO2GalvoSettingsPage() {
|
||||||
entry.mat?.name,
|
entry.mat?.name,
|
||||||
entry.mat_coat?.name,
|
entry.mat_coat?.name,
|
||||||
entry.source?.model,
|
entry.source?.model,
|
||||||
entry.lens?.field_size,
|
entry.lens?.field_size as any,
|
||||||
];
|
];
|
||||||
return fieldsToSearch
|
return fieldsToSearch
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.some((field: string) => String(field).toLowerCase().includes(q));
|
.some((field: string) => String(field).toLowerCase().includes(q));
|
||||||
});
|
});
|
||||||
}, [settings, debouncedQuery]);
|
}, [settings, debouncedQuery, ownerMap]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-7xl mx-auto">
|
<div className="p-6 max-w-7xl mx-auto">
|
||||||
|
|
@ -117,7 +215,7 @@ export default function CO2GalvoSettingsPage() {
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-muted">
|
<tr className="bg-muted">
|
||||||
<th className="px-2 py-2 text-left">Title</th>
|
<th className="px-2 py-2 text-left">Title</th>
|
||||||
<th className="px-2 py-2 text-left">Owner</th>
|
<th className="px-2 py-2 text-left">Owner {resolvingOwners ? "…resolving" : ""}</th>
|
||||||
<th className="px-2 py-2 text-left">Material</th>
|
<th className="px-2 py-2 text-left">Material</th>
|
||||||
<th className="px-2 py-2 text-left">Coating</th>
|
<th className="px-2 py-2 text-left">Coating</th>
|
||||||
<th className="px-2 py-2 text-left">Model</th>
|
<th className="px-2 py-2 text-left">Model</th>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue