import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Head from 'next/head'; import Link from 'next/link'; import { toast } from 'react-hot-toast'; import { PhoneIcon, VideoCameraIcon, UserGroupIcon, ClockIcon, CurrencyDollarIcon, ExclamationTriangleIcon, PlayIcon, StopIcon, UserPlusIcon, ArrowRightOnRectangleIcon } from '@heroicons/react/24/outline'; import { auth, db, TABLES, realtime, supabase } from '@/lib/supabase'; import { getDemoData } from '@/lib/demo-data'; import { Call, CallStats, Interpreter, User } from '@/types'; import { formatCurrency, formatTime, formatDuration, getCallStatusText, getCallModeText, getStatusColor } from '@/utils'; import Layout from '@/components/Layout'; interface DashboardProps { user?: User; } export default function Dashboard({ user }: DashboardProps) { const router = useRouter(); const [loading, setLoading] = useState(true); const [stats, setStats] = useState({ total_calls_today: 0, active_calls: 0, average_response_time: 0, online_interpreters: 0, total_revenue_today: 0, currency: 'CNY', }); const [activeCalls, setActiveCalls] = useState([]); const [onlineInterpreters, setOnlineInterpreters] = useState([]); const [isDemoMode, setIsDemoMode] = useState(false); // 获取仪表盘数据 const fetchDashboardData = async () => { try { setLoading(true); // 检查是否为演示模式 const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const isDemoMode = !supabaseUrl || supabaseUrl === 'https://demo.supabase.co' || supabaseUrl === ''; setIsDemoMode(isDemoMode); if (isDemoMode) { // 使用演示数据 const [statsData, callsData, interpretersData] = await Promise.all([ getDemoData.stats(), getDemoData.calls(), getDemoData.interpreters(), ]); // 转换演示数据格式以匹配类型定义 setStats({ total_calls_today: statsData.todayCalls, active_calls: statsData.activeCalls, average_response_time: statsData.avgResponseTime, online_interpreters: statsData.onlineInterpreters, total_revenue_today: statsData.todayRevenue, currency: 'CNY', }); // 转换通话数据格式 const formattedCalls = callsData .filter(call => call.status === 'active') .map(call => ({ id: call.id, caller_id: call.user_id, callee_id: call.interpreter_id, call_type: 'audio' as const, call_mode: 'human_interpreter' as const, status: call.status as 'active', start_time: call.start_time, end_time: call.end_time, duration: call.duration, cost: call.cost, currency: 'CNY' as const, created_at: call.created_at, updated_at: call.created_at, })); // 转换翻译员数据格式 const formattedInterpreters = interpretersData .filter(interpreter => interpreter.status !== 'offline') .map(interpreter => ({ id: interpreter.id, user_id: interpreter.id, name: interpreter.name, avatar_url: interpreter.avatar_url, languages: interpreter.languages, specializations: interpreter.specialties, hourly_rate: 100, currency: 'CNY' as const, rating: interpreter.rating, total_calls: 50, status: interpreter.status === 'busy' ? 'busy' as const : 'online' as const, is_certified: true, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), })); setActiveCalls(formattedCalls); setOnlineInterpreters(formattedInterpreters); } else { // 使用真实数据 const today = new Date(); today.setHours(0, 0, 0, 0); // 获取今日通话统计 const { data: todayCalls } = await supabase .from(TABLES.CALLS) .select('*') .gte('created_at', today.toISOString()); // 获取活跃通话 const { data: activeCallsData } = await supabase .from(TABLES.CALLS) .select(` *, user:users(full_name, email), interpreter:interpreters(name, rating) `) .eq('status', 'active'); // 获取在线翻译员 const { data: interpretersData } = await supabase .from(TABLES.INTERPRETERS) .select('*') .neq('status', 'offline'); // 计算统计数据 const totalRevenue = todayCalls && todayCalls.length > 0 ? todayCalls .filter(call => call.status === 'ended') .reduce((sum, call) => sum + call.cost, 0) : 0; const avgResponseTime = todayCalls && todayCalls.length > 0 ? todayCalls.reduce((sum, call) => { const startTime = new Date(call.start_time); const createdTime = new Date(call.created_at); return sum + (startTime.getTime() - createdTime.getTime()) / 1000; }, 0) / todayCalls.length : 0; setStats({ total_calls_today: todayCalls?.length || 0, active_calls: activeCallsData?.length || 0, average_response_time: Math.round(avgResponseTime), online_interpreters: interpretersData?.length || 0, total_revenue_today: totalRevenue, currency: 'CNY', }); setActiveCalls(activeCallsData || []); setOnlineInterpreters(interpretersData || []); } } catch (error) { console.error('获取仪表盘数据失败:', error); toast.error('获取数据失败,请稍后重试'); // 如果获取真实数据失败,切换到演示模式 setIsDemoMode(true); const [statsData, callsData, interpretersData] = await Promise.all([ getDemoData.stats(), getDemoData.calls(), getDemoData.interpreters(), ]); setStats({ total_calls_today: statsData.todayCalls, active_calls: statsData.activeCalls, average_response_time: statsData.avgResponseTime, online_interpreters: statsData.onlineInterpreters, total_revenue_today: statsData.todayRevenue, currency: 'CNY', }); // 设置空数组避免类型错误 setActiveCalls([]); setOnlineInterpreters([]); } finally { setLoading(false); } }; // 强制结束通话 const handleEndCall = async (callId: string) => { try { await db.update(TABLES.CALLS, callId, { status: 'ended', end_time: new Date().toISOString() }); toast.success('通话已结束'); fetchDashboardData(); } catch (error) { console.error('Error ending call:', error); toast.error('结束通话失败'); } }; // 分配翻译员 const handleAssignInterpreter = async (callId: string, interpreterId: string) => { try { await db.update(TABLES.CALLS, callId, { callee_id: interpreterId, call_mode: 'human_interpreter' }); toast.success('翻译员已分配'); fetchDashboardData(); } catch (error) { console.error('Error assigning interpreter:', error); toast.error('分配翻译员失败'); } }; useEffect(() => { // 在演示模式下不检查用户认证 const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const isDemoMode = !supabaseUrl || supabaseUrl === 'https://demo.supabase.co' || supabaseUrl === ''; if (!isDemoMode && !user) { router.push('/auth/login'); return; } fetchDashboardData(); // 设置实时数据更新 const callsChannel = realtime.subscribe( TABLES.CALLS, () => { fetchDashboardData(); } ); const interpretersChannel = realtime.subscribe( TABLES.INTERPRETERS, () => { fetchDashboardData(); } ); // 每30秒刷新一次数据 const interval = setInterval(fetchDashboardData, 30000); return () => { clearInterval(interval); realtime.unsubscribe(callsChannel); realtime.unsubscribe(interpretersChannel); }; }, [user, router]); if (loading) { return (
); } return ( 仪表盘 - 口译服务管理后台 {/* 主要内容区域 */}
{/* 统计卡片 */}
今日通话总量
{stats.total_calls_today}
当前活跃通话
{stats.active_calls}
在线翻译员
{stats.online_interpreters}
今日收入
{formatCurrency(stats.total_revenue_today, 'CNY')}
{/* 主要内容区域 */}
{/* 活跃通话列表 */}

实时通话列表

{activeCalls.length === 0 ? (

当前没有活跃通话

) : ( activeCalls.map((call) => (

{getCallModeText(call.call_mode)}

{formatTime(call.start_time)}

{getCallStatusText(call.status)}
)) )}
{/* 在线翻译员 */}

在线翻译员

{onlineInterpreters.length === 0 ? (

暂无翻译员在线

) : ( onlineInterpreters.slice(0, 5).map((interpreter) => (
{interpreter.name}

{interpreter.name}

评分: {interpreter.rating}/5

在线
)) )}
); }