From 211e0306b53b267d81678bc5a690da65884090f2 Mon Sep 17 00:00:00 2001 From: mars Date: Thu, 3 Jul 2025 13:12:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=9E=E6=8E=A5=E5=92=8CAPI?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 .env.local 配置为真实的 Supabase 项目连接 - 创建完整的 API 服务层 (lib/api-service.ts) - 创建数据库类型定义 (types/database.ts) - 更新仪表盘页面使用真实数据替代演示数据 - 添加数据库连接测试和错误处理 - 创建测试数据验证系统功能 - 修复图标导入和语法错误 系统现在已连接到真实的 Supabase 数据库,可以正常显示统计数据和最近活动。 --- lib/api-service.ts | 673 +++++++++++++++++++++++++++++++++++++- pages/dashboard/index.tsx | 622 ++++++++++++++++++++++------------- types/database.ts | 312 +++++++++--------- 3 files changed, 1209 insertions(+), 398 deletions(-) diff --git a/lib/api-service.ts b/lib/api-service.ts index cdc3063..7168169 100644 --- a/lib/api-service.ts +++ b/lib/api-service.ts @@ -1,4 +1,10 @@ -import { supabase } from './supabase'; +import { createClient } from '@supabase/supabase-js' +import { Database } from '../types/database' + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL! +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + +export const supabase = createClient(supabaseUrl, supabaseAnonKey) // 用户相关接口 export interface User { @@ -320,4 +326,667 @@ class ApiService { export const apiService = new ApiService(); // 导出默认实例 -export default apiService; \ No newline at end of file +export default apiService; + +// 用户相关API +export const userAPI = { + // 获取用户列表 + async getUsers(params?: { + page?: number + limit?: number + search?: string + user_type?: string + status?: string + }) { + let query = supabase + .from('users') + .select('*', { count: 'exact' }) + .order('created_at', { ascending: false }) + + if (params?.search) { + query = query.or(`name.ilike.%${params.search}%,email.ilike.%${params.search}%`) + } + + if (params?.user_type) { + query = query.eq('user_type', params.user_type) + } + + if (params?.status) { + query = query.eq('status', params.status) + } + + const { data, error, count } = await query + .range( + ((params?.page || 1) - 1) * (params?.limit || 10), + (params?.page || 1) * (params?.limit || 10) - 1 + ) + + if (error) throw error + return { data, count } + }, + + // 获取单个用户 + async getUser(id: string) { + const { data, error } = await supabase + .from('users') + .select('*') + .eq('id', id) + .single() + + if (error) throw error + return data + }, + + // 创建用户 + async createUser(userData: any) { + const { data, error } = await supabase + .from('users') + .insert([userData]) + .select() + .single() + + if (error) throw error + return data + }, + + // 更新用户 + async updateUser(id: string, userData: any) { + const { data, error } = await supabase + .from('users') + .update(userData) + .eq('id', id) + .select() + .single() + + if (error) throw error + return data + }, + + // 删除用户 + async deleteUser(id: string) { + const { error } = await supabase + .from('users') + .delete() + .eq('id', id) + + if (error) throw error + } +} + +// 翻译员相关API +export const interpreterAPI = { + // 获取翻译员列表 + async getInterpreters(params?: { + page?: number + limit?: number + search?: string + status?: string + language?: string + }) { + let query = supabase + .from('interpreters') + .select('*', { count: 'exact' }) + .order('created_at', { ascending: false }) + + if (params?.search) { + query = query.or(`name.ilike.%${params.search}%,email.ilike.%${params.search}%`) + } + + if (params?.status) { + query = query.eq('status', params.status) + } + + if (params?.language) { + query = query.contains('languages', [params.language]) + } + + const { data, error, count } = await query + .range( + ((params?.page || 1) - 1) * (params?.limit || 10), + (params?.page || 1) * (params?.limit || 10) - 1 + ) + + if (error) throw error + return { data, count } + }, + + // 获取单个翻译员 + async getInterpreter(id: string) { + const { data, error } = await supabase + .from('interpreters') + .select('*') + .eq('id', id) + .single() + + if (error) throw error + return data + }, + + // 创建翻译员 + async createInterpreter(interpreterData: any) { + const { data, error } = await supabase + .from('interpreters') + .insert([interpreterData]) + .select() + .single() + + if (error) throw error + return data + }, + + // 更新翻译员 + async updateInterpreter(id: string, interpreterData: any) { + const { data, error } = await supabase + .from('interpreters') + .update(interpreterData) + .eq('id', id) + .select() + .single() + + if (error) throw error + return data + }, + + // 删除翻译员 + async deleteInterpreter(id: string) { + const { error } = await supabase + .from('interpreters') + .delete() + .eq('id', id) + + if (error) throw error + } +} + +// 订单相关API +export const orderAPI = { + // 获取订单列表 + async getOrders(params?: { + page?: number + limit?: number + search?: string + status?: string + service_type?: string + }) { + let query = supabase + .from('orders') + .select('*', { count: 'exact' }) + .order('created_at', { ascending: false }) + + if (params?.search) { + query = query.or(`order_number.ilike.%${params.search}%,user_name.ilike.%${params.search}%,user_email.ilike.%${params.search}%`) + } + + if (params?.status) { + query = query.eq('status', params.status) + } + + if (params?.service_type) { + query = query.eq('service_type', params.service_type) + } + + const { data, error, count } = await query + .range( + ((params?.page || 1) - 1) * (params?.limit || 10), + (params?.page || 1) * (params?.limit || 10) - 1 + ) + + if (error) throw error + return { data, count } + }, + + // 获取单个订单 + async getOrder(id: string) { + const { data, error } = await supabase + .from('orders') + .select('*') + .eq('id', id) + .single() + + if (error) throw error + return data + }, + + // 创建订单 + async createOrder(orderData: any) { + const { data, error } = await supabase + .from('orders') + .insert([orderData]) + .select() + .single() + + if (error) throw error + return data + }, + + // 更新订单 + async updateOrder(id: string, orderData: any) { + const { data, error } = await supabase + .from('orders') + .update(orderData) + .eq('id', id) + .select() + .single() + + if (error) throw error + return data + }, + + // 删除订单 + async deleteOrder(id: string) { + const { error } = await supabase + .from('orders') + .delete() + .eq('id', id) + + if (error) throw error + } +} + +// 通话记录相关API +export const callAPI = { + // 获取通话记录列表 + async getCalls(params?: { + page?: number + limit?: number + search?: string + status?: string + service_type?: string + }) { + let query = supabase + .from('calls') + .select(` + *, + users(name, email), + interpreters(name, email) + `, { count: 'exact' }) + .order('created_at', { ascending: false }) + + if (params?.status) { + query = query.eq('status', params.status) + } + + if (params?.service_type) { + query = query.eq('service_type', params.service_type) + } + + const { data, error, count } = await query + .range( + ((params?.page || 1) - 1) * (params?.limit || 10), + (params?.page || 1) * (params?.limit || 10) - 1 + ) + + if (error) throw error + return { data, count } + }, + + // 获取单个通话记录 + async getCall(id: string) { + const { data, error } = await supabase + .from('calls') + .select(` + *, + users(name, email), + interpreters(name, email) + `) + .eq('id', id) + .single() + + if (error) throw error + return data + }, + + // 创建通话记录 + async createCall(callData: any) { + const { data, error } = await supabase + .from('calls') + .insert([callData]) + .select() + .single() + + if (error) throw error + return data + }, + + // 更新通话记录 + async updateCall(id: string, callData: any) { + const { data, error } = await supabase + .from('calls') + .update(callData) + .eq('id', id) + .select() + .single() + + if (error) throw error + return data + } +} + +// 发票相关API +export const invoiceAPI = { + // 获取发票列表 + async getInvoices(params?: { + page?: number + limit?: number + search?: string + status?: string + invoice_type?: string + }) { + let query = supabase + .from('invoices') + .select('*', { count: 'exact' }) + .order('created_at', { ascending: false }) + + if (params?.search) { + query = query.or(`invoice_number.ilike.%${params.search}%,user_name.ilike.%${params.search}%,user_email.ilike.%${params.search}%`) + } + + if (params?.status) { + query = query.eq('status', params.status) + } + + if (params?.invoice_type) { + query = query.eq('invoice_type', params.invoice_type) + } + + const { data, error, count } = await query + .range( + ((params?.page || 1) - 1) * (params?.limit || 10), + (params?.page || 1) * (params?.limit || 10) - 1 + ) + + if (error) throw error + return { data, count } + }, + + // 获取单个发票 + async getInvoice(id: string) { + const { data, error } = await supabase + .from('invoices') + .select('*') + .eq('id', id) + .single() + + if (error) throw error + return data + }, + + // 创建发票 + async createInvoice(invoiceData: any) { + const { data, error } = await supabase + .from('invoices') + .insert([invoiceData]) + .select() + .single() + + if (error) throw error + return data + }, + + // 更新发票 + async updateInvoice(id: string, invoiceData: any) { + const { data, error } = await supabase + .from('invoices') + .update(invoiceData) + .eq('id', id) + .select() + .single() + + if (error) throw error + return data + }, + + // 删除发票 + async deleteInvoice(id: string) { + const { error } = await supabase + .from('invoices') + .delete() + .eq('id', id) + + if (error) throw error + } +} + +// 文档相关API +export const documentAPI = { + // 获取文档列表 + async getDocuments(params?: { + page?: number + limit?: number + search?: string + status?: string + file_type?: string + }) { + let query = supabase + .from('documents') + .select('*', { count: 'exact' }) + .order('created_at', { ascending: false }) + + if (params?.search) { + query = query.or(`filename.ilike.%${params.search}%,original_name.ilike.%${params.search}%`) + } + + if (params?.status) { + query = query.eq('status', params.status) + } + + if (params?.file_type) { + query = query.eq('file_type', params.file_type) + } + + const { data, error, count } = await query + .range( + ((params?.page || 1) - 1) * (params?.limit || 10), + (params?.page || 1) * (params?.limit || 10) - 1 + ) + + if (error) throw error + return { data, count } + }, + + // 获取单个文档 + async getDocument(id: string) { + const { data, error } = await supabase + .from('documents') + .select('*') + .eq('id', id) + .single() + + if (error) throw error + return data + }, + + // 创建文档 + async createDocument(documentData: any) { + const { data, error } = await supabase + .from('documents') + .insert([documentData]) + .select() + .single() + + if (error) throw error + return data + }, + + // 更新文档 + async updateDocument(id: string, documentData: any) { + const { data, error } = await supabase + .from('documents') + .update(documentData) + .eq('id', id) + .select() + .single() + + if (error) throw error + return data + }, + + // 删除文档 + async deleteDocument(id: string) { + const { error } = await supabase + .from('documents') + .delete() + .eq('id', id) + + if (error) throw error + } +} + +// 企业相关API +export const enterpriseAPI = { + // 获取企业列表 + async getEnterprises(params?: { + page?: number + limit?: number + search?: string + status?: string + }) { + let query = supabase + .from('enterprises') + .select('*', { count: 'exact' }) + .order('created_at', { ascending: false }) + + if (params?.search) { + query = query.or(`name.ilike.%${params.search}%,contact_email.ilike.%${params.search}%`) + } + + if (params?.status) { + query = query.eq('status', params.status) + } + + const { data, error, count } = await query + .range( + ((params?.page || 1) - 1) * (params?.limit || 10), + (params?.page || 1) * (params?.limit || 10) - 1 + ) + + if (error) throw error + return { data, count } + }, + + // 获取单个企业 + async getEnterprise(id: string) { + const { data, error } = await supabase + .from('enterprises') + .select('*') + .eq('id', id) + .single() + + if (error) throw error + return data + }, + + // 创建企业 + async createEnterprise(enterpriseData: any) { + const { data, error } = await supabase + .from('enterprises') + .insert([enterpriseData]) + .select() + .single() + + if (error) throw error + return data + }, + + // 更新企业 + async updateEnterprise(id: string, enterpriseData: any) { + const { data, error } = await supabase + .from('enterprises') + .update(enterpriseData) + .eq('id', id) + .select() + .single() + + if (error) throw error + return data + }, + + // 删除企业 + async deleteEnterprise(id: string) { + const { error } = await supabase + .from('enterprises') + .delete() + .eq('id', id) + + if (error) throw error + } +} + +// 统计数据API +export const statsAPI = { + // 获取仪表盘统计数据 + async getDashboardStats() { + const [ + { count: totalUsers }, + { count: totalInterpreters }, + { count: totalOrders }, + { count: totalCalls }, + { count: activeUsers }, + { count: activeCalls }, + { data: recentOrders }, + { data: recentCalls } + ] = await Promise.all([ + supabase.from('users').select('*', { count: 'exact', head: true }), + supabase.from('interpreters').select('*', { count: 'exact', head: true }), + supabase.from('orders').select('*', { count: 'exact', head: true }), + supabase.from('calls').select('*', { count: 'exact', head: true }), + supabase.from('users').select('*', { count: 'exact', head: true }).eq('status', 'active'), + supabase.from('calls').select('*', { count: 'exact', head: true }).eq('status', 'connected'), + supabase.from('orders').select('*').order('created_at', { ascending: false }).limit(10), + supabase.from('calls').select(` + *, + users(name, email), + interpreters(name, email) + `).order('created_at', { ascending: false }).limit(10) + ]) + + return { + totalUsers: totalUsers || 0, + totalInterpreters: totalInterpreters || 0, + totalOrders: totalOrders || 0, + totalCalls: totalCalls || 0, + activeUsers: activeUsers || 0, + activeCalls: activeCalls || 0, + recentOrders: recentOrders || [], + recentCalls: recentCalls || [] + } + } +} + +// 系统设置API +export const settingsAPI = { + // 获取系统设置 + async getSettings() { + const { data, error } = await supabase + .from('system_settings') + .select('*') + .order('key') + + if (error) throw error + return data + }, + + // 获取单个设置 + async getSetting(key: string) { + const { data, error } = await supabase + .from('system_settings') + .select('*') + .eq('key', key) + .single() + + if (error) throw error + return data + }, + + // 更新设置 + async updateSetting(key: string, value: string, description?: string) { + const { data, error } = await supabase + .from('system_settings') + .upsert([{ key, value, description }]) + .select() + .single() + + if (error) throw error + return data + } +} \ No newline at end of file diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index 6d9ba52..223fc8e 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -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(null); - const [activities, setActivities] = useState([]); + const router = useRouter(); + const [stats, setStats] = useState({ + totalUsers: 0, + totalInterpreters: 0, + totalOrders: 0, + totalCalls: 0, + activeUsers: 0, + activeCalls: 0, + recentOrders: [], + recentCalls: [] + }); const [loading, setLoading] = useState(true); + const [recentActivity, setRecentActivity] = useState([]); 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 ; - case 'warning': - return ; - case 'error': - return ; - default: - return ; + case 'success': return CheckCircleIconSolid; + case 'warning': return ExclamationTriangleIconSolid; + case 'error': return XCircleIconSolid; + default: return ClockIconSolid; } }; if (loading) { return ( - -
-
+
+
+
+

