"use client" import * as React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" import { Plus, X, FileIcon } from "lucide-react" import { toast } from "sonner" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" import { Progress } from "@/components/ui/progress" import { Dropzone, DropzoneDescription, DropzoneInput, DropzoneTitle, DropzoneUploadIcon, DropzoneZone, } from "@/components/ui/dropzone" import { FileList, FileListAction, FileListDescription, FileListHeader, FileListIcon, FileListInfo, FileListItem, FileListName, FileListSize, } from "@/components/ui/file-list" import { useRouter } from "next/navigation"; const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB const MAX_FILES = 10; // 최대 10개 파일 const addAttachmentSchema = z.object({ description: z.string().optional(), files: z .array(z.instanceof(File)) .min(1, "최소 1개 이상의 파일을 선택해주세요.") .max(MAX_FILES, `최대 ${MAX_FILES}개까지 업로드 가능합니다.`) .refine( (files) => files.every((file) => file.size <= MAX_FILE_SIZE), `각 파일 크기는 100MB를 초과할 수 없습니다.` ), }) type AddAttachmentFormData = z.infer interface AddAttachmentDialogProps { rfqId: number; attachmentType: "구매" | "설계"; onSuccess?: () => void; } export function AddAttachmentDialog({ rfqId, attachmentType, onSuccess }: AddAttachmentDialogProps) { const [open, setOpen] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false) const [uploadProgress, setUploadProgress] = React.useState(0) const [selectedFiles, setSelectedFiles] = React.useState([]) const router = useRouter(); const form = useForm({ resolver: zodResolver(addAttachmentSchema), defaultValues: { description: "", files: [], }, }) // 파일 크기 포맷팅 함수 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 getFileExtension = (fileName: string) => { return fileName.split('.').pop()?.toUpperCase() || 'FILE'; }; // 파일 추가 처리 const handleFilesAdd = (newFiles: FileList | null) => { if (!newFiles) return; const filesArray = Array.from(newFiles); const totalFiles = selectedFiles.length + filesArray.length; if (totalFiles > MAX_FILES) { toast.error(`최대 ${MAX_FILES}개까지만 업로드할 수 있습니다.`); return; } // 파일 크기 체크 const oversizedFiles = filesArray.filter(file => file.size > MAX_FILE_SIZE); if (oversizedFiles.length > 0) { toast.error(`다음 파일들이 100MB를 초과합니다: ${oversizedFiles.map(f => f.name).join(", ")}`); return; } const updatedFiles = [...selectedFiles, ...filesArray]; setSelectedFiles(updatedFiles); form.setValue("files", updatedFiles, { shouldValidate: true }); }; const onSubmit = async (data: AddAttachmentFormData) => { setIsSubmitting(true); setUploadProgress(0); try { const formData = new FormData(); formData.append("rfqId", rfqId.toString()); formData.append("attachmentType", attachmentType); formData.append("description", data.description || ""); // 모든 파일 추가 data.files.forEach((file) => { formData.append("files", file); }); // 진행률 시뮬레이션 const progressInterval = setInterval(() => { setUploadProgress((prev) => { if (prev >= 90) { clearInterval(progressInterval); return 90; } return prev + 10; }); }, 200); const response = await fetch("/api/rfq-attachments/upload", { method: "POST", body: formData, }); clearInterval(progressInterval); setUploadProgress(100); if (!response.ok) { const error = await response.json(); throw new Error(error.message || "파일 업로드 실패"); } const result = await response.json(); if (result.success) { toast.success(`${result.uploadedCount}개 파일이 성공적으로 업로드되었습니다`); form.reset(); setSelectedFiles([]); setOpen(false); router.refresh() onSuccess?.(); } else { toast.error(result.message); } } catch (error) { toast.error(error instanceof Error ? error.message : "파일 업로드 중 오류가 발생했습니다"); } finally { setIsSubmitting(false); setUploadProgress(0); } }; // 다이얼로그 닫을 때 상태 초기화 const handleOpenChange = (newOpen: boolean) => { if (!newOpen) { form.reset(); setSelectedFiles([]); setUploadProgress(0); } setOpen(newOpen); }; const handleDropAccepted = (acceptedFiles: File[]) => { const newFiles = [...selectedFiles, ...acceptedFiles] setSelectedFiles(newFiles) form.setValue('files', newFiles, { shouldValidate: true }) } const removeFile = (index: number) => { const updatedFiles = [...selectedFiles] updatedFiles.splice(index, 1) setSelectedFiles(updatedFiles) form.setValue('files', updatedFiles, { shouldValidate: true }) } return ( 새 첨부파일 추가 {attachmentType} 문서를 업로드합니다. (파일당 최대 100MB, 최대 {MAX_FILES}개)
( 파일 첨부
파일을 여기에 드롭하세요 또는 클릭하여 파일을 선택하세요
)} /> {/* 선택된 파일 목록 */} {selectedFiles.length > 0 && (
선택된 파일 ({selectedFiles.length}/{MAX_FILES}) 총 {formatFileSize(selectedFiles.reduce((acc, file) => acc + file.size, 0))}
{selectedFiles.map((file, index) => ( {file.name} {getFileExtension(file.name)} • {formatFileSize(file.size)} ))}
)} ( 설명 (선택)