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 {
|
export interface User {
|
||||||
@ -321,3 +327,666 @@ 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 { useState, useEffect } from 'react';
|
||||||
import DashboardLayout from '../../components/Layout/DashboardLayout';
|
import { useRouter } from 'next/router';
|
||||||
import { getDemoData } from '../../lib/demo-data';
|
|
||||||
import {
|
import {
|
||||||
UsersIcon,
|
UserGroupIcon,
|
||||||
PhoneIcon,
|
PhoneIcon,
|
||||||
DocumentTextIcon,
|
DocumentTextIcon,
|
||||||
CurrencyDollarIcon,
|
CurrencyDollarIcon,
|
||||||
CheckCircleIcon,
|
ChartBarIcon,
|
||||||
ClockIcon,
|
ClockIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
ExclamationTriangleIcon,
|
ExclamationTriangleIcon,
|
||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
ArrowDownIcon,
|
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';
|
} 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 {
|
interface DashboardStats {
|
||||||
totalUsers: number;
|
totalUsers: number;
|
||||||
activeUsers: number;
|
totalInterpreters: number;
|
||||||
totalCalls: number;
|
|
||||||
activeCalls: number;
|
|
||||||
totalOrders: number;
|
totalOrders: number;
|
||||||
pendingOrders: number;
|
totalCalls: number;
|
||||||
completedOrders: number;
|
activeUsers: number;
|
||||||
totalRevenue: number;
|
activeCalls: number;
|
||||||
monthlyRevenue: number;
|
recentOrders: any[];
|
||||||
activeInterpreters: number;
|
recentCalls: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RecentActivity {
|
interface RecentActivity {
|
||||||
id: string;
|
id: string;
|
||||||
type: 'call' | 'order' | 'user' | 'system';
|
type: 'order' | 'call' | 'user' | 'interpreter';
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
time: string;
|
time: string;
|
||||||
status: 'success' | 'warning' | 'error' | 'info';
|
status: 'success' | 'warning' | 'error' | 'info';
|
||||||
|
icon: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
const router = useRouter();
|
||||||
const [activities, setActivities] = useState<RecentActivity[]>([]);
|
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 [loading, setLoading] = useState(true);
|
||||||
|
const [recentActivity, setRecentActivity] = useState<RecentActivity[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
loadDashboardData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const loadDashboardData = async () => {
|
const loadDashboardData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
const dashboardStats = await statsAPI.getDashboardStats();
|
||||||
|
setStats(dashboardStats);
|
||||||
|
|
||||||
// 模拟加载延迟
|
// 生成最近活动记录
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
const activities: RecentActivity[] = [];
|
||||||
|
|
||||||
// 使用演示数据
|
// 添加最近订单活动
|
||||||
const mockStats: DashboardStats = {
|
dashboardStats.recentOrders.forEach((order: any) => {
|
||||||
totalUsers: 1248,
|
activities.push({
|
||||||
activeUsers: 856,
|
id: order.id,
|
||||||
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',
|
type: 'order',
|
||||||
title: '订单完成',
|
title: `订单 ${order.order_number}`,
|
||||||
description: '订单ORD-2024-001已完成,费用¥180',
|
description: `${order.user_name} - ${order.service_name}`,
|
||||||
time: '5分钟前',
|
time: formatTime(order.created_at),
|
||||||
status: 'success'
|
status: getOrderStatus(order.status),
|
||||||
},
|
icon: getOrderIcon(order.service_type)
|
||||||
{
|
});
|
||||||
id: '3',
|
});
|
||||||
type: 'user',
|
|
||||||
title: '新用户注册',
|
// 添加最近通话活动
|
||||||
description: 'ABC公司注册了企业账户',
|
dashboardStats.recentCalls.forEach((call: any) => {
|
||||||
time: '10分钟前',
|
activities.push({
|
||||||
status: 'info'
|
id: call.id,
|
||||||
},
|
type: 'call',
|
||||||
{
|
title: `${call.service_type === 'phone' ? '电话' : '视频'}通话`,
|
||||||
id: '4',
|
description: `${call.users?.name || '用户'} - ${call.interpreters?.name || '翻译员'}`,
|
||||||
type: 'system',
|
time: formatTime(call.created_at),
|
||||||
title: '系统维护',
|
status: getCallStatus(call.status),
|
||||||
description: '系统将在今晚22:00-23:00进行维护',
|
icon: call.service_type === 'phone' ? PhoneIcon : VideoCameraIcon
|
||||||
time: '30分钟前',
|
});
|
||||||
status: 'warning'
|
});
|
||||||
},
|
|
||||||
{
|
// 按时间排序
|
||||||
id: '5',
|
activities.sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime());
|
||||||
type: 'call',
|
setRecentActivity(activities.slice(0, 10));
|
||||||
title: '通话异常',
|
|
||||||
description: '通话CALL-2024-003出现连接问题',
|
|
||||||
time: '1小时前',
|
|
||||||
status: 'error'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
setStats(mockStats);
|
|
||||||
setActivities(mockActivities);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load dashboard data:', error);
|
console.error('加载仪表盘数据失败:', error);
|
||||||
|
toast.error('加载仪表盘数据失败');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadDashboardData();
|
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) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'success':
|
case 'success': return 'text-green-600 bg-green-50';
|
||||||
return 'text-green-600 bg-green-100';
|
case 'warning': return 'text-yellow-600 bg-yellow-50';
|
||||||
case 'warning':
|
case 'error': return 'text-red-600 bg-red-50';
|
||||||
return 'text-yellow-600 bg-yellow-100';
|
default: return 'text-blue-600 bg-blue-50';
|
||||||
case 'error':
|
|
||||||
return 'text-red-600 bg-red-100';
|
|
||||||
default:
|
|
||||||
return 'text-blue-600 bg-blue-100';
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusIcon = (status: string) => {
|
const getStatusIcon = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'success':
|
case 'success': return CheckCircleIconSolid;
|
||||||
return <CheckCircleIcon className="h-5 w-5 text-green-500" />;
|
case 'warning': return ExclamationTriangleIconSolid;
|
||||||
case 'warning':
|
case 'error': return XCircleIconSolid;
|
||||||
return <ExclamationTriangleIcon className="h-5 w-5 text-yellow-500" />;
|
default: return ClockIconSolid;
|
||||||
case 'error':
|
|
||||||
return <ExclamationTriangleIcon className="h-5 w-5 text-red-500" />;
|
|
||||||
default:
|
|
||||||
return <ClockIcon className="h-5 w-5 text-blue-500" />;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<DashboardLayout title="仪表盘">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="text-center">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</DashboardLayout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout title="仪表盘">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<div className="space-y-6">
|
{/* Header */}
|
||||||
{/* 欢迎区域 */}
|
<div className="bg-white shadow">
|
||||||
<div className="bg-white shadow rounded-lg p-6">
|
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||||
<h1 className="text-2xl font-bold text-gray-900">欢迎回来!</h1>
|
<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 className="mt-1 text-sm text-gray-600">
|
||||||
这里是您的管理仪表板,查看最新的业务数据和活动。
|
欢迎回来,管理员
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
|
{/* 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="bg-white overflow-hidden shadow rounded-lg">
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0">
|
<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>
|
||||||
<div className="ml-5 w-0 flex-1">
|
<div className="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt className="text-sm font-medium text-gray-500 truncate">总用户数</dt>
|
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||||
<dd className="flex items-baseline">
|
总用户数
|
||||||
<div className="text-2xl font-semibold text-gray-900">{stats?.totalUsers || 0}</div>
|
</dt>
|
||||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
<dd className="text-lg font-medium text-gray-900">
|
||||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
{stats.totalUsers.toLocaleString()}
|
||||||
<span className="sr-only">增加了</span>
|
|
||||||
12%
|
|
||||||
</div>
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
@ -190,8 +297,9 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 px-5 py-3">
|
<div className="bg-gray-50 px-5 py-3">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<span className="font-medium text-gray-500">活跃用户: </span>
|
<span className="text-green-600 font-medium">
|
||||||
<span className="text-gray-900">{stats?.activeUsers || 0}</span>
|
{stats.activeUsers} 活跃用户
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -200,18 +308,15 @@ export default function Dashboard() {
|
|||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0">
|
<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>
|
||||||
<div className="ml-5 w-0 flex-1">
|
<div className="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt className="text-sm font-medium text-gray-500 truncate">总通话数</dt>
|
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||||
<dd className="flex items-baseline">
|
翻译员总数
|
||||||
<div className="text-2xl font-semibold text-gray-900">{stats?.totalCalls || 0}</div>
|
</dt>
|
||||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
<dd className="text-lg font-medium text-gray-900">
|
||||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
{stats.totalInterpreters.toLocaleString()}
|
||||||
<span className="sr-only">增加了</span>
|
|
||||||
8%
|
|
||||||
</div>
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
@ -219,8 +324,9 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 px-5 py-3">
|
<div className="bg-gray-50 px-5 py-3">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<span className="font-medium text-gray-500">进行中: </span>
|
<span className="text-green-600 font-medium">
|
||||||
<span className="text-gray-900">{stats?.activeCalls || 0}</span>
|
在线翻译员
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -229,18 +335,15 @@ export default function Dashboard() {
|
|||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0">
|
<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>
|
||||||
<div className="ml-5 w-0 flex-1">
|
<div className="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt className="text-sm font-medium text-gray-500 truncate">总订单数</dt>
|
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||||
<dd className="flex items-baseline">
|
总订单数
|
||||||
<div className="text-2xl font-semibold text-gray-900">{stats?.totalOrders || 0}</div>
|
</dt>
|
||||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
<dd className="text-lg font-medium text-gray-900">
|
||||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
{stats.totalOrders.toLocaleString()}
|
||||||
<span className="sr-only">增加了</span>
|
|
||||||
15%
|
|
||||||
</div>
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
@ -248,8 +351,9 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 px-5 py-3">
|
<div className="bg-gray-50 px-5 py-3">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<span className="font-medium text-gray-500">待处理: </span>
|
<span className="text-green-600 font-medium">
|
||||||
<span className="text-gray-900">{stats?.pendingOrders || 0}</span>
|
本月新增
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -258,18 +362,15 @@ export default function Dashboard() {
|
|||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0">
|
<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>
|
||||||
<div className="ml-5 w-0 flex-1">
|
<div className="ml-5 w-0 flex-1">
|
||||||
<dl>
|
<dl>
|
||||||
<dt className="text-sm font-medium text-gray-500 truncate">总收入</dt>
|
<dt className="text-sm font-medium text-gray-500 truncate">
|
||||||
<dd className="flex items-baseline">
|
总通话数
|
||||||
<div className="text-2xl font-semibold text-gray-900">¥{stats?.totalRevenue?.toLocaleString() || 0}</div>
|
</dt>
|
||||||
<div className="ml-2 flex items-baseline text-sm font-semibold text-green-600">
|
<dd className="text-lg font-medium text-gray-900">
|
||||||
<ArrowUpIcon className="self-center flex-shrink-0 h-4 w-4 text-green-500" />
|
{stats.totalCalls.toLocaleString()}
|
||||||
<span className="sr-only">增加了</span>
|
|
||||||
22%
|
|
||||||
</div>
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
@ -277,97 +378,150 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 px-5 py-3">
|
<div className="bg-gray-50 px-5 py-3">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<span className="font-medium text-gray-500">本月: </span>
|
<span className="text-orange-600 font-medium">
|
||||||
<span className="text-gray-900">¥{stats?.monthlyRevenue?.toLocaleString() || 0}</span>
|
{stats.activeCalls} 进行中
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 最近活动和快速操作 */}
|
{/* Main Content */}
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
<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="bg-white shadow rounded-lg">
|
||||||
<div className="px-4 py-5 sm:p-6">
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">最近活动</h3>
|
<h3 className="text-lg font-medium text-gray-900">最近活动</h3>
|
||||||
<div className="space-y-4">
|
</div>
|
||||||
{activities.map((activity) => (
|
<div className="divide-y divide-gray-200">
|
||||||
<div key={activity.id} className="flex items-start space-x-3">
|
{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="flex-shrink-0">
|
||||||
{getStatusIcon(activity.status)}
|
<div className={`p-2 rounded-full ${getStatusColor(activity.status)}`}>
|
||||||
</div>
|
<ActivityIcon className="h-5 w-5" />
|
||||||
<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>
|
||||||
</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>
|
||||||
<div className="mt-6">
|
</div>
|
||||||
<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">
|
<p className="text-sm text-gray-500">
|
||||||
<EyeIcon className="h-4 w-4 mr-2" />
|
{activity.description}
|
||||||
查看所有活动
|
</p>
|
||||||
</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 快速操作 */}
|
{/* Quick Actions */}
|
||||||
|
<div>
|
||||||
<div className="bg-white shadow rounded-lg">
|
<div className="bg-white shadow rounded-lg">
|
||||||
<div className="px-4 py-5 sm:p-6">
|
<div className="px-6 py-4 border-b border-gray-200">
|
||||||
<h3 className="text-lg leading-6 font-medium text-gray-900 mb-4">快速操作</h3>
|
<h3 className="text-lg font-medium text-gray-900">快速操作</h3>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
</div>
|
||||||
<button className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-left hover:bg-blue-100 transition-colors">
|
<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">
|
<div className="flex items-center">
|
||||||
<UsersIcon className="h-8 w-8 text-blue-600" />
|
<UserGroupIcon className="h-5 w-5 text-blue-600 mr-3" />
|
||||||
<div className="ml-3">
|
<span className="text-sm font-medium text-gray-900">用户管理</span>
|
||||||
<div className="text-sm font-medium text-blue-900">用户管理</div>
|
|
||||||
<div className="text-xs text-blue-700">管理用户账户</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className="bg-green-50 border border-green-200 rounded-lg p-4 text-left hover:bg-green-100 transition-colors">
|
<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">
|
<div className="flex items-center">
|
||||||
<PhoneIcon className="h-8 w-8 text-green-600" />
|
<UsersIcon className="h-5 w-5 text-green-600 mr-3" />
|
||||||
<div className="ml-3">
|
<span className="text-sm font-medium text-gray-900">翻译员管理</span>
|
||||||
<div className="text-sm font-medium text-green-900">通话监控</div>
|
|
||||||
<div className="text-xs text-green-700">实时通话状态</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 text-left hover:bg-yellow-100 transition-colors">
|
<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">
|
<div className="flex items-center">
|
||||||
<DocumentTextIcon className="h-8 w-8 text-yellow-600" />
|
<DocumentTextIcon className="h-5 w-5 text-purple-600 mr-3" />
|
||||||
<div className="ml-3">
|
<span className="text-sm font-medium text-gray-900">订单管理</span>
|
||||||
<div className="text-sm font-medium text-yellow-900">订单管理</div>
|
|
||||||
<div className="text-xs text-yellow-700">处理订单请求</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ArrowUpIcon className="h-4 w-4 text-gray-400 transform rotate-45" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button className="bg-purple-50 border border-purple-200 rounded-lg p-4 text-left hover:bg-purple-100 transition-colors">
|
<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">
|
<div className="flex items-center">
|
||||||
<CurrencyDollarIcon className="h-8 w-8 text-purple-600" />
|
<PhoneIcon className="h-5 w-5 text-orange-600 mr-3" />
|
||||||
<div className="ml-3">
|
<span className="text-sm font-medium text-gray-900">通话管理</span>
|
||||||
<div className="text-sm font-medium text-purple-900">财务报表</div>
|
|
||||||
<div className="text-xs text-purple-700">查看收入统计</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
</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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DashboardLayout>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -14,11 +14,11 @@ export interface Database {
|
|||||||
id: string
|
id: string
|
||||||
email: string
|
email: string
|
||||||
name: string
|
name: string
|
||||||
phone?: string
|
phone: string | null
|
||||||
user_type: 'individual' | 'enterprise'
|
user_type: 'individual' | 'enterprise'
|
||||||
status: 'active' | 'inactive'
|
status: 'active' | 'inactive' | 'suspended'
|
||||||
enterprise_id?: string
|
enterprise_id: string | null
|
||||||
avatar_url?: string
|
avatar_url: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -26,11 +26,11 @@ export interface Database {
|
|||||||
id?: string
|
id?: string
|
||||||
email: string
|
email: string
|
||||||
name: string
|
name: string
|
||||||
phone?: string
|
phone?: string | null
|
||||||
user_type: 'individual' | 'enterprise'
|
user_type: 'individual' | 'enterprise'
|
||||||
status?: 'active' | 'inactive'
|
status?: 'active' | 'inactive' | 'suspended'
|
||||||
enterprise_id?: string
|
enterprise_id?: string | null
|
||||||
avatar_url?: string
|
avatar_url?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -38,11 +38,11 @@ export interface Database {
|
|||||||
id?: string
|
id?: string
|
||||||
email?: string
|
email?: string
|
||||||
name?: string
|
name?: string
|
||||||
phone?: string
|
phone?: string | null
|
||||||
user_type?: 'individual' | 'enterprise'
|
user_type?: 'individual' | 'enterprise'
|
||||||
status?: 'active' | 'inactive'
|
status?: 'active' | 'inactive' | 'suspended'
|
||||||
enterprise_id?: string
|
enterprise_id?: string | null
|
||||||
avatar_url?: string
|
avatar_url?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -53,10 +53,10 @@ export interface Database {
|
|||||||
name: string
|
name: string
|
||||||
contact_person: string
|
contact_person: string
|
||||||
contact_email: string
|
contact_email: string
|
||||||
contact_phone: string
|
contact_phone: string | null
|
||||||
address: string
|
address: string | null
|
||||||
tax_number?: string
|
tax_number: string | null
|
||||||
status: 'active' | 'inactive'
|
status: 'active' | 'inactive' | 'suspended'
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -65,10 +65,10 @@ export interface Database {
|
|||||||
name: string
|
name: string
|
||||||
contact_person: string
|
contact_person: string
|
||||||
contact_email: string
|
contact_email: string
|
||||||
contact_phone: string
|
contact_phone?: string | null
|
||||||
address: string
|
address?: string | null
|
||||||
tax_number?: string
|
tax_number?: string | null
|
||||||
status?: 'active' | 'inactive'
|
status?: 'active' | 'inactive' | 'suspended'
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -77,10 +77,10 @@ export interface Database {
|
|||||||
name?: string
|
name?: string
|
||||||
contact_person?: string
|
contact_person?: string
|
||||||
contact_email?: string
|
contact_email?: string
|
||||||
contact_phone?: string
|
contact_phone?: string | null
|
||||||
address?: string
|
address?: string | null
|
||||||
tax_number?: string
|
tax_number?: string | null
|
||||||
status?: 'active' | 'inactive'
|
status?: 'active' | 'inactive' | 'suspended'
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -90,13 +90,13 @@ export interface Database {
|
|||||||
id: string
|
id: string
|
||||||
enterprise_id: string
|
enterprise_id: string
|
||||||
contract_number: string
|
contract_number: string
|
||||||
contract_type: string
|
contract_type: 'basic' | 'premium' | 'enterprise'
|
||||||
start_date: string
|
start_date: string
|
||||||
end_date: string
|
end_date: string
|
||||||
total_amount: number
|
total_amount: number
|
||||||
currency: string
|
currency: string
|
||||||
status: 'active' | 'expired' | 'terminated'
|
status: 'active' | 'expired' | 'cancelled'
|
||||||
service_rates: Json
|
service_rates: any
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -104,13 +104,13 @@ export interface Database {
|
|||||||
id?: string
|
id?: string
|
||||||
enterprise_id: string
|
enterprise_id: string
|
||||||
contract_number: string
|
contract_number: string
|
||||||
contract_type: string
|
contract_type: 'basic' | 'premium' | 'enterprise'
|
||||||
start_date: string
|
start_date: string
|
||||||
end_date: string
|
end_date: string
|
||||||
total_amount: number
|
total_amount: number
|
||||||
currency?: string
|
currency: string
|
||||||
status?: 'active' | 'expired' | 'terminated'
|
status?: 'active' | 'expired' | 'cancelled'
|
||||||
service_rates?: Json
|
service_rates: any
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -118,13 +118,13 @@ export interface Database {
|
|||||||
id?: string
|
id?: string
|
||||||
enterprise_id?: string
|
enterprise_id?: string
|
||||||
contract_number?: string
|
contract_number?: string
|
||||||
contract_type?: string
|
contract_type?: 'basic' | 'premium' | 'enterprise'
|
||||||
start_date?: string
|
start_date?: string
|
||||||
end_date?: string
|
end_date?: string
|
||||||
total_amount?: number
|
total_amount?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
status?: 'active' | 'expired' | 'terminated'
|
status?: 'active' | 'expired' | 'cancelled'
|
||||||
service_rates?: Json
|
service_rates?: any
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ export interface Database {
|
|||||||
total_amount: number
|
total_amount: number
|
||||||
currency: string
|
currency: string
|
||||||
status: 'draft' | 'sent' | 'paid' | 'overdue'
|
status: 'draft' | 'sent' | 'paid' | 'overdue'
|
||||||
items: Json
|
items: any[]
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -150,9 +150,9 @@ export interface Database {
|
|||||||
billing_period_start: string
|
billing_period_start: string
|
||||||
billing_period_end: string
|
billing_period_end: string
|
||||||
total_amount: number
|
total_amount: number
|
||||||
currency?: string
|
currency: string
|
||||||
status?: 'draft' | 'sent' | 'paid' | 'overdue'
|
status?: 'draft' | 'sent' | 'paid' | 'overdue'
|
||||||
items?: Json
|
items: any[]
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ export interface Database {
|
|||||||
total_amount?: number
|
total_amount?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
status?: 'draft' | 'sent' | 'paid' | 'overdue'
|
status?: 'draft' | 'sent' | 'paid' | 'overdue'
|
||||||
items?: Json
|
items?: any[]
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -177,21 +177,21 @@ export interface Database {
|
|||||||
user_id: string
|
user_id: string
|
||||||
user_name: string
|
user_name: string
|
||||||
user_email: string
|
user_email: string
|
||||||
service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' | 'document_translation'
|
service_type: 'phone' | 'video' | 'document'
|
||||||
service_name: string
|
service_name: string
|
||||||
source_language: string
|
source_language: string
|
||||||
target_language: string
|
target_language: string
|
||||||
duration?: number
|
duration: number | null
|
||||||
status: 'pending' | 'processing' | 'completed' | 'cancelled' | 'failed'
|
status: 'pending' | 'confirmed' | 'in_progress' | 'completed' | 'cancelled'
|
||||||
priority: 'urgent' | 'high' | 'normal' | 'low'
|
priority: 'low' | 'medium' | 'high' | 'urgent'
|
||||||
cost: number
|
cost: number
|
||||||
currency: string
|
currency: string
|
||||||
scheduled_time?: string
|
scheduled_time: string | null
|
||||||
started_time?: string
|
started_time: string | null
|
||||||
completed_time?: string
|
completed_time: string | null
|
||||||
interpreter_id?: string
|
interpreter_id: string | null
|
||||||
interpreter_name?: string
|
interpreter_name: string | null
|
||||||
notes?: string
|
notes: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -201,21 +201,21 @@ export interface Database {
|
|||||||
user_id: string
|
user_id: string
|
||||||
user_name: string
|
user_name: string
|
||||||
user_email: string
|
user_email: string
|
||||||
service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' | 'document_translation'
|
service_type: 'phone' | 'video' | 'document'
|
||||||
service_name: string
|
service_name: string
|
||||||
source_language: string
|
source_language: string
|
||||||
target_language: string
|
target_language: string
|
||||||
duration?: number
|
duration?: number | null
|
||||||
status?: 'pending' | 'processing' | 'completed' | 'cancelled' | 'failed'
|
status?: 'pending' | 'confirmed' | 'in_progress' | 'completed' | 'cancelled'
|
||||||
priority?: 'urgent' | 'high' | 'normal' | 'low'
|
priority?: 'low' | 'medium' | 'high' | 'urgent'
|
||||||
cost: number
|
cost: number
|
||||||
currency?: string
|
currency: string
|
||||||
scheduled_time?: string
|
scheduled_time?: string | null
|
||||||
started_time?: string
|
started_time?: string | null
|
||||||
completed_time?: string
|
completed_time?: string | null
|
||||||
interpreter_id?: string
|
interpreter_id?: string | null
|
||||||
interpreter_name?: string
|
interpreter_name?: string | null
|
||||||
notes?: string
|
notes?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -225,21 +225,21 @@ export interface Database {
|
|||||||
user_id?: string
|
user_id?: string
|
||||||
user_name?: string
|
user_name?: string
|
||||||
user_email?: string
|
user_email?: string
|
||||||
service_type?: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation' | 'document_translation'
|
service_type?: 'phone' | 'video' | 'document'
|
||||||
service_name?: string
|
service_name?: string
|
||||||
source_language?: string
|
source_language?: string
|
||||||
target_language?: string
|
target_language?: string
|
||||||
duration?: number
|
duration?: number | null
|
||||||
status?: 'pending' | 'processing' | 'completed' | 'cancelled' | 'failed'
|
status?: 'pending' | 'confirmed' | 'in_progress' | 'completed' | 'cancelled'
|
||||||
priority?: 'urgent' | 'high' | 'normal' | 'low'
|
priority?: 'low' | 'medium' | 'high' | 'urgent'
|
||||||
cost?: number
|
cost?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
scheduled_time?: string
|
scheduled_time?: string | null
|
||||||
started_time?: string
|
started_time?: string | null
|
||||||
completed_time?: string
|
completed_time?: string | null
|
||||||
interpreter_id?: string
|
interpreter_id?: string | null
|
||||||
interpreter_name?: string
|
interpreter_name?: string | null
|
||||||
notes?: string
|
notes?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -251,21 +251,21 @@ export interface Database {
|
|||||||
user_id: string
|
user_id: string
|
||||||
user_name: string
|
user_name: string
|
||||||
user_email: string
|
user_email: string
|
||||||
order_id?: string
|
order_id: string | null
|
||||||
invoice_type: 'personal' | 'enterprise'
|
invoice_type: 'individual' | 'enterprise'
|
||||||
personal_name?: string
|
personal_name: string | null
|
||||||
company_name?: string
|
company_name: string | null
|
||||||
tax_number?: string
|
tax_number: string | null
|
||||||
company_address?: string
|
company_address: string | null
|
||||||
subtotal: number
|
subtotal: number
|
||||||
tax_amount: number
|
tax_amount: number
|
||||||
total_amount: number
|
total_amount: number
|
||||||
currency: string
|
currency: string
|
||||||
status: 'draft' | 'issued' | 'paid' | 'cancelled'
|
status: 'draft' | 'sent' | 'paid' | 'cancelled' | 'overdue'
|
||||||
issue_date?: string
|
issue_date: string
|
||||||
due_date?: string
|
due_date: string
|
||||||
paid_date?: string
|
paid_date: string | null
|
||||||
items: Json
|
items: any[]
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -275,21 +275,21 @@ export interface Database {
|
|||||||
user_id: string
|
user_id: string
|
||||||
user_name: string
|
user_name: string
|
||||||
user_email: string
|
user_email: string
|
||||||
order_id?: string
|
order_id?: string | null
|
||||||
invoice_type: 'personal' | 'enterprise'
|
invoice_type: 'individual' | 'enterprise'
|
||||||
personal_name?: string
|
personal_name?: string | null
|
||||||
company_name?: string
|
company_name?: string | null
|
||||||
tax_number?: string
|
tax_number?: string | null
|
||||||
company_address?: string
|
company_address?: string | null
|
||||||
subtotal: number
|
subtotal: number
|
||||||
tax_amount: number
|
tax_amount: number
|
||||||
total_amount: number
|
total_amount: number
|
||||||
currency?: string
|
currency: string
|
||||||
status?: 'draft' | 'issued' | 'paid' | 'cancelled'
|
status?: 'draft' | 'sent' | 'paid' | 'cancelled' | 'overdue'
|
||||||
issue_date?: string
|
issue_date: string
|
||||||
due_date?: string
|
due_date: string
|
||||||
paid_date?: string
|
paid_date?: string | null
|
||||||
items?: Json
|
items: any[]
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -299,21 +299,21 @@ export interface Database {
|
|||||||
user_id?: string
|
user_id?: string
|
||||||
user_name?: string
|
user_name?: string
|
||||||
user_email?: string
|
user_email?: string
|
||||||
order_id?: string
|
order_id?: string | null
|
||||||
invoice_type?: 'personal' | 'enterprise'
|
invoice_type?: 'individual' | 'enterprise'
|
||||||
personal_name?: string
|
personal_name?: string | null
|
||||||
company_name?: string
|
company_name?: string | null
|
||||||
tax_number?: string
|
tax_number?: string | null
|
||||||
company_address?: string
|
company_address?: string | null
|
||||||
subtotal?: number
|
subtotal?: number
|
||||||
tax_amount?: number
|
tax_amount?: number
|
||||||
total_amount?: number
|
total_amount?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
status?: 'draft' | 'issued' | 'paid' | 'cancelled'
|
status?: 'draft' | 'sent' | 'paid' | 'cancelled' | 'overdue'
|
||||||
issue_date?: string
|
issue_date?: string
|
||||||
due_date?: string
|
due_date?: string
|
||||||
paid_date?: string
|
paid_date?: string | null
|
||||||
items?: Json
|
items?: any[]
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -323,16 +323,16 @@ export interface Database {
|
|||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
email: string
|
email: string
|
||||||
phone: string
|
phone: string | null
|
||||||
languages: string[]
|
languages: string[]
|
||||||
specialties: string[]
|
specialties: string[]
|
||||||
status: 'online' | 'offline' | 'busy'
|
status: 'active' | 'inactive' | 'busy'
|
||||||
rating: number
|
rating: number
|
||||||
total_calls: number
|
total_calls: number
|
||||||
hourly_rate: number
|
hourly_rate: number
|
||||||
currency: string
|
currency: string
|
||||||
avatar_url?: string
|
avatar_url: string | null
|
||||||
bio?: string
|
bio: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -340,16 +340,16 @@ export interface Database {
|
|||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
email: string
|
email: string
|
||||||
phone: string
|
phone?: string | null
|
||||||
languages: string[]
|
languages: string[]
|
||||||
specialties: string[]
|
specialties: string[]
|
||||||
status?: 'online' | 'offline' | 'busy'
|
status?: 'active' | 'inactive' | 'busy'
|
||||||
rating?: number
|
rating?: number
|
||||||
total_calls?: number
|
total_calls?: number
|
||||||
hourly_rate: number
|
hourly_rate: number
|
||||||
currency?: string
|
currency: string
|
||||||
avatar_url?: string
|
avatar_url?: string | null
|
||||||
bio?: string
|
bio?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -357,16 +357,16 @@ export interface Database {
|
|||||||
id?: string
|
id?: string
|
||||||
name?: string
|
name?: string
|
||||||
email?: string
|
email?: string
|
||||||
phone?: string
|
phone?: string | null
|
||||||
languages?: string[]
|
languages?: string[]
|
||||||
specialties?: string[]
|
specialties?: string[]
|
||||||
status?: 'online' | 'offline' | 'busy'
|
status?: 'active' | 'inactive' | 'busy'
|
||||||
rating?: number
|
rating?: number
|
||||||
total_calls?: number
|
total_calls?: number
|
||||||
hourly_rate?: number
|
hourly_rate?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
avatar_url?: string
|
avatar_url?: string | null
|
||||||
bio?: string
|
bio?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -375,34 +375,34 @@ export interface Database {
|
|||||||
Row: {
|
Row: {
|
||||||
id: string
|
id: string
|
||||||
user_id: string
|
user_id: string
|
||||||
interpreter_id?: string
|
interpreter_id: string
|
||||||
service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation'
|
service_type: 'phone' | 'video'
|
||||||
source_language: string
|
source_language: string
|
||||||
target_language: string
|
target_language: string
|
||||||
status: 'waiting' | 'connecting' | 'active' | 'completed' | 'failed'
|
status: 'waiting' | 'connected' | 'ended' | 'cancelled'
|
||||||
duration?: number
|
duration: number | null
|
||||||
cost: number
|
cost: number
|
||||||
currency: string
|
currency: string
|
||||||
quality_rating?: number
|
quality_rating: number | null
|
||||||
started_at?: string
|
started_at: string | null
|
||||||
ended_at?: string
|
ended_at: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
id?: string
|
id?: string
|
||||||
user_id: string
|
user_id: string
|
||||||
interpreter_id?: string
|
interpreter_id: string
|
||||||
service_type: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation'
|
service_type: 'phone' | 'video'
|
||||||
source_language: string
|
source_language: string
|
||||||
target_language: string
|
target_language: string
|
||||||
status?: 'waiting' | 'connecting' | 'active' | 'completed' | 'failed'
|
status?: 'waiting' | 'connected' | 'ended' | 'cancelled'
|
||||||
duration?: number
|
duration?: number | null
|
||||||
cost: number
|
cost: number
|
||||||
currency?: string
|
currency: string
|
||||||
quality_rating?: number
|
quality_rating?: number | null
|
||||||
started_at?: string
|
started_at?: string | null
|
||||||
ended_at?: string
|
ended_at?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -410,16 +410,16 @@ export interface Database {
|
|||||||
id?: string
|
id?: string
|
||||||
user_id?: string
|
user_id?: string
|
||||||
interpreter_id?: string
|
interpreter_id?: string
|
||||||
service_type?: 'ai_voice' | 'ai_video' | 'sign_language' | 'human_interpretation'
|
service_type?: 'phone' | 'video'
|
||||||
source_language?: string
|
source_language?: string
|
||||||
target_language?: string
|
target_language?: string
|
||||||
status?: 'waiting' | 'connecting' | 'active' | 'completed' | 'failed'
|
status?: 'waiting' | 'connected' | 'ended' | 'cancelled'
|
||||||
duration?: number
|
duration?: number | null
|
||||||
cost?: number
|
cost?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
quality_rating?: number
|
quality_rating?: number | null
|
||||||
started_at?: string
|
started_at?: string | null
|
||||||
ended_at?: string
|
ended_at?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -432,13 +432,9 @@ export interface Database {
|
|||||||
original_name: string
|
original_name: string
|
||||||
file_size: number
|
file_size: number
|
||||||
file_type: string
|
file_type: string
|
||||||
source_language: string
|
source_language: string | null
|
||||||
target_language: string
|
target_language: string | null
|
||||||
status: 'uploaded' | 'processing' | 'completed' | 'failed'
|
status: 'pending' | 'processing' | 'completed' | 'failed'
|
||||||
progress: number
|
|
||||||
cost: number
|
|
||||||
currency: string
|
|
||||||
translated_file_url?: string
|
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
@ -449,13 +445,9 @@ export interface Database {
|
|||||||
original_name: string
|
original_name: string
|
||||||
file_size: number
|
file_size: number
|
||||||
file_type: string
|
file_type: string
|
||||||
source_language: string
|
source_language?: string | null
|
||||||
target_language: string
|
target_language?: string | null
|
||||||
status?: 'uploaded' | 'processing' | 'completed' | 'failed'
|
status?: 'pending' | 'processing' | 'completed' | 'failed'
|
||||||
progress?: number
|
|
||||||
cost: number
|
|
||||||
currency?: string
|
|
||||||
translated_file_url?: string
|
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -466,13 +458,9 @@ export interface Database {
|
|||||||
original_name?: string
|
original_name?: string
|
||||||
file_size?: number
|
file_size?: number
|
||||||
file_type?: string
|
file_type?: string
|
||||||
source_language?: string
|
source_language?: string | null
|
||||||
target_language?: string
|
target_language?: string | null
|
||||||
status?: 'uploaded' | 'processing' | 'completed' | 'failed'
|
status?: 'pending' | 'processing' | 'completed' | 'failed'
|
||||||
progress?: number
|
|
||||||
cost?: number
|
|
||||||
currency?: string
|
|
||||||
translated_file_url?: string
|
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -481,24 +469,24 @@ export interface Database {
|
|||||||
Row: {
|
Row: {
|
||||||
id: string
|
id: string
|
||||||
key: string
|
key: string
|
||||||
value: Json
|
value: string
|
||||||
description?: string
|
description: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
value: Json
|
value: string
|
||||||
description?: string
|
description?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
Update: {
|
Update: {
|
||||||
id?: string
|
id?: string
|
||||||
key?: string
|
key?: string
|
||||||
value?: Json
|
value?: string
|
||||||
description?: string
|
description?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user