import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Head from 'next/head'; import DashboardLayout from '../../components/Layout/DashboardLayout'; import { toast } from 'react-hot-toast'; import { MagnifyingGlassIcon, PlusIcon, EyeIcon, PencilIcon, TrashIcon, DocumentTextIcon, ArrowDownTrayIcon, PrinterIcon, CurrencyDollarIcon, CalendarIcon, CheckCircleIcon, XCircleIcon, ClockIcon, ExclamationTriangleIcon, UserIcon, BuildingOfficeIcon, EnvelopeIcon, PhoneIcon } from '@heroicons/react/24/outline'; import { getDemoData } from '../../lib/demo-data'; import { formatTime } from '../../lib/utils'; interface Invoice { id: string; invoice_number: string; order_id: string; order_number: string; user_name: string; user_email: string; user_company?: string; user_phone?: string; interpreter_name: string; service_type: string; service_description: string; amount: number; tax_amount: number; total_amount: number; status: 'draft' | 'sent' | 'paid' | 'overdue' | 'cancelled'; issue_date: string; due_date: string; paid_date?: string; notes?: string; created_at: string; updated_at: string; } interface InvoiceFilters { search: string; status: string; date_range: string; amount_range: string; } export default function Invoices() { const router = useRouter(); const [invoices, setInvoices] = useState([]); const [loading, setLoading] = useState(true); const [selectedInvoices, setSelectedInvoices] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalCount, setTotalCount] = useState(0); const [filters, setFilters] = useState({ search: '', status: '', date_range: '', amount_range: '' }); const pageSize = 10; useEffect(() => { fetchInvoices(); }, [currentPage, filters]); const fetchInvoices = async () => { try { setLoading(true); // 模拟加载延迟 await new Promise(resolve => setTimeout(resolve, 800)); // 使用演示数据 const mockInvoices: Invoice[] = [ { id: '1', invoice_number: 'INV-2024-001', order_id: '1', order_number: 'ORD-2024-001', user_name: '张先生', user_email: 'zhang@example.com', user_company: '北京科技有限公司', user_phone: '13800138001', interpreter_name: '王翻译', service_type: '视频通话翻译', service_description: '商务会议翻译服务(中英互译)', amount: 450, tax_amount: 27, total_amount: 477, status: 'paid', issue_date: '2024-01-20T00:00:00Z', due_date: '2024-02-19T00:00:00Z', paid_date: '2024-01-25T00:00:00Z', notes: '已按时完成支付', created_at: '2024-01-20T10:00:00Z', updated_at: '2024-01-25T14:30:00Z' }, { id: '2', invoice_number: 'INV-2024-002', order_id: '2', order_number: 'ORD-2024-002', user_name: '李女士', user_email: 'li@example.com', user_company: '上海医疗中心', user_phone: '13800138002', interpreter_name: '陈口译', service_type: '语音通话翻译', service_description: '医疗咨询翻译服务(中日互译)', amount: 300, tax_amount: 18, total_amount: 318, status: 'sent', issue_date: '2024-01-21T00:00:00Z', due_date: '2024-02-20T00:00:00Z', notes: '等待客户支付', created_at: '2024-01-21T09:00:00Z', updated_at: '2024-01-21T09:00:00Z' }, { id: '3', invoice_number: 'INV-2024-003', order_id: '3', order_number: 'ORD-2024-003', user_name: '王总', user_email: 'wangzong@example.com', user_company: '深圳国际贸易公司', user_phone: '13800138003', interpreter_name: '刘同传', service_type: '现场翻译', service_description: '大型会议同声传译服务(中英互译)', amount: 1800, tax_amount: 108, total_amount: 1908, status: 'draft', issue_date: '2024-01-22T00:00:00Z', due_date: '2024-02-21T00:00:00Z', notes: '待客户确认后发送', created_at: '2024-01-22T16:00:00Z', updated_at: '2024-01-22T16:00:00Z' }, { id: '4', invoice_number: 'INV-2024-004', order_id: '4', order_number: 'ORD-2024-004', user_name: '赵经理', user_email: 'zhao@example.com', user_company: '广州制造业集团', user_phone: '13800138004', interpreter_name: '李专家', service_type: '视频通话翻译', service_description: '技术交流翻译服务(中韩互译)', amount: 200, tax_amount: 12, total_amount: 212, status: 'cancelled', issue_date: '2024-01-20T00:00:00Z', due_date: '2024-02-19T00:00:00Z', notes: '订单取消,发票作废', created_at: '2024-01-20T14:00:00Z', updated_at: '2024-01-20T16:00:00Z' }, { id: '5', invoice_number: 'INV-2024-005', order_id: '5', order_number: 'ORD-2024-005', user_name: '孙先生', user_email: 'sun@example.com', user_company: '杭州技术服务公司', user_phone: '13800138005', interpreter_name: '张语言', service_type: '语音通话翻译', service_description: '技术文档咨询翻译服务(中德互译)', amount: 250, tax_amount: 15, total_amount: 265, status: 'overdue', issue_date: '2024-01-15T00:00:00Z', due_date: '2024-01-30T00:00:00Z', notes: '已逾期,需要催收', created_at: '2024-01-15T13:00:00Z', updated_at: '2024-01-31T10:00:00Z' }, { id: '6', invoice_number: 'INV-2024-006', order_id: '6', order_number: 'ORD-2024-006', user_name: '周女士', user_email: 'zhou@example.com', user_company: '成都教育机构', user_phone: '13800138006', interpreter_name: '赵技术', service_type: '视频通话翻译', service_description: '学术会议翻译服务(中英互译)', amount: 200, tax_amount: 12, total_amount: 212, status: 'sent', issue_date: '2024-01-19T00:00:00Z', due_date: '2024-02-18T00:00:00Z', notes: '已发送给客户', created_at: '2024-01-19T11:00:00Z', updated_at: '2024-01-19T11:00:00Z' } ]; // 应用过滤器 let filteredInvoices = mockInvoices; if (filters.search) { filteredInvoices = filteredInvoices.filter(invoice => invoice.invoice_number.toLowerCase().includes(filters.search.toLowerCase()) || invoice.order_number.toLowerCase().includes(filters.search.toLowerCase()) || invoice.user_name.toLowerCase().includes(filters.search.toLowerCase()) || invoice.user_email.toLowerCase().includes(filters.search.toLowerCase()) || invoice.user_company?.toLowerCase().includes(filters.search.toLowerCase()) || invoice.interpreter_name.toLowerCase().includes(filters.search.toLowerCase()) ); } if (filters.status) { filteredInvoices = filteredInvoices.filter(invoice => invoice.status === filters.status); } // 分页 const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; const paginatedInvoices = filteredInvoices.slice(startIndex, endIndex); setInvoices(paginatedInvoices); setTotalCount(filteredInvoices.length); setTotalPages(Math.ceil(filteredInvoices.length / pageSize)); } catch (error) { console.error('Failed to fetch invoices:', error); toast.error('加载发票失败'); } finally { setLoading(false); } }; const handleSearch = (value: string) => { setFilters(prev => ({ ...prev, search: value })); setCurrentPage(1); }; const handleFilterChange = (key: keyof InvoiceFilters, value: string) => { setFilters(prev => ({ ...prev, [key]: value })); setCurrentPage(1); }; const handleSelectInvoice = (invoiceId: string) => { setSelectedInvoices(prev => prev.includes(invoiceId) ? prev.filter(id => id !== invoiceId) : [...prev, invoiceId] ); }; const handleSelectAll = () => { if (selectedInvoices.length === invoices.length) { setSelectedInvoices([]); } else { setSelectedInvoices(invoices.map(invoice => invoice.id)); } }; const handleBulkAction = async (action: 'send' | 'mark_paid' | 'cancel' | 'delete') => { if (selectedInvoices.length === 0) { toast.error('请选择要操作的发票'); return; } try { const actionText = action === 'send' ? '发送' : action === 'mark_paid' ? '标记已付' : action === 'cancel' ? '取消' : '删除'; toast.loading(`正在${actionText}发票...`, { id: 'bulk-action' }); // 模拟操作延迟 await new Promise(resolve => setTimeout(resolve, 1500)); toast.success(`成功${actionText} ${selectedInvoices.length} 张发票`, { id: 'bulk-action' }); setSelectedInvoices([]); fetchInvoices(); } catch (error) { toast.error('操作失败', { id: 'bulk-action' }); } }; const handleExport = async () => { try { toast.loading('正在导出发票数据...', { id: 'export' }); // 模拟导出延迟 await new Promise(resolve => setTimeout(resolve, 2000)); toast.success('发票数据导出成功', { id: 'export' }); } catch (error) { toast.error('导出失败', { id: 'export' }); } }; const handlePrint = async (invoiceId: string) => { try { toast.loading('正在准备打印...', { id: 'print' }); // 模拟打印准备延迟 await new Promise(resolve => setTimeout(resolve, 1000)); toast.success('发票已发送到打印机', { id: 'print' }); } catch (error) { toast.error('打印失败', { id: 'print' }); } }; const getStatusColor = (status: string) => { switch (status) { case 'paid': return 'text-green-800 bg-green-100'; case 'sent': return 'text-blue-800 bg-blue-100'; case 'draft': return 'text-gray-800 bg-gray-100'; case 'overdue': return 'text-red-800 bg-red-100'; case 'cancelled': return 'text-red-800 bg-red-100'; default: return 'text-gray-800 bg-gray-100'; } }; const getStatusText = (status: string) => { switch (status) { case 'paid': return '已支付'; case 'sent': return '已发送'; case 'draft': return '草稿'; case 'overdue': return '逾期'; case 'cancelled': return '已取消'; default: return status; } }; const getStatusIcon = (status: string) => { switch (status) { case 'paid': return ; case 'sent': return ; case 'draft': return ; case 'overdue': return ; case 'cancelled': return ; default: return ; } }; const formatCurrency = (amount: number) => { return `¥${amount.toLocaleString('zh-CN', { minimumFractionDigits: 2 })}`; }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('zh-CN'); }; return ( <> 发票管理 - 翻译服务管理系统
{/* 页面标题和操作 */}

发票管理

管理所有翻译服务发票,包括发票状态、支付跟踪和账单处理。

{/* 搜索和过滤器 */}
handleSearch(e.target.value)} />
{/* 批量操作 */} {selectedInvoices.length > 0 && (
已选择 {selectedInvoices.length} 张发票
)} {/* 发票列表 */}
{loading ? (
) : ( <>
{invoices.map((invoice) => ( ))}
0} onChange={handleSelectAll} /> 发票信息 客户信息 服务详情 金额 日期 状态 操作
handleSelectInvoice(invoice.id)} />
{invoice.invoice_number}
订单: {invoice.order_number}
创建于 {formatTime(invoice.created_at)}
{invoice.user_name}
{invoice.user_email}
{invoice.user_company && (
{invoice.user_company}
)} {invoice.user_phone && (
{invoice.user_phone}
)}
{invoice.service_type}
翻译员: {invoice.interpreter_name}
{invoice.service_description}
{formatCurrency(invoice.total_amount)}
服务费: {formatCurrency(invoice.amount)}
税费: {formatCurrency(invoice.tax_amount)}
开票: {formatDate(invoice.issue_date)}
到期: {formatDate(invoice.due_date)}
{invoice.paid_date && (
支付: {formatDate(invoice.paid_date)}
)}
{getStatusIcon(invoice.status)} {getStatusText(invoice.status)}
{/* 分页 */} {totalPages > 1 && (

显示第 {(currentPage - 1) * pageSize + 1} 到{' '} {Math.min(currentPage * pageSize, totalCount)} 项, 共 {totalCount}

)} )}
); }