858 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import { toast } from 'react-hot-toast';
import {
MagnifyingGlassIcon,
BuildingOfficeIcon,
UsersIcon,
DocumentTextIcon,
CurrencyDollarIcon,
PlusIcon,
PencilIcon,
TrashIcon,
EyeIcon,
ChevronLeftIcon,
ChevronRightIcon
} from '@heroicons/react/24/outline';
import { supabase, TABLES } from '@/lib/supabase';
import { getDemoData } from '@/lib/demo-data';
import { formatTime } from '@/utils';
import Layout from '@/components/Layout';
interface EnterpriseContract {
id: string;
enterprise_id: string;
enterprise_name: string;
contract_number: string;
contract_type: 'annual' | 'monthly' | 'project';
start_date: string;
end_date: string;
total_amount: number;
currency: string;
status: 'active' | 'expired' | 'terminated';
service_rates: {
ai_voice: number; // AI语音翻译费率元/分钟)
ai_video: number; // AI视频翻译费率元/分钟)
sign_language: number; // 手语翻译费率(元/分钟)
human_interpreter: number; // 真人翻译费率(元/分钟)
document_translation: number; // 文档翻译费率(元/字)
};
created_at: string;
updated_at: string;
}
interface EnterpriseEmployee {
id: string;
enterprise_id: string;
enterprise_name: string;
name: string;
email: string;
department: string;
position: string;
status: 'active' | 'inactive';
total_calls: number;
total_cost: number;
created_at: string;
}
interface EnterpriseBilling {
id: string;
enterprise_id: string;
enterprise_name: string;
period: string;
total_calls: number;
total_duration: number;
total_amount: number;
currency: string;
status: 'pending' | 'paid' | 'overdue';
due_date: string;
paid_date?: string;
created_at: string;
}
interface EnterpriseFilters {
search: string;
tab: 'contracts' | 'employees' | 'billing';
status: 'all' | 'active' | 'inactive' | 'expired' | 'pending' | 'paid' | 'overdue';
enterprise: 'all' | string;
sortBy: 'created_at' | 'name' | 'amount' | 'total_calls';
sortOrder: 'asc' | 'desc';
}
export default function EnterprisePage() {
const [contracts, setContracts] = useState<EnterpriseContract[]>([]);
const [employees, setEmployees] = useState<EnterpriseEmployee[]>([]);
const [billing, setBilling] = useState<EnterpriseBilling[]>([]);
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [totalCount, setTotalCount] = useState(0);
const [isDemoMode, setIsDemoMode] = useState(false);
const [filters, setFilters] = useState<EnterpriseFilters>({
search: '',
tab: 'contracts',
status: 'all',
enterprise: 'all',
sortBy: 'created_at',
sortOrder: 'desc'
});
const router = useRouter();
const pageSize = 20;
// 获取企业数据
const fetchEnterpriseData = async (page = 1) => {
try {
setLoading(true);
// 检查是否为演示模式
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const isDemo = !supabaseUrl || supabaseUrl === 'https://demo.supabase.co' || supabaseUrl === '';
setIsDemoMode(isDemo);
if (isDemo) {
// 使用演示数据
const result = await getDemoData.enterprise();
switch (filters.tab) {
case 'contracts':
setContracts(result.contracts);
setTotalCount(result.contracts.length);
break;
case 'employees':
setEmployees(result.employees);
setTotalCount(result.employees.length);
break;
case 'billing':
setBilling(result.billing);
setTotalCount(result.billing.length);
break;
}
setTotalPages(Math.ceil(totalCount / pageSize));
setCurrentPage(page);
} else {
// 使用真实数据 - 这里需要根据实际数据库结构调整
// 暂时使用演示数据
const result = await getDemoData.enterprise();
switch (filters.tab) {
case 'contracts':
setContracts(result.contracts);
setTotalCount(result.contracts.length);
break;
case 'employees':
setEmployees(result.employees);
setTotalCount(result.employees.length);
break;
case 'billing':
setBilling(result.billing);
setTotalCount(result.billing.length);
break;
}
setTotalPages(Math.ceil(totalCount / pageSize));
setCurrentPage(page);
}
} catch (error) {
console.error('Error fetching enterprise data:', error);
toast.error('获取企业数据失败');
} finally {
setLoading(false);
}
};
// 处理筛选变更
const handleFilterChange = (key: keyof EnterpriseFilters, value: any) => {
setFilters(prev => ({
...prev,
[key]: value
}));
};
// 应用筛选
const applyFilters = () => {
setCurrentPage(1);
fetchEnterpriseData(1);
};
// 重置筛选
const resetFilters = () => {
setFilters({
search: '',
tab: filters.tab,
status: 'all',
enterprise: 'all',
sortBy: 'created_at',
sortOrder: 'desc'
});
setCurrentPage(1);
fetchEnterpriseData(1);
};
// 获取状态颜色
const getStatusColor = (status: string) => {
switch (status) {
case 'active':
return 'bg-green-100 text-green-800';
case 'inactive':
case 'expired':
return 'bg-red-100 text-red-800';
case 'terminated':
return 'bg-gray-100 text-gray-800';
case 'pending':
return 'bg-yellow-100 text-yellow-800';
case 'paid':
return 'bg-green-100 text-green-800';
case 'overdue':
return 'bg-red-100 text-red-800';
default:
return 'bg-gray-100 text-gray-800';
}
};
// 获取状态文本
const getStatusText = (status: string) => {
switch (status) {
case 'active':
return '活跃';
case 'inactive':
return '非活跃';
case 'expired':
return '已过期';
case 'terminated':
return '已终止';
case 'pending':
return '待付款';
case 'paid':
return '已付款';
case 'overdue':
return '逾期';
default:
return '未知';
}
};
// 删除员工
const handleDeleteEmployee = async (employeeId: string) => {
if (confirm('确定要删除此员工吗?')) {
try {
// 这里应该调用删除API
toast.success('员工删除成功');
fetchEnterpriseData();
} catch (error) {
toast.error('删除员工失败');
}
}
};
// 员工结算
const handleEmployeeSettlement = async (employeeId: string) => {
try {
// 这里应该调用结算API
toast.success('员工结算完成');
fetchEnterpriseData();
} catch (error) {
toast.error('员工结算失败');
}
};
// 获取唯一的企业列表(用于筛选下拉框)
const getUniqueEnterprises = () => {
const enterprises = new Set();
employees.forEach(emp => enterprises.add(emp.enterprise_name));
contracts.forEach(contract => enterprises.add(contract.enterprise_name));
return Array.from(enterprises) as string[];
};
useEffect(() => {
fetchEnterpriseData();
}, [filters.tab]);
const renderContracts = () => (
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<div className="sm:flex sm:items-center sm:justify-between mb-6">
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900"></h3>
<p className="mt-1 text-sm text-gray-500"></p>
</div>
</div>
{/* 搜索和筛选 */}
<div className="mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div className="relative">
<MagnifyingGlassIcon className="h-5 w-5 absolute left-3 top-3 text-gray-400" />
<input
type="text"
placeholder="搜索企业名称或合同号..."
className="pl-10 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
value={filters.search}
onChange={(e) => handleFilterChange('search', e.target.value)}
/>
</div>
<select
className="rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
value={filters.status}
onChange={(e) => handleFilterChange('status', e.target.value)}
>
<option value="all"></option>
<option value="active"></option>
<option value="expired"></option>
<option value="terminated"></option>
</select>
<div></div>
<div className="flex space-x-2">
<button
onClick={applyFilters}
className="flex-1 bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
</button>
<button
onClick={resetFilters}
className="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500"
>
</button>
</div>
</div>
{/* 合同列表 */}
<div className="space-y-6">
{contracts.map((contract) => (
<div key={contract.id} className="bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<div>
<h4 className="text-lg font-medium text-gray-900">{contract.enterprise_name}</h4>
<p className="text-sm text-gray-500">: {contract.contract_number}</p>
</div>
<span className={`inline-flex px-3 py-1 text-sm font-semibold rounded-full ${getStatusColor(contract.status)}`}>
{getStatusText(contract.status)}
</span>
</div>
</div>
<div className="px-6 py-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
<div>
<dt className="text-sm font-medium text-gray-500"></dt>
<dd className="mt-1 text-sm text-gray-900">
{contract.contract_type === 'annual' ? '年度合同' :
contract.contract_type === 'monthly' ? '月度合同' : '项目合同'}
</dd>
</div>
<div>
<dt className="text-sm font-medium text-gray-500"></dt>
<dd className="mt-1 text-sm text-gray-900">
{formatTime(contract.start_date)} - {formatTime(contract.end_date)}
</dd>
</div>
<div>
<dt className="text-sm font-medium text-gray-500"></dt>
<dd className="mt-1 text-sm text-gray-900">
¥{contract.total_amount.toLocaleString()}
</dd>
</div>
</div>
{/* 服务费率配置 */}
<div className="mt-4">
<h5 className="text-sm font-medium text-gray-900 mb-3"></h5>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
<div className="bg-blue-50 p-3 rounded-lg">
<div className="text-xs text-blue-600 font-medium">AI语音翻译</div>
<div className="text-sm text-blue-900 font-semibold">¥{contract.service_rates.ai_voice}/</div>
</div>
<div className="bg-green-50 p-3 rounded-lg">
<div className="text-xs text-green-600 font-medium">AI视频翻译</div>
<div className="text-sm text-green-900 font-semibold">¥{contract.service_rates.ai_video}/</div>
</div>
<div className="bg-purple-50 p-3 rounded-lg">
<div className="text-xs text-purple-600 font-medium"></div>
<div className="text-sm text-purple-900 font-semibold">¥{contract.service_rates.sign_language}/</div>
</div>
<div className="bg-orange-50 p-3 rounded-lg">
<div className="text-xs text-orange-600 font-medium"></div>
<div className="text-sm text-orange-900 font-semibold">¥{contract.service_rates.human_interpreter}/</div>
</div>
<div className="bg-indigo-50 p-3 rounded-lg">
<div className="text-xs text-indigo-600 font-medium"></div>
<div className="text-sm text-indigo-900 font-semibold">¥{contract.service_rates.document_translation}/</div>
</div>
</div>
</div>
<div className="mt-4 flex justify-end space-x-2">
<button className="text-indigo-600 hover:text-indigo-900 text-sm font-medium">
<EyeIcon className="h-4 w-4 inline mr-1" />
</button>
<button className="text-green-600 hover:text-green-900 text-sm font-medium">
<PencilIcon className="h-4 w-4 inline mr-1" />
</button>
</div>
</div>
</div>
))}
</div>
{/* 分页 */}
{totalPages > 1 && (
<div className="mt-6 flex items-center justify-between">
<div className="text-sm text-gray-700">
{(currentPage - 1) * pageSize + 1} - {Math.min(currentPage * pageSize, totalCount)} {totalCount}
</div>
<div className="flex space-x-2">
<button
onClick={() => fetchEnterpriseData(currentPage - 1)}
disabled={currentPage === 1}
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronLeftIcon className="h-4 w-4" />
</button>
<span className="px-3 py-2 text-sm font-medium text-gray-700">
{currentPage} / {totalPages}
</span>
<button
onClick={() => fetchEnterpriseData(currentPage + 1)}
disabled={currentPage === totalPages}
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
<ChevronRightIcon className="h-4 w-4" />
</button>
</div>
</div>
)}
</div>
</div>
);
const renderEmployees = () => (
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<div className="sm:flex sm:items-center sm:justify-between mb-6">
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900"></h3>
<p className="mt-1 text-sm text-gray-500"></p>
</div>
</div>
{/* 搜索和筛选 */}
<div className="mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div className="relative">
<MagnifyingGlassIcon className="h-5 w-5 absolute left-3 top-3 text-gray-400" />
<input
type="text"
placeholder="搜索员工姓名或邮箱..."
className="pl-10 w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
value={filters.search}
onChange={(e) => handleFilterChange('search', e.target.value)}
/>
</div>
<select
className="rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
value={filters.enterprise}
onChange={(e) => handleFilterChange('enterprise', e.target.value)}
>
<option value="all"></option>
{getUniqueEnterprises().map(enterprise => (
<option key={enterprise} value={enterprise}>{enterprise}</option>
))}
</select>
<select
className="rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
value={filters.status}
onChange={(e) => handleFilterChange('status', e.target.value)}
>
<option value="all"></option>
<option value="active"></option>
<option value="inactive"></option>
</select>
<div className="flex space-x-2">
<button
onClick={applyFilters}
className="flex-1 bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
</button>
<button
onClick={resetFilters}
className="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500"
>
</button>
</div>
</div>
{/* 员工列表 */}
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table className="min-w-full divide-y divide-gray-300">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">
/
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wide">
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{employees.map((employee) => (
<tr key={employee.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center">
<span className="text-sm font-medium text-indigo-800">
{employee.name.charAt(0)}
</span>
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">{employee.name}</div>
<div className="text-sm text-gray-500">{employee.email}</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{employee.enterprise_name}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{employee.department}</div>
<div className="text-sm text-gray-500">{employee.position}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">{employee.total_calls} </div>
<div className="text-sm text-gray-500">¥{employee.total_cost.toFixed(2)}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(employee.status)}`}>
{getStatusText(employee.status)}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<div className="flex space-x-2">
<button className="text-indigo-600 hover:text-indigo-900">
<EyeIcon className="h-4 w-4" />
</button>
<button className="text-green-600 hover:text-green-900" onClick={() => handleEmployeeSettlement(employee.id)}>
<CurrencyDollarIcon className="h-4 w-4" />
</button>
<button className="text-red-600 hover:text-red-900" onClick={() => handleDeleteEmployee(employee.id)}>
<TrashIcon className="h-4 w-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
const renderBilling = () => (
<div className="space-y-4">
{billing.map((bill) => (
<div key={bill.id} className="p-4 border border-gray-200 rounded-lg">
<div className="flex items-center justify-between">
<div className="flex-1">
<h4 className="text-sm font-medium text-gray-900">
{bill.enterprise_name}
</h4>
<p className="text-sm text-gray-500">
: {bill.period}
</p>
<p className="text-xs text-gray-400">
: {bill.total_calls} | : {Math.floor(bill.total_duration / 60)}
</p>
<p className="text-sm font-medium text-gray-900 mt-1">
: ¥{bill.total_amount.toFixed(2)}
</p>
<p className="text-xs text-gray-400">
: {formatTime(bill.due_date)}
{bill.paid_date && ` | 付款日期: ${formatTime(bill.paid_date)}`}
</p>
</div>
<div className="flex items-center space-x-4">
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusColor(bill.status)}`}>
{getStatusText(bill.status)}
</span>
<div className="flex space-x-2">
<button className="text-blue-600 hover:text-blue-900">
<EyeIcon className="h-4 w-4" />
</button>
</div>
</div>
</div>
</div>
))}
</div>
);
return (
<Layout>
<Head>
<title> - </title>
</Head>
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
{/* 页面标题 */}
<div className="flex items-center justify-between mb-6">
<h1 className="text-2xl font-bold text-gray-900"></h1>
<button className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700">
<PlusIcon className="h-4 w-4 mr-2" />
{filters.tab === 'contracts' ? '新增合同' : filters.tab === 'employees' ? '添加员工' : '生成账单'}
</button>
</div>
{/* 标签页 */}
<div className="mb-6">
<nav className="flex space-x-8">
<button
onClick={() => handleFilterChange('tab', 'contracts')}
className={`${
filters.tab === 'contracts'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm`}
>
<BuildingOfficeIcon className="h-5 w-5 inline mr-2" />
</button>
<button
onClick={() => handleFilterChange('tab', 'employees')}
className={`${
filters.tab === 'employees'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm`}
>
<UsersIcon className="h-5 w-5 inline mr-2" />
</button>
<button
onClick={() => handleFilterChange('tab', 'billing')}
className={`${
filters.tab === 'billing'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
} whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm`}
>
<CurrencyDollarIcon className="h-5 w-5 inline mr-2" />
</button>
</nav>
</div>
{/* 搜索和筛选 */}
<div className="bg-white shadow rounded-lg mb-6">
<div className="px-4 py-5 sm:p-6">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{/* 搜索框 */}
<div className="lg:col-span-2">
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<MagnifyingGlassIcon className="h-5 w-5 text-gray-400" />
</div>
<input
type="text"
placeholder="搜索企业名称、合同编号或员工姓名..."
value={filters.search}
onChange={(e) => handleFilterChange('search', e.target.value)}
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
{/* 状态筛选 */}
<div>
<select
value={filters.status}
onChange={(e) => handleFilterChange('status', e.target.value)}
className="block w-full px-3 py-2 border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all"></option>
{filters.tab === 'contracts' && (
<>
<option value="active"></option>
<option value="expired"></option>
<option value="terminated"></option>
</>
)}
{filters.tab === 'employees' && (
<>
<option value="active"></option>
<option value="inactive"></option>
</>
)}
{filters.tab === 'billing' && (
<>
<option value="pending"></option>
<option value="paid"></option>
<option value="overdue"></option>
</>
)}
</select>
</div>
{/* 排序 */}
<div>
<select
value={`${filters.sortBy}-${filters.sortOrder}`}
onChange={(e) => {
const [sortBy, sortOrder] = e.target.value.split('-');
handleFilterChange('sortBy', sortBy);
handleFilterChange('sortOrder', sortOrder);
}}
className="block w-full px-3 py-2 border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
<option value="created_at-desc"> ()</option>
<option value="created_at-asc"> ()</option>
<option value="name-asc"> (A-Z)</option>
<option value="name-desc"> (Z-A)</option>
{filters.tab !== 'contracts' && (
<>
<option value="amount-desc"> ()</option>
<option value="amount-asc"> ()</option>
</>
)}
</select>
</div>
</div>
<div className="mt-4 flex space-x-3">
<button
onClick={applyFilters}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
>
</button>
<button
onClick={resetFilters}
className="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
>
</button>
</div>
</div>
</div>
{/* 内容区域 */}
{loading ? (
<div className="flex items-center justify-center py-12">
<div className="loading-spinner"></div>
</div>
) : (
<div className="bg-white shadow overflow-hidden sm:rounded-md">
<div className="px-4 py-5 sm:p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg leading-6 font-medium text-gray-900">
{filters.tab === 'contracts' ? '企业合同' : filters.tab === 'employees' ? '企业员工' : '结算记录'} ({totalCount} )
</h3>
</div>
{filters.tab === 'contracts' && renderContracts()}
{filters.tab === 'employees' && renderEmployees()}
{filters.tab === 'billing' && renderBilling()}
{/* 分页 */}
{totalPages > 1 && (
<div className="mt-6 flex items-center justify-between">
<div className="flex-1 flex justify-between sm:hidden">
<button
onClick={() => fetchEnterpriseData(currentPage - 1)}
disabled={currentPage === 1}
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
>
</button>
<button
onClick={() => fetchEnterpriseData(currentPage + 1)}
disabled={currentPage === totalPages}
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
>
</button>
</div>
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p className="text-sm text-gray-700">
<span className="font-medium">{(currentPage - 1) * pageSize + 1}</span> {' '}
<span className="font-medium">
{Math.min(currentPage * pageSize, totalCount)}
</span>{' '}
<span className="font-medium">{totalCount}</span>
</p>
</div>
<div>
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
<button
onClick={() => fetchEnterpriseData(currentPage - 1)}
disabled={currentPage === 1}
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<ChevronLeftIcon className="h-5 w-5" />
</button>
{[...Array(Math.min(totalPages, 5))].map((_, i) => {
const page = i + 1;
return (
<button
key={page}
onClick={() => fetchEnterpriseData(page)}
className={`relative inline-flex items-center px-4 py-2 border text-sm font-medium ${
page === currentPage
? 'z-10 bg-blue-50 border-blue-500 text-blue-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
}`}
>
{page}
</button>
);
})}
<button
onClick={() => fetchEnterpriseData(currentPage + 1)}
disabled={currentPage === totalPages}
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<ChevronRightIcon className="h-5 w-5" />
</button>
</nav>
</div>
</div>
</div>
)}
</div>
</div>
)}
</div>
</div>
</Layout>
);
}