first
This commit is contained in:
140
frontend/src/pages/UploadPage.tsx
Normal file
140
frontend/src/pages/UploadPage.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { useToast } from '@/components/ui/toast'
|
||||
import { useUploadStore } from '@/store/uploadStore'
|
||||
import { apiClient } from '@/services/api'
|
||||
|
||||
export default function UploadPage() {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { toast } = useToast()
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([])
|
||||
const { setBatchId, setFiles, setUploadProgress } = useUploadStore()
|
||||
|
||||
const uploadMutation = useMutation({
|
||||
mutationFn: (files: File[]) => apiClient.uploadFiles(files),
|
||||
onSuccess: (data) => {
|
||||
setBatchId(data.batch_id)
|
||||
setFiles(data.files)
|
||||
toast({
|
||||
title: t('upload.uploadSuccess'),
|
||||
description: t('upload.fileCount', { count: data.files.length }),
|
||||
variant: 'success',
|
||||
})
|
||||
navigate('/processing')
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast({
|
||||
title: t('upload.uploadError'),
|
||||
description: error.response?.data?.detail || t('errors.networkError'),
|
||||
variant: 'destructive',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const handleFilesSelected = (files: File[]) => {
|
||||
setSelectedFiles((prev) => [...prev, ...files])
|
||||
}
|
||||
|
||||
const handleRemoveFile = (index: number) => {
|
||||
setSelectedFiles((prev) => prev.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
const handleClearAll = () => {
|
||||
setSelectedFiles([])
|
||||
setUploadProgress(0)
|
||||
}
|
||||
|
||||
const handleUpload = () => {
|
||||
if (selectedFiles.length === 0) {
|
||||
toast({
|
||||
title: t('errors.validationError'),
|
||||
description: '請選擇至少一個檔案',
|
||||
variant: 'destructive',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uploadMutation.mutate(selectedFiles)
|
||||
}
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">{t('upload.title')}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
選擇要進行 OCR 處理的檔案,支援圖片、PDF 和 Office 文件
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FileUpload
|
||||
onFilesSelected={handleFilesSelected}
|
||||
disabled={uploadMutation.isPending}
|
||||
/>
|
||||
|
||||
{selectedFiles.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg">
|
||||
{t('upload.selectedFiles')} ({selectedFiles.length})
|
||||
</CardTitle>
|
||||
<Button variant="outline" size="sm" onClick={handleClearAll}>
|
||||
{t('upload.clearAll')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
{selectedFiles.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-3 bg-muted rounded-md"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-foreground truncate">{file.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{formatFileSize(file.size)}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRemoveFile(index)}
|
||||
disabled={uploadMutation.isPending}
|
||||
>
|
||||
{t('upload.removeFile')}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClearAll}
|
||||
disabled={uploadMutation.isPending}
|
||||
>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleUpload} disabled={uploadMutation.isPending}>
|
||||
{uploadMutation.isPending ? t('upload.uploading') : t('upload.uploadButton')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user