"use client" import * as React from "react" import { useState, useEffect, useRef } from "react" import { toast } from "sonner" import { Send, Paperclip, DownloadCloud, File, FileText, Image as ImageIcon, AlertCircle, X, User, Building } from "lucide-react" import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { ScrollArea } from "@/components/ui/scroll-area" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { formatDateTime, formatFileSize } from "@/lib/utils" import { useSession } from "next-auth/react" import { fetchBuyerVendorComments } from "../services" // 타입 정의 interface Comment { id: number; rfqId: number; vendorId: number | null // null 허용으로 변경 userId?: number | null // null 허용으로 변경 content: string; isVendorComment: boolean | null; // null 허용으로 변경 createdAt: Date; updatedAt: Date; userName?: string | null // null 허용으로 변경 vendorName?: string | null // null 허용으로 변경 attachments: Attachment[]; isRead: boolean | null // null 허용으로 변경 } interface Attachment { id: number; fileName: string; fileSize: number; fileType: string | null; // null 허용으로 변경 filePath: string; uploadedAt: Date; } // 프롭스 정의 interface BuyerCommunicationDrawerProps { open: boolean; onOpenChange: (open: boolean) => void; quotation: { id: number; rfqId: number; vendorId: number; quotationCode: string; rfq?: { rfqCode: string; }; } | null; onSuccess?: () => void; } // 벤더 코멘트 전송 함수 export function sendVendorCommentClient(params: { rfqId: number; vendorId: number; content: string; attachments?: File[]; }): Promise { // 폼 데이터 생성 (파일 첨부를 위해) const formData = new FormData(); formData.append('rfqId', params.rfqId.toString()); formData.append('vendorId', params.vendorId.toString()); formData.append('content', params.content); formData.append('isVendorComment', 'true'); // 벤더가 보내는 메시지이므로 true // 첨부파일 추가 if (params.attachments && params.attachments.length > 0) { params.attachments.forEach((file) => { formData.append(`attachments`, file); }); } // API 엔드포인트 구성 (벤더 API 경로) const url = `/api/procurement-rfqs/${params.rfqId}/vendors/${params.vendorId}/comments`; // API 호출 return fetch(url, { method: 'POST', body: formData, // multipart/form-data 형식 사용 }) .then(response => { if (!response.ok) { return response.text().then(text => { throw new Error(`API 요청 실패: ${response.status} ${text}`); }); } return response.json(); }) .then(result => { if (!result.success || !result.data) { throw new Error(result.message || '코멘트 전송 중 오류가 발생했습니다'); } return result.data.comment; }); } export function BuyerCommunicationDrawer({ open, onOpenChange, quotation, onSuccess }: BuyerCommunicationDrawerProps) { // 세션 정보 const { data: session } = useSession(); // 상태 관리 const [comments, setComments] = useState([]); const [newComment, setNewComment] = useState(""); const [attachments, setAttachments] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const fileInputRef = useRef(null); const messagesEndRef = useRef(null); // 첨부파일 관련 상태 const [previewDialogOpen, setPreviewDialogOpen] = useState(false); const [selectedAttachment, setSelectedAttachment] = useState(null); // 드로어가 열릴 때 데이터 로드 useEffect(() => { if (open && quotation) { loadComments(); } }, [open, quotation]); // 스크롤 최하단으로 이동 useEffect(() => { if (messagesEndRef.current) { messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [comments]); // 코멘트 로드 함수 const loadComments = async () => { if (!quotation) return; try { setIsLoading(true); // API를 사용하여 코멘트 데이터 가져오기 const commentsData = await fetchBuyerVendorComments(quotation.rfqId, quotation.vendorId); setComments(commentsData); // 읽음 상태 처리는 API 측에서 처리되는 것으로 가정 } catch (error) { console.error("코멘트 로드 오류:", error); toast.error("메시지를 불러오는 중 오류가 발생했습니다"); } finally { setIsLoading(false); } }; // 파일 선택 핸들러 const handleFileSelect = () => { fileInputRef.current?.click(); }; // 파일 변경 핸들러 const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const newFiles = Array.from(e.target.files); setAttachments(prev => [...prev, ...newFiles]); } }; // 파일 제거 핸들러 const handleRemoveFile = (index: number) => { setAttachments(prev => prev.filter((_, i) => i !== index)); }; // 코멘트 전송 핸들러 const handleSubmitComment = async () => { if (!newComment.trim() && attachments.length === 0) return; if (!quotation) return; try { setIsSubmitting(true); // API를 사용하여 새 코멘트 전송 (파일 업로드 때문에 FormData 사용) const newCommentObj = await sendVendorCommentClient({ rfqId: quotation.rfqId, vendorId: quotation.vendorId, content: newComment, attachments: attachments }); // 상태 업데이트 setComments(prev => [...prev, newCommentObj]); setNewComment(""); setAttachments([]); toast.success("메시지가 전송되었습니다"); // 데이터 새로고침 if (onSuccess) { onSuccess(); } } catch (error) { console.error("코멘트 전송 오류:", error); toast.error("메시지 전송 중 오류가 발생했습니다"); } finally { setIsSubmitting(false); } }; // 첨부파일 미리보기 const handleAttachmentPreview = (attachment: Attachment) => { setSelectedAttachment(attachment); setPreviewDialogOpen(true); }; // 첨부파일 다운로드 const handleAttachmentDownload = (attachment: Attachment) => { // 실제 다운로드 구현 window.open(attachment.filePath, '_blank'); }; // 파일 아이콘 선택 const getFileIcon = (fileType: string) => { if (fileType.startsWith("image/")) return ; if (fileType.includes("pdf")) return ; if (fileType.includes("spreadsheet") || fileType.includes("excel")) return ; if (fileType.includes("document") || fileType.includes("word")) return ; return ; }; // 첨부파일 미리보기 다이얼로그 const renderAttachmentPreviewDialog = () => { if (!selectedAttachment) return null; const isImage = selectedAttachment.fileType.startsWith("image/"); const isPdf = selectedAttachment.fileType.includes("pdf"); return ( {getFileIcon(selectedAttachment.fileType)} {selectedAttachment.fileName} {formatFileSize(selectedAttachment.fileSize)} • {formatDateTime(selectedAttachment.uploadedAt)}
{isImage ? ( {selectedAttachment.fileName} ) : isPdf ? (