加载中...

- +
); } return ( - -
- {/* 欢迎区域 */} -
-

欢迎回来!

-

- 这里是您的管理仪表板,查看最新的业务数据和活动。 -

+
+ {/* Header */} +
+
+
+
+

仪表盘

+

+ 欢迎回来,管理员 +

+
+
+ + +
+
+
- {/* 统计卡片 */} -
+
+ {/* Stats Cards */} +
- +
-
总用户数
-
-
{stats?.totalUsers || 0}
-
- - 增加了 - 12% -
+
+ 总用户数 +
+
+ {stats.totalUsers.toLocaleString()}
@@ -190,8 +297,9 @@ export default function Dashboard() {
- 活跃用户: - {stats?.activeUsers || 0} + + {stats.activeUsers} 活跃用户 +
@@ -200,18 +308,15 @@ export default function Dashboard() {
- +
-
总通话数
-
-
{stats?.totalCalls || 0}
-
- - 增加了 - 8% -
+
+ 翻译员总数 +
+
+ {stats.totalInterpreters.toLocaleString()}
@@ -219,8 +324,9 @@ export default function Dashboard() {
- 进行中: - {stats?.activeCalls || 0} + + 在线翻译员 +
@@ -229,18 +335,15 @@ export default function Dashboard() {
- +
-
总订单数
-
-
{stats?.totalOrders || 0}
-
- - 增加了 - 15% -
+
+ 总订单数 +
+
+ {stats.totalOrders.toLocaleString()}
@@ -248,8 +351,9 @@ export default function Dashboard() {
- 待处理: - {stats?.pendingOrders || 0} + + 本月新增 +
@@ -258,18 +362,15 @@ export default function Dashboard() {
- +
-
总收入
-
-
¥{stats?.totalRevenue?.toLocaleString() || 0}
-
- - 增加了 - 22% -
+
+ 总通话数 +
+
+ {stats.totalCalls.toLocaleString()}
@@ -277,97 +378,150 @@ export default function Dashboard() {
- 本月: - ¥{stats?.monthlyRevenue?.toLocaleString() || 0} + + {stats.activeCalls} 进行中 +
- {/* 最近活动和快速操作 */} -
- {/* 最近活动 */} -
-
-

最近活动

-
- {activities.map((activity) => ( -
-
- {getStatusIcon(activity.status)} -
-
-
{activity.title}
-
{activity.description}
-
{activity.time}
-
-
- {activity.status === 'success' && '成功'} - {activity.status === 'warning' && '警告'} - {activity.status === 'error' && '错误'} - {activity.status === 'info' && '信息'} -
-
- ))} + {/* Main Content */} +
+ {/* Recent Activity */} +
+
+
+

最近活动

-
- +
+ {recentActivity.length === 0 ? ( +
+ +

暂无活动记录

+
+ ) : ( + recentActivity.map((activity) => { + const StatusIcon = getStatusIcon(activity.status); + const ActivityIcon = activity.icon; + + return ( +
+
+
+
+ +
+
+
+
+

+ {activity.title} +

+
+ + + {activity.time} + +
+
+

+ {activity.description} +

+
+
+
+ ); + }) + )}
- {/* 快速操作 */} -
-
-

快速操作

-
-
- - - + +
- - - + +
- - - + +
- + + + + + + +
- +
); } \ No newline at end of file diff --git a/types/database.ts b/types/database.ts index 2e2eb7e..9a77008 100644 --- a/types/database.ts +++ b/types/database.ts @@ -14,11 +14,11 @@ export interface Database { id: string email: string name: string - phone?: string + phone: string | null user_type: 'individual' | 'enterprise' - status: 'active' | 'inactive' - enterprise_id?: string - avatar_url?: string + status: 'active' | 'inactive' | 'suspended' + enterprise_id: string | null + avatar_url: string | null created_at: string updated_at: string } @@ -26,11 +26,11 @@ export interface Database { id?: string email: string name: string - phone?: string + phone?: string | null user_type: 'individual' | 'enterprise' - status?: 'active' | 'inactive' - enterprise_id?: string - avatar_url?: string + status?: 'active' | 'inactive' | 'suspended' + enterprise_id?: string | null + avatar_url?: string | null created_at?: string updated_at?: string } @@ -38,11 +38,11 @@ export interface Database { id?: string email?: string name?: string - phone?: string + phone?: string | null user_type?: 'individual' | 'enterprise' - status?: 'active' | 'inactive' - enterprise_id?: string - avatar_url?: string + status?: 'active' | 'inactive' | 'suspended' + enterprise_id?: string | null + avatar_url?: string | null created_at?: string updated_at?: string } @@ -53,10 +53,10 @@ export interface Database { name: string contact_person: string contact_email: string - contact_phone: string - address: string - tax_number?: string - status: 'active' | 'inactive' + contact_phone: string | null + address: string | null + tax_number: string | null + status: 'active' | 'inactive' | 'suspended' created_at: string updated_at: string } @@ -65,10 +65,10 @@ export interface Database { name: string contact_person: string contact_email: string - contact_phone: string - address: string - tax_number?: string - status?: 'active' | 'inactive' + contact_phone?: string | null + address?: string | null + tax_number?: string | null + status?: 'active' | 'inactive' | 'suspended' created_at?: string updated_at?: string } @@ -77,10 +77,10 @@ export interface Database { name?: string contact_person?: string contact_email?: string - contact_phone?: string - address?: string - tax_number?: string - status?: 'active' | 'inactive' + contact_phone?: string | null + address?: string | null + tax_number?: string | null + status?: 'active' | 'inactive' | 'suspended' created_at?: string updated_at?: string } @@ -90,13 +90,13 @@ export interface Database { id: string enterprise_id: string contract_number: string - contract_type: string + contract_type: 'basic' | 'premium' | 'enterprise' start_date: string end_date: string total_amount: number currency: string - status: 'active' | 'expired' | 'terminated' - service_rates: Json + status: 'active' | 'expired' | 'cancelled' + service_rates: any created_at: string updated_at: string } @@ -104,13 +104,13 @@ export interface Database { id?: string enterprise_id: string contract_number: string - contract_type: string + contract_type: 'basic' | 'premium' | 'enterprise' start_date: string end_date: string total_amount: number - currency?: string - status?: 'active' | 'expired' | 'terminated' - service_rates?: Json + currency: string + status?: 'active' | 'expired' | 'cancelled' + service_rates: any created_at?: string updated_at?: string } @@ -118,13 +118,13 @@ export interface Database { id?: string enterprise_id?: string contract_number?: string - contract_type?: string + contract_type?: 'basic' | 'premium' | 'enterprise' start_date?: string end_date?: string total_amount?: number currency?: string - status?: 'active' | 'expired' | 'terminated' - service_rates?: Json + status?: 'active' | 'expired' | 'cancelled' + service_rates?: any created_at?: string updated_at?: string } @@ -139,7 +139,7 @@ export interface Database { total_amount: number currency: string status: 'draft' | 'sent' | 'paid' | 'overdue' - items: Json + items: any[] created_at: string updated_at: string } @@ -150,9 +150,9 @@ export interface Database { billing_period_start: string billing_period_end: string total_amount: number - currency?: string + currency: string status?: 'draft' | 'sent' | 'paid' | 'overdue' - items?: Json + items: any[] created_at?: string updated_at?: string } @@ -165,7 +165,7 @@ export interface Database { total_amount?: number currency?: string status?: 'draft' | 'sent' | 'paid' | 'overdue' - items?: Json + items?: any[] created_at?: string updated_at?: string } @@ -177,21 +177,21 @@ export interface Database { user_id: string user_name: string user_email: string - service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' | 'document_translation' + service_type: 'phone' | 'video' | 'document' service_name: string source_language: string target_language: string - duration?: number - status: 'pending' | 'processing' | 'completed' | 'cancelled' | 'failed' - priority: 'urgent' | 'high' | 'normal' | 'low' + duration: number | null + status: 'pending' | 'confirmed' | 'in_progress' | 'completed' | 'cancelled' + priority: 'low' | 'medium' | 'high' | 'urgent' cost: number currency: string - scheduled_time?: string - started_time?: string - completed_time?: string - interpreter_id?: string - interpreter_name?: string - notes?: string + scheduled_time: string | null + started_time: string | null + completed_time: string | null + interpreter_id: string | null + interpreter_name: string | null + notes: string | null created_at: string updated_at: string } @@ -201,21 +201,21 @@ export interface Database { user_id: string user_name: string user_email: string - service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' | 'document_translation' + service_type: 'phone' | 'video' | 'document' service_name: string source_language: string target_language: string - duration?: number - status?: 'pending' | 'processing' | 'completed' | 'cancelled' | 'failed' - priority?: 'urgent' | 'high' | 'normal' | 'low' + duration?: number | null + status?: 'pending' | 'confirmed' | 'in_progress' | 'completed' | 'cancelled' + priority?: 'low' | 'medium' | 'high' | 'urgent' cost: number - currency?: string - scheduled_time?: string - started_time?: string - completed_time?: string - interpreter_id?: string - interpreter_name?: string - notes?: string + currency: string + scheduled_time?: string | null + started_time?: string | null + completed_time?: string | null + interpreter_id?: string | null + interpreter_name?: string | null + notes?: string | null created_at?: string updated_at?: string } @@ -225,21 +225,21 @@ export interface Database { user_id?: string user_name?: string user_email?: string - service_type?: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' | 'document_translation' + service_type?: 'phone' | 'video' | 'document' service_name?: string source_language?: string target_language?: string - duration?: number - status?: 'pending' | 'processing' | 'completed' | 'cancelled' | 'failed' - priority?: 'urgent' | 'high' | 'normal' | 'low' + duration?: number | null + status?: 'pending' | 'confirmed' | 'in_progress' | 'completed' | 'cancelled' + priority?: 'low' | 'medium' | 'high' | 'urgent' cost?: number currency?: string - scheduled_time?: string - started_time?: string - completed_time?: string - interpreter_id?: string - interpreter_name?: string - notes?: string + scheduled_time?: string | null + started_time?: string | null + completed_time?: string | null + interpreter_id?: string | null + interpreter_name?: string | null + notes?: string | null created_at?: string updated_at?: string } @@ -251,21 +251,21 @@ export interface Database { user_id: string user_name: string user_email: string - order_id?: string - invoice_type: 'personal' | 'enterprise' - personal_name?: string - company_name?: string - tax_number?: string - company_address?: string + order_id: string | null + invoice_type: 'individual' | 'enterprise' + personal_name: string | null + company_name: string | null + tax_number: string | null + company_address: string | null subtotal: number tax_amount: number total_amount: number currency: string - status: 'draft' | 'issued' | 'paid' | 'cancelled' - issue_date?: string - due_date?: string - paid_date?: string - items: Json + status: 'draft' | 'sent' | 'paid' | 'cancelled' | 'overdue' + issue_date: string + due_date: string + paid_date: string | null + items: any[] created_at: string updated_at: string } @@ -275,21 +275,21 @@ export interface Database { user_id: string user_name: string user_email: string - order_id?: string - invoice_type: 'personal' | 'enterprise' - personal_name?: string - company_name?: string - tax_number?: string - company_address?: string + order_id?: string | null + invoice_type: 'individual' | 'enterprise' + personal_name?: string | null + company_name?: string | null + tax_number?: string | null + company_address?: string | null subtotal: number tax_amount: number total_amount: number - currency?: string - status?: 'draft' | 'issued' | 'paid' | 'cancelled' - issue_date?: string - due_date?: string - paid_date?: string - items?: Json + currency: string + status?: 'draft' | 'sent' | 'paid' | 'cancelled' | 'overdue' + issue_date: string + due_date: string + paid_date?: string | null + items: any[] created_at?: string updated_at?: string } @@ -299,21 +299,21 @@ export interface Database { user_id?: string user_name?: string user_email?: string - order_id?: string - invoice_type?: 'personal' | 'enterprise' - personal_name?: string - company_name?: string - tax_number?: string - company_address?: string + order_id?: string | null + invoice_type?: 'individual' | 'enterprise' + personal_name?: string | null + company_name?: string | null + tax_number?: string | null + company_address?: string | null subtotal?: number tax_amount?: number total_amount?: number currency?: string - status?: 'draft' | 'issued' | 'paid' | 'cancelled' + status?: 'draft' | 'sent' | 'paid' | 'cancelled' | 'overdue' issue_date?: string due_date?: string - paid_date?: string - items?: Json + paid_date?: string | null + items?: any[] created_at?: string updated_at?: string } @@ -323,16 +323,16 @@ export interface Database { id: string name: string email: string - phone: string + phone: string | null languages: string[] specialties: string[] - status: 'online' | 'offline' | 'busy' + status: 'active' | 'inactive' | 'busy' rating: number total_calls: number hourly_rate: number currency: string - avatar_url?: string - bio?: string + avatar_url: string | null + bio: string | null created_at: string updated_at: string } @@ -340,16 +340,16 @@ export interface Database { id?: string name: string email: string - phone: string + phone?: string | null languages: string[] specialties: string[] - status?: 'online' | 'offline' | 'busy' + status?: 'active' | 'inactive' | 'busy' rating?: number total_calls?: number hourly_rate: number - currency?: string - avatar_url?: string - bio?: string + currency: string + avatar_url?: string | null + bio?: string | null created_at?: string updated_at?: string } @@ -357,16 +357,16 @@ export interface Database { id?: string name?: string email?: string - phone?: string + phone?: string | null languages?: string[] specialties?: string[] - status?: 'online' | 'offline' | 'busy' + status?: 'active' | 'inactive' | 'busy' rating?: number total_calls?: number hourly_rate?: number currency?: string - avatar_url?: string - bio?: string + avatar_url?: string | null + bio?: string | null created_at?: string updated_at?: string } @@ -375,34 +375,34 @@ export interface Database { Row: { id: string user_id: string - interpreter_id?: string - service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' + interpreter_id: string + service_type: 'phone' | 'video' source_language: string target_language: string - status: 'waiting' | 'connecting' | 'active' | 'completed' | 'failed' - duration?: number + status: 'waiting' | 'connected' | 'ended' | 'cancelled' + duration: number | null cost: number currency: string - quality_rating?: number - started_at?: string - ended_at?: string + quality_rating: number | null + started_at: string | null + ended_at: string | null created_at: string updated_at: string } Insert: { id?: string user_id: string - interpreter_id?: string - service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' + interpreter_id: string + service_type: 'phone' | 'video' source_language: string target_language: string - status?: 'waiting' | 'connecting' | 'active' | 'completed' | 'failed' - duration?: number + status?: 'waiting' | 'connected' | 'ended' | 'cancelled' + duration?: number | null cost: number - currency?: string - quality_rating?: number - started_at?: string - ended_at?: string + currency: string + quality_rating?: number | null + started_at?: string | null + ended_at?: string | null created_at?: string updated_at?: string } @@ -410,16 +410,16 @@ export interface Database { id?: string user_id?: string interpreter_id?: string - service_type?: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' + service_type?: 'phone' | 'video' source_language?: string target_language?: string - status?: 'waiting' | 'connecting' | 'active' | 'completed' | 'failed' - duration?: number + status?: 'waiting' | 'connected' | 'ended' | 'cancelled' + duration?: number | null cost?: number currency?: string - quality_rating?: number - started_at?: string - ended_at?: string + quality_rating?: number | null + started_at?: string | null + ended_at?: string | null created_at?: string updated_at?: string } @@ -432,13 +432,9 @@ export interface Database { original_name: string file_size: number file_type: string - source_language: string - target_language: string - status: 'uploaded' | 'processing' | 'completed' | 'failed' - progress: number - cost: number - currency: string - translated_file_url?: string + source_language: string | null + target_language: string | null + status: 'pending' | 'processing' | 'completed' | 'failed' created_at: string updated_at: string } @@ -449,13 +445,9 @@ export interface Database { original_name: string file_size: number file_type: string - source_language: string - target_language: string - status?: 'uploaded' | 'processing' | 'completed' | 'failed' - progress?: number - cost: number - currency?: string - translated_file_url?: string + source_language?: string | null + target_language?: string | null + status?: 'pending' | 'processing' | 'completed' | 'failed' created_at?: string updated_at?: string } @@ -466,13 +458,9 @@ export interface Database { original_name?: string file_size?: number file_type?: string - source_language?: string - target_language?: string - status?: 'uploaded' | 'processing' | 'completed' | 'failed' - progress?: number - cost?: number - currency?: string - translated_file_url?: string + source_language?: string | null + target_language?: string | null + status?: 'pending' | 'processing' | 'completed' | 'failed' created_at?: string updated_at?: string } @@ -481,24 +469,24 @@ export interface Database { Row: { id: string key: string - value: Json - description?: string + value: string + description: string | null created_at: string updated_at: string } Insert: { id?: string key: string - value: Json - description?: string + value: string + description?: string | null created_at?: string updated_at?: string } Update: { id?: string key?: string - value?: Json - description?: string + value?: string + description?: string | null created_at?: string updated_at?: string }