From 4ee8b24cfadf47452807fa2af801385ed60ab47c Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 15 Sep 2025 14:41:01 +0000 Subject: (대표님) 작업사항 - rfqLast, tbeLast, pdfTron, userAuth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendor/vendor-document-upload-dialog.tsx | 326 +++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 lib/tbe-last/vendor/vendor-document-upload-dialog.tsx (limited to 'lib/tbe-last/vendor/vendor-document-upload-dialog.tsx') diff --git a/lib/tbe-last/vendor/vendor-document-upload-dialog.tsx b/lib/tbe-last/vendor/vendor-document-upload-dialog.tsx new file mode 100644 index 00000000..c6f6c3d5 --- /dev/null +++ b/lib/tbe-last/vendor/vendor-document-upload-dialog.tsx @@ -0,0 +1,326 @@ +// lib/vendor-rfq-response/vendor-tbe-table/vendor-document-upload-dialog.tsx + +"use client" + +import * as React from "react" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Textarea } from "@/components/ui/textarea" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Badge } from "@/components/ui/badge" +import { ScrollArea } from "@/components/ui/scroll-area" +import { toast } from "sonner" +import { Upload, FileText, X, Loader2 } from "lucide-react" +import { uploadVendorDocument } from "@/lib/tbe-last/service" + +interface VendorDocumentUploadDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + sessionId: number | null + sessionDetail: any + onUploadSuccess: () => void +} + +interface FileUpload { + id: string + file: File + documentType: string + description: string + status: "pending" | "uploading" | "success" | "error" + errorMessage?: string +} + +export function VendorDocumentUploadDialog({ + open, + onOpenChange, + sessionId, + sessionDetail, + onUploadSuccess +}: VendorDocumentUploadDialogProps) { + + const [files, setFiles] = React.useState([]) + const [isUploading, setIsUploading] = React.useState(false) + const fileInputRef = React.useRef(null) + + // Document types for vendor + const documentTypes = [ + { value: "technical_spec", label: "Technical Specification" }, + { value: "compliance_cert", label: "Compliance Certificate" }, + { value: "test_report", label: "Test Report" }, + { value: "drawing", label: "Drawing" }, + { value: "datasheet", label: "Datasheet" }, + { value: "quality_doc", label: "Quality Document" }, + { value: "warranty", label: "Warranty Document" }, + { value: "other", label: "Other" }, + ] + + // Handle file selection + const handleFileSelect = (e: React.ChangeEvent) => { + const selectedFiles = Array.from(e.target.files || []) + + const newFiles: FileUpload[] = selectedFiles.map(file => ({ + id: Math.random().toString(36).substr(2, 9), + file, + documentType: "technical_spec", + description: "", + status: "pending" as const + })) + + setFiles(prev => [...prev, ...newFiles]) + + // Reset input + if (fileInputRef.current) { + fileInputRef.current.value = "" + } + } + + // Remove file + const handleRemoveFile = (id: string) => { + setFiles(prev => prev.filter(f => f.id !== id)) + } + + // Update file details + const handleUpdateFile = (id: string, field: keyof FileUpload, value: string) => { + setFiles(prev => prev.map(f => + f.id === id ? { ...f, [field]: value } : f + )) + } + + // Upload all files + const handleUploadAll = async () => { + if (!sessionId || files.length === 0) return + + setIsUploading(true) + + try { + for (const fileUpload of files) { + if (fileUpload.status === "success") continue + + // Update status to uploading + setFiles(prev => prev.map(f => + f.id === fileUpload.id ? { ...f, status: "uploading" } : f + )) + + try { + // Create FormData for upload + const formData = new FormData() + formData.append("file", fileUpload.file) + formData.append("sessionId", sessionId.toString()) + formData.append("documentType", fileUpload.documentType) + formData.append("description", fileUpload.description) + + // Upload file (API call) + const response = await fetch("/api/tbe/vendor-documents/upload", { + method: "POST", + body: formData + }) + + if (!response.ok) throw new Error("Upload failed") + + // Update status to success + setFiles(prev => prev.map(f => + f.id === fileUpload.id ? { ...f, status: "success" } : f + )) + + } catch (error) { + // Update status to error + setFiles(prev => prev.map(f => + f.id === fileUpload.id ? { + ...f, + status: "error", + errorMessage: error instanceof Error ? error.message : "Upload failed" + } : f + )) + } + } + + // Check if all files uploaded successfully + const allSuccess = files.every(f => f.status === "success") + + if (allSuccess) { + toast.success("모든 문서가 업로드되었습니다") + onUploadSuccess() + onOpenChange(false) + setFiles([]) + } else { + const failedCount = files.filter(f => f.status === "error").length + toast.error(`${failedCount}개 문서 업로드 실패`) + } + + } catch (error) { + console.error("Upload error:", error) + toast.error("문서 업로드 중 오류가 발생했습니다") + } finally { + setIsUploading(false) + } + } + + // Get file size in readable format + const formatFileSize = (bytes: number) => { + if (bytes < 1024) return bytes + " B" + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB" + return (bytes / (1024 * 1024)).toFixed(1) + " MB" + } + + return ( + + + + Upload Documents for TBE + + {sessionDetail?.session?.sessionCode} - Technical Bid Evaluation 문서 업로드 + + + +
+ {/* File Upload Area */} +
+ +

+ 파일을 드래그하거나 클릭하여 선택하세요 +

+ + +
+ + {/* Selected Files List */} + {files.length > 0 && ( + +
+ {files.map(fileUpload => ( +
+
+
+ +
+

{fileUpload.file.name}

+

+ {formatFileSize(fileUpload.file.size)} +

+
+
+
+ {fileUpload.status === "uploading" && ( + + )} + {fileUpload.status === "success" && ( + Uploaded + )} + {fileUpload.status === "error" && ( + Failed + )} + +
+
+ + {fileUpload.status !== "success" && ( + <> +
+
+ + +
+
+ +
+ +