summaryrefslogtreecommitdiff
path: root/lib/welding/table/ocr-table-toolbar-actions.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/welding/table/ocr-table-toolbar-actions.tsx')
-rw-r--r--lib/welding/table/ocr-table-toolbar-actions.tsx297
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