diff options
Diffstat (limited to 'lib/welding/table/ocr-table-toolbar-actions.tsx')
| -rw-r--r-- | lib/welding/table/ocr-table-toolbar-actions.tsx | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/lib/welding/table/ocr-table-toolbar-actions.tsx b/lib/welding/table/ocr-table-toolbar-actions.tsx new file mode 100644 index 00000000..001b21cb --- /dev/null +++ b/lib/welding/table/ocr-table-toolbar-actions.tsx @@ -0,0 +1,297 @@ +"use client" + +import * as React from "react" +import { type Table } from "@tanstack/react-table" +import { Download, RefreshCcw, Upload, FileText, Loader2 } from "lucide-react" + +import { exportTableToExcel } from "@/lib/export" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Progress } from "@/components/ui/progress" +import { Badge } from "@/components/ui/badge" +import { toast } from "sonner" +import { OcrRow } from "@/db/schema" + +interface OcrTableToolbarActionsProps { + table: Table<OcrRow> +} + +interface UploadProgress { + stage: string + progress: number + message: string +} + +export function OcrTableToolbarActions({ table }: OcrTableToolbarActionsProps) { + const [isLoading, setIsLoading] = React.useState(false) + const [isUploading, setIsUploading] = React.useState(false) + const [uploadProgress, setUploadProgress] = React.useState<UploadProgress | null>(null) + const [isUploadDialogOpen, setIsUploadDialogOpen] = React.useState(false) + const [selectedFile, setSelectedFile] = React.useState<File | null>(null) + const fileInputRef = React.useRef<HTMLInputElement>(null) + + + const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => { + const file = event.target.files?.[0] + if (file) { + setSelectedFile(file) + } + } + + const validateFile = (file: File): string | null => { + // 파일 크기 체크 (10MB) + if (file.size > 10 * 1024 * 1024) { + return "File size must be less than 10MB" + } + + // 파일 타입 체크 + const allowedTypes = [ + 'application/pdf', + 'image/jpeg', + 'image/jpg', + 'image/png', + 'image/tiff', + 'image/bmp' + ] + + if (!allowedTypes.includes(file.type)) { + return "Only PDF and image files (JPG, PNG, TIFF, BMP) are supported" + } + + return null + } + + const uploadFile = async () => { + if (!selectedFile) { + toast.error("Please select a file first") + return + } + + const validationError = validateFile(selectedFile) + if (validationError) { + toast.error(validationError) + return + } + + try { + setIsUploading(true) + setUploadProgress({ + stage: "preparing", + progress: 10, + message: "Preparing file upload..." + }) + + const formData = new FormData() + formData.append('file', selectedFile) + + setUploadProgress({ + stage: "uploading", + progress: 30, + message: "Uploading file and processing..." + }) + + const response = await fetch('/api/ocr/enhanced', { + method: 'POST', + body: formData, + }) + + setUploadProgress({ + stage: "processing", + progress: 70, + message: "Analyzing document with OCR..." + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.error || 'OCR processing failed') + } + + const result = await response.json() + + setUploadProgress({ + stage: "saving", + progress: 90, + message: "Saving results to database..." + }) + + if (result.success) { + setUploadProgress({ + stage: "complete", + progress: 100, + message: "OCR processing completed successfully!" + }) + + toast.success( + `OCR completed! Extracted ${result.metadata.totalRows} rows from ${result.metadata.totalTables} tables`, + { + description: result.warnings?.length + ? `Warnings: ${result.warnings.join(', ')}` + : undefined + } + ) + + // 성공 후 다이얼로그 닫기 및 상태 초기화 + setTimeout(() => { + setIsUploadDialogOpen(false) + setSelectedFile(null) + setUploadProgress(null) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + + // 테이블 새로고침 + window.location.reload() + }, 2000) + + } else { + throw new Error(result.error || 'Unknown error occurred') + } + + } catch (error) { + console.error('Error uploading file:', error) + toast.error( + error instanceof Error + ? error.message + : 'An error occurred while processing the file' + ) + setUploadProgress(null) + } finally { + setIsUploading(false) + } + } + + const resetUpload = () => { + setSelectedFile(null) + setUploadProgress(null) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + } + + return ( + <div className="flex items-center gap-2"> + {/* OCR 업로드 다이얼로그 */} + <Dialog open={isUploadDialogOpen} onOpenChange={setIsUploadDialogOpen}> + <DialogTrigger asChild> + <Button variant="samsung" size="sm" className="gap-2"> + <Upload className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">Upload OCR</span> + </Button> + </DialogTrigger> + <DialogContent className="sm:max-w-md"> + <DialogHeader> + <DialogTitle>Upload Document for OCR</DialogTitle> + <DialogDescription> + Upload a PDF or image file to extract table data using OCR technology. + </DialogDescription> + </DialogHeader> + + <div className="space-y-4"> + {/* 파일 선택 */} + <div className="space-y-2"> + <Label htmlFor="file-upload">Select File</Label> + <Input + ref={fileInputRef} + id="file-upload" + type="file" + accept=".pdf,.jpg,.jpeg,.png,.tiff,.bmp" + onChange={handleFileSelect} + disabled={isUploading} + /> + <p className="text-xs text-muted-foreground"> + Supported formats: PDF, JPG, PNG, TIFF, BMP (Max 10MB) + </p> + </div> + + {/* 선택된 파일 정보 */} + {selectedFile && ( + <div className="rounded-lg border p-3 space-y-2"> + <div className="flex items-center gap-2"> + <FileText className="size-4 text-muted-foreground" /> + <span className="text-sm font-medium">{selectedFile.name}</span> + </div> + <div className="flex items-center gap-4 text-xs text-muted-foreground"> + <span>Size: {(selectedFile.size / 1024 / 1024).toFixed(2)} MB</span> + <span>Type: {selectedFile.type}</span> + </div> + </div> + )} + + {/* 업로드 진행상황 */} + {uploadProgress && ( + <div className="space-y-3"> + <div className="flex items-center justify-between"> + <span className="text-sm font-medium">Processing...</span> + <Badge variant={uploadProgress.stage === "complete" ? "default" : "secondary"}> + {uploadProgress.stage} + </Badge> + </div> + <Progress value={uploadProgress.progress} className="h-2" /> + <p className="text-xs text-muted-foreground"> + {uploadProgress.message} + </p> + </div> + )} + + {/* 액션 버튼들 */} + <div className="flex justify-end gap-2"> + <Button + variant="outline" + size="sm" + onClick={() => { + if (isUploading) { + // 업로드 중이면 취소 불가능하다는 메시지 + toast.warning("Cannot cancel while processing. Please wait...") + } else { + setIsUploadDialogOpen(false) + resetUpload() + } + }} + disabled={isUploading && uploadProgress?.stage !== "complete"} + > + {isUploading ? "Close" : "Cancel"} + </Button> + <Button + size="sm" + onClick={uploadFile} + disabled={!selectedFile || isUploading} + className="gap-2" + > + {isUploading ? ( + <Loader2 className="size-4 animate-spin" aria-hidden="true" /> + ) : ( + <Upload className="size-4" aria-hidden="true" /> + )} + {isUploading ? "Processing..." : "Start OCR"} + </Button> + </div> + </div> + </DialogContent> + </Dialog> + {/* Export 버튼 */} + <Button + variant="outline" + size="sm" + onClick={() => + exportTableToExcel(table, { + filename: "OCR Result", + excludeColumns: ["select", "actions"], + }) + } + className="gap-2" + > + <Download className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">Export</span> + </Button> + </div> + ) +}
\ No newline at end of file |
