"use client" import React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Button } from "@/components/ui/button" import { Progress } from "@/components/ui/progress" import { Upload, FileText, X, Loader2, CheckCircle, Paperclip } from "lucide-react" import { toast } from "sonner" import { useSession } from "next-auth/react" /* ------------------------------------------------------------------------------------------------- * Schema & Types * -----------------------------------------------------------------------------------------------*/ // 파일 검증 스키마 const MAX_FILE_SIZE = 1024 * 1024 * 1024 // 50MB const ACCEPTED_FILE_TYPES = [ 'application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel', 'image/jpeg', 'image/png', 'image/gif', 'text/plain', 'application/zip', 'application/x-zip-compressed', // Presentations (added) 'application/vnd.ms-powerpoint', // .ppt 'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx ] const attachmentUploadSchema = z.object({ attachments: z .array(z.instanceof(File)) .min(1, "Please upload at least 1 file") // .max(10, "Maximum 10 files can be uploaded") .refine( (files) => files.every((file) => file.size <= MAX_FILE_SIZE), "File size must be 50MB or less" ) .refine( (files) => files.every((file) => ACCEPTED_FILE_TYPES.includes(file.type)), "Unsupported file format" ), }) interface AddAttachmentDialogProps { open: boolean onOpenChange: (open: boolean) => void revisionId: number revisionName: string onSuccess?: (result?: any) => void } /* ------------------------------------------------------------------------------------------------- * File Upload Component * -----------------------------------------------------------------------------------------------*/ function FileUploadArea({ files, onFilesChange }: { files: File[] onFilesChange: (files: File[]) => void }) { const fileInputRef = React.useRef(null) const handleFileSelect = (event: React.ChangeEvent) => { const selectedFiles = Array.from(event.target.files || []) if (selectedFiles.length > 0) { onFilesChange([...files, ...selectedFiles]) } } const handleDrop = (event: React.DragEvent) => { event.preventDefault() const droppedFiles = Array.from(event.dataTransfer.files) if (droppedFiles.length > 0) { onFilesChange([...files, ...droppedFiles]) } } const handleDragOver = (event: React.DragEvent) => { event.preventDefault() } const removeFile = (index: number) => { onFilesChange(files.filter((_, i) => i !== index)) } 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] } return (
fileInputRef.current?.click()} >

Drag files to add here or click to select

Supports PDF, Word, Excel, Image, Text, ZIP files (max 1GB)

{files.length > 0 && (

Selected Files ({files.length})

{files.map((file, index) => (

{file.name}

{formatFileSize(file.size)}

))}
)}
) } /* ------------------------------------------------------------------------------------------------- * Main Dialog Component * -----------------------------------------------------------------------------------------------*/ export function AddAttachmentDialog({ open, onOpenChange, revisionId, revisionName, onSuccess }: AddAttachmentDialogProps) { const [isUploading, setIsUploading] = React.useState(false) const [uploadProgress, setUploadProgress] = React.useState(0) const { data: session } = useSession() const userName = React.useMemo(() => { return session?.user?.name ? session.user.name : null; }, [session]); type AttachmentUploadSchema = z.infer const form = useForm({ resolver: zodResolver(attachmentUploadSchema), defaultValues: { attachments: [], }, }) const watchedFiles = form.watch("attachments") const handleDialogClose = () => { if (!isUploading) { form.reset() setUploadProgress(0) onOpenChange(false) } } const onSubmit = async (data: AttachmentUploadSchema) => { setIsUploading(true) setUploadProgress(0) try { const formData = new FormData() formData.append("revisionId", String(revisionId)) formData.append("uploaderName", userName || "evcp") // 파일들 추가 data.attachments.forEach((file) => { formData.append("attachments", file) }) // 진행률 업데이트 시뮬레이션 const totalSize = data.attachments.reduce((sum, file) => sum + file.size, 0) let uploadedSize = 0 const progressInterval = setInterval(() => { uploadedSize += totalSize * 0.1 const progress = Math.min((uploadedSize / totalSize) * 100, 90) setUploadProgress(progress) }, 300) const response = await fetch('/api/revision-attachment', { method: 'POST', body: formData, }) clearInterval(progressInterval) if (!response.ok) { const errorData = await response.json() throw new Error(errorData.error || errorData.details || 'Failed to upload attachments.') } const result = await response.json() setUploadProgress(100) toast.success( result.message || `${result.data?.uploadedFiles?.length || 0} attachments added.` ) console.log('✅ Attachment upload successful:', result) setTimeout(() => { handleDialogClose() onSuccess?.(result) }, 1000) } catch (error) { console.error('❌ Attachment upload error:', error) toast.error(error instanceof Error ? error.message : "An error occurred while uploading attachments") } finally { setIsUploading(false) setTimeout(() => setUploadProgress(0), 2000) } } return ( {/* 고정 헤더 */} Add Attachments Upload additional attachments to revision {revisionName}
{/* 스크롤 가능한 중간 영역 */}
{/* 파일 업로드 */} ( Attachments )} /> {/* 업로드 진행률 */} {isUploading && (
Upload Progress {uploadProgress.toFixed(0)}%
{uploadProgress === 100 && (
Upload Complete
)}
)}
{/* 고정 버튼 영역 */}
) }