From 4ee8b24cfadf47452807fa2af801385ed60ab47c Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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<FileUpload[]>([])
+  const [isUploading, setIsUploading] = React.useState(false)
+  const fileInputRef = React.useRef<HTMLInputElement>(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<HTMLInputElement>) => {
+    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 (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-4xl max-h-[80vh]">
+        <DialogHeader>
+          <DialogTitle>Upload Documents for TBE</DialogTitle>
+          <DialogDescription>
+            {sessionDetail?.session?.sessionCode} - Technical Bid Evaluation 문서 업로드
+          </DialogDescription>
+        </DialogHeader>
+        
+        <div className="space-y-4">
+          {/* File Upload Area */}
+          <div className="border-2 border-dashed rounded-lg p-6 text-center">
+            <Upload className="h-12 w-12 mx-auto text-muted-foreground mb-2" />
+            <p className="text-sm text-muted-foreground mb-2">
+              파일을 드래그하거나 클릭하여 선택하세요
+            </p>
+            <Input
+              ref={fileInputRef}
+              type="file"
+              multiple
+              onChange={handleFileSelect}
+              className="hidden"
+              accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png,.zip"
+            />
+            <Button
+              variant="outline"
+              onClick={() => fileInputRef.current?.click()}
+              disabled={isUploading}
+            >
+              파일 선택
+            </Button>
+          </div>
+          
+          {/* Selected Files List */}
+          {files.length > 0 && (
+            <ScrollArea className="h-[300px] border rounded-lg p-4">
+              <div className="space-y-4">
+                {files.map(fileUpload => (
+                  <div key={fileUpload.id} className="border rounded-lg p-4 space-y-3">
+                    <div className="flex items-start justify-between">
+                      <div className="flex items-center gap-2">
+                        <FileText className="h-5 w-5 text-muted-foreground" />
+                        <div>
+                          <p className="font-medium text-sm">{fileUpload.file.name}</p>
+                          <p className="text-xs text-muted-foreground">
+                            {formatFileSize(fileUpload.file.size)}
+                          </p>
+                        </div>
+                      </div>
+                      <div className="flex items-center gap-2">
+                        {fileUpload.status === "uploading" && (
+                          <Loader2 className="h-4 w-4 animate-spin" />
+                        )}
+                        {fileUpload.status === "success" && (
+                          <Badge variant="default">Uploaded</Badge>
+                        )}
+                        {fileUpload.status === "error" && (
+                          <Badge variant="destructive">Failed</Badge>
+                        )}
+                        <Button
+                          variant="ghost"
+                          size="sm"
+                          onClick={() => handleRemoveFile(fileUpload.id)}
+                          disabled={isUploading}
+                        >
+                          <X className="h-4 w-4" />
+                        </Button>
+                      </div>
+                    </div>
+                    
+                    {fileUpload.status !== "success" && (
+                      <>
+                        <div className="grid grid-cols-2 gap-3">
+                          <div>
+                            <Label className="text-xs">Document Type</Label>
+                            <Select
+                              value={fileUpload.documentType}
+                              onValueChange={(value) => handleUpdateFile(fileUpload.id, "documentType", value)}
+                              disabled={isUploading}
+                            >
+                              <SelectTrigger className="h-8 text-xs">
+                                <SelectValue />
+                              </SelectTrigger>
+                              <SelectContent>
+                                {documentTypes.map(type => (
+                                  <SelectItem key={type.value} value={type.value}>
+                                    {type.label}
+                                  </SelectItem>
+                                ))}
+                              </SelectContent>
+                            </Select>
+                          </div>
+                        </div>
+                        
+                        <div>
+                          <Label className="text-xs">Description (Optional)</Label>
+                          <Textarea
+                            value={fileUpload.description}
+                            onChange={(e) => handleUpdateFile(fileUpload.id, "description", e.target.value)}
+                            placeholder="문서에 대한 설명을 입력하세요..."
+                            className="min-h-[60px] text-xs"
+                            disabled={isUploading}
+                          />
+                        </div>
+                      </>
+                    )}
+                    
+                    {fileUpload.errorMessage && (
+                      <p className="text-xs text-red-600">{fileUpload.errorMessage}</p>
+                    )}
+                  </div>
+                ))}
+              </div>
+            </ScrollArea>
+          )}
+        </div>
+        
+        <DialogFooter>
+          <Button
+            variant="outline"
+            onClick={() => onOpenChange(false)}
+            disabled={isUploading}
+          >
+            취소
+          </Button>
+          <Button
+            onClick={handleUploadAll}
+            disabled={files.length === 0 || isUploading}
+          >
+            {isUploading ? (
+              <>
+                <Loader2 className="h-4 w-4 mr-2 animate-spin" />
+                업로드 중...
+              </>
+            ) : (
+              <>
+                <Upload className="h-4 w-4 mr-2" />
+                업로드 ({files.filter(f => f.status !== "success").length}개)
+              </>
+            )}
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

