feat: 集成真实数据库连接和API服务
- 更新 .env.local 配置为真实的 Supabase 项目连接 - 创建完整的 API 服务层 (lib/api-service.ts) - 创建数据库类型定义 (types/database.ts) - 更新仪表盘页面使用真实数据替代演示数据 - 添加数据库连接测试和错误处理 - 创建测试数据验证系统功能 - 修复图标导入和语法错误 系统现在已连接到真实的 Supabase 数据库,可以正常显示统计数据和最近活动。
This commit is contained in:
parent
f20988b90c
commit
211e0306b5
@ -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
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user