summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/plant/upload/components/single-upload-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-document-list/plant/upload/components/single-upload-dialog.tsx')
-rw-r--r--lib/vendor-document-list/plant/upload/components/single-upload-dialog.tsx265
1 files changed, 265 insertions, 0 deletions
diff --git a/lib/vendor-document-list/plant/upload/components/single-upload-dialog.tsx b/lib/vendor-document-list/plant/upload/components/single-upload-dialog.tsx
new file mode 100644
index 00000000..a33a7160
--- /dev/null
+++ b/lib/vendor-document-list/plant/upload/components/single-upload-dialog.tsx
@@ -0,0 +1,265 @@
+// lib/vendor-document-list/plant/upload/components/single-upload-dialog.tsx
+"use client"
+
+import * as React from "react"
+import { useState } from "react"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} 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 { Badge } from "@/components/ui/badge"
+import { Alert, AlertDescription } from "@/components/ui/alert"
+import {
+ FileList,
+ FileListAction,
+ FileListIcon,
+ FileListInfo,
+ FileListItem,
+ FileListName,
+ FileListSize,
+} from "@/components/ui/file-list"
+import {
+ Upload,
+ X,
+ FileIcon,
+ Loader2,
+ AlertCircle
+} from "lucide-react"
+import { toast } from "sonner"
+import { StageSubmissionView } from "@/db/schema"
+
+interface SingleUploadDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ submission: StageSubmissionView
+ onUploadComplete?: () => void
+}
+
+export function SingleUploadDialog({
+ open,
+ onOpenChange,
+ submission,
+ onUploadComplete
+}: SingleUploadDialogProps) {
+ const [files, setFiles] = useState<File[]>([])
+ const [description, setDescription] = useState("")
+ const [isUploading, setIsUploading] = useState(false)
+ const fileInputRef = React.useRef<HTMLInputElement>(null)
+
+ // 파일 선택
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const fileList = e.target.files
+ if (fileList) {
+ setFiles(Array.from(fileList))
+ }
+ }
+
+ // 파일 제거
+ const removeFile = (index: number) => {
+ setFiles(prev => prev.filter((_, i) => i !== index))
+ }
+
+ // 업로드 처리
+ const handleUpload = async () => {
+ if (files.length === 0) {
+ toast.error("Please select files to upload")
+ return
+ }
+
+ setIsUploading(true)
+
+ try {
+ const formData = new FormData()
+
+ files.forEach((file) => {
+ formData.append("files", file)
+ })
+
+ formData.append("documentId", submission.documentId.toString())
+ formData.append("stageId", submission.stageId!.toString())
+ formData.append("description", description)
+
+ // 현재 리비전 + 1
+ const nextRevision = (submission.latestRevisionNumber || 0) + 1
+ formData.append("revision", nextRevision.toString())
+
+ const response = await fetch("/api/stage-submissions/upload", {
+ method: "POST",
+ body: formData,
+ })
+
+ if (!response.ok) {
+ throw new Error("Upload failed")
+ }
+
+ const result = await response.json()
+ toast.success(`Successfully uploaded ${files.length} file(s)`)
+
+ // 초기화 및 닫기
+ setFiles([])
+ setDescription("")
+ onOpenChange(false)
+ onUploadComplete?.()
+
+ } catch (error) {
+ console.error("Upload error:", error)
+ toast.error("Failed to upload files")
+ } finally {
+ setIsUploading(false)
+ }
+ }
+
+ const formatFileSize = (bytes: number) => {
+ if (bytes === 0) return '0 Bytes'
+ const k = 1024
+ const sizes = ['Bytes', 'KB', 'MB', 'GB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+ }
+
+ const totalSize = files.reduce((acc, file) => acc + file.size, 0)
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-2xl">
+ <DialogHeader>
+ <DialogTitle>Upload Documents</DialogTitle>
+ <DialogDescription>
+ Upload documents for this stage submission
+ </DialogDescription>
+ </DialogHeader>
+
+ {/* Document Info */}
+ <div className="grid gap-2 p-4 bg-muted rounded-lg">
+ <div className="flex items-center gap-2">
+ <span className="text-sm font-medium">Document:</span>
+ <span className="text-sm">{submission.docNumber}</span>
+ {submission.vendorDocNumber && (
+ <span className="text-sm text-muted-foreground">
+ ({submission.vendorDocNumber})
+ </span>
+ )}
+ </div>
+ <div className="flex items-center gap-2">
+ <span className="text-sm font-medium">Stage:</span>
+ <Badge variant="secondary">{submission.stageName}</Badge>
+ </div>
+ <div className="flex items-center gap-2">
+ <span className="text-sm font-medium">Current Revision:</span>
+ <span className="text-sm">Rev. {submission.latestRevisionNumber || 0}</span>
+ <Badge variant="outline" className="ml-2">
+ Next: Rev. {(submission.latestRevisionNumber || 0) + 1}
+ </Badge>
+ </div>
+ </div>
+
+ {/* File Upload Area */}
+ <div
+ className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors cursor-pointer"
+ onClick={() => fileInputRef.current?.click()}
+ >
+ <input
+ ref={fileInputRef}
+ type="file"
+ multiple
+ className="hidden"
+ onChange={handleFileChange}
+ accept="*/*"
+ />
+ <Upload className="mx-auto h-10 w-10 text-gray-400 mb-3" />
+ <p className="text-sm font-medium">Click to browse files</p>
+ <p className="text-xs text-gray-500 mt-1">
+ You can select multiple files
+ </p>
+ </div>
+
+ {/* File List */}
+ {files.length > 0 && (
+ <>
+ <FileList>
+ {files.map((file, index) => (
+ <FileListItem key={index}>
+ <FileListIcon>
+ <FileIcon className="h-4 w-4 text-muted-foreground" />
+ </FileListIcon>
+ <FileListInfo>
+ <FileListName>{file.name}</FileListName>
+ </FileListInfo>
+ <FileListSize>
+ {file.size}
+ </FileListSize>
+ <FileListAction>
+ <Button
+ variant="ghost"
+ size="icon"
+ className="h-8 w-8"
+ onClick={(e) => {
+ e.stopPropagation()
+ removeFile(index)
+ }}
+ disabled={isUploading}
+ >
+ <X className="h-4 w-4" />
+ </Button>
+ </FileListAction>
+ </FileListItem>
+ ))}
+ </FileList>
+
+ <div className="flex justify-between text-sm text-muted-foreground">
+ <span>{files.length} file(s) selected</span>
+ <span>Total: {formatFileSize(totalSize)}</span>
+ </div>
+ </>
+ )}
+
+ {/* Description */}
+ <div className="space-y-2">
+ <Label htmlFor="description">Description (Optional)</Label>
+ <Textarea
+ id="description"
+ placeholder="Add a description for this submission..."
+ value={description}
+ onChange={(e) => setDescription(e.target.value)}
+ rows={3}
+ />
+ </div>
+
+ <DialogFooter>
+ <Button
+ variant="outline"
+ onClick={() => onOpenChange(false)}
+ disabled={isUploading}
+ >
+ Cancel
+ </Button>
+ <Button
+ onClick={handleUpload}
+ disabled={files.length === 0 || isUploading}
+ className="gap-2"
+ >
+ {isUploading ? (
+ <>
+ <Loader2 className="h-4 w-4 animate-spin" />
+ Uploading...
+ </>
+ ) : (
+ <>
+ <Upload className="h-4 w-4" />
+ Upload
+ </>
+ )}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
+} \ No newline at end of file