summaryrefslogtreecommitdiff
path: root/lib/tbe-last/vendor/vendor-document-upload-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tbe-last/vendor/vendor-document-upload-dialog.tsx')
-rw-r--r--lib/tbe-last/vendor/vendor-document-upload-dialog.tsx326
1 files changed, 326 insertions, 0 deletions
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