From 0dbf2c3d7a40349c9badd0ec18ac524855a0af55 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Fri, 11 Sep 2026 02:39:09 +0200 Subject: [PATCH] Redesign admin users as master-detail layout for clearer management. Co-authored-by: Cursor --- apps/admin-ui/src/app/viewers/page.tsx | 1538 +++++++++++++----------- 1 file changed, 847 insertions(+), 691 deletions(-) diff --git a/apps/admin-ui/src/app/viewers/page.tsx b/apps/admin-ui/src/app/viewers/page.tsx index 88aee49..ea7db5c 100644 --- a/apps/admin-ui/src/app/viewers/page.tsx +++ b/apps/admin-ui/src/app/viewers/page.tsx @@ -132,9 +132,16 @@ function formFromIptv(iptv: ViewerIptv | null | undefined): IptvFormState { } function iptvBadge(iptv: ViewerIptv | null | undefined): { label: string; tone: "ok" | "bad" | "off" } { - if (!iptv) return { label: "IPTV Geen", tone: "off" }; + if (!iptv) return { label: "IPTV geen", tone: "off" }; if (iptv.ok) return { label: "IPTV OK", tone: "ok" }; - return { label: "IPTV Fout", tone: "bad" }; + return { label: "IPTV fout", tone: "bad" }; +} + +function playTone(v: Viewer): "ok" | "warn" | "bad" | "off" { + if (v.googlePlaySyncStatus === "ERROR") return "bad"; + if (v.googlePlayAccess && v.googlePlaySyncStatus === "ACTIVE") return "ok"; + if (v.googlePlayAccess) return "warn"; + return "off"; } export default function ViewersPage() { @@ -149,7 +156,9 @@ export default function ViewersPage() { const [msg, setMsg] = useState(null); const [err, setErr] = useState(null); const [playEmails, setPlayEmails] = useState>({}); - const [expandedId, setExpandedId] = useState(null); + const [selectedId, setSelectedId] = useState(null); + const [mobileShowDetail, setMobileShowDetail] = useState(false); + const [query, setQuery] = useState(""); const [showCreate, setShowCreate] = useState(false); const [actionId, setActionId] = useState(null); const [iptvForms, setIptvForms] = useState>({}); @@ -191,13 +200,36 @@ export default function ViewersPage() { return { total: viewers.length, active, appOn, playOn, playErr }; }, [viewers]); - function openViewer(v: Viewer) { - const open = expandedId === v.id; - if (open) { - setExpandedId(null); + const filtered = useMemo(() => { + const q = query.trim().toLowerCase(); + if (!q) return viewers; + return viewers.filter((v) => { + const hay = `${v.name ?? ""} ${v.email}`.toLowerCase(); + return hay.includes(q); + }); + }, [viewers, query]); + + const selected = useMemo( + () => viewers.find((v) => v.id === selectedId) ?? null, + [viewers, selectedId] + ); + + useEffect(() => { + if (viewers.length === 0) { + setSelectedId(null); return; } - setExpandedId(v.id); + if (selectedId && viewers.some((v) => v.id === selectedId)) return; + const first = viewers[0]; + setSelectedId(first.id); + setIptvForms((prev) => + prev[first.id] ? prev : { ...prev, [first.id]: formFromIptv(first.iptv) } + ); + }, [viewers, selectedId]); + + function selectViewer(v: Viewer) { + setSelectedId(v.id); + setMobileShowDetail(true); setIptvForms((prev) => ({ ...prev, [v.id]: formFromIptv(v.iptv) })); setIptvLocalMsg((prev) => ({ ...prev, [v.id]: null })); setIptvLocalErr((prev) => ({ ...prev, [v.id]: null })); @@ -232,7 +264,8 @@ export default function ViewersPage() { setPassword(""); setName(""); setShowCreate(false); - setExpandedId(data.viewer.id); + setSelectedId(data.viewer.id); + setMobileShowDetail(true); setIptvForms((prev) => ({ ...prev, [data.viewer.id]: emptyIptvForm() })); load(); } finally { @@ -540,8 +573,8 @@ export default function ViewersPage() { setErr(data.error?.message ?? "Code koppelen mislukt"); return; } - const name = data.deviceName ? ` (${data.deviceName})` : ""; - setMsg(`Apparaat gekoppeld${name} — de app/TV rondt zelf af`); + const deviceName = data.deviceName ? ` (${data.deviceName})` : ""; + setMsg(`Apparaat gekoppeld${deviceName} — de app/TV rondt zelf af`); setClaimCodes((prev) => ({ ...prev, [viewerId]: "" })); load(); } finally { @@ -590,7 +623,10 @@ export default function ViewersPage() { setErr(data.error?.message ?? "Verwijderen mislukt"); return; } - if (expandedId === v.id) setExpandedId(null); + if (selectedId === v.id) { + setSelectedId(null); + setMobileShowDetail(false); + } setMsg(`${label} verwijderd`); load(); } finally { @@ -598,17 +634,26 @@ export default function ViewersPage() { } } + const iptv = selected?.iptv ?? null; + const iptvForm = selected + ? iptvForms[selected.id] ?? formFromIptv(iptv) + : emptyIptvForm(); + const tokens = selected?.addonTokens ?? []; + const installUrl = selected ? addonInstallUrls[selected.id] ?? null : null; + const badge = iptvBadge(iptv); + const pTone = selected ? playTone(selected) : "off"; + return ( <>