import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { apiClientV2 } from '@/services/apiV2' import type { Task, TaskStats, TaskStatus } from '@/types/apiV2' import { Clock, CheckCircle2, XCircle, Loader2, Download, Trash2, Eye, FileText, AlertCircle, RefreshCw, Filter, Play, X, RotateCcw, } from 'lucide-react' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' export default function TaskHistoryPage() { const navigate = useNavigate() const [tasks, setTasks] = useState([]) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState('') // Filters const [statusFilter, setStatusFilter] = useState('all') const [filenameSearch, setFilenameSearch] = useState('') const [dateFrom, setDateFrom] = useState('') const [dateTo, setDateTo] = useState('') const [page, setPage] = useState(1) const [pageSize] = useState(20) const [total, setTotal] = useState(0) const [hasMore, setHasMore] = useState(false) // Fetch tasks const fetchTasks = async () => { try { setLoading(true) setError('') const response = await apiClientV2.listTasks({ status: statusFilter === 'all' ? undefined : statusFilter, filename: filenameSearch || undefined, date_from: dateFrom || undefined, date_to: dateTo || undefined, page, page_size: pageSize, order_by: 'created_at', order_desc: true, }) setTasks(response.tasks) setTotal(response.total) setHasMore(response.has_more) } catch (err: any) { setError(err.response?.data?.detail || '載入任務失敗') } finally { setLoading(false) } } // Reset to page 1 when filters change const handleFilterChange = () => { setPage(1) } // Fetch stats const fetchStats = async () => { try { const statsData = await apiClientV2.getTaskStats() setStats(statsData) } catch (err) { console.error('Failed to fetch stats:', err) } } // Initial load useEffect(() => { fetchTasks() }, [statusFilter, filenameSearch, dateFrom, dateTo, page]) useEffect(() => { fetchStats() }, []) // Delete task const handleDelete = async (taskId: string) => { if (!confirm('確定要刪除此任務嗎?')) return try { await apiClientV2.deleteTask(taskId) fetchTasks() fetchStats() } catch (err: any) { alert(err.response?.data?.detail || '刪除任務失敗') } } // View task details const handleViewDetails = (taskId: string) => { navigate(`/tasks/${taskId}`) } // Download handlers const handleDownload = async (taskId: string, format: 'json' | 'markdown' | 'pdf') => { try { if (format === 'json') { await apiClientV2.downloadJSON(taskId) } else if (format === 'markdown') { await apiClientV2.downloadMarkdown(taskId) } else if (format === 'pdf') { await apiClientV2.downloadPDF(taskId) } } catch (err: any) { alert(err.response?.data?.detail || `下載 ${format.toUpperCase()} 檔案失敗`) } } // Task management handlers const handleStartTask = async (taskId: string) => { try { await apiClientV2.startTask(taskId) fetchTasks() } catch (err: any) { alert(err.response?.data?.detail || '啟動任務失敗') } } const handleCancelTask = async (taskId: string) => { if (!confirm('確定要取消此任務嗎?')) return try { await apiClientV2.cancelTask(taskId) fetchTasks() fetchStats() } catch (err: any) { alert(err.response?.data?.detail || '取消任務失敗') } } const handleRetryTask = async (taskId: string) => { try { await apiClientV2.retryTask(taskId) fetchTasks() fetchStats() } catch (err: any) { alert(err.response?.data?.detail || '重試任務失敗') } } // Format date const formatDate = (dateStr: string) => { const date = new Date(dateStr) return date.toLocaleString('zh-TW') } // Format processing time const formatProcessingTime = (ms: number | null) => { if (!ms) return '-' if (ms < 1000) return `${ms}ms` return `${(ms / 1000).toFixed(2)}s` } // Get status badge const getStatusBadge = (status: TaskStatus) => { const variants: Record = { pending: { variant: 'secondary', icon: Clock, label: '待處理', }, processing: { variant: 'default', icon: Loader2, label: '處理中', }, completed: { variant: 'default', icon: CheckCircle2, label: '已完成', }, failed: { variant: 'destructive', icon: XCircle, label: '失敗', }, } const config = variants[status] const Icon = config.icon return ( {config.label} ) } return (
{/* Header */}

任務歷史

查看和管理您的 OCR 任務

{/* Statistics */} {stats && (
總計
{stats.total}
待處理
{stats.pending}
處理中
{stats.processing}
已完成
{stats.completed}
失敗
{stats.failed}
)} {/* Filters */} 篩選條件
{ setFilenameSearch(e.target.value) handleFilterChange() }} placeholder="搜尋檔案名稱" className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
{ setDateFrom(e.target.value) handleFilterChange() }} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
{ setDateTo(e.target.value) handleFilterChange() }} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
{(statusFilter !== 'all' || filenameSearch || dateFrom || dateTo) && (
)}
{/* Error Alert */} {error && (

{error}

)} {/* Task List */} 任務列表 共 {total} 個任務 {hasMore && `(顯示第 ${page} 頁)`} {loading ? (
) : tasks.length === 0 ? (

暫無任務

) : ( <> 檔案名稱 狀態 建立時間 完成時間 處理時間 操作 {tasks.map((task) => ( {task.filename || '未命名檔案'} {getStatusBadge(task.status)} {formatDate(task.created_at)} {task.completed_at ? formatDate(task.completed_at) : '-'} {formatProcessingTime(task.processing_time_ms)}
{/* Task management actions */} {task.status === 'pending' && ( <> )} {task.status === 'processing' && ( )} {task.status === 'failed' && ( )} {/* Download actions for completed tasks */} {task.status === 'completed' && ( <> {task.result_json_path && ( )} {task.result_markdown_path && ( )} {task.result_pdf_path && ( )} )} {/* Delete button for all statuses */}
))}
{/* Pagination */}
顯示 {(page - 1) * pageSize + 1} - {Math.min(page * pageSize, total)} / 共{' '} {total} 個
)}
) }