"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 { useLanguage } from "@/app/context/LanguageContext"; import { useTranslation } from "@/app/hooks/useTranslation"; 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 InfoItem({ label, value, isEmail = false, isStatus = false, status = "", statusMap = {} as any }: { label: string; value?: string; isEmail?: boolean; isStatus?: boolean; status?: string; statusMap?: any; }) { if (!value && !isStatus) return (
{label}
); return (
{label} {isStatus ? ( {value} ) : ( {value} )}
); } 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, lang: string) { 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; const isVi = lang === "vi"; return data.stats.map((s) => { switch (s.id) { case "total": return { ...s, value: formatCount(total), hint: isVi ? `${formatCount(approved)} đã duyệt · ${formatCount(pending)} chờ duyệt · ${formatCount(rejected)} từ chối` : `${formatCount(approved)} approved · ${formatCount(pending)} pending · ${formatCount(rejected)} rejected`, }; case "pending": return { ...s, value: formatCount(pending), hint: isVi ? (pending >= PENDING_THRESHOLD ? `Đạt hoặc vượt ngưỡng (${PENDING_THRESHOLD})` : `Dưới ngưỡng (${PENDING_THRESHOLD})`) : (pending >= PENDING_THRESHOLD ? `At or above threshold (${PENDING_THRESHOLD})` : `Below threshold (${PENDING_THRESHOLD})`), }; case "confirmed": return { ...s, value: formatCount(approved), hint: total > 0 ? (isVi ? `${Math.round((approved / total) * 100)}% trên tổng số yêu cầu` : `${Math.round((approved / total) * 100)}% of all requests`) : (isVi ? "Chưa có yêu cầu nào" : "No requests yet"), }; case "conversion": return { ...s, value: `${conversionPct}%`, hint: total > 0 ? (isVi ? `Tỷ lệ đã duyệt: ${approved} / ${total}` : `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 { lang, setLang } = useLanguage(); const { admin: t } = useTranslation(); const [userDropdown, setUserDropdown] = useState(false); const handleLogout = async () => { try { await fetch("/ipv6/api/auth/logout", { method: "POST" }); router.push("/login"); } catch (error) { console.error("Logout failed", error); } }; // Merge real requests from server prop with translated UI labels const tData = { ...t, requests: data.requests, table: { ...t.table, pageSize: data.table.pageSize }, stats: t.stats as AdminData["stats"], } as AdminData; const liveStats = useMemo(() => buildLiveStats(tData, lang), [tData, lang]); 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" ? t.meta.title : view === "pending" ? (lang === "vi" ? "Chờ Duyệt" : "Pending Approval") : view === "approved" ? (lang === "vi" ? "Đã Duyệt" : "Approved Requests") : (lang === "vi" ? "Từ Chối" : "Rejected Requests")}

{t.meta.subtitle}

search { setSearch(e.target.value); setPage(1); }} />
{/* Language Switcher — Admin Header */}
|
{userDropdown && (
)}
{liveStats.map((s) => ( ))}
{view === "all" ? (
) : (
{t.filters.status.label}

{t.statusMap[view as StatusKey]?.label ?? view}

)}
{ setAfterDate(e.target.value); setPage(1); }} />
{pageRows.map((row) => { const sm = t.statusMap[row.status as StatusKey]; return ( ); })}
{t.table.columns.id} {t.table.columns.submitted} {t.table.columns.name} {t.table.columns.company} {t.table.columns.contact} {t.table.columns.status} {t.table.columns.actions}
#{row.id} {row.displayDate}
{row.fullName}
{row.jobTitle}
{row.company || "—"}
{row.email || "—"}
{row.phone || "—"}
{sm.label}
{row.status === "pending" ? ( <> ) : ( <> {row.status === "approved" ? ( <> ) : ( <> )} )}

{lang === "vi" ? `Hiển thị ${showingFrom}–${showingTo} / ${filtered.length} mục` : `Showing ${showingFrom} to ${showingTo} of ${filtered.length} entries`}

{lang === "vi" ? `Trang ${safePage} / ${totalPages}` : `Page ${safePage} of ${totalPages}`}
IPv6 SUMMIT 2026 | {t.meta.footerTag}

{lang === "vi" ? "© 2026 Hội Nghị IPv6 cho AI & Trung Tâm Dữ Liệu. Hạ Tầng Cho Tương Lai." : "© 2026 IPv6 for AI & Data Centre Summit. Infrastructure for the Future."}

{t.footer.legal.map((label) => ( ))}
{detail ? (
{/* Modal Body */}
{/* Primary Info Grid */}

{lang === "vi" ? "Thông tin cá nhân" : "Identity & Professional"}

{lang === "vi" ? "Thông tin liên hệ" : "Contact Details"}

{/* Form Content / Notes Section */} {detail.notes && (

{lang === "vi" ? "Nội dung phản hồi" : "Form Submission Content"}

{detail.notes.startsWith("Source:") ? (
{detail.notes.split(/\s(?=[A-Z][a-z\s]+:)/).map((part, idx) => { const [key, ...val] = part.split(":"); if (!val.length) return

{part}

; return (
{key.replace("Source:", "").trim()} {val.join(":").trim()}
); })}
) : (

{detail.notes}

)}
)}
{/* Modal Footer Actions */}
{detail.status === "pending" && ( )}
) : null}
); }