Adding Vietnamese version of IPV6, Rewrite content of README.md

This commit is contained in:
2026-05-13 21:09:35 +07:00
parent 778f5f735d
commit b0d6bde50c
22 changed files with 1412 additions and 385 deletions
+294 -117
View File
@@ -5,6 +5,8 @@ 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;
@@ -50,6 +52,44 @@ export type AdminData = {
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 (
<div className="flex flex-col gap-1">
<span className="text-[10px] font-[var(--font-label-caps)] text-outline uppercase tracking-widest">{label}</span>
<span className="text-sm text-on-surface-variant/40 italic"></span>
</div>
);
return (
<div className="flex flex-col gap-1">
<span className="text-[10px] font-[var(--font-label-caps)] text-outline uppercase tracking-widest">{label}</span>
{isStatus ? (
<span className={`px-2 py-0.5 rounded text-[10px] font-bold border w-fit uppercase tracking-wider ${statusMap[status]?.borderClass || ""}`}>
{value}
</span>
) : (
<span className={`text-sm font-medium ${isEmail ? "text-primary hover:underline cursor-pointer" : "text-on-surface"}`}>
{value}
</span>
)}
</div>
);
}
function StatCard({
item,
}: {
@@ -120,7 +160,7 @@ function formatCount(n: number) {
return n.toLocaleString("en-US");
}
function buildLiveStats(data: AdminData) {
function buildLiveStats(data: AdminData, lang: string) {
const req = data.requests;
const total = req.length;
const pending = req.filter((r) => r.status === "pending").length;
@@ -128,34 +168,45 @@ function buildLiveStats(data: AdminData) {
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: `${formatCount(approved)} approved · ${formatCount(pending)} pending · ${formatCount(rejected)} rejected`,
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:
pending >= PENDING_THRESHOLD
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})`,
: `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",
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 ? `Approved share: ${approved} / ${total}` : "",
hint: total > 0
? (isVi ? `Tỷ lệ đã duyệt: ${approved} / ${total}` : `Approved share: ${approved} / ${total}`)
: "",
progressPercent: conversionPct,
};
default:
@@ -174,8 +225,18 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
const [afterDate, setAfterDate] = useState("");
const [page, setPage] = useState(1);
const [detail, setDetail] = useState<AdminRequest | null>(null);
const { lang, setLang } = useLanguage();
const { admin: t } = useTranslation();
const liveStats = useMemo(() => buildLiveStats(data), [data]);
// 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;
@@ -228,10 +289,10 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<nav className="flex flex-col gap-8 flex-1" aria-label="Admin sections">
{(
[
{ href: "/admin", icon: "dashboard", label: "Panel" },
{ href: "/admin/pending", icon: "hourglass_empty", label: "Pending" },
{ href: "/admin/approved", icon: "check_circle", label: "Approved" },
{ href: "/admin/rejected", icon: "cancel", label: "Rejected" },
{ href: "/admin", icon: "dashboard", label: lang === "vi" ? "Bảng điều khiển" : "Panel" },
{ href: "/admin/pending", icon: "hourglass_empty", label: lang === "vi" ? "Chờ duyệt" : "Pending" },
{ href: "/admin/approved", icon: "check_circle", label: lang === "vi" ? "Đã duyệt" : "Approved" },
{ href: "/admin/rejected", icon: "cancel", label: lang === "vi" ? "Từ chối" : "Rejected" },
] as const
).map((item) => {
const active = pathname === item.href;
@@ -261,7 +322,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
className="group flex flex-col items-center gap-1 text-on-surface-variant hover:text-primary mt-auto"
>
<span className="material-symbols-outlined text-2xl transition-all duration-300 group-hover:rotate-45">settings</span>
<span className="font-[var(--font-label-caps)] text-[8px] uppercase tracking-widest">Settings</span>
<span className="font-[var(--font-label-caps)] text-[8px] uppercase tracking-widest">{lang === "vi" ? "Cài đặt" : "Settings"}</span>
</button>
</aside>
@@ -270,15 +331,15 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<div className="hidden md:block min-w-0">
<h1 className="font-[var(--font-display-lg)] text-xl text-primary uppercase tracking-tighter truncate">
{view === "all"
? data.meta.title
? t.meta.title
: view === "pending"
? "Pending approval"
? (lang === "vi" ? "Chờ Duyệt" : "Pending Approval")
: view === "approved"
? "Approved requests"
: "Rejected requests"}
? (lang === "vi" ? "Đã Duyệt" : "Approved Requests")
: (lang === "vi" ? "Từ Chối" : "Rejected Requests")}
</h1>
<p className="font-[var(--font-label-caps)] text-on-surface-variant text-[10px] uppercase tracking-widest">
{data.meta.subtitle}
{t.meta.subtitle}
</p>
</div>
<div className="flex-1 max-w-xl mx-0 lg:mx-8 min-w-0">
@@ -288,7 +349,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
</span>
<input
className="w-full bg-surface-container-low border-none rounded-full py-2.5 pl-12 pr-4 text-on-surface placeholder:text-outline/50 focus:outline-none focus:ring-1 focus:ring-primary/40 transition-all"
placeholder={data.filters.searchPlaceholder}
placeholder={t.filters.searchPlaceholder}
type="search"
value={search}
onChange={(e) => {
@@ -299,15 +360,37 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
</div>
</div>
<div className="flex items-center gap-4 shrink-0 justify-end">
{/* Language Switcher — Admin Header */}
<div className="flex items-center gap-1 font-[var(--font-label-caps)] text-[10px]">
<button
type="button"
onClick={() => setLang("en")}
className={`flex items-center gap-1 px-2 py-1 rounded transition-all duration-200 ${
lang === "en" ? "text-primary border-b border-primary" : "text-on-surface-variant/50 hover:text-primary"
}`}
>
<span>EN</span>
</button>
<span className="text-on-surface-variant/30">|</span>
<button
type="button"
onClick={() => setLang("vi")}
className={`flex items-center gap-1 px-2 py-1 rounded transition-all duration-200 ${
lang === "vi" ? "text-primary border-b border-primary" : "text-on-surface-variant/50 hover:text-primary"
}`}
>
<span>VIE</span>
</button>
</div>
<div className="flex flex-col items-end">
<span className="font-[var(--font-label-caps)] text-[10px] text-primary uppercase tracking-widest">
{data.meta.user.role}
{t.meta.user.role}
</span>
</div>
</div>
</header>
<main className="p-[var(--spacing-gutter)] max-w-7xl mx-auto w-full flex flex-col gap-8 flex-1">
<main className="p-4 md:p-6 w-full flex flex-col gap-6 flex-1">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-[var(--spacing-gutter)]">
{liveStats.map((s) => (
<StatCard key={s.id} item={s} />
@@ -319,7 +402,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
{view === "all" ? (
<div className="flex flex-col gap-1 min-w-[140px]">
<label className="font-[var(--font-label-caps)] text-[10px] text-outline uppercase tracking-wider">
{data.filters.status.label}
{t.filters.status.label}
</label>
<select
className="bg-surface-container-high border border-outline/30 text-on-surface text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-1 focus:ring-primary/50"
@@ -329,7 +412,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
setPage(1);
}}
>
{data.filters.status.options.map((o) => (
{t.filters.status.options.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
@@ -339,16 +422,16 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
) : (
<div className="flex flex-col gap-1 min-w-[140px]">
<span className="font-[var(--font-label-caps)] text-[10px] text-outline uppercase tracking-wider">
Status
{t.filters.status.label}
</span>
<p className="text-sm text-on-surface capitalize py-2 border border-outline/20 rounded-lg px-3 bg-surface-container-high/50">
{view} only
{t.statusMap[view as StatusKey]?.label ?? view}
</p>
</div>
)}
<div className="flex flex-col gap-1 min-w-[160px]">
<label className="font-[var(--font-label-caps)] text-[10px] text-outline uppercase tracking-wider">
{data.filters.companyGroup.label}
{t.filters.companyGroup.label}
</label>
<select
className="bg-surface-container-high border border-outline/30 text-on-surface text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-1 focus:ring-primary/50"
@@ -358,7 +441,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
setPage(1);
}}
>
{data.filters.companyGroup.options.map((o) => (
{t.filters.companyGroup.options.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
@@ -367,7 +450,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
</div>
<div className="flex flex-col gap-1 min-w-[160px]">
<label className="font-[var(--font-label-caps)] text-[10px] text-outline uppercase tracking-wider">
Submitted after
{lang === "vi" ? "Nộp sau ngày" : "Submitted after"}
</label>
<input
className="bg-surface-container-high border border-outline/30 text-on-surface text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-1 focus:ring-primary/50"
@@ -386,7 +469,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
onClick={() => alert("Export is not wired (frontend-only demo).")}
>
<span className="material-symbols-outlined text-sm">download</span>
{data.filters.exportLabel}
{t.filters.exportLabel}
</button>
</section>
@@ -395,50 +478,69 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<table className="w-full text-left border-collapse min-w-[880px]">
<thead>
<tr className="bg-surface-container-high border-b border-outline/20">
{Object.entries(data.table.columns).map(([key, label]) => (
<th
key={key}
className={`px-6 py-4 font-[var(--font-label-caps)] text-outline text-[11px] uppercase tracking-widest ${
key === "actions" ? "text-right" : ""
}`}
>
{label}
</th>
))}
<th className="px-4 py-3 font-[var(--font-label-caps)] text-outline text-[10px] uppercase tracking-widest w-[80px]">
{t.table.columns.id}
</th>
<th className="px-4 py-3 font-[var(--font-label-caps)] text-outline text-[10px] uppercase tracking-widest w-[100px]">
{t.table.columns.submitted}
</th>
<th className="px-4 py-3 font-[var(--font-label-caps)] text-outline text-[10px] uppercase tracking-widest min-w-[160px]">
{t.table.columns.name}
</th>
<th className="px-4 py-3 font-[var(--font-label-caps)] text-outline text-[10px] uppercase tracking-widest min-w-[140px]">
{t.table.columns.company}
</th>
<th className="px-4 py-3 font-[var(--font-label-caps)] text-outline text-[10px] uppercase tracking-widest min-w-[160px]">
{t.table.columns.contact}
</th>
<th className="px-4 py-3 font-[var(--font-label-caps)] text-outline text-[10px] uppercase tracking-widest w-[110px]">
{t.table.columns.status}
</th>
<th className="px-4 py-3 font-[var(--font-label-caps)] text-outline text-[10px] uppercase tracking-widest text-right w-[160px]">
{t.table.columns.actions}
</th>
</tr>
</thead>
<tbody className="divide-y divide-outline/10">
{pageRows.map((row) => {
const sm = data.statusMap[row.status];
const sm = t.statusMap[row.status as StatusKey];
return (
<tr key={row.id} className="hover:bg-surface-container-high/40 transition-colors">
<td className="px-6 py-4 font-[var(--font-mono)] text-sm text-primary whitespace-nowrap">
<tr key={row.id} className="hover:bg-surface-container-high/40 transition-colors border-b border-outline/5 last:border-0">
<td className="px-4 py-3 font-[var(--font-mono)] text-[13px] text-primary whitespace-nowrap">
#{row.id}
</td>
<td className="px-6 py-4 text-sm text-on-surface-variant whitespace-nowrap">{row.displayDate}</td>
<td className="px-6 py-4">
<div className="font-[var(--font-body-base)] text-sm font-semibold text-on-surface">{row.fullName}</div>
<div className="text-xs text-outline">{row.jobTitle}</div>
<td className="px-4 py-3 text-[13px] text-on-surface-variant whitespace-nowrap">
{row.displayDate}
</td>
<td className="px-6 py-4 text-sm">{row.company}</td>
<td className="px-6 py-4">
<div className="text-sm">{row.email}</div>
<div className="text-xs text-outline">{row.phone}</div>
<td className="px-4 py-3">
<div className="font-[var(--font-body-base)] text-[13px] font-semibold text-on-surface truncate max-w-[200px]" title={row.fullName}>
{row.fullName}
</div>
<div className="text-[11px] text-outline truncate max-w-[200px]" title={row.jobTitle}>
{row.jobTitle}
</div>
</td>
<td className="px-6 py-4">
<td className="px-4 py-3 text-[13px] text-on-surface-variant truncate max-w-[150px]" title={row.company}>
{row.company || "—"}
</td>
<td className="px-4 py-3">
<div className="text-[13px] truncate max-w-[180px]" title={row.email}>{row.email || "—"}</div>
<div className="text-[11px] text-outline truncate max-w-[180px]" title={row.phone}>{row.phone || "—"}</div>
</td>
<td className="px-4 py-3">
<span
className={`px-3 py-1 rounded-full text-[10px] font-[var(--font-label-caps)] border uppercase tracking-wider flex items-center gap-1.5 w-fit ${sm.borderClass}`}
className={`px-2 py-0.5 rounded-full text-[9px] font-[var(--font-label-caps)] border uppercase tracking-wider flex items-center gap-1.5 w-fit ${sm.borderClass}`}
>
<span className={`w-1.5 h-1.5 rounded-full status-led ${sm.dotClass}`} />
<span className={`w-1 h-1 rounded-full status-led ${sm.dotClass}`} />
{sm.label}
</span>
</td>
<td className="px-6 py-4 text-right">
<div className="flex items-center justify-end gap-1 flex-wrap justify-end">
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-0.5">
<button
type="button"
className="p-2 rounded-lg hover:bg-surface-container-highest text-on-surface-variant hover:text-on-surface transition-all disabled:opacity-40"
title="View"
className="p-1.5 rounded-lg hover:bg-surface-container-highest text-on-surface-variant hover:text-on-surface transition-all disabled:opacity-40"
title={lang === "vi" ? "Xem" : "View"}
disabled={isPending}
onClick={() => setDetail(row)}
>
@@ -448,17 +550,17 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<>
<button
type="button"
className="p-2 rounded-lg hover:bg-primary/20 text-primary transition-all disabled:opacity-40"
title="Edit"
className="p-1.5 rounded-lg hover:bg-primary/20 text-primary transition-all disabled:opacity-40"
title={lang === "vi" ? "Sửa" : "Edit"}
disabled={isPending}
onClick={() => alert(`Edit ${row.id} (not implemented).`)}
onClick={() => alert(lang === "vi" ? `Sửa ${row.id} (chưa hỗ trợ).` : `Edit ${row.id} (not implemented).`)}
>
<span className="material-symbols-outlined text-lg">edit</span>
</button>
<button
type="button"
className="p-2 rounded-lg text-white hover:bg-primary hover:text-white transition-all disabled:opacity-40"
title="Approve"
className="p-1.5 rounded-lg text-white hover:bg-primary hover:text-white transition-all disabled:opacity-40"
title={lang === "vi" ? "Duyệt" : "Approve"}
disabled={isPending}
onClick={() => commitStatus(row.id, "approved")}
>
@@ -468,8 +570,8 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
</button>
<button
type="button"
className="p-2 rounded-lg hover:bg-red-500/15 text-red-300 transition-all disabled:opacity-40"
title="Reject"
className="p-1.5 rounded-lg hover:bg-red-500/15 text-red-300 transition-all disabled:opacity-40"
title={lang === "vi" ? "Từ chối" : "Reject"}
disabled={isPending}
onClick={() => commitStatus(row.id, "rejected")}
>
@@ -480,10 +582,10 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<>
<button
type="button"
className="p-2 rounded-lg hover:bg-surface-container-highest text-on-surface-variant disabled:opacity-40"
title="History"
className="p-1.5 rounded-lg hover:bg-surface-container-highest text-on-surface-variant disabled:opacity-40"
title={lang === "vi" ? "Lịch sử" : "History"}
disabled={isPending}
onClick={() => alert(`History for ${row.id} (placeholder).`)}
onClick={() => alert(lang === "vi" ? `Lịch sử cho ${row.id} (đang cập nhật).` : `History for ${row.id} (placeholder).`)}
>
<span className="material-symbols-outlined text-lg">history</span>
</button>
@@ -491,8 +593,8 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<>
<button
type="button"
className="p-2 rounded-lg hover:bg-surface-container-high text-on-surface-variant disabled:opacity-40"
title="Mark pending"
className="p-1.5 rounded-lg hover:bg-surface-container-high text-on-surface-variant disabled:opacity-40"
title={lang === "vi" ? "Hoàn tác" : "Mark pending"}
disabled={isPending}
onClick={() => commitStatus(row.id, "pending")}
>
@@ -500,8 +602,8 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
</button>
<button
type="button"
className="p-2 rounded-lg hover:bg-red-500/15 text-red-300 transition-all disabled:opacity-40"
title="Reject"
className="p-1.5 rounded-lg hover:bg-red-500/15 text-red-300 transition-all disabled:opacity-40"
title={lang === "vi" ? "Từ chối" : "Reject"}
disabled={isPending}
onClick={() => commitStatus(row.id, "rejected")}
>
@@ -512,8 +614,8 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<>
<button
type="button"
className="p-2 rounded-lg text-white hover:bg-primary hover:text-white transition-all disabled:opacity-40"
title="Approve"
className="p-1.5 rounded-lg text-white hover:bg-primary hover:text-white transition-all disabled:opacity-40"
title={lang === "vi" ? "Duyệt" : "Approve"}
disabled={isPending}
onClick={() => commitStatus(row.id, "approved")}
>
@@ -524,7 +626,7 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<button
type="button"
className="p-2 rounded-lg hover:bg-surface-container-high text-on-surface-variant disabled:opacity-40"
title="Mark pending"
title={lang === "vi" ? "Hoàn tác" : "Mark pending"}
disabled={isPending}
onClick={() => commitStatus(row.id, "pending")}
>
@@ -544,11 +646,13 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
</div>
<div className="px-6 py-4 border-t border-outline/10 flex flex-col sm:flex-row items-center justify-between gap-4">
<p className="text-xs text-outline">
Showing {showingFrom} to {showingTo} of {filtered.length} entries
{lang === "vi"
? `Hiển thị ${showingFrom}${showingTo} / ${filtered.length} mục`
: `Showing ${showingFrom} to ${showingTo} of ${filtered.length} entries`}
</p>
<div className="flex items-center gap-3">
<span className="text-xs text-on-surface-variant hidden sm:inline">
Page {safePage} of {totalPages}
{lang === "vi" ? `Trang ${safePage} / ${totalPages}` : `Page ${safePage} of ${totalPages}`}
</span>
<button
type="button"
@@ -574,13 +678,15 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
<footer className="mt-auto border-t border-outline/20 py-8 px-[var(--spacing-gutter)] flex flex-col md:flex-row items-center justify-between gap-4">
<div className="flex flex-wrap items-center gap-4 justify-center md:justify-start">
<span className="font-[var(--font-display-lg)] text-sm text-primary uppercase tracking-tighter">IPv6 SUMMIT 2026</span>
<span className="text-xs text-outline">| {data.meta.footerTag}</span>
<span className="text-xs text-outline">| {t.meta.footerTag}</span>
</div>
<p className="text-[11px] font-[var(--font-label-caps)] text-outline tracking-widest text-center">
© 2026 IPv6 for AI & Data Centre Summit. Infrastructure for the Future.
{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."}
</p>
<div className="flex items-center gap-6 justify-center">
{data.footer.legal.map((label) => (
{t.footer.legal.map((label) => (
<button
key={label}
type="button"
@@ -610,52 +716,123 @@ export default function AdminConsole({ data, view = "all" }: { data: AdminData;
aria-label="Close"
onClick={() => setDetail(null)}
/>
<div className="relative w-full max-w-lg glass-panel rounded-xl border border-outline/20 p-6 shadow-2xl z-10">
<div className="flex items-start justify-between gap-4 mb-4">
<div>
<h2 id="admin-detail-title" className="font-[var(--font-display-lg)] text-xl text-primary">
#{detail.id}
</h2>
<p className="text-sm text-on-surface-variant">{detail.displayDate}</p>
<div className="relative w-full max-w-2xl glass-panel rounded-2xl border border-white/10 shadow-[0_20px_50px_rgba(0,0,0,0.5)] z-10 overflow-hidden flex flex-col max-h-[90vh]">
{/* Modal Header */}
<div className="p-6 border-b border-white/5 bg-white/5 flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="w-10 h-10 gold-gradient-bg rounded-lg flex items-center justify-center shadow-lg">
<span className="material-symbols-outlined text-on-primary">
{detail.source === "feedback" ? "rate_review" : "person_add"}
</span>
</div>
<div>
<h2 id="admin-detail-title" className="font-[var(--font-display-lg)] text-xl text-primary leading-tight">
{detail.fullName || (lang === "vi" ? "Yêu cầu mới" : "New Request")}
</h2>
<p className="text-[10px] font-[var(--font-label-caps)] text-outline uppercase tracking-widest mt-0.5">
ID: #{detail.id} {detail.displayDate}
</p>
</div>
</div>
<button
type="button"
className="p-2 rounded-lg hover:bg-surface-container-high text-on-surface-variant"
className="w-10 h-10 rounded-full hover:bg-white/10 text-on-surface-variant flex items-center justify-center transition-colors"
onClick={() => setDetail(null)}
>
<span className="material-symbols-outlined">close</span>
<span className="material-symbols-outlined text-2xl">close</span>
</button>
</div>
<dl className="space-y-3 text-sm">
<div>
<dt className="text-outline font-[var(--font-label-caps)] text-[10px] uppercase tracking-widest">Name</dt>
<dd className="text-on-surface">
{detail.fullName} {detail.jobTitle}
</dd>
</div>
<div>
<dt className="text-outline font-[var(--font-label-caps)] text-[10px] uppercase tracking-widest">Company</dt>
<dd className="text-on-surface">{detail.company}</dd>
</div>
<div>
<dt className="text-outline font-[var(--font-label-caps)] text-[10px] uppercase tracking-widest">Contact</dt>
<dd className="text-on-surface">
{detail.email}
<br />
{detail.phone}
</dd>
</div>
<div>
<dt className="text-outline font-[var(--font-label-caps)] text-[10px] uppercase tracking-widest">Status</dt>
<dd className="text-on-surface capitalize">{detail.status}</dd>
</div>
{detail.notes ? (
<div>
<dt className="text-outline font-[var(--font-label-caps)] text-[10px] uppercase tracking-widest">Notes</dt>
<dd className="text-on-surface">{detail.notes}</dd>
{/* Modal Body */}
<div className="flex-1 overflow-y-auto p-8 space-y-8 custom-scrollbar">
{/* Primary Info Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="space-y-6">
<h3 className="text-xs font-[var(--font-label-caps)] text-primary uppercase tracking-[0.2em] border-b border-primary/20 pb-2">
{lang === "vi" ? "Thông tin cá nhân" : "Identity & Professional"}
</h3>
<div className="space-y-4">
<InfoItem label={lang === "vi" ? "Họ và tên" : "Full Name"} value={detail.fullName} />
<InfoItem label={lang === "vi" ? "Chức vụ" : "Job Title"} value={detail.jobTitle} />
<InfoItem label={lang === "vi" ? "Công ty / Tổ chức" : "Company"} value={detail.company} />
<InfoItem label={lang === "vi" ? "Phân khúc" : "Segment"} value={detail.segment} />
</div>
</div>
) : null}
</dl>
<div className="space-y-6">
<h3 className="text-xs font-[var(--font-label-caps)] text-primary uppercase tracking-[0.2em] border-b border-primary/20 pb-2">
{lang === "vi" ? "Thông tin liên hệ" : "Contact Details"}
</h3>
<div className="space-y-4">
<InfoItem label="Email" value={detail.email} isEmail />
<InfoItem label={lang === "vi" ? "Số điện thoại" : "Phone Number"} value={detail.phone} />
<InfoItem
label={lang === "vi" ? "Trạng thái hồ sơ" : "Application Status"}
value={lang === "vi" ? (t.statusMap[detail.status as StatusKey]?.label || detail.status) : detail.status}
isStatus
status={detail.status}
statusMap={t.statusMap}
/>
</div>
</div>
</div>
{/* Form Content / Notes Section */}
{detail.notes && (
<div className="space-y-6">
<h3 className="text-xs font-[var(--font-label-caps)] text-primary uppercase tracking-[0.2em] border-b border-primary/20 pb-2">
{lang === "vi" ? "Nội dung phản hồi" : "Form Submission Content"}
</h3>
<div className="glass-panel p-6 rounded-xl bg-white/5 border border-white/5">
{detail.notes.startsWith("Source:") ? (
<div className="grid grid-cols-1 gap-4">
{detail.notes.split(/\s(?=[A-Z][a-z\s]+:)/).map((part, idx) => {
const [key, ...val] = part.split(":");
if (!val.length) return <p key={idx} className="text-sm text-on-surface-variant leading-relaxed">{part}</p>;
return (
<div key={idx} className="flex flex-col sm:flex-row sm:items-baseline gap-1 sm:gap-4 border-b border-white/5 pb-3 last:border-0 last:pb-0">
<span className="text-[11px] font-[var(--font-label-caps)] text-outline uppercase tracking-wider min-w-[140px] shrink-0">
{key.replace("Source:", "").trim()}
</span>
<span className="text-sm text-on-surface font-medium">
{val.join(":").trim()}
</span>
</div>
);
})}
</div>
) : (
<p className="text-sm text-on-surface-variant leading-relaxed whitespace-pre-wrap italic">
{detail.notes}
</p>
)}
</div>
</div>
)}
</div>
{/* Modal Footer Actions */}
<div className="p-6 border-t border-white/5 bg-white/5 flex items-center justify-end gap-4">
<button
type="button"
className="px-6 py-2.5 rounded-lg border border-white/10 text-on-surface-variant text-xs font-bold uppercase tracking-wider hover:bg-white/5 transition-all"
onClick={() => setDetail(null)}
>
{lang === "vi" ? "Đóng" : "Close"}
</button>
{detail.status === "pending" && (
<button
type="button"
className="px-6 py-2.5 rounded-lg gold-gradient-bg text-on-primary text-xs font-bold uppercase tracking-wider shadow-lg shadow-primary/20 active:scale-95 transition-all"
onClick={() => {
commitStatus(detail.id, "approved");
setDetail(null);
}}
>
{lang === "vi" ? "Phê duyệt hồ sơ" : "Approve Application"}
</button>
)}
</div>
</div>
</div>
) : null}