From b0d6bde50cf1992560bf7111df183d8ab61e106a Mon Sep 17 00:00:00 2001 From: DuongNguyendev Date: Wed, 13 May 2026 21:09:35 +0700 Subject: [PATCH] Adding Vietnamese version of IPV6, Rewrite content of README.md --- README.md | 365 ++++++++++------ app/(site)/feedback/page.tsx | 36 +- app/(site)/page.tsx | 16 +- app/(site)/registration/page.tsx | 37 +- app/(site)/sponsor/page.tsx | 19 +- app/components/admin/AdminConsole.tsx | 411 +++++++++++++----- app/components/forms/FeedbackPageClient.tsx | 42 ++ .../forms/RegistrationPageClient.tsx | 42 ++ app/components/home/HomePageClient.tsx | 20 + app/components/home/VisionSection.tsx | 22 +- app/components/layout/Footer/Footer.tsx | 12 +- app/components/layout/Header/HeaderClient.tsx | 79 +++- app/components/sponsor/SponsorPageClient.tsx | 20 + app/context/LanguageContext.tsx | 41 ++ app/data/admin.vi.json | 168 +++++++ app/data/feedback.vi.json | 194 +++++++++ app/data/home.vi.json | 69 +++ app/data/registration.vi.json | 38 ++ app/data/site.vi.json | 20 + app/data/sponsor.vi.json | 114 +++++ app/hooks/useTranslation.ts | 29 ++ app/layout.tsx | 3 +- 22 files changed, 1412 insertions(+), 385 deletions(-) create mode 100644 app/components/forms/FeedbackPageClient.tsx create mode 100644 app/components/forms/RegistrationPageClient.tsx create mode 100644 app/components/home/HomePageClient.tsx create mode 100644 app/components/sponsor/SponsorPageClient.tsx create mode 100644 app/context/LanguageContext.tsx create mode 100644 app/data/admin.vi.json create mode 100644 app/data/feedback.vi.json create mode 100644 app/data/home.vi.json create mode 100644 app/data/registration.vi.json create mode 100644 app/data/site.vi.json create mode 100644 app/data/sponsor.vi.json create mode 100644 app/hooks/useTranslation.ts diff --git a/README.md b/README.md index 1cc3745..d03a4c5 100644 --- a/README.md +++ b/README.md @@ -1,218 +1,297 @@ -# VisaService - Website Dịch Vụ Visa +# IPv6 Summit 2026 — Hệ Thống Website Chính Thức -Website dịch vụ visa chuyên nghiệp được xây dựng với Next.js 15, TypeScript và Tailwind CSS. Hỗ trợ tư vấn visa cho hơn 50 quốc gia với tỷ lệ thành công 99%. +> **IPv6 for AI & Data Centre Summit 2026** — Hội nghị quốc tế về hạ tầng IPv6 cho AI và Trung tâm Dữ liệu khu vực ASEAN. Website được xây dựng với Next.js 16, TypeScript, Tailwind CSS v4 và hỗ trợ song ngữ Anh–Việt hoàn chỉnh. -## 🚀 Tính Năng +--- -- **8 Trang Chính**: Trang chủ, Giới thiệu, Dịch vụ, Visa, Quốc gia, Bảng giá, Blog, Liên hệ -- **Hệ Thống Đặt Lịch**: Đặt lịch tư vấn với chuyên gia -- **Responsive Design**: Tối ưu cho mọi thiết bị -- **JSON-Based Content**: Dễ dàng quản lý nội dung -- **Interactive Components**: Form, accordion, filter, modal -- **SEO Optimized**: Metadata và structure tối ưu +## ✨ Tổng Quan + +Website phục vụ toàn bộ vòng đời của sự kiện: từ trang giới thiệu hội nghị, đăng ký tham dự, thu thập phản hồi (feedback), trưng bày nhà tài trợ, đến bảng quản trị dành riêng cho ban tổ chức. Toàn bộ nội dung được lưu trữ trong các file JSON riêng biệt theo ngôn ngữ, giúp dễ dàng cập nhật mà không cần sửa code. + +--- + +## 🚀 Tính Năng Nổi Bật + +| Tính năng | Mô tả | +|---|---| +| 🌐 **Song ngữ (EN / VI)** | Chuyển đổi ngôn ngữ tức thì, mọi nhãn và nội dung đều được Việt hoá hoàn toàn | +| 📋 **Form Đăng Ký** | Đăng ký tham dự hội nghị, lưu trữ vào MongoDB | +| 💬 **Form Phản Hồi** | Đánh giá sự kiện chi tiết sau khi tham dự | +| 🏢 **Trang Nhà Tài Trợ** | Giới thiệu các đối tác và nhà tài trợ của hội nghị | +| 🗂️ **Admin Console** | Bảng quản trị xem, duyệt, từ chối hồ sơ đăng ký; xuất Excel | +| 🔐 **Bảo Mật Admin** | HTTP Basic Auth tuỳ chỉnh cho khu vực `/admin` | +| 📱 **Responsive** | Tối ưu hiển thị trên mọi thiết bị từ mobile đến desktop | +| 🎨 **Premium UI** | Glassmorphism, gold gradient, Material Symbols, micro-animations | +| 🗄️ **MongoDB** | Lưu trữ và truy vấn hồ sơ đăng ký/phản hồi theo thời gian thực | + +--- ## 🛠️ Công Nghệ Sử Dụng -- **Framework**: Next.js 15 (App Router) -- **Language**: TypeScript -- **Styling**: Tailwind CSS -- **Icons**: Emoji icons -- **Deployment**: Docker support +- **Framework**: Next.js 16 (App Router) +- **Language**: TypeScript 5 +- **Styling**: Tailwind CSS v4 + SCSS (custom design tokens) +- **Database**: MongoDB (Mongoose v9) +- **Icons**: Google Material Symbols (Outlined) +- **Font**: Inter (Google Fonts) +- **Containerization**: Docker + Docker Compose +- **HTTP Client**: Axios + +--- ## 📁 Cấu Trúc Thư Mục ``` -app/ -├── components/ # Shared components -│ ├── Header.tsx # Navigation header -│ └── Footer.tsx # Site footer -├── about/ # Trang giới thiệu -│ ├── about.json # Dữ liệu về công ty -│ └── page.tsx # Component trang -├── services/ # Trang dịch vụ -│ ├── services.json # Dữ liệu dịch vụ -│ └── page.tsx # Component trang -├── visa/ # Trang visa -│ ├── visa.json # Dữ liệu các loại visa -│ └── page.tsx # Component trang -├── countries/ # Trang quốc gia -│ ├── countries.json # Dữ liệu quốc gia -│ └── page.tsx # Component trang -├── pricing/ # Trang bảng giá -│ ├── pricing.json # Dữ liệu giá cả -│ └── page.tsx # Component trang -├── blog/ # Trang blog -│ ├── blog.json # Dữ liệu bài viết -│ └── page.tsx # Component trang -├── contact/ # Trang liên hệ -│ ├── contact.json # Thông tin liên hệ -│ └── page.tsx # Component trang -├── appointment/ # Trang đặt lịch -│ ├── appointment.json # Dữ liệu booking -│ └── page.tsx # Component trang -├── layout.tsx # Root layout -├── page.tsx # Trang chủ -└── globals.css # Global styles +├── app/ +│ ├── (site)/ # Các trang công khai +│ │ ├── page.tsx # Trang chủ +│ │ ├── registration/ # Form đăng ký tham dự +│ │ ├── feedback/ # Form phản hồi sau sự kiện +│ │ └── sponsor/ # Trang nhà tài trợ +│ ├── (internal)/ +│ │ └── admin/ # Bảng quản trị (yêu cầu xác thực) +│ │ ├── page.tsx # Dashboard tổng hợp +│ │ ├── pending/ # Danh sách chờ duyệt +│ │ ├── approved/ # Danh sách đã duyệt +│ │ └── rejected/ # Danh sách từ chối +│ ├── api/ # API Routes (Next.js) +│ ├── components/ +│ │ ├── home/ # Hero, Stats, Vision, Agenda sections +│ │ ├── admin/ # AdminConsole component +│ │ ├── forms/ # RegistrationForm, FeedbackForm +│ │ ├── layout/ # Header, Footer +│ │ ├── sponsor/ # Sponsor components +│ │ └── ui/ # Shared UI primitives +│ ├── context/ # LanguageContext (EN/VI) +│ ├── data/ # JSON content files (song ngữ) +│ │ ├── admin.json / admin.vi.json +│ │ ├── home.json / home.vi.json +│ │ ├── registration.json / registration.vi.json +│ │ ├── feedback.json / feedback.vi.json +│ │ ├── sponsor.json / sponsor.vi.json +│ │ └── site.json / site.vi.json +│ ├── hooks/ # useTranslation hook +│ └── layout.tsx # Root layout +├── models/ +│ └── SummitRequest.ts # Mongoose schema +├── lib/ +│ └── mongodb.ts # Kết nối MongoDB +├── scripts/ +│ └── seed-summit-requests.ts # Seed dữ liệu mẫu +├── types/ +│ └── admin-submission.ts # TypeScript types +├── middleware.ts # HTTP Basic Auth cho /admin +├── Dockerfile +├── docker-compose.yml +└── .env.example ``` -## 🚀 Hướng Dẫn Chạy Dự Án +--- + +## ⚙️ Hướng Dẫn Cài Đặt ### Yêu Cầu Hệ Thống -- Node.js 18+ -- npm, yarn, hoặc pnpm +- **Node.js** 18+ +- **MongoDB** 6+ (local hoặc Atlas) +- npm / yarn / pnpm -### Cài Đặt và Chạy - -1. **Clone repository** +### 1. Clone & Cài Dependencies ```bash git clone -cd visa-service-website +cd IPV6 + +npm install ``` -2. **Cài đặt dependencies** +### 2. Cấu Hình Môi Trường + +Sao chép file `.env.example` thành `.env.local` và điền các giá trị: ```bash -npm install -# hoặc -yarn install -# hoặc -pnpm install +cp .env.example .env.local ``` -3. **Chạy development server** +```env +# MongoDB — lưu trữ hồ sơ đăng ký và phản hồi +MONGODB_URI=mongodb://localhost:27017/ipv6_summit + +# URL công khai (tuỳ chọn) +NEXT_PUBLIC_BASE_URL=http://localhost:3000 + +# Bảo vệ trang Admin bằng HTTP Basic Auth (tuỳ chọn) +ADMIN_BASIC_AUTH_USER=admin +ADMIN_BASIC_AUTH_PASS=your_password +``` + +### 3. Seed Dữ Liệu Mẫu (Tuỳ chọn) + +```bash +npm run db:seed +``` + +### 4. Chạy Development Server ```bash npm run dev -# hoặc -yarn dev -# hoặc -pnpm dev ``` -4. **Mở trình duyệt** - Truy cập [http://localhost:3000](http://localhost:3000) để xem website +Truy cập [http://localhost:3000](http://localhost:3000) để xem website. +Truy cập [http://localhost:3000/admin](http://localhost:3000/admin) để vào bảng quản trị. -### Scripts Có Sẵn +--- + +## 📜 Scripts ```bash -npm run dev # Chạy development server -npm run build # Build production -npm run start # Chạy production server -npm run lint # Kiểm tra code style +npm run dev # Chạy development server (Next.js Turbopack) +npm run build # Build production +npm run start # Chạy production server +npm run lint # Kiểm tra ESLint +npm run db:seed # Seed dữ liệu mẫu vào MongoDB +npm run scss:build # Compile SCSS → CSS (một lần) +npm run scss:watch # Compile SCSS → CSS (watch mode) ``` +--- + ## 🐳 Chạy Với Docker -### Development với Docker +### Development ```bash -# Build và chạy container +# Build và chạy toàn bộ stack (Next.js + MongoDB) docker-compose up --build # Chạy ở background docker-compose up -d -# Dừng container +# Dừng docker-compose down ``` -### Production với Docker +### Production ```bash # Build production image -docker build -t visa-service . +docker build -t ipv6-summit . # Chạy container -docker run -p 3000:3000 visa-service +docker run -p 3000:3000 --env-file .env.local ipv6-summit ``` -## 📝 Tùy Chỉnh Nội Dung +--- -### Cập Nhật Dữ Liệu +## 🌐 Hệ Thống Đa Ngôn Ngữ (i18n) -Mỗi trang có file JSON riêng chứa dữ liệu: +Dự án sử dụng kiến trúc i18n tự xây dựng (không phụ thuộc thư viện bên ngoài): -- `about/about.json` - Thông tin công ty -- `services/services.json` - Danh sách dịch vụ -- `visa/visa.json` - Các loại visa -- `countries/countries.json` - Quốc gia hỗ trợ -- `pricing/pricing.json` - Bảng giá dịch vụ -- `blog/blog.json` - Bài viết blog -- `contact/contact.json` - Thông tin liên hệ -- `appointment/appointment.json` - Cấu hình đặt lịch +- **`LanguageContext`** (`app/context/`) — quản lý trạng thái ngôn ngữ toàn cục (`en` | `vi`). +- **`useTranslation` hook** (`app/hooks/`) — load nội dung JSON tương ứng với ngôn ngữ hiện tại. +- **File JSON song ngữ** — mỗi module có 2 file: `*.json` (EN) và `*.vi.json` (VI). -### Thêm Trang Mới +Để thêm nội dung mới, chỉ cần cập nhật cả hai file JSON song ngữ tương ứng. -1. Tạo thư mục mới trong `app/` -2. Tạo file `data.json` chứa dữ liệu -3. Tạo file `page.tsx` với React component -4. Cập nhật navigation trong `Header.tsx` +--- -### Tùy Chỉnh Styling +## 🗄️ Database Schema -- Sử dụng Tailwind CSS classes -- Tùy chỉnh theme trong `tailwind.config.js` -- Global styles trong `app/globals.css` +Collection `summitrequest` (MongoDB): -## 🌐 Deployment +| Trường | Kiểu | Mô tả | +|---|---|---| +| `publicId` | String | ID công khai (ví dụ: `V6-4821`) | +| `fullName` | String | Họ và tên người đăng ký | +| `jobTitle` | String | Chức vụ | +| `company` | String | Công ty / Tổ chức | +| `email` | String | Địa chỉ email | +| `phone` | String | Số điện thoại | +| `segment` | String | Phân khúc (`tier1`, `cloud`, `enterprise`) | +| `status` | String | Trạng thái (`pending`, `approved`, `rejected`) | +| `source` | String | Nguồn gốc (`registration`, `feedback`) | +| `notes` | String | Ghi chú hoặc nội dung phản hồi | +| `submittedAt` | String | Ngày nộp hồ sơ | + +--- + +## 🔒 Bảo Mật Admin + +Khu vực `/admin` được bảo vệ thông qua `middleware.ts` bằng HTTP Basic Auth. Để kích hoạt, thiết lập hai biến môi trường: + +```env +ADMIN_BASIC_AUTH_USER=admin +ADMIN_BASIC_AUTH_PASS=your_secure_password +``` + +Nếu không thiết lập, trang Admin vẫn truy cập được (phù hợp cho môi trường development nội bộ). + +--- + +## 📝 Cập Nhật Nội Dung + +Mọi nội dung văn bản đều được tách biệt khỏi code và lưu trong `app/data/`. Để thay đổi nội dung: + +1. Mở file JSON tương ứng (ví dụ: `app/data/home.vi.json`) +2. Chỉnh sửa giá trị cần thay đổi +3. Lưu file — Next.js sẽ tự động hot-reload + +**Không cần sửa code TypeScript/TSX** khi chỉ thay đổi nội dung văn bản. + +--- + +## 🚀 Triển Khai (Deployment) ### Vercel (Khuyến nghị) -1. Push code lên GitHub -2. Kết nối repository với Vercel -3. Deploy tự động +1. Push code lên GitHub / GitLab +2. Kết nối repository với [Vercel](https://vercel.com) +3. Thêm biến môi trường `MONGODB_URI` trong Vercel dashboard +4. Deploy tự động mỗi khi push lên `main` -### Netlify +### VPS / Server (PM2) -1. Build project: `npm run build` -2. Upload thư mục `out/` lên Netlify - -### VPS/Server - -1. Build project: `npm run build` -2. Chạy: `npm run start` -3. Sử dụng PM2 hoặc Docker cho production - -## 📱 Responsive Design - -Website được tối ưu cho: - -- **Mobile**: < 768px -- **Tablet**: 768px - 1024px -- **Desktop**: > 1024px - -## 🔧 Cấu Hình Môi Trường - -Tạo file `.env.local` (tùy chọn): - -```env -NEXT_PUBLIC_SITE_URL=http://localhost:3000 -NEXT_PUBLIC_CONTACT_EMAIL=info@visaservice.com -NEXT_PUBLIC_PHONE=1900-1234 +```bash +npm run build +pm2 start npm --name "ipv6-summit" -- start ``` +### Docker Compose (Production) + +```bash +docker-compose -f docker-compose.yml up -d --build +``` + +--- + +## 📱 Responsive Breakpoints + +| Màn hình | Kích thước | +|---|---| +| Mobile | < 768px | +| Tablet | 768px – 1024px | +| Laptop | 1024px – 1440px | +| Desktop | > 1440px | + +--- + ## 🤝 Đóng Góp 1. Fork repository -2. Tạo feature branch: `git checkout -b feature/new-feature` -3. Commit changes: `git commit -m 'Add new feature'` -4. Push branch: `git push origin feature/new-feature` -5. Tạo Pull Request - -## 📄 License - -MIT License - xem file [LICENSE](LICENSE) để biết thêm chi tiết. - -## 📞 Hỗ Trợ - -- **Email**: info@visaservice.com -- **Phone**: 1900 1234 -- **Website**: [visaservice.com](https://visaservice.com) +2. Tạo feature branch: `git checkout -b feature/ten-tinh-nang` +3. Commit: `git commit -m 'feat: mô tả thay đổi'` +4. Push: `git push origin feature/ten-tinh-nang` +5. Tạo Pull Request vào nhánh `main` --- -**Phát triển bởi**: VisaService Team -**Phiên bản**: 1.0.0 -**Cập nhật**: January 2025 +## 📄 License + +MIT License — xem file [LICENSE](LICENSE) để biết thêm chi tiết. + +--- + +**Dự án**: IPv6 Summit 2026 — Official Website +**Công nghệ**: Next.js 16 · TypeScript · Tailwind CSS v4 · MongoDB +**Phiên bản**: 0.1.0 +**Cập nhật**: Tháng 5, 2026 diff --git a/app/(site)/feedback/page.tsx b/app/(site)/feedback/page.tsx index db47cc7..2d7013a 100644 --- a/app/(site)/feedback/page.tsx +++ b/app/(site)/feedback/page.tsx @@ -1,37 +1,5 @@ -import Link from "next/link"; -import FeedbackForm from "@/app/components/forms/FeedbackForm"; -import data from "@/app/data/feedback.json"; +import FeedbackPageClient from "@/app/components/forms/FeedbackPageClient"; export default function FeedbackPage() { - return ( -
-
- -
-
- - - arrow_back - - Back to Home - -
- -
-

