makearmy-app/app/settings/co2-galvo/page.tsx
2025-10-02 17:21:43 -04:00

151 lines
4.8 KiB
TypeScript

"use client";
import { useEffect, useState, useMemo } from "react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
type Owner =
| string
| number
| { id?: string | number; username?: string | null; first_name?: string | null; last_name?: string | null; email?: string | null }
| null
| undefined;
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<any[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const t = setTimeout(() => setDebouncedQuery(query), 300);
return () => clearTimeout(t);
}, [query]);
useEffect(() => {
// ✅ include parent field `owner` AND subfields; use auth proxy
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",
"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 res.json().catch(() => ({}));
throw new Error(j?.errors?.[0]?.message || `HTTP ${res.status}`);
}
return res.json();
})
.then((json) => setSettings(json?.data || []))
.catch((e) => {
console.error("CO2 Galvo list fetch failed:", e);
setSettings([]);
})
.finally(() => setLoading(false));
}, []);
const ownerLabel = (o: Owner) => {
if (!o) return "—";
if (typeof o === "string" || typeof o === "number") return String(o);
return (
o.username ||
[o.first_name, o.last_name].filter(Boolean).join(" ").trim() ||
o.email ||
String(o.id || "—")
);
};
const highlight = (text?: string) => {
if (!debouncedQuery) return text || "";
const regex = new RegExp(`(${debouncedQuery.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi");
return (text || "").replace(regex, "<mark>$1</mark>");
};
const filtered = useMemo(() => {
const q = debouncedQuery.toLowerCase();
return settings.filter((entry) => {
const fieldsToSearch = [
entry.setting_title,
ownerLabel(entry.owner),
entry.uploader,
entry.mat?.name,
entry.mat_coat?.name,
entry.source?.model,
entry.lens?.field_size,
];
return fieldsToSearch
.filter(Boolean)
.some((field: string) => String(field).toLowerCase().includes(q));
});
}, [settings, debouncedQuery]);
return (
<div className="p-6 max-w-7xl mx-auto">
<div className="mb-4">
<input
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
placeholder="Search by title, owner, material, model…"
className="w-full max-w-lg border rounded px-3 py-2"
/>
</div>
{loading ? (
<p>Loading</p>
) : (
<div className="overflow-x-auto">
<table className="min-w-full text-sm">
<thead>
<tr className="bg-muted">
<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">Material</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">Field</th>
</tr>
</thead>
<tbody>
{filtered.map((s) => (
<tr key={s.submission_id} className="border-b hover:bg-muted/30">
<td className="px-2 py-2 whitespace-nowrap">
<Link href={`/settings/co2-galvo/${s.submission_id}`} className="underline">
<span dangerouslySetInnerHTML={{ __html: highlight(s.setting_title || "Untitled") }} />
</Link>
</td>
<td
className="px-2 py-2 whitespace-nowrap"
dangerouslySetInnerHTML={{ __html: highlight(ownerLabel(s.owner)) }}
/>
<td className="px-2 py-2 whitespace-nowrap">{s.mat?.name || "—"}</td>
<td className="px-2 py-2 whitespace-nowrap">{s.mat_coat?.name || "—"}</td>
<td className="px-2 py-2 whitespace-nowrap">{s.source?.model || "—"}</td>
<td className="px-2 py-2 whitespace-nowrap">{s.lens?.field_size || "—"}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}