"use client"; import { setRequestStatusAction } from "@/app/(internal)/admin/actions"; import Image from "next/image"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { useMemo, useState, useTransition } from "react"; import type { AdminRequestRow, SubmissionStatus } from "@/types/admin-submission"; type AdminRequest = AdminRequestRow; type StatusKey = SubmissionStatus; export type AdminConsoleView = "all" | "pending" | "approved" | "rejected"; export type AdminData = { meta: { title: string; subtitle: string; user: { name: string; role: string; avatarUrl: string }; footerTag: string; }; stats: Array<{ id: string; label: string; value: string; hint: string; icon: string; accent: "none" | "pending" | "confirmed" | "progress"; progressPercent?: number; }>; filters: { status: { label: string; options: { value: string; label: string }[] }; companyGroup: { label: string; options: { value: string; label: string }[] }; searchPlaceholder: string; exportLabel: string; }; table: { columns: Record; pageSize: number; }; statusMap: Record< StatusKey, { label: string; borderClass: string; dotClass: string; } >; requests: AdminRequest[]; footer: { legal: string[] }; }; function StatCard({ item, }: { item: AdminData["stats"][0]; }) { const accent = item.accent === "pending" ? "border-l-4 border-secondary" : item.accent === "confirmed" ? "border-l-4 border-primary" : ""; return (
{item.icon}

{item.accent === "pending" || item.accent === "confirmed" ? ( ) : null} {item.label}

{item.value}

{item.accent === "progress" && typeof item.progressPercent === "number" ? (
) : (
{item.hint ? ( {item.hint} ) : null}
)}
); } const PENDING_THRESHOLD = 50; function formatCount(n: number) { return n.toLocaleString("en-US"); } function buildLiveStats(data: AdminData) { const req = data.requests; const total = req.length; const pending = req.filter((r) => r.status === "pending").length; const approved = req.filter((r) => r.status === "approved").length; const rejected = req.filter((r) => r.status === "rejected").length; const conversionPct = total > 0 ? Math.round((approved / total) * 100) : 0; return data.stats.map((s) => { switch (s.id) { case "total": return { ...s, value: formatCount(total), hint: `${formatCount(approved)} approved · ${formatCount(pending)} pending · ${formatCount(rejected)} rejected`, }; case "pending": return { ...s, value: formatCount(pending), hint: pending >= PENDING_THRESHOLD ? `At or above threshold (${PENDING_THRESHOLD})` : `Below threshold (${PENDING_THRESHOLD})`, }; case "confirmed": return { ...s, value: formatCount(approved), hint: total > 0 ? `${Math.round((approved / total) * 100)}% of all requests` : "No requests yet", }; case "conversion": return { ...s, value: `${conversionPct}%`, hint: total > 0 ? `Approved share: ${approved} / ${total}` : "", progressPercent: conversionPct, }; default: return s; } }); } export default function AdminConsole({ data, view = "all" }: { data: AdminData; view?: AdminConsoleView }) { const router = useRouter(); const pathname = usePathname(); const [isPending, startTransition] = useTransition(); const [search, setSearch] = useState(""); const [status, setStatus] = useState("all"); const [companyGroup, setCompanyGroup] = useState("all"); const [afterDate, setAfterDate] = useState(""); const [page, setPage] = useState(1); const [detail, setDetail] = useState(null); const liveStats = useMemo(() => buildLiveStats(data), [data]); const statusFilter = view === "all" ? status : view; const filtered = useMemo(() => { let q = search.trim().toLowerCase(); // Remove leading # if searching for ID if (q.startsWith("#")) { q = q.substring(1); } return data.requests.filter((r) => { if (statusFilter !== "all" && r.status !== statusFilter) return false; if (companyGroup !== "all" && r.segment !== companyGroup) return false; if (afterDate && r.submittedAt < afterDate) return false; if (!q) return true; const hay = `${r.id} ${r.fullName} ${r.company} ${r.email} ${r.phone} ${r.jobTitle} ${r.status}`.toLowerCase(); return hay.includes(q); }); }, [data.requests, search, statusFilter, companyGroup, afterDate]); async function commitStatus(publicId: string, next: SubmissionStatus) { try { await setRequestStatusAction(publicId, next); startTransition(() => router.refresh()); } catch (e) { alert(e instanceof Error ? e.message : "Could not update status"); } } const pageSize = data.table.pageSize; const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); const safePage = Math.min(page, totalPages); const sliceStart = (safePage - 1) * pageSize; const pageRows = filtered.slice(sliceStart, sliceStart + pageSize); const showingFrom = filtered.length === 0 ? 0 : sliceStart + 1; const showingTo = sliceStart + pageRows.length; return (

{view === "all" ? data.meta.title : view === "pending" ? "Pending approval" : view === "approved" ? "Approved requests" : "Rejected requests"}

{data.meta.subtitle}

search { setSearch(e.target.value); setPage(1); }} />
{data.meta.user.role}
{liveStats.map((s) => ( ))}
{view === "all" ? (
) : (
Status

{view} only

)}
{ setAfterDate(e.target.value); setPage(1); }} />
{Object.entries(data.table.columns).map(([key, label]) => ( ))} {pageRows.map((row) => { const sm = data.statusMap[row.status]; return ( ); })}
{label}
#{row.id} {row.displayDate}
{row.fullName}
{row.jobTitle}
{row.company}
{row.email}
{row.phone}
{sm.label}
{row.status === "pending" ? ( <> ) : ( <> {row.status === "approved" ? ( <> ) : ( <> )} )}

Showing {showingFrom} to {showingTo} of {filtered.length} entries

Page {safePage} of {totalPages}
IPv6 SUMMIT 2026 | {data.meta.footerTag}

© 2026 IPv6 for AI & Data Centre Summit. Infrastructure for the Future.

{data.footer.legal.map((label) => ( ))}
{detail ? (
Name
{detail.fullName} — {detail.jobTitle}
Company
{detail.company}
Contact
{detail.email}
{detail.phone}
Status
{detail.status}
{detail.notes ? (
Notes
{detail.notes}
) : null}
) : null}
); }