feat: 集成真实数据库连接和API服务
- 更新 .env.local 配置为真实的 Supabase 项目连接 - 创建完整的 API 服务层 (lib/api-service.ts) - 创建数据库类型定义 (types/database.ts) - 更新仪表盘页面使用真实数据替代演示数据 - 添加数据库连接测试和错误处理 - 创建测试数据验证系统功能 - 修复图标导入和语法错误 系统现在已连接到真实的 Supabase 数据库,可以正常显示统计数据和最近活动。
This commit is contained in:
+388
-234
@@ -1,188 +1,295 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import DashboardLayout from '../../components/Layout/DashboardLayout';
|
||||
import { getDemoData } from '../../lib/demo-data';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
UsersIcon,
|
||||
UserGroupIcon,
|
||||
PhoneIcon,
|
||||
DocumentTextIcon,
|
||||
CurrencyDollarIcon,
|
||||
CheckCircleIcon,
|
||||
ChartBarIcon,
|
||||
ClockIcon,
|
||||
CheckCircleIcon,
|
||||
ExclamationTriangleIcon,
|
||||
ArrowUpIcon,
|
||||
ArrowDownIcon,
|
||||
EyeIcon
|
||||
EyeIcon,
|
||||
PencilIcon,
|
||||
TrashIcon,
|
||||
PlayIcon,
|
||||
PauseIcon,
|
||||
StopIcon,
|
||||
MicrophoneIcon,
|
||||
VideoCameraIcon,
|
||||
GlobeAltIcon,
|
||||
BellIcon,
|
||||
CogIcon,
|
||||
UserIcon,
|
||||
BuildingOfficeIcon,
|
||||
CalendarDaysIcon,
|
||||
ChatBubbleLeftRightIcon,
|
||||
BanknotesIcon,
|
||||
UsersIcon,
|
||||
LanguageIcon,
|
||||
DocumentDuplicateIcon,
|
||||
InboxIcon,
|
||||
PhoneArrowUpRightIcon,
|
||||
PhoneArrowDownLeftIcon,
|
||||
TrophyIcon,
|
||||
StarIcon,
|
||||
HeartIcon,
|
||||
FireIcon,
|
||||
LightBulbIcon,
|
||||
ShieldCheckIcon,
|
||||
SparklesIcon,
|
||||
RocketLaunchIcon,
|
||||
MegaphoneIcon,
|
||||
GiftIcon,
|
||||
AcademicCapIcon,
|
||||
MapIcon,
|
||||
SunIcon,
|
||||
MoonIcon,
|
||||
ComputerDesktopIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import {
|
||||
CheckCircleIcon as CheckCircleIconSolid,
|
||||
ExclamationTriangleIcon as ExclamationTriangleIconSolid,
|
||||
ClockIcon as ClockIconSolid,
|
||||
XCircleIcon as XCircleIconSolid,
|
||||
UserGroupIcon as UserGroupIconSolid,
|
||||
PhoneIcon as PhoneIconSolid,
|
||||
DocumentTextIcon as DocumentTextIconSolid,
|
||||
CurrencyDollarIcon as CurrencyDollarIconSolid,
|
||||
ChartBarIcon as ChartBarIconSolid,
|
||||
BellIcon as BellIconSolid,
|
||||
StarIcon as StarIconSolid,
|
||||
HeartIcon as HeartIconSolid,
|
||||
FireIcon as FireIconSolid,
|
||||
TrophyIcon as TrophyIconSolid,
|
||||
SparklesIcon as SparklesIconSolid,
|
||||
RocketLaunchIcon as RocketLaunchIconSolid,
|
||||
GiftIcon as GiftIconSolid,
|
||||
AcademicCapIcon as AcademicCapIconSolid,
|
||||
ShieldCheckIcon as ShieldCheckIconSolid,
|
||||
LightBulbIcon as LightBulbIconSolid,
|
||||
MegaphoneIcon as MegaphoneIconSolid,
|
||||
MapIcon as MapIconSolid,
|
||||
SunIcon as SunIconSolid,
|
||||
MoonIcon as MoonIconSolid,
|
||||
ComputerDesktopIcon as ComputerDesktopIconSolid,
|
||||
} from '@heroicons/react/24/solid';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { statsAPI } from '../../lib/api-service';
|
||||
|
||||
interface DashboardStats {
|
||||
totalUsers: number;
|
||||
activeUsers: number;
|
||||
totalCalls: number;
|
||||
activeCalls: number;
|
||||
totalInterpreters: number;
|
||||
totalOrders: number;
|
||||
pendingOrders: number;
|
||||
completedOrders: number;
|
||||
totalRevenue: number;
|
||||
monthlyRevenue: number;
|
||||
activeInterpreters: number;
|
||||
totalCalls: number;
|
||||
activeUsers: number;
|
||||
activeCalls: number;
|
||||
recentOrders: any[];
|
||||
recentCalls: any[];
|
||||
}
|
||||
|
||||
interface RecentActivity {
|
||||
id: string;
|
||||
type: 'call' | 'order' | 'user' | 'system';
|
||||
type: 'order' | 'call' | 'user' | 'interpreter';
|
||||
title: string;
|
||||
description: string;
|
||||
time: string;
|
||||
status: 'success' | 'warning' | 'error' | 'info';
|
||||
icon: any;
|
||||
}
|
||||
|
||||
export default function Dashboard() {
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
const [activities, setActivities] = useState<RecentActivity[]>([]);
|
||||
const router = useRouter();
|
||||
const [stats, setStats] = useState<DashboardStats>({
|
||||
totalUsers: 0,
|
||||
totalInterpreters: 0,
|
||||
totalOrders: 0,
|
||||
totalCalls: 0,
|
||||
activeUsers: 0,
|
||||
activeCalls: 0,
|
||||
recentOrders: [],
|
||||
recentCalls: []
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [recentActivity, setRecentActivity] = useState<RecentActivity[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const loadDashboardData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 模拟加载延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 使用演示数据
|
||||
const mockStats: DashboardStats = {
|
||||
totalUsers: 1248,
|
||||
activeUsers: 856,
|
||||
totalCalls: 3456,
|
||||
activeCalls: 12,
|
||||
totalOrders: 2789,
|
||||
pendingOrders: 45,
|
||||
completedOrders: 2654,
|
||||
totalRevenue: 125000,
|
||||
monthlyRevenue: 15600,
|
||||
activeInterpreters: 23
|
||||
};
|
||||
|
||||
const mockActivities: RecentActivity[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'call',
|
||||
title: '新通话开始',
|
||||
description: '张三开始了中英互译通话',
|
||||
time: '2分钟前',
|
||||
status: 'success'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'order',
|
||||
title: '订单完成',
|
||||
description: '订单ORD-2024-001已完成,费用¥180',
|
||||
time: '5分钟前',
|
||||
status: 'success'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'user',
|
||||
title: '新用户注册',
|
||||
description: 'ABC公司注册了企业账户',
|
||||
time: '10分钟前',
|
||||
status: 'info'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'system',
|
||||
title: '系统维护',
|
||||
description: '系统将在今晚22:00-23:00进行维护',
|
||||
time: '30分钟前',
|
||||
status: 'warning'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'call',
|
||||
title: '通话异常',
|
||||
description: '通话CALL-2024-003出现连接问题',
|
||||
time: '1小时前',
|
||||
status: 'error'
|
||||
}
|
||||
];
|
||||
|
||||
setStats(mockStats);
|
||||
setActivities(mockActivities);
|
||||
} catch (error) {
|
||||
console.error('Failed to load dashboard data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadDashboardData();
|
||||
}, []);
|
||||
|
||||
const loadDashboardData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const dashboardStats = await statsAPI.getDashboardStats();
|
||||
setStats(dashboardStats);
|
||||
|
||||
// 生成最近活动记录
|
||||
const activities: RecentActivity[] = [];
|
||||
|
||||
// 添加最近订单活动
|
||||
dashboardStats.recentOrders.forEach((order: any) => {
|
||||
activities.push({
|
||||
id: order.id,
|
||||
type: 'order',
|
||||
title: `订单 ${order.order_number}`,
|
||||
description: `${order.user_name} - ${order.service_name}`,
|
||||
time: formatTime(order.created_at),
|
||||
status: getOrderStatus(order.status),
|
||||
icon: getOrderIcon(order.service_type)
|
||||
});
|
||||
});
|
||||
|
||||
// 添加最近通话活动
|
||||
dashboardStats.recentCalls.forEach((call: any) => {
|
||||
activities.push({
|
||||
id: call.id,
|
||||
type: 'call',
|
||||
title: `${call.service_type === 'phone' ? '电话' : '视频'}通话`,
|
||||
description: `${call.users?.name || '用户'} - ${call.interpreters?.name || '翻译员'}`,
|
||||
time: formatTime(call.created_at),
|
||||
status: getCallStatus(call.status),
|
||||
icon: call.service_type === 'phone' ? PhoneIcon : VideoCameraIcon
|
||||
});
|
||||
});
|
||||
|
||||
// 按时间排序
|
||||
activities.sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime());
|
||||
setRecentActivity(activities.slice(0, 10));
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载仪表盘数据失败:', error);
|
||||
toast.error('加载仪表盘数据失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getOrderStatus = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed': return 'success';
|
||||
case 'cancelled': return 'error';
|
||||
case 'in_progress': return 'warning';
|
||||
default: return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
const getCallStatus = (status: string) => {
|
||||
switch (status) {
|
||||
case 'ended': return 'success';
|
||||
case 'cancelled': return 'error';
|
||||
case 'connected': return 'warning';
|
||||
default: return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
const getOrderIcon = (serviceType: string) => {
|
||||
switch (serviceType) {
|
||||
case 'phone': return PhoneIcon;
|
||||
case 'video': return VideoCameraIcon;
|
||||
case 'document': return DocumentTextIcon;
|
||||
default: return LanguageIcon;
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diff = now.getTime() - date.getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
if (days > 0) return `${days}天前`;
|
||||
if (hours > 0) return `${hours}小时前`;
|
||||
if (minutes > 0) return `${minutes}分钟前`;
|
||||
return '刚刚';
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat('zh-CN', {
|
||||
style: 'currency',
|
||||
currency: 'CNY'
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return 'text-green-600 bg-green-100';
|
||||
case 'warning':
|
||||
return 'text-yellow-600 bg-yellow-100';
|
||||
case 'error':
|
||||
return 'text-red-600 bg-red-100';
|
||||
default:
|
||||
return 'text-blue-600 bg-blue-100';
|
||||
case 'success': return 'text-green-600 bg-green-50';
|
||||
case 'warning': return 'text-yellow-600 bg-yellow-50';
|
||||
case 'error': return 'text-red-600 bg-red-50';
|
||||
default: return 'text-blue-600 bg-blue-50';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return <CheckCircleIcon className="h-5 w-5 text-green-500" />;
|
||||
case 'warning':
|
||||
return <ExclamationTriangleIcon className="h-5 w-5 text-yellow-500" />;
|
||||
case 'error':
|
||||
return <ExclamationTriangleIcon className="h-5 w-5 text-red-500" />;
|
||||
default:
|
||||
return <ClockIcon className="h-5 w-5 text-blue-500" />;
|
||||
case 'success': return CheckCircleIconSolid;
|
||||
case 'warning': return ExclamationTriangleIconSolid;
|
||||
case 'error': return XCircleIconSolid;
|
||||
default: return ClockIconSolid;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<DashboardLayout title="仪表盘">
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600 mx-auto"></div>
|
||||
<p className="mt-4 text-gray-600">加载中...</p>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardLayout title="仪表盘">
|
||||
<div className="space-y-6">
|
||||
{/* 欢迎区域 */}
|
||||
<div className="bg-white shadow rounded-lg p-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900">欢迎回来!</h1>
|
||||
<p className="mt-1 text-sm text-gray-600">
|
||||
这里是您的管理仪表板,查看最新的业务数据和活动。
|
||||
</p>
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{/* Header */}
|
||||
<div className="bg-white shadow">
|
||||
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">仪表盘</h1>
|
||||
<p className="mt-1 text-sm text-gray-600">
|
||||
欢迎回来,管理员
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/notifications')}
|
||||
className="relative p-2 text-gray-400 hover:text-gray-500"
|
||||
>
|
||||
<BellIcon className="h-6 w-6" />
|
||||
<span className="absolute top-0 right-0 block h-2 w-2 rounded-full bg-red-400 ring-2 ring-white" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/settings')}
|
||||
className="p-2 text-gray-400 hover:text-gray-500"
|
||||
>
|
||||
<CogIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div className="p-5">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<UsersIcon className="h-6 w-6 text-blue-400" />
|
||||
<UserGroupIconSolid className="h-8 w-8 text-blue-600" />
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">总用户数</dt>
|
||||
<dd className="flex items-baseline">
|
||||
<div className="text-2xl font-semibold text-gray-900">{stats?.totalUsers || 0}</div>
|
||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
||||
<span className="sr-only">增加了</span>
|
||||
12%
|
||||
</div>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||
总用户数
|
||||
</dt>
|
||||
<dd className="text-lg font-medium text-gray-900">
|
||||
{stats.totalUsers.toLocaleString()}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@@ -190,8 +297,9 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="bg-gray-50 px-5 py-3">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium text-gray-500">活跃用户: </span>
|
||||
<span className="text-gray-900">{stats?.activeUsers || 0}</span>
|
||||
<span className="text-green-600 font-medium">
|
||||
{stats.activeUsers} 活跃用户
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -200,18 +308,15 @@ export default function Dashboard() {
|
||||
<div className="p-5">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<PhoneIcon className="h-6 w-6 text-green-400" />
|
||||
<UsersIcon className="h-8 w-8 text-green-600" />
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">总通话数</dt>
|
||||
<dd className="flex items-baseline">
|
||||
<div className="text-2xl font-semibold text-gray-900">{stats?.totalCalls || 0}</div>
|
||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
||||
<span className="sr-only">增加了</span>
|
||||
8%
|
||||
</div>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||
翻译员总数
|
||||
</dt>
|
||||
<dd className="text-lg font-medium text-gray-900">
|
||||
{stats.totalInterpreters.toLocaleString()}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@@ -219,8 +324,9 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="bg-gray-50 px-5 py-3">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium text-gray-500">进行中: </span>
|
||||
<span className="text-gray-900">{stats?.activeCalls || 0}</span>
|
||||
<span className="text-green-600 font-medium">
|
||||
在线翻译员
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,18 +335,15 @@ export default function Dashboard() {
|
||||
<div className="p-5">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<DocumentTextIcon className="h-6 w-6 text-yellow-400" />
|
||||
<DocumentTextIconSolid className="h-8 w-8 text-purple-600" />
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">总订单数</dt>
|
||||
<dd className="flex items-baseline">
|
||||
<div className="text-2xl font-semibold text-gray-900">{stats?.totalOrders || 0}</div>
|
||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
||||
<span className="sr-only">增加了</span>
|
||||
15%
|
||||
</div>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||
总订单数
|
||||
</dt>
|
||||
<dd className="text-lg font-medium text-gray-900">
|
||||
{stats.totalOrders.toLocaleString()}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@@ -248,8 +351,9 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="bg-gray-50 px-5 py-3">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium text-gray-500">待处理: </span>
|
||||
<span className="text-gray-900">{stats?.pendingOrders || 0}</span>
|
||||
<span className="text-green-600 font-medium">
|
||||
本月新增
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,18 +362,15 @@ export default function Dashboard() {
|
||||
<div className="p-5">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<CurrencyDollarIcon className="h-6 w-6 text-purple-400" />
|
||||
<PhoneIconSolid className="h-8 w-8 text-orange-600" />
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">总收入</dt>
|
||||
<dd className="flex items-baseline">
|
||||
<div className="text-2xl font-semibold text-gray-900">¥{stats?.totalRevenue?.toLocaleString() || 0}</div>
|
||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
||||
<span className="sr-only">增加了</span>
|
||||
22%
|
||||
</div>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||
总通话数
|
||||
</dt>
|
||||
<dd className="text-lg font-medium text-gray-900">
|
||||
{stats.totalCalls.toLocaleString()}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
@@ -277,97 +378,150 @@ export default function Dashboard() {
|
||||
</div>
|
||||
<div className="bg-gray-50 px-5 py-3">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium text-gray-500">本月: </span>
|
||||
<span className="text-gray-900">¥{stats?.monthlyRevenue?.toLocaleString() || 0}</span>
|
||||
<span className="text-orange-600 font-medium">
|
||||
{stats.activeCalls} 进行中
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 最近活动和快速操作 */}
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{/* 最近活动 */}
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">最近活动</h3>
|
||||
<div className="space-y-4">
|
||||
{activities.map((activity) => (
|
||||
<div key={activity.id} className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0">
|
||||
{getStatusIcon(activity.status)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-gray-900">{activity.title}</div>
|
||||
<div className="text-sm text-gray-500">{activity.description}</div>
|
||||
<div className="text-xs text-gray-400 mt-1">{activity.time}</div>
|
||||
</div>
|
||||
<div className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(activity.status)}`}>
|
||||
{activity.status === 'success' && '成功'}
|
||||
{activity.status === 'warning' && '警告'}
|
||||
{activity.status === 'error' && '错误'}
|
||||
{activity.status === 'info' && '信息'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Main Content */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Recent Activity */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h3 className="text-lg font-medium text-gray-900">最近活动</h3>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button className="w-full bg-gray-50 border border-gray-300 rounded-md py-2 px-4 inline-flex justify-center items-center text-sm font-medium text-gray-700 hover:bg-gray-100">
|
||||
<EyeIcon className="h-4 w-4 mr-2" />
|
||||
查看所有活动
|
||||
</button>
|
||||
<div className="divide-y divide-gray-200">
|
||||
{recentActivity.length === 0 ? (
|
||||
<div className="px-6 py-8 text-center">
|
||||
<InboxIcon className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<p className="mt-2 text-sm text-gray-500">暂无活动记录</p>
|
||||
</div>
|
||||
) : (
|
||||
recentActivity.map((activity) => {
|
||||
const StatusIcon = getStatusIcon(activity.status);
|
||||
const ActivityIcon = activity.icon;
|
||||
|
||||
return (
|
||||
<div key={activity.id} className="px-6 py-4">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<div className={`p-2 rounded-full ${getStatusColor(activity.status)}`}>
|
||||
<ActivityIcon className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-4 flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{activity.title}
|
||||
</p>
|
||||
<div className="flex items-center">
|
||||
<StatusIcon className={`h-4 w-4 mr-1 ${
|
||||
activity.status === 'success' ? 'text-green-500' :
|
||||
activity.status === 'warning' ? 'text-yellow-500' :
|
||||
activity.status === 'error' ? 'text-red-500' :
|
||||
'text-blue-500'
|
||||
}`} />
|
||||
<span className="text-xs text-gray-500">
|
||||
{activity.time}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
{activity.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 快速操作 */}
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">快速操作</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<button className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-left hover:bg-blue-100 transition-colors">
|
||||
<div className="flex items-center">
|
||||
<UsersIcon className="h-8 w-8 text-blue-600" />
|
||||
<div className="ml-3">
|
||||
<div className="text-sm font-medium text-blue-900">用户管理</div>
|
||||
<div className="text-xs text-blue-700">管理用户账户</div>
|
||||
{/* Quick Actions */}
|
||||
<div>
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h3 className="text-lg font-medium text-gray-900">快速操作</h3>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/users')}
|
||||
className="w-full flex items-center justify-between p-3 text-left border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<UserGroupIcon className="h-5 w-5 text-blue-600 mr-3" />
|
||||
<span className="text-sm font-medium text-gray-900">用户管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button className="bg-green-50 border border-green-200 rounded-lg p-4 text-left hover:bg-green-100 transition-colors">
|
||||
<div className="flex items-center">
|
||||
<PhoneIcon className="h-8 w-8 text-green-600" />
|
||||
<div className="ml-3">
|
||||
<div className="text-sm font-medium text-green-900">通话监控</div>
|
||||
<div className="text-xs text-green-700">实时通话状态</div>
|
||||
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/interpreters')}
|
||||
className="w-full flex items-center justify-between p-3 text-left border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<UsersIcon className="h-5 w-5 text-green-600 mr-3" />
|
||||
<span className="text-sm font-medium text-gray-900">翻译员管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 text-left hover:bg-yellow-100 transition-colors">
|
||||
<div className="flex items-center">
|
||||
<DocumentTextIcon className="h-8 w-8 text-yellow-600" />
|
||||
<div className="ml-3">
|
||||
<div className="text-sm font-medium text-yellow-900">订单管理</div>
|
||||
<div className="text-xs text-yellow-700">处理订单请求</div>
|
||||
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/orders')}
|
||||
className="w-full flex items-center justify-between p-3 text-left border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<DocumentTextIcon className="h-5 w-5 text-purple-600 mr-3" />
|
||||
<span className="text-sm font-medium text-gray-900">订单管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button className="bg-purple-50 border border-purple-200 rounded-lg p-4 text-left hover:bg-purple-100 transition-colors">
|
||||
<div className="flex items-center">
|
||||
<CurrencyDollarIcon className="h-8 w-8 text-purple-600" />
|
||||
<div className="ml-3">
|
||||
<div className="text-sm font-medium text-purple-900">财务报表</div>
|
||||
<div className="text-xs text-purple-700">查看收入统计</div>
|
||||
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/calls')}
|
||||
className="w-full flex items-center justify-between p-3 text-left border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<PhoneIcon className="h-5 w-5 text-orange-600 mr-3" />
|
||||
<span className="text-sm font-medium text-gray-900">通话管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/invoices')}
|
||||
className="w-full flex items-center justify-between p-3 text-left border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<CurrencyDollarIcon className="h-5 w-5 text-emerald-600 mr-3" />
|
||||
<span className="text-sm font-medium text-gray-900">发票管理</span>
|
||||
</div>
|
||||
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/documents')}
|
||||
className="w-full flex items-center justify-between p-3 text-left border border-gray-200 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<DocumentDuplicateIcon className="h-5 w-5 text-indigo-600 mr-3" />
|
||||
<span className="text-sm font-medium text-gray-900">文档管理</span>
|
||||
</div>
|
||||
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user