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-attachment-dialog.tsx | 355 +++++++++++++++++++++ lib/b-rfq/attachment/add-revision-dialog.tsx | 336 +++++++++++++++++++ lib/b-rfq/attachment/attachment-columns.tsx | 290 +++++++++++++++++ lib/b-rfq/attachment/attachment-table.tsx | 190 +++++++++++ lib/b-rfq/attachment/attachment-toolbar-action.tsx | 60 ++++ lib/b-rfq/attachment/confirm-documents-dialog.tsx | 141 ++++++++ lib/b-rfq/attachment/delete-attachment-dialog.tsx | 182 +++++++++++ lib/b-rfq/attachment/revision-dialog.tsx | 196 ++++++++++++ lib/b-rfq/attachment/tbe-request-dialog.tsx | 200 ++++++++++++ lib/b-rfq/attachment/vendor-responses-panel.tsx | 205 ++++++++++++ 10 files changed, 2155 insertions(+) create mode 100644 lib/b-rfq/attachment/add-attachment-dialog.tsx create mode 100644 lib/b-rfq/attachment/add-revision-dialog.tsx create mode 100644 lib/b-rfq/attachment/attachment-columns.tsx create mode 100644 lib/b-rfq/attachment/attachment-table.tsx create mode 100644 lib/b-rfq/attachment/attachment-toolbar-action.tsx create mode 100644 lib/b-rfq/attachment/confirm-documents-dialog.tsx create mode 100644 lib/b-rfq/attachment/delete-attachment-dialog.tsx create mode 100644 lib/b-rfq/attachment/revision-dialog.tsx create mode 100644 lib/b-rfq/attachment/tbe-request-dialog.tsx create mode 100644 lib/b-rfq/attachment/vendor-responses-panel.tsx (limited to 'lib/b-rfq/attachment') diff --git a/lib/b-rfq/attachment/add-attachment-dialog.tsx b/lib/b-rfq/attachment/add-attachment-dialog.tsx new file mode 100644 index 00000000..665e0f88 --- /dev/null +++ b/lib/b-rfq/attachment/add-attachment-dialog.tsx @@ -0,0 +1,355 @@ +"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} 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 { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +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 { addRfqAttachmentRecord } from "../service" + +// 첨부파일 추가 폼 스키마 (단일 파일) +const addAttachmentSchema = z.object({ + attachmentType: z.enum(["구매", "설계"], { + required_error: "문서 타입을 선택해주세요.", + }), + description: z.string().optional(), + file: z.instanceof(File, { + message: "파일을 선택해주세요.", + }), +}) + +type AddAttachmentFormData = z.infer + +interface AddAttachmentDialogProps { + rfqId: number +} + +export function AddAttachmentDialog({ rfqId }: AddAttachmentDialogProps) { + const [open, setOpen] = React.useState(false) + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [uploadProgress, setUploadProgress] = React.useState(0) + + const form = useForm({ + resolver: zodResolver(addAttachmentSchema), + defaultValues: { + attachmentType: undefined, + description: "", + file: undefined, + }, + }) + + const selectedFile = form.watch("file") + + // 다이얼로그 닫기 핸들러 + const handleOpenChange = (newOpen: boolean) => { + if (!newOpen && !isSubmitting) { + form.reset() + } + setOpen(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("rfqId", rfqId.toString()) + formData.append("file", file) + + const response = await fetch("/api/upload/rfq-attachment", { + method: "POST", + body: formData, + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.message || "파일 업로드 실패") + } + + return response.json() + } + + // 폼 제출 + const onSubmit = async (data: AddAttachmentFormData) => { + setIsSubmitting(true) + setUploadProgress(0) + + try { + // 1단계: 파일 업로드 + setUploadProgress(30) + const uploadedFile = await uploadFile(data.file) + + // 2단계: DB 레코드 생성 (시리얼 번호 자동 생성) + setUploadProgress(70) + const attachmentRecord = { + rfqId, + attachmentType: data.attachmentType, + description: data.description, + fileName: uploadedFile.fileName, + originalFileName: uploadedFile.originalFileName, + filePath: uploadedFile.filePath, + fileSize: uploadedFile.fileSize, + fileType: uploadedFile.fileType, + } + + const result = await addRfqAttachmentRecord(attachmentRecord) + + setUploadProgress(100) + + if (result.success) { + toast.success(result.message) + form.reset() + handleOpenChange(false) + } else { + toast.error(result.message) + } + + } catch (error) { + console.error("Upload error:", error) + toast.error(error instanceof Error ? error.message : "파일 업로드 중 오류가 발생했습니다.") + } finally { + setIsSubmitting(false) + setUploadProgress(0) + } + } + + return ( + + + + + + + + 새 첨부파일 추가 + + RFQ에 첨부할 문서를 업로드합니다. 시리얼 번호는 자동으로 부여됩니다. + + + +
+ + {/* 문서 타입 선택 */} + ( + + 문서 타입 + + + + )} + /> + + {/* 설명 */} + ( + + 설명 (선택) + +