- {data.title} -

-

{data.subtitle}

-
- -
-
- -
-
-
- ); + return ; } diff --git a/app/(site)/page.tsx b/app/(site)/page.tsx index 25a16d6..ad75f72 100644 --- a/app/(site)/page.tsx +++ b/app/(site)/page.tsx @@ -1,17 +1,5 @@ -import home from "@/app/data/home.json"; -import HeroSection from "@/app/components/home/HeroSection"; -import VisionSection from "@/app/components/home/VisionSection"; -import StatsSection from "@/app/components/home/StatsSection"; -import AgendaSection from "@/app/components/home/AgendaSection"; +import HomePageClient from "@/app/components/home/HomePageClient"; export default function HomePage() { - return ( -
- - - - -
- ); + return ; } - diff --git a/app/(site)/registration/page.tsx b/app/(site)/registration/page.tsx index 02c7dcd..d593eb0 100644 --- a/app/(site)/registration/page.tsx +++ b/app/(site)/registration/page.tsx @@ -1,38 +1,5 @@ -import Link from "next/link"; -import RegistrationForm from "@/app/components/forms/RegistrationForm"; -import data from "@/app/data/registration.json"; +import RegistrationPageClient from "@/app/components/forms/RegistrationPageClient"; export default function RegistrationPage() { - return ( -
-
-
-
- - - arrow_back - - Back to Home - -
- -
-

- {data.title} -

-

{data.subtitle}

-
- -
-
- -
-
-
-
- ); + return ; } - diff --git a/app/(site)/sponsor/page.tsx b/app/(site)/sponsor/page.tsx index 42d0038..75f8d6d 100644 --- a/app/(site)/sponsor/page.tsx +++ b/app/(site)/sponsor/page.tsx @@ -1,23 +1,12 @@ import type { Metadata } from "next"; -import sponsor from "@/app/data/sponsor.json"; -import SponsorHero from "@/app/components/sponsor/SponsorHero"; -import SponsorPackages from "@/app/components/sponsor/SponsorPackages"; -import SponsorItemBased from "@/app/components/sponsor/SponsorItemBased"; -import SponsorContact from "@/app/components/sponsor/SponsorContact"; +import SponsorPageClient from "@/app/components/sponsor/SponsorPageClient"; export const metadata: Metadata = { - title: "Sponsorship | IPv6 Summit 2026", + title: "Nhà Tài Trợ | IPv6 Summit 2026", description: - "Become a sponsor at the IPv6 for AI & Data Centre Summit 2026. Align your brand with the architects of tomorrow's infrastructure.", + "Trở thành nhà tài trợ tại Hội Nghị IPv6 cho AI & Trung Tâm Dữ Liệu 2026. Kết nối thương hiệu của bạn với những kiến trúc sư của hạ tầng ngày mai.", }; export default function SponsorPage() { - return ( -
- - - - -
- ); + return ; } diff --git a/app/components/admin/AdminConsole.tsx b/app/components/admin/AdminConsole.tsx index 5b5d24f..8be6a5f 100644 --- a/app/components/admin/AdminConsole.tsx +++ b/app/components/admin/AdminConsole.tsx @@ -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 ( +
+ {label} + +
+ ); + + return ( +
+ {label} + {isStatus ? ( + + {value} + + ) : ( + + {value} + + )} +
+ ); +} + 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(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;