import { useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import PDFViewer from '@/components/PDFViewer' import { useToast } from '@/components/ui/toast' import { apiClientV2 } from '@/services/apiV2' import { FileText, Download, AlertCircle, TrendingUp, Clock, Layers, FileJson, Loader2 } from 'lucide-react' import { Badge } from '@/components/ui/badge' import TaskNotFound from '@/components/TaskNotFound' import { useTaskValidation } from '@/hooks/useTaskValidation' export default function ResultsPage() { const { t } = useTranslation() const navigate = useNavigate() const { toast } = useToast() // Use shared hook for task validation const { taskId, taskDetail, isLoading, isNotFound, clearAndReset } = useTaskValidation({ refetchInterval: (query) => { const data = query.state.data if (!data) return 2000 if (data.status === 'completed' || data.status === 'failed') { return false } return 2000 }, }) // Construct PDF URL for preview - memoize to prevent unnecessary reloads // Must be called unconditionally before any early returns (React hooks rule) const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000' const pdfUrl = useMemo(() => { return taskId ? `${API_BASE_URL}/api/v2/tasks/${taskId}/download/pdf` : '' }, [taskId, API_BASE_URL]) // Get auth token for PDF preview - memoize to prevent new object reference each render const pdfHttpHeaders = useMemo(() => { const authToken = localStorage.getItem('auth_token_v2') return authToken ? { Authorization: `Bearer ${authToken}` } : undefined }, []) const handleDownloadPDF = async () => { if (!taskId) return try { await apiClientV2.downloadPDF(taskId) toast({ title: t('export.exportSuccess'), description: 'PDF 已下載', variant: 'success', }) } catch (error: any) { toast({ title: t('export.exportError'), description: error.response?.data?.detail || t('errors.networkError'), variant: 'destructive', }) } } const handleDownloadMarkdown = async () => { if (!taskId) return try { await apiClientV2.downloadMarkdown(taskId) toast({ title: t('export.exportSuccess'), description: 'Markdown 已下載', variant: 'success', }) } catch (error: any) { toast({ title: t('export.exportError'), description: error.response?.data?.detail || t('errors.networkError'), variant: 'destructive', }) } } const handleDownloadJSON = async () => { if (!taskId) return try { await apiClientV2.downloadJSON(taskId) toast({ title: t('export.exportSuccess'), description: 'JSON 已下載', variant: 'success', }) } catch (error: any) { toast({ title: t('export.exportError'), description: error.response?.data?.detail || t('errors.networkError'), variant: 'destructive', }) } } const getStatusBadge = (status: string) => { switch (status) { case 'completed': return 已完成 case 'processing': return 處理中 case 'failed': return 失敗 default: return 待處理 } } // Show loading while validating task if (isLoading) { return (

載入任務結果...

) } // Show message when task was deleted if (isNotFound) { return } // Show helpful message when no task is selected if (!taskId) { return (
{t('results.title')}

{t('results.noBatchMessage', { defaultValue: '尚未選擇任何任務。請先上傳並處理檔案。' })}

) } // Fallback for no task detail (shouldn't happen with proper validation) if (!taskDetail) { return (
任務不存在
) } const isCompleted = taskDetail.status === 'completed' return (
{/* Page Header */}

{t('results.title')}

任務 ID: {taskId} {taskDetail.filename && ` · ${taskDetail.filename}`}

{getStatusBadge(taskDetail.status)} {isCompleted && ( <> )}
{/* Stats Grid */} {isCompleted && (

處理時間

{taskDetail.processing_time_ms ? (taskDetail.processing_time_ms / 1000).toFixed(2) : '0'}s

處理狀態

成功

任務類型

OCR

)} {/* Results Preview */} {isCompleted ? ( ) : taskDetail.status === 'processing' ? (

正在處理中...

請稍候,OCR 處理需要一些時間

) : taskDetail.status === 'failed' ? (

處理失敗

{taskDetail.error_message && (

{taskDetail.error_message}

)}
) : (

等待處理

請前往處理頁面啟動 OCR 處理

)}
) }