import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { useMutation, useQuery } from '@tanstack/react-query' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { useToast } from '@/components/ui/toast' import { useUploadStore } from '@/store/uploadStore' import { apiClient } from '@/services/api' import type { ExportRequest, ExportOptions } from '@/types/api' import { Download, FileText, FileJson, FileSpreadsheet, FileCode, FileType, AlertCircle, Settings, CheckCircle2, ArrowLeft } from 'lucide-react' type ExportFormat = 'txt' | 'json' | 'excel' | 'markdown' | 'pdf' export default function ExportPage() { const { t } = useTranslation() const navigate = useNavigate() const { toast } = useToast() const { batchId } = useUploadStore() const [format, setFormat] = useState('txt') const [selectedRuleId, setSelectedRuleId] = useState() const [options, setOptions] = useState({ confidence_threshold: 0.5, include_metadata: true, filename_pattern: '{filename}_ocr', css_template: 'default', }) // Fetch export rules const { data: exportRules } = useQuery({ queryKey: ['exportRules'], queryFn: () => apiClient.getExportRules(), enabled: true, }) // Fetch CSS templates const { data: cssTemplates } = useQuery({ queryKey: ['cssTemplates'], queryFn: () => apiClient.getCSSTemplates(), enabled: format === 'pdf', }) // Export mutation const exportMutation = useMutation({ mutationFn: async (data: ExportRequest) => { const blob = await apiClient.exportResults(data) return { blob, format: data.format } }, onSuccess: ({ blob, format: exportFormat }) => { // Create download link const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url // Determine file extension const extensions: Record = { txt: 'txt', json: 'json', excel: 'xlsx', markdown: 'md', pdf: 'pdf', } a.download = `batch_${batchId}_export.${extensions[exportFormat]}` document.body.appendChild(a) a.click() window.URL.revokeObjectURL(url) document.body.removeChild(a) toast({ title: t('export.exportSuccess'), description: `已成功匯出為 ${exportFormat.toUpperCase()} 格式`, variant: 'success', }) }, onError: (error: any) => { toast({ title: t('export.exportError'), description: error.response?.data?.detail || t('errors.networkError'), variant: 'destructive', }) }, }) const handleExport = () => { if (!batchId) { toast({ title: t('errors.validationError'), description: '請先上傳並處理檔案', variant: 'destructive', }) return } const exportRequest: ExportRequest = { batch_id: batchId, format, rule_id: selectedRuleId, options, } exportMutation.mutate(exportRequest) } const handleFormatChange = (newFormat: ExportFormat) => { setFormat(newFormat) // Reset CSS template if switching away from PDF if (newFormat !== 'pdf') { setOptions((prev) => ({ ...prev, css_template: undefined })) } else { setOptions((prev) => ({ ...prev, css_template: 'default' })) } } const handleRuleChange = (ruleId: number | undefined) => { setSelectedRuleId(ruleId) if (ruleId && exportRules) { const rule = exportRules.find((r) => r.id === ruleId) if (rule && rule.config_json) { // Apply rule configuration setOptions((prev) => ({ ...prev, ...rule.config_json, css_template: rule.css_template || prev.css_template, })) } } } const formatIcons = { txt: FileText, json: FileJson, excel: FileSpreadsheet, markdown: FileCode, pdf: FileType, } // Show helpful message when no batch is selected if (!batchId) { return (
{t('export.title')}

{t('export.noBatchMessage', { defaultValue: '尚未選擇任何批次。請先上傳並完成處理檔案。' })}

) } return (
{/* Page Header */}

{t('export.title')}

批次 ID: {batchId}

{/* Left Column - Configuration */}
{/* Format Selection */} {t('export.format')} 選擇要匯出的檔案格式
{(['txt', 'json', 'excel', 'markdown', 'pdf'] as ExportFormat[]).map((fmt) => { const Icon = formatIcons[fmt] return ( ) })}
{/* Export Rules */} {exportRules && exportRules.length > 0 && ( {t('export.rules.title')} 選擇預設的匯出規則 )} {/* Export Options */} {t('export.options.title')} 自訂匯出參數 {/* Confidence Threshold */}
{(options.confidence_threshold * 100).toFixed(0)}%
setOptions((prev) => ({ ...prev, confidence_threshold: Number(e.target.value), })) } className="w-full h-2 bg-muted rounded-lg appearance-none cursor-pointer accent-primary" />
0% 50% 100%
{/* Include Metadata */}
setOptions((prev) => ({ ...prev, include_metadata: e.target.checked, })) } className="w-5 h-5 border border-border rounded accent-primary" />
{/* Filename Pattern */}
setOptions((prev) => ({ ...prev, filename_pattern: e.target.value, })) } className="w-full px-4 py-3 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-colors font-mono text-sm" placeholder="{filename}_ocr" />

可用變數: {'{filename}'},{' '} {'{batch_id}'},{' '} {'{date}'}

{/* CSS Template (PDF only) */} {format === 'pdf' && cssTemplates && cssTemplates.length > 0 && (
)}
{/* Right Column - Preview */}
匯出預覽 當前設定概覽
格式
{(() => { const Icon = formatIcons[format] return })()} {format.toUpperCase()}
{selectedRuleId && exportRules && (
匯出規則
{exportRules.find((r) => r.id === selectedRuleId)?.rule_name || '未選擇'}
)}
準確率門檻
{(options.confidence_threshold * 100).toFixed(0)}%
包含元數據
{options.include_metadata ? '是' : '否'}
檔名模式
{options.filename_pattern || '{filename}_ocr'}
) }