// components/rfq/upload-response-dialog.tsx "use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { Upload, FileText, X, Loader2 } from "lucide-react"; import { useToast } from "@/hooks/use-toast" import { useRouter } from "next/navigation"; const uploadFormSchema = z.object({ files: z.array(z.instanceof(File)).min(1, "최소 1개의 파일을 선택해주세요"), responseComment: z.string().optional(), vendorComment: z.string().optional(), }); type UploadFormData = z.infer; interface UploadResponseDialogProps { responseId: number; attachmentType: string; serialNo: string; currentRevision: string; trigger?: React.ReactNode; onSuccess?: () => void; } export function UploadResponseDialog({ responseId, attachmentType, serialNo, currentRevision, trigger, onSuccess, }: UploadResponseDialogProps) { const [open, setOpen] = useState(false); const [isUploading, setIsUploading] = useState(false); const { toast } = useToast(); const router = useRouter(); const form = useForm({ resolver: zodResolver(uploadFormSchema), defaultValues: { files: [], responseComment: "", vendorComment: "", }, }); const selectedFiles = form.watch("files"); const handleFileSelect = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); if (files.length > 0) { form.setValue("files", files); } }; const removeFile = (index: number) => { const currentFiles = form.getValues("files"); const newFiles = currentFiles.filter((_, i) => i !== index); form.setValue("files", newFiles); }; const formatFileSize = (bytes: number): string => { 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 handleOpenChange = (newOpen: boolean) => { setOpen(newOpen); // 다이얼로그가 닫힐 때 form 리셋 if (!newOpen) { form.reset(); } }; const handleCancel = () => { form.reset(); setOpen(false); }; const onSubmit = async (data: UploadFormData) => { setIsUploading(true); try { // 1. 각 파일을 업로드 API로 전송 const uploadedFiles = []; for (const file of data.files) { const formData = new FormData(); formData.append("file", file); formData.append("responseId", responseId.toString()); formData.append("description", ""); // 필요시 파일별 설명 추가 가능 const uploadResponse = await fetch("/api/vendor-responses/upload", { method: "POST", body: formData, }); if (!uploadResponse.ok) { const error = await uploadResponse.json(); throw new Error(error.message || "파일 업로드 실패"); } const uploadResult = await uploadResponse.json(); uploadedFiles.push(uploadResult); } // 2. vendor response 상태 업데이트 (서버에서 자동으로 리비전 증가) const updateResponse = await fetch("/api/vendor-responses/update", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ responseId, responseStatus: "RESPONDED", // respondedRevision 제거 - 서버에서 자동 처리 responseComment: data.responseComment, vendorComment: data.vendorComment, respondedAt: new Date().toISOString(), }), }); if (!updateResponse.ok) { const error = await updateResponse.json(); throw new Error(error.message || "응답 상태 업데이트 실패"); } const updateResult = await updateResponse.json(); toast({ title: "업로드 완료", description: `${data.files.length}개 파일이 성공적으로 업로드되었습니다. (${updateResult.newRevision})`, }); setOpen(false); form.reset(); router.refresh(); onSuccess?.(); } catch (error) { console.error("Upload error:", error); toast({ title: "업로드 실패", description: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.", variant: "destructive", }); } finally { setIsUploading(false); } }; return ( {trigger || ( )} 응답 파일 업로드
{serialNo} {attachmentType} {currentRevision} → 벤더 응답 리비전 자동 증가
{/* 파일 선택 */} ( 파일 선택
지원 파일: PDF, DOC, DOCX, XLS, XLSX, PNG, JPG, ZIP, RAR (최대 10MB)
)} /> {/* 선택된 파일 목록 */} {selectedFiles.length > 0 && (
선택된 파일 ({selectedFiles.length}개)
{selectedFiles.map((file, index) => (
{file.name}
{formatFileSize(file.size)}
))}
)} {/* 응답 코멘트 */} ( 응답 코멘트