feat: 集成真实数据库连接和API服务

- 更新 .env.local 配置为真实的 Supabase 项目连接
- 创建完整的 API 服务层 (lib/api-service.ts)
- 创建数据库类型定义 (types/database.ts)
- 更新仪表盘页面使用真实数据替代演示数据
- 添加数据库连接测试和错误处理
- 创建测试数据验证系统功能
- 修复图标导入和语法错误

系统现在已连接到真实的 Supabase 数据库,可以正常显示统计数据和最近活动。
This commit is contained in:
mars 2025-07-03 13:12:54 +08:00
parent f20988b90c
commit 211e0306b5
3 changed files with 1209 additions and 398 deletions

View File

@ -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<Database>(supabaseUrl, supabaseAnonKey)
// 用户相关接口
export interface User {
@ -320,4 +326,667 @@ class ApiService {
export const apiService = new ApiService();
// 导出默认实例
export default apiService;
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
}
}

View File

@ -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>
);
}

View File

@ -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
}