From c72d0897f7b37843109c86f61d97eba05ba3ca0d Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 13 Jun 2025 07:08:01 +0000 Subject: (대표님) 20250613 16시 08분 b-rfq, document 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/b-rfq/attachment/add-revision-dialog.tsx | 336 +++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 lib/b-rfq/attachment/add-revision-dialog.tsx (limited to 'lib/b-rfq/attachment/add-revision-dialog.tsx') diff --git a/lib/b-rfq/attachment/add-revision-dialog.tsx b/lib/b-rfq/attachment/add-revision-dialog.tsx new file mode 100644 index 00000000..1abefb02 --- /dev/null +++ b/lib/b-rfq/attachment/add-revision-dialog.tsx @@ -0,0 +1,336 @@ +"use client" + +import * as React from "react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import { z } from "zod" +import { Upload } from "lucide-react" +import { toast } from "sonner" + +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +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 { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { addRevisionToAttachment } from "../service" + +// 리비전 추가 폼 스키마 +const addRevisionSchema = z.object({ + revisionComment: z.string().optional(), + file: z.instanceof(File, { + message: "파일을 선택해주세요.", + }), +}) + +type AddRevisionFormData = z.infer + +interface AddRevisionDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + attachmentId: number + currentRevision: string + originalFileName: string + onSuccess?: () => void +} + +export function AddRevisionDialog({ + open, + onOpenChange, + attachmentId, + currentRevision, + originalFileName, + onSuccess +}: AddRevisionDialogProps) { + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [uploadProgress, setUploadProgress] = React.useState(0) + + const form = useForm({ + resolver: zodResolver(addRevisionSchema), + defaultValues: { + revisionComment: "", + file: undefined, + }, + }) + + const selectedFile = form.watch("file") + + // 다이얼로그 닫기 핸들러 + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen && !isSubmitting) { + form.reset() + } + onOpenChange(newOpen) + } + + // 파일 선택 처리 + const handleFileChange = (files: File[]) => { + if (files.length === 0) return + + const file = files[0] + + // 파일 크기 검증 + const maxFileSize = 10 * 1024 * 1024 // 10MB + if (file.size > maxFileSize) { + toast.error(`파일이 너무 큽니다. (최대 10MB)`) + return + } + + form.setValue("file", file) + form.clearErrors("file") + } + + // 파일 제거 + const removeFile = () => { + form.resetField("file") + } + + // 파일 업로드 API 호출 + const uploadFile = async (file: File): Promise<{ + fileName: string + originalFileName: string + filePath: string + fileSize: number + fileType: string + }> => { + const formData = new FormData() + formData.append("attachmentId", attachmentId.toString()) + formData.append("file", file) + formData.append("isRevision", "true") + + const response = await fetch("/api/upload/rfq-attachment-revision", { + method: "POST", + body: formData, + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || "파일 업로드 실패") + } + + return response.json() + } + + // 폼 제출 + const onSubmit = async (data: AddRevisionFormData) => { + setIsSubmitting(true) + setUploadProgress(0) + + try { + // 1단계: 파일 업로드 + setUploadProgress(30) + const uploadedFile = await uploadFile(data.file) + + // 2단계: DB 리비전 레코드 생성 + setUploadProgress(70) + const result = await addRevisionToAttachment(attachmentId, { + fileName: uploadedFile.fileName, + originalFileName: uploadedFile.originalFileName, + filePath: uploadedFile.filePath, + fileSize: uploadedFile.fileSize, + fileType: uploadedFile.fileType, + revisionComment: data.revisionComment, + }) + + setUploadProgress(100) + + if (result.success) { + toast.success(result.message) + form.reset() + handleOpenChange(false) + onSuccess?.() + } else { + toast.error(result.message) + } + + } catch (error) { + console.error("Upload error:", error) + toast.error(error instanceof Error ? error.message : "리비전 추가 중 오류가 발생했습니다.") + } finally { + setIsSubmitting(false) + setUploadProgress(0) + } + } + + // 다음 리비전 번호 계산 + const getNextRevision = (current: string) => { + const match = current.match(/Rev\.(\d+)/) + if (match) { + const num = parseInt(match[1]) + 1 + return `Rev.${num}` + } + return "Rev.1" + } + + const nextRevision = getNextRevision(currentRevision) + + return ( + + + + + + 새 리비전 추가 + + + "{originalFileName}"의 새 버전을 업로드합니다. + 현재 {currentRevision} → {nextRevision} + + + +
+ + {/* 리비전 코멘트 */} + ( + + 리비전 코멘트 (선택) + +