Update dependencies and enhance Tailwind CSS configuration for web applications
- Added new dependencies including clsx, lucide-react, recharts, and various Radix UI components to improve UI functionality. - Upgraded Tailwind CSS to version 4.0.0 and updated configuration to utilize CSS variables for theming and responsive design. - Introduced global styles and improved accessibility features in the layout and components. - Removed outdated login page and refactored authentication store for better state management. - Enhanced API service with additional authentication methods and improved error handling. These changes aim to modernize the web applications and improve user experience through better design and functionality.
This commit is contained in:
149
apps/web-admin/src/app/(dashboard)/dashboard/page.tsx
Normal file
149
apps/web-admin/src/app/(dashboard)/dashboard/page.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { AnalyticsCard } from '@/components/admin/analytics-card';
|
||||
import { RecentActivityTable, type RecentActivity } from '@/components/admin/recent-activity-table';
|
||||
// EN: Lazy load heavy chart components / VI: Lazy load các component chart nặng
|
||||
const UserGrowthChart = React.lazy(() => import('@/components/admin/charts/user-growth-chart').then(m => ({ default: m.UserGrowthChart })));
|
||||
const RevenueChart = React.lazy(() => import('@/components/admin/charts/revenue-chart').then(m => ({ default: m.RevenueChart })));
|
||||
import { Users, MessageSquare, TrendingUp, DollarSign } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* EN: Dashboard overview page component
|
||||
* VI: Component trang tổng quan Dashboard
|
||||
*
|
||||
* Features:
|
||||
* - Metrics row with key statistics
|
||||
* - Charts row (User Growth, Revenue)
|
||||
* - Recent activity table
|
||||
*
|
||||
* Tính năng:
|
||||
* - Hàng metrics với các thống kê chính
|
||||
* - Hàng charts (Tăng trưởng người dùng, Doanh thu)
|
||||
* - Bảng hoạt động gần đây
|
||||
*/
|
||||
export default function DashboardPage() {
|
||||
// EN: Mock data - replace with actual API calls / VI: Dữ liệu mock - thay thế bằng API calls thực tế
|
||||
const [activities] = React.useState<RecentActivity[]>([
|
||||
{
|
||||
id: '1',
|
||||
user: {
|
||||
id: 'user1',
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
action: 'user_created',
|
||||
description: 'Created new user account',
|
||||
status: 'success',
|
||||
timestamp: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
user: {
|
||||
id: 'user2',
|
||||
name: 'Jane Smith',
|
||||
email: 'jane@example.com',
|
||||
},
|
||||
action: 'message_sent',
|
||||
description: 'Sent message in conversation',
|
||||
status: 'info',
|
||||
timestamp: new Date(Date.now() - 1000 * 60 * 15), // 15 minutes ago
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* EN: Page header / VI: Header trang */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-text-primary">
|
||||
Dashboard / Bảng điều khiển
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-text-tertiary">
|
||||
Overview of your platform metrics and activities / Tổng quan về các metric và hoạt động của nền tảng
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* EN: Metrics row / VI: Hàng metrics */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<AnalyticsCard
|
||||
title="Total Users / Tổng người dùng"
|
||||
value={12543}
|
||||
change="+12.5%"
|
||||
trend="up"
|
||||
icon={Users}
|
||||
variant="metric"
|
||||
/>
|
||||
<AnalyticsCard
|
||||
title="Messages / Tin nhắn"
|
||||
value={89234}
|
||||
change="+8.2%"
|
||||
trend="up"
|
||||
icon={MessageSquare}
|
||||
variant="metric"
|
||||
/>
|
||||
<AnalyticsCard
|
||||
title="Active Users / Người dùng hoạt động"
|
||||
value={3421}
|
||||
change="+5.1%"
|
||||
trend="up"
|
||||
icon={TrendingUp}
|
||||
variant="metric"
|
||||
/>
|
||||
<AnalyticsCard
|
||||
title="Revenue / Doanh thu"
|
||||
value="$45,231"
|
||||
change="-2.3%"
|
||||
trend="down"
|
||||
icon={DollarSign}
|
||||
variant="metric"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* EN: Charts row / VI: Hàng charts */}
|
||||
{/* EN: Tablet: Side-by-side charts / VI: Tablet: Charts cạnh nhau */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 sm:gap-6 lg:grid-cols-2">
|
||||
{/* EN: User Growth Chart / VI: User Growth Chart */}
|
||||
<React.Suspense fallback={<div className="h-[300px] bg-bg-secondary rounded-lg border border-border-primary flex items-center justify-center"><div className="text-text-tertiary">Loading chart... / Đang tải chart...</div></div>}>
|
||||
<UserGrowthChart
|
||||
data={[
|
||||
{ date: 'Jan', users: 1000, newUsers: 150 },
|
||||
{ date: 'Feb', users: 1200, newUsers: 200 },
|
||||
{ date: 'Mar', users: 1450, newUsers: 250 },
|
||||
{ date: 'Apr', users: 1700, newUsers: 250 },
|
||||
{ date: 'May', users: 1950, newUsers: 250 },
|
||||
{ date: 'Jun', users: 2200, newUsers: 250 },
|
||||
]}
|
||||
title="User Growth / Tăng trưởng người dùng"
|
||||
description="Monthly user growth trend / Xu hướng tăng trưởng người dùng hàng tháng"
|
||||
/>
|
||||
</React.Suspense>
|
||||
|
||||
{/* EN: Revenue Chart / VI: Revenue Chart */}
|
||||
<React.Suspense fallback={<div className="h-[300px] bg-bg-secondary rounded-lg border border-border-primary flex items-center justify-center"><div className="text-text-tertiary">Loading chart... / Đang tải chart...</div></div>}>
|
||||
<RevenueChart
|
||||
data={[
|
||||
{ date: 'Jan', revenue: 45000, previousRevenue: 40000 },
|
||||
{ date: 'Feb', revenue: 52000, previousRevenue: 45000 },
|
||||
{ date: 'Mar', revenue: 48000, previousRevenue: 52000 },
|
||||
{ date: 'Apr', revenue: 55000, previousRevenue: 48000 },
|
||||
{ date: 'May', revenue: 60000, previousRevenue: 55000 },
|
||||
{ date: 'Jun', revenue: 65000, previousRevenue: 60000 },
|
||||
]}
|
||||
title="Revenue / Doanh thu"
|
||||
description="Monthly revenue trend / Xu hướng doanh thu hàng tháng"
|
||||
currency="$"
|
||||
/>
|
||||
</React.Suspense>
|
||||
</div>
|
||||
|
||||
{/* EN: Recent Activity Table / VI: Bảng hoạt động gần đây */}
|
||||
<RecentActivityTable
|
||||
activities={activities}
|
||||
currentPage={1}
|
||||
itemsPerPage={10}
|
||||
onPageChange={(page) => console.log('Page changed:', page)}
|
||||
onQuickAction={(id, action) => console.log('Action:', id, action)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
217
apps/web-admin/src/app/(dashboard)/layout.tsx
Normal file
217
apps/web-admin/src/app/(dashboard)/layout.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
BarChart3,
|
||||
MessageSquare,
|
||||
Settings,
|
||||
LogOut,
|
||||
Menu,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
|
||||
/**
|
||||
* EN: Admin navigation items configuration
|
||||
* VI: Cấu hình các mục điều hướng Admin
|
||||
*/
|
||||
const adminNavItems = [
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Dashboard / Bảng điều khiển',
|
||||
href: '/dashboard',
|
||||
icon: LayoutDashboard,
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
label: 'Users / Người dùng',
|
||||
href: '/dashboard/users',
|
||||
icon: Users,
|
||||
},
|
||||
{
|
||||
id: 'analytics',
|
||||
label: 'Analytics / Phân tích',
|
||||
href: '/dashboard/analytics',
|
||||
icon: BarChart3,
|
||||
},
|
||||
{
|
||||
id: 'messages',
|
||||
label: 'Messages / Tin nhắn',
|
||||
href: '/dashboard/messages',
|
||||
icon: MessageSquare,
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
label: 'Settings / Cài đặt',
|
||||
href: '/dashboard/settings',
|
||||
icon: Settings,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* EN: Admin Dashboard layout component with sidebar navigation
|
||||
* VI: Component layout Admin Dashboard với điều hướng sidebar
|
||||
*
|
||||
* Features:
|
||||
* - Sidebar navigation with icons
|
||||
* - Responsive design (collapsible on mobile)
|
||||
* - User profile section
|
||||
* - Logout functionality
|
||||
* - Active route highlighting
|
||||
*
|
||||
* Tính năng:
|
||||
* - Điều hướng sidebar với icons
|
||||
* - Responsive design (có thể thu gọn trên mobile)
|
||||
* - Phần profile người dùng
|
||||
* - Chức năng logout
|
||||
* - Highlight route đang active
|
||||
*/
|
||||
export default function AdminDashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const { user, logout } = useAuthStore();
|
||||
const [sidebarOpen, setSidebarOpen] = React.useState(false);
|
||||
|
||||
/**
|
||||
* EN: Handle logout
|
||||
* VI: Xử lý logout
|
||||
*/
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
window.location.href = '/login';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full overflow-hidden bg-bg-primary">
|
||||
{/* EN: Mobile sidebar overlay / VI: Overlay sidebar mobile */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* EN: Sidebar / VI: Sidebar */}
|
||||
<aside
|
||||
className={cn(
|
||||
// EN: Base sidebar styles / VI: Style sidebar cơ bản
|
||||
'flex flex-col bg-bg-secondary border-r border-border-primary transition-all duration-[250ms] ease-out',
|
||||
// EN: Desktop: Fixed width / VI: Desktop: Chiều rộng cố định
|
||||
'w-64 flex-shrink-0',
|
||||
// EN: Mobile: Fixed position, slide in/out / VI: Mobile: Vị trí cố định, trượt vào/ra
|
||||
'max-md:fixed max-md:inset-y-0 max-md:left-0 max-md:z-50',
|
||||
'max-md:transform max-md:transition-transform',
|
||||
sidebarOpen ? 'max-md:translate-x-0' : 'max-md:-translate-x-full'
|
||||
)}
|
||||
aria-label="Admin sidebar / Sidebar Admin"
|
||||
>
|
||||
{/* EN: Sidebar header with mobile menu button / VI: Header sidebar với nút menu mobile */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-border-primary">
|
||||
<h1 className="text-xl font-semibold text-text-primary">
|
||||
GoodGo Admin / Quản trị GoodGo
|
||||
</h1>
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="md:hidden p-2 rounded-md hover:bg-bg-tertiary transition-colors"
|
||||
aria-label="Close sidebar / Đóng sidebar"
|
||||
>
|
||||
<X className="h-5 w-5 text-text-secondary" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* EN: Navigation menu / VI: Menu điều hướng */}
|
||||
<nav className="flex-1 overflow-y-auto p-4 space-y-1">
|
||||
{adminNavItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.href || pathname.startsWith(item.href + '/');
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={item.href}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className={cn(
|
||||
// EN: Base nav item styles / VI: Style nav item cơ bản
|
||||
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-[150ms]',
|
||||
// EN: Active state / VI: Trạng thái active
|
||||
isActive
|
||||
? 'bg-accent-primary text-white shadow-md'
|
||||
: 'text-text-secondary hover:bg-bg-tertiary hover:text-text-primary',
|
||||
)}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
>
|
||||
<Icon
|
||||
className={cn(
|
||||
'h-5 w-5 flex-shrink-0',
|
||||
isActive ? 'text-white' : 'text-text-tertiary',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* EN: User profile section / VI: Phần profile người dùng */}
|
||||
{user && (
|
||||
<div className="p-4 border-t border-border-primary">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="h-10 w-10 rounded-full bg-chat-ai-bubble flex items-center justify-center">
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
{user.email?.charAt(0).toUpperCase() || 'A'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-text-primary truncate">
|
||||
{user.email}
|
||||
</p>
|
||||
<p className="text-xs text-text-tertiary truncate">
|
||||
{user.role || 'Admin / Quản trị viên'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-text-secondary hover:bg-bg-tertiary hover:text-text-primary transition-all duration-[150ms]"
|
||||
aria-label="Logout / Đăng xuất"
|
||||
>
|
||||
<LogOut className="h-5 w-5 flex-shrink-0" aria-hidden="true" />
|
||||
<span>Logout / Đăng xuất</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
{/* EN: Main content area / VI: Khu vực nội dung chính */}
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
{/* EN: Mobile header with menu button / VI: Header mobile với nút menu */}
|
||||
<header className="sticky top-0 z-30 flex items-center justify-between p-4 bg-bg-secondary border-b border-border-primary md:hidden">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="p-2 rounded-md hover:bg-bg-tertiary transition-colors"
|
||||
aria-label="Open sidebar / Mở sidebar"
|
||||
>
|
||||
<Menu className="h-5 w-5 text-text-secondary" />
|
||||
</button>
|
||||
<h1 className="text-lg font-semibold text-text-primary">
|
||||
GoodGo Admin / Quản trị GoodGo
|
||||
</h1>
|
||||
<div className="w-9" /> {/* EN: Spacer for centering / VI: Khoảng trống để căn giữa */}
|
||||
</header>
|
||||
|
||||
{/* EN: Page content / VI: Nội dung trang */}
|
||||
<div className="p-6">{children}</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
103
apps/web-admin/src/app/(dashboard)/settings/page.tsx
Normal file
103
apps/web-admin/src/app/(dashboard)/settings/page.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
// EN: Lazy load settings forms / VI: Lazy load các form cài đặt
|
||||
const GeneralSettings = React.lazy(() => import('@/components/admin/settings/general-settings').then(m => ({ default: m.GeneralSettings })));
|
||||
const EmailSettings = React.lazy(() => import('@/components/admin/settings/email-settings').then(m => ({ default: m.EmailSettings })));
|
||||
const SecuritySettings = React.lazy(() => import('@/components/admin/settings/security-settings').then(m => ({ default: m.SecuritySettings })));
|
||||
|
||||
/**
|
||||
* EN: System Settings page component
|
||||
* VI: Component trang cài đặt hệ thống
|
||||
*
|
||||
* Features:
|
||||
* - Tab navigation (General, Email, Security, API, Advanced)
|
||||
* - Settings forms for each category
|
||||
*
|
||||
* Tính năng:
|
||||
* - Điều hướng tab (General, Email, Security, API, Advanced)
|
||||
* - Forms cài đặt cho mỗi danh mục
|
||||
*/
|
||||
export default function SystemSettingsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* EN: Page header / VI: Header trang */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-text-primary">
|
||||
System Settings / Cài đặt hệ thống
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-text-tertiary">
|
||||
Configure system-wide settings and preferences / Cấu hình cài đặt và tùy chọn toàn hệ thống
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* EN: Settings tabs / VI: Tabs cài đặt */}
|
||||
<Tabs defaultValue="general" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-5">
|
||||
<TabsTrigger value="general">General / Chung</TabsTrigger>
|
||||
<TabsTrigger value="email">Email / Email</TabsTrigger>
|
||||
<TabsTrigger value="security">Security / Bảo mật</TabsTrigger>
|
||||
<TabsTrigger value="api">API / API</TabsTrigger>
|
||||
<TabsTrigger value="advanced">Advanced / Nâng cao</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* EN: General settings tab / VI: Tab cài đặt chung */}
|
||||
<TabsContent value="general" className="space-y-6">
|
||||
<React.Suspense fallback={<div className="p-8 text-center"><div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-accent-primary border-r-transparent" /><p className="mt-4 text-sm text-text-tertiary">Loading... / Đang tải...</p></div>}>
|
||||
<GeneralSettings />
|
||||
</React.Suspense>
|
||||
</TabsContent>
|
||||
|
||||
{/* EN: Email settings tab / VI: Tab cài đặt email */}
|
||||
<TabsContent value="email" className="space-y-6">
|
||||
<React.Suspense fallback={<div className="p-8 text-center"><div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-accent-primary border-r-transparent" /><p className="mt-4 text-sm text-text-tertiary">Loading... / Đang tải...</p></div>}>
|
||||
<EmailSettings />
|
||||
</React.Suspense>
|
||||
</TabsContent>
|
||||
|
||||
{/* EN: Security settings tab / VI: Tab cài đặt bảo mật */}
|
||||
<TabsContent value="security" className="space-y-6">
|
||||
<React.Suspense fallback={<div className="p-8 text-center"><div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-accent-primary border-r-transparent" /><p className="mt-4 text-sm text-text-tertiary">Loading... / Đang tải...</p></div>}>
|
||||
<SecuritySettings />
|
||||
</React.Suspense>
|
||||
</TabsContent>
|
||||
|
||||
{/* EN: API settings tab / VI: Tab cài đặt API */}
|
||||
<TabsContent value="api" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>API Settings / Cài đặt API</CardTitle>
|
||||
<CardDescription>
|
||||
Configure API settings and webhooks / Cấu hình cài đặt API và webhooks
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-text-tertiary">
|
||||
API settings form will be implemented here / Form cài đặt API sẽ được implement ở đây
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* EN: Advanced settings tab / VI: Tab cài đặt nâng cao */}
|
||||
<TabsContent value="advanced" className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Advanced Settings / Cài đặt nâng cao</CardTitle>
|
||||
<CardDescription>
|
||||
Advanced system configuration / Cấu hình hệ thống nâng cao
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-text-tertiary">
|
||||
Advanced settings form will be implemented here / Form cài đặt nâng cao sẽ được implement ở đây
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
139
apps/web-admin/src/app/(dashboard)/users/page.tsx
Normal file
139
apps/web-admin/src/app/(dashboard)/users/page.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { DataTable, type DataTableColumn } from '@/components/admin/data-table';
|
||||
// EN: Lazy load modal component / VI: Lazy load component modal
|
||||
const UserDetailsModal = React.lazy(() => import('@/components/admin/user-details-modal').then(m => ({ default: m.UserDetailsModal })));
|
||||
import type { User } from '@/components/admin/user-details-modal';
|
||||
import { Search, Filter, Download, MoreVertical } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* EN: User Management page component
|
||||
* VI: Component trang quản lý người dùng
|
||||
*
|
||||
* Features:
|
||||
* - Search bar
|
||||
* - Filter dropdowns
|
||||
* - User list table (will use DataTable component)
|
||||
* - Export functionality
|
||||
*
|
||||
* Tính năng:
|
||||
* - Thanh tìm kiếm
|
||||
* - Dropdowns lọc
|
||||
* - Bảng danh sách người dùng (sẽ sử dụng component DataTable)
|
||||
* - Chức năng export
|
||||
*/
|
||||
export default function UserManagementPage() {
|
||||
const [searchQuery, setSearchQuery] = React.useState('');
|
||||
const [selectedRole, setSelectedRole] = React.useState<string>('all');
|
||||
const [selectedStatus, setSelectedStatus] = React.useState<string>('all');
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* EN: Page header / VI: Header trang */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-text-primary">
|
||||
User Management / Quản lý người dùng
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-text-tertiary">
|
||||
Manage users, roles, and permissions / Quản lý người dùng, vai trò và quyền
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* EN: Search and filters card / VI: Card tìm kiếm và lọc */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Search & Filters / Tìm kiếm & Lọc</CardTitle>
|
||||
<CardDescription>
|
||||
Find and filter users by name, email, role, or status / Tìm và lọc người dùng theo tên, email, vai trò hoặc trạng thái
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
{/* EN: Search input / VI: Input tìm kiếm */}
|
||||
<div className="md:col-span-2">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search by name or email... / Tìm kiếm theo tên hoặc email..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* EN: Filter buttons / VI: Nút lọc */}
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" size="md" className="flex-1">
|
||||
<Filter className="h-4 w-4 mr-2" />
|
||||
Filters / Lọc
|
||||
</Button>
|
||||
<Button variant="secondary" size="md">
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* EN: User list table / VI: Bảng danh sách người dùng */}
|
||||
<DataTable
|
||||
data={users}
|
||||
columns={columns}
|
||||
currentPage={currentPage}
|
||||
itemsPerPage={10}
|
||||
onPageChange={setCurrentPage}
|
||||
selectable
|
||||
selectedRows={selectedRows}
|
||||
onSelectionChange={setSelectedRows}
|
||||
getRowId={(row) => row.id}
|
||||
bulkActions={[
|
||||
{
|
||||
label: 'Activate / Kích hoạt',
|
||||
action: handleBulkActivate,
|
||||
variant: 'primary',
|
||||
},
|
||||
{
|
||||
label: 'Deactivate / Vô hiệu hóa',
|
||||
action: handleBulkDeactivate,
|
||||
variant: 'secondary',
|
||||
},
|
||||
{
|
||||
label: 'Delete / Xóa',
|
||||
action: handleBulkDelete,
|
||||
variant: 'danger',
|
||||
},
|
||||
]}
|
||||
exportable
|
||||
onExport={() => console.log('Export users')}
|
||||
/>
|
||||
|
||||
{/* EN: User details modal / VI: Modal chi tiết người dùng */}
|
||||
{selectedUser && (
|
||||
<React.Suspense fallback={null}>
|
||||
<UserDetailsModal
|
||||
user={selectedUser}
|
||||
open={!!selectedUser}
|
||||
onOpenChange={(open) => !open && setSelectedUser(null)}
|
||||
onEdit={(user) => {
|
||||
console.log('Edit user:', user);
|
||||
// EN: TODO: Implement edit / VI: TODO: Implement edit
|
||||
}}
|
||||
onDelete={(userId) => {
|
||||
console.log('Delete user:', userId);
|
||||
setSelectedUser(null);
|
||||
// EN: TODO: Implement delete / VI: TODO: Implement delete
|
||||
}}
|
||||
onDeactivate={(userId) => {
|
||||
console.log('Deactivate user:', userId);
|
||||
setSelectedUser(null);
|
||||
// EN: TODO: Implement deactivate / VI: TODO: Implement deactivate
|
||||
}}
|
||||
/>
|
||||
</React.Suspense>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +1,199 @@
|
||||
/**
|
||||
* EN: Global Styles with Tailwind CSS 4
|
||||
* VI: Styles toàn cục với Tailwind CSS 4
|
||||
*
|
||||
* Import theme variables first, then Tailwind directives
|
||||
* Import các biến theme trước, sau đó là các directives của Tailwind
|
||||
*/
|
||||
@import "../styles/theme.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/**
|
||||
* EN: Theme CSS Variables - Dark Mode (Default)
|
||||
* VI: CSS Variables cho Theme - Dark Mode (Mặc định)
|
||||
*/
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
/* Background Colors */
|
||||
--bg-primary: #0A0A0A;
|
||||
--bg-secondary: #121212;
|
||||
--bg-tertiary: #1A1A1A;
|
||||
--bg-elevated: #242424;
|
||||
|
||||
/* Text Colors */
|
||||
--text-primary: #FAFAFA;
|
||||
--text-secondary: #E0E0E0;
|
||||
--text-tertiary: #A0A0A0;
|
||||
--text-inverse: #1A1A1A;
|
||||
|
||||
/* Brand/Accent Colors */
|
||||
--accent-primary: #3B82F6;
|
||||
--accent-secondary: #8B5CF6;
|
||||
--accent-success: #10B981;
|
||||
--accent-warning: #F59E0B;
|
||||
--accent-error: #EF4444;
|
||||
--accent-info: #06B6D4;
|
||||
|
||||
/* Chat Specific Colors */
|
||||
--chat-user-bubble: #2563EB;
|
||||
--chat-ai-bubble: #374151;
|
||||
--chat-user-text: #FFFFFF;
|
||||
--chat-ai-text: #F3F4F6;
|
||||
--chat-timestamp: #9CA3AF;
|
||||
--chat-divider: #1F2937;
|
||||
|
||||
/* Border Colors */
|
||||
--border-primary: #2A2A2A;
|
||||
--border-secondary: #3A3A3A;
|
||||
--border-focus: #3B82F6;
|
||||
|
||||
/* Font Stack */
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-mono: "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
|
||||
/* Type Scale */
|
||||
--text-6xl: 3.75rem;
|
||||
--text-5xl: 3rem;
|
||||
--text-4xl: 2.25rem;
|
||||
--text-3xl: 1.875rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-base: 1rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-xs: 0.75rem;
|
||||
|
||||
/* Font Weights */
|
||||
--font-light: 300;
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
|
||||
/* Spacing Scale (Base: 4px) */
|
||||
--space-0: 0;
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.25rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-10: 2.5rem;
|
||||
--space-12: 3rem;
|
||||
--space-16: 4rem;
|
||||
--space-20: 5rem;
|
||||
|
||||
/* Container Widths */
|
||||
--container-sm: 640px;
|
||||
--container-md: 768px;
|
||||
--container-lg: 1024px;
|
||||
--container-xl: 1280px;
|
||||
--container-2xl: 1536px;
|
||||
--chat-max-width: 768px;
|
||||
--sidebar-width: 280px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
--radius-2xl: 1.5rem;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.5);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.6);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.7);
|
||||
--shadow-glow: 0 0 20px rgba(59, 130, 246, 0.3);
|
||||
|
||||
/* Animation Timing */
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
|
||||
/* Duration */
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 250ms;
|
||||
--duration-slow: 350ms;
|
||||
--duration-slower: 500ms;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
/**
|
||||
* EN: Light Mode Theme Variables
|
||||
* VI: CSS Variables cho Light Mode
|
||||
*/
|
||||
:root.light {
|
||||
/* Background Colors */
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F9FAFB;
|
||||
--bg-tertiary: #F3F4F6;
|
||||
--bg-elevated: #FFFFFF;
|
||||
|
||||
/* Text Colors */
|
||||
--text-primary: #111827;
|
||||
--text-secondary: #4B5563;
|
||||
--text-tertiary: #9CA3AF;
|
||||
--text-inverse: #FAFAFA;
|
||||
|
||||
/* Chat Specific Colors */
|
||||
--chat-user-bubble: #3B82F6;
|
||||
--chat-ai-bubble: #F3F4F6;
|
||||
--chat-user-text: #FFFFFF;
|
||||
--chat-ai-text: #111827;
|
||||
--chat-timestamp: #6B7280;
|
||||
--chat-divider: #E5E7EB;
|
||||
|
||||
/* Border Colors */
|
||||
--border-primary: #E5E7EB;
|
||||
--border-secondary: #D1D5DB;
|
||||
--border-focus: #3B82F6;
|
||||
|
||||
/* Shadows (lighter for light mode) */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
|
||||
--shadow-glow: 0 0 20px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Base styles
|
||||
* VI: Styles cơ bản
|
||||
*/
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-base);
|
||||
line-height: 1.5;
|
||||
transition: background-color var(--duration-normal) var(--ease-in-out),
|
||||
color var(--duration-normal) var(--ease-in-out);
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Smooth transitions for theme switching
|
||||
* VI: Chuyển đổi mượt mà cho việc chuyển theme
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
transition: background-color var(--duration-normal) var(--ease-in-out),
|
||||
color var(--duration-normal) var(--ease-in-out),
|
||||
border-color var(--duration-normal) var(--ease-in-out);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
import { ThemeProvider } from '../contexts/theme-context';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'GoodGo Platform',
|
||||
@@ -12,8 +13,10 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user