From 9c7cfb3aaad9a7c4299ca2271979e2aa1c8c4064 Mon Sep 17 00:00:00 2001 From: makearmy Date: Thu, 2 Oct 2025 22:20:25 -0400 Subject: [PATCH] fix to populate results in my-settings --- app/portal/my-settings/page.tsx | 256 +++++++++++++++++++------------- 1 file changed, 153 insertions(+), 103 deletions(-) diff --git a/app/portal/my-settings/page.tsx b/app/portal/my-settings/page.tsx index 6e9755df..a5629493 100644 --- a/app/portal/my-settings/page.tsx +++ b/app/portal/my-settings/page.tsx @@ -4,33 +4,27 @@ import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; +type Coll = "settings_co2gal" | "settings_co2gan" | "settings_fiber" | "settings_uv"; + type Row = { id: string | number; submission_id?: string | number | null; setting_title?: string | null; status?: string | null; last_modified_date?: string | null; - collection: "settings_co2gal" | "settings_co2gan" | "settings_fiber" | "settings_uv"; }; -const COLLECTIONS: Array = [ - "settings_co2gal", -"settings_co2gan", -"settings_fiber", -"settings_uv", -]; - -const LABEL: Record = { +const COLLECTIONS: Coll[] = ["settings_co2gal", "settings_co2gan", "settings_fiber", "settings_uv"]; +const LABEL: Record = { settings_co2gal: "CO₂ Galvo", settings_co2gan: "CO₂ Gantry", settings_fiber: "Fiber", settings_uv: "UV", }; -// Route to the existing detail page for view/edit (you can customize) -function detailHref(row: Row) { - const subId = row.submission_id ?? row.id; - switch (row.collection) { +function detailHref(coll: Coll, idOrSubmission: string | number | null | undefined) { + const subId = idOrSubmission ?? ""; + switch (coll) { case "settings_co2gal": return `/settings/co2-galvo/${subId}?edit=1`; case "settings_co2gan": return `/settings/co2-gantry/${subId}?edit=1`; case "settings_fiber": return `/settings/fiber/${subId}?edit=1`; @@ -40,103 +34,144 @@ function detailHref(row: Row) { export default function MySettingsPage() { const [loading, setLoading] = useState(true); - const [me, setMe] = useState<{ id: string; username?: string | null } | null>(null); - const [rows, setRows] = useState([]); + const [meId, setMeId] = useState(null); + const [meUsername, setMeUsername] = useState(null); const [q, setQ] = useState(""); + const [byColl, setByColl] = useState>({ + settings_co2gal: [], + settings_co2gan: [], + settings_fiber: [], + settings_uv: [], + }); + const [errs, setErrs] = useState>({ me: null, settings_co2gal: null, settings_co2gan: null, settings_fiber: null, settings_uv: null }); - // 1) get current user id + // Safe JSON reader so HTML/404s don't explode parsing + async function readJson(res: Response) { + const text = await res.text(); + try { return text ? JSON.parse(text) : null; } catch { throw new Error(`Unexpected response (HTTP ${res.status})`); } + } + + // 1) Who am I (id + username) useEffect(() => { - let canceled = false; + let dead = false; (async () => { try { - const r = await fetch(`/api/dx/users/me?fields=id,username`, { - credentials: "include", - cache: "no-store", - }); - const j = await r.json(); - const id = j?.data?.id ?? j?.id; - if (!canceled) setMe(id ? { id: String(id), username: j?.data?.username ?? j?.username } : null); - } catch { - if (!canceled) setMe(null); + const r = await fetch(`/api/dx/users/me?fields=id,username`, { credentials: "include", cache: "no-store" }); + if (!r.ok) { + const j = await readJson(r).catch(() => null); + throw new Error(j?.errors?.[0]?.message || `HTTP ${r.status}`); + } + const j = await readJson(r); + const id = j?.data?.id ?? j?.id ?? null; + const un = j?.data?.username ?? j?.username ?? null; + if (!dead) { + setMeId(id ? String(id) : null); + setMeUsername(un ? String(un) : null); + setErrs((e) => ({ ...e, me: null })); + } + } catch (e: any) { + if (!dead) setErrs((er) => ({ ...er, me: e?.message || String(e) })); } })(); - return () => { canceled = true; }; + return () => { dead = true; }; }, []); - // 2) fetch my settings from each collection + // 2) Load my items per collection with OR(filters) useEffect(() => { - if (!me?.id) return; - let canceled = false; + if (!meId && !meUsername) return; + let dead = false; setLoading(true); + setErrs((e) => ({ ...e, settings_co2gal: null, settings_co2gan: null, settings_fiber: null, settings_uv: null })); (async () => { - const all: Row[] = []; + const acc: Record = { settings_co2gal: [], settings_co2gan: [], settings_fiber: [], settings_uv: [] }; + for (const coll of COLLECTIONS) { - const url = new URL(`/api/dx/items/${coll}`, window.location.origin); - url.searchParams.set("limit", "-1"); - url.searchParams.set("sort", "-last_modified_date"); - url.searchParams.set("fields", "id,submission_id,setting_title,status,last_modified_date"); - url.searchParams.set("filter[owner][_eq]", me.id); + const qs = new URLSearchParams(); + qs.set("limit", "-1"); + qs.set("sort", "-last_modified_date"); + qs.set("fields", "id,submission_id,setting_title,status,last_modified_date"); + + // Robust OR filter: + // - owner == meId + // - owner.id == meId (some Directus setups require nested id filter) + // - uploader == meUsername (fallback for legacy rows with missing owner) + let orIdx = 0; + if (meId) { + qs.set(`filter[_or][${orIdx}][owner][_eq]`, meId); orIdx++; + qs.set(`filter[_or][${orIdx}][owner][id][_eq]`, meId); orIdx++; + } + if (meUsername) { + qs.set(`filter[_or][${orIdx}][uploader][_eq]`, meUsername); orIdx++; + } + + const url = `/api/dx/items/${coll}?${qs.toString()}`; try { - const r = await fetch(url.toString(), { credentials: "include", cache: "no-store" }); - const j = await r.json(); - const data = Array.isArray(j?.data) ? j.data : []; - for (const item of data) { - all.push({ - id: item.id, - submission_id: item.submission_id ?? null, - setting_title: item.setting_title ?? null, - status: item.status ?? null, - last_modified_date: item.last_modified_date ?? null, - collection: coll, - }); + const r = await fetch(url, { credentials: "include", cache: "no-store" }); + if (!r.ok) { + const j = await readJson(r).catch(() => null); + throw new Error(j?.errors?.[0]?.message || `HTTP ${r.status}`); } - } catch (e) { - console.warn(`Failed to load ${coll}:`, e); + const j = await readJson(r); + const rows: Row[] = Array.isArray(j?.data) ? j.data : []; + acc[coll] = rows; + } catch (e: any) { + acc[coll] = []; + setErrs((er) => ({ ...er, [coll]: e?.message || String(e) })); } } - if (!canceled) { - setRows(all); + if (!dead) { + setByColl(acc); setLoading(false); } })(); - return () => { canceled = true; }; - }, [me?.id]); + return () => { dead = true; }; + }, [meId, meUsername]); + // 3) Filter client-side const filtered = useMemo(() => { const needle = q.trim().toLowerCase(); - if (!needle) return rows; - return rows.filter((r) => - [r.setting_title, LABEL[r.collection], r.status, r.last_modified_date] - .filter(Boolean) - .some((v) => String(v).toLowerCase().includes(needle)) - ); - }, [rows, q]); + if (!needle) return byColl; + const out: Record = { settings_co2gal: [], settings_co2gan: [], settings_fiber: [], settings_uv: [] }; + for (const coll of COLLECTIONS) { + out[coll] = (byColl[coll] || []).filter(r => + [r.setting_title, r.status, r.last_modified_date] + .filter(Boolean) + .some(v => String(v).toLowerCase().includes(needle)) + ); + } + return out; + }, [byColl, q]); - async function onDelete(row: Row) { - if (!confirm(`Delete "${row.setting_title || "Untitled"}" from ${LABEL[row.collection]}?`)) return; + async function onDelete(coll: Coll, row: Row) { + if (!confirm(`Delete "${row.setting_title || "Untitled"}" from ${LABEL[coll]}?`)) return; try { - const r = await fetch(`/api/dx/items/${row.collection}/${row.id}`, { - method: "DELETE", - credentials: "include", - }); + const r = await fetch(`/api/dx/items/${coll}/${row.id}`, { method: "DELETE", credentials: "include" }); if (!r.ok) { - const j = await r.json().catch(() => null); + const j = await readJson(r).catch(() => null); throw new Error(j?.errors?.[0]?.message || `HTTP ${r.status}`); } - setRows((prev) => prev.filter((x) => !(x.collection === row.collection && String(x.id) === String(row.id)))); + setByColl(prev => ({ ...prev, [coll]: prev[coll].filter(x => String(x.id) !== String(row.id)) })); } catch (e: any) { alert(`Delete failed: ${e?.message || e}`); } } + const total = COLLECTIONS.reduce((n, c) => n + (filtered[c]?.length || 0), 0); + return (

My Settings

+ {!!errs.me && ( +
+ Couldn’t load your profile: {errs.me} +
+ )} +
setQ(e.currentTarget.value)} /> - {rows.length} total + {total} total
{loading ? (

Loading…

- ) : filtered.length === 0 ? ( -

No settings yet.

) : ( -
- - - - - - - - - - - - {filtered.map((r) => ( - - - - - - - - ))} - -
TitleCollectionStatusUpdatedActions
{r.setting_title || "Untitled"}{LABEL[r.collection]}{r.status || "—"}{r.last_modified_date ? new Date(r.last_modified_date).toLocaleString() : "—"} -
- Edit - -
-
-
+ COLLECTIONS.map((coll) => { + const rows = filtered[coll] || []; + return ( +
+
+

+ {LABEL[coll]} ({rows.length}) +

+ {!!errs[coll] && ( + Error: {errs[coll]} + )} +
+ {rows.length === 0 ? ( +

No items.

+ ) : ( +
+ + + + + + + + + + + {rows.map((r) => ( + + + + + + + ))} + +
TitleStatusUpdatedActions
{r.setting_title || "Untitled"}{r.status || "—"} + {r.last_modified_date ? new Date(r.last_modified_date).toLocaleString() : "—"} + +
+ Edit + +
+
+
+ )} +
+ ); + }) )}
);