From e484964b1d78cedabbe182c789a8e4c9b53e29d3 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Thu, 29 May 2025 05:12:36 +0000
Subject: (김준회) 기술영업 조선 RFQ 파일첨부 및 채팅 기능 구현 / menuConfig
 수정 (벤더 기술영업)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../vendor-response/detail/communication-tab.tsx   | 292 ++++++++++-----------
 1 file changed, 143 insertions(+), 149 deletions(-)

(limited to 'lib/techsales-rfq/vendor-response/detail/communication-tab.tsx')

diff --git a/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx b/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx
index 0332232c..3f2a5280 100644
--- a/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx
+++ b/lib/techsales-rfq/vendor-response/detail/communication-tab.tsx
@@ -1,21 +1,22 @@
 "use client"
 
 import * as React from "react"
-import { useState } from "react"
+import { useState, useEffect } from "react"
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
 import { Button } from "@/components/ui/button"
-import { Textarea } from "@/components/ui/textarea"
 import { Badge } from "@/components/ui/badge"
 import { ScrollArea } from "@/components/ui/scroll-area"
-import { Separator } from "@/components/ui/separator"
-import { Avatar, AvatarFallback } from "@/components/ui/avatar"
-import { Send, MessageCircle } from "lucide-react"
-import { formatDateTime } from "@/lib/utils"
-import { toast } from "sonner"
+import { Skeleton } from "@/components/ui/skeleton"
+import { MessageSquare, Paperclip } from "lucide-react"
+import { fetchTechSalesVendorCommentsClient, TechSalesComment } from "../buyer-communication-drawer"
+import { BuyerCommunicationDrawer } from "../buyer-communication-drawer"
 
 interface CommunicationTabProps {
   quotation: {
     id: number
+    rfqId: number
+    vendorId: number
+    quotationCode: string | null
     rfq: {
       id: number
       rfqCode: string | null
@@ -31,100 +32,73 @@ interface CommunicationTabProps {
   }
 }
 
-// 임시 코멘트 데이터 (실제로는 API에서 가져와야 함)
-const MOCK_COMMENTS = [
-  {
-    id: 1,
-    content: "안녕하세요. 해당 자재에 대한 견적 요청 드립니다. 납기일은 언제까지 가능한지 문의드립니다.",
-    createdAt: new Date("2024-01-15T09:00:00"),
-    author: {
-      name: "김구매",
-      email: "buyer@company.com",
-      role: "구매담당자"
-    }
-  },
-  {
-    id: 2,
-    content: "안녕하세요. 견적 요청 확인했습니다. 해당 자재의 경우 약 2주 정도의 제작 기간이 필요합니다. 상세한 견적은 내일까지 제출하겠습니다.",
-    createdAt: new Date("2024-01-15T14:30:00"),
-    author: {
-      name: "이벤더",
-      email: "vendor@supplier.com",
-      role: "벤더"
-    }
-  },
-  {
-    id: 3,
-    content: "감사합니다. 추가로 품질 인증서도 함께 제출 가능한지 확인 부탁드립니다.",
-    createdAt: new Date("2024-01-16T10:15:00"),
-    author: {
-      name: "김구매",
-      email: "buyer@company.com",
-      role: "구매담당자"
-    }
-  }
-]
-
 export function CommunicationTab({ quotation }: CommunicationTabProps) {
-  const [newComment, setNewComment] = useState("")
-  const [isLoading, setIsLoading] = useState(false)
-  const [comments, setComments] = useState(MOCK_COMMENTS)
+  const [comments, setComments] = useState<TechSalesComment[]>([]);
+  const [unreadCount, setUnreadCount] = useState(0);
+  const [loadingComments, setLoadingComments] = useState(false);
+  const [communicationDrawerOpen, setCommunicationDrawerOpen] = useState(false);
 
-  const handleSendComment = async () => {
-    if (!newComment.trim()) {
-      toast.error("메시지를 입력해주세요.")
-      return
+  // 컴포넌트 마운트 시 메시지 미리 로드
+  useEffect(() => {
+    if (quotation) {
+      loadCommunicationData();
     }
+  }, [quotation]);
 
-    setIsLoading(true)
+  // 메시지 데이터 로드 함수
+  const loadCommunicationData = async () => {
     try {
-      // TODO: API 호출로 코멘트 전송
-      const newCommentData = {
-        id: comments.length + 1,
-        content: newComment,
-        createdAt: new Date(),
-        author: {
-          name: "현재사용자", // 실제로는 세션에서 가져와야 함
-          email: "current@user.com",
-          role: "벤더"
-        }
-      }
-      
-      setComments([...comments, newCommentData])
-      setNewComment("")
-      toast.success("메시지가 전송되었습니다.")
-    } catch {
-      toast.error("메시지 전송 중 오류가 발생했습니다.")
+      setLoadingComments(true);
+      const commentsData = await fetchTechSalesVendorCommentsClient(quotation.rfqId, quotation.vendorId);
+      setComments(commentsData);
+
+      // 읽지 않은 메시지 수 계산 (구매자가 보낸 메시지 중 읽지 않은 것)
+      const unread = commentsData.filter(
+        comment => !comment.isVendorComment && !comment.isRead
+      ).length;
+      setUnreadCount(unread);
+    } catch (error) {
+      console.error("메시지 데이터 로드 오류:", error);
     } finally {
-      setIsLoading(false)
+      setLoadingComments(false);
     }
-  }
+  };
 
-  const getAuthorInitials = (name: string) => {
-    return name
-      .split(" ")
-      .map(word => word[0])
-      .join("")
-      .toUpperCase()
-      .slice(0, 2)
-  }
-
-  const getRoleBadgeVariant = (role: string) => {
-    return role === "구매담당자" ? "default" : "secondary"
-  }
+  // 커뮤니케이션 드로어가 닫힐 때 데이터 새로고침
+  const handleCommunicationDrawerChange = (open: boolean) => {
+    setCommunicationDrawerOpen(open);
+    if (!open) {
+      loadCommunicationData(); // 드로어가 닫힐 때 데이터 새로고침
+    }
+  };
 
   return (
     <div className="h-full flex flex-col">
       {/* 헤더 */}
       <Card className="mb-4">
-        <CardHeader>
-          <CardTitle className="flex items-center gap-2">
-            <MessageCircle className="h-5 w-5" />
-            커뮤니케이션
-          </CardTitle>
-          <CardDescription>
-            RFQ {quotation.rfq?.rfqCode || "미할당"}에 대한 구매담당자와의 커뮤니케이션
-          </CardDescription>
+        <CardHeader className="flex flex-row items-center justify-between">
+          <div>
+            <CardTitle className="flex items-center gap-2">
+              <MessageSquare className="h-5 w-5" />
+              커뮤니케이션
+              {unreadCount > 0 && (
+                <Badge variant="destructive" className="ml-2">
+                  새 메시지 {unreadCount}
+                </Badge>
+              )}
+            </CardTitle>
+            <CardDescription>
+              RFQ {quotation.rfq?.rfqCode || "미할당"}에 대한 구매담당자와의 커뮤니케이션
+            </CardDescription>
+          </div>
+          <Button
+            onClick={() => setCommunicationDrawerOpen(true)}
+            variant="outline"
+            size="sm"
+          >
+            <MessageSquare className="h-4 w-4 mr-2" />
+            {unreadCount > 0 ? "새 메시지 확인" : "메시지 보내기"}
+          </Button>
         </CardHeader>
         <CardContent>
           <div className="flex items-center gap-4 text-sm text-muted-foreground">
@@ -135,81 +109,101 @@ export function CommunicationTab({ quotation }: CommunicationTabProps) {
         </CardContent>
       </Card>
 
-      {/* 메시지 목록 */}
+      {/* 메시지 미리보기 */}
       <Card className="flex-1 flex flex-col min-h-0">
         <CardHeader>
           <CardTitle className="text-lg">메시지 ({comments.length})</CardTitle>
         </CardHeader>
-        <CardContent className="flex-1 flex flex-col min-h-0">
-          <ScrollArea className="flex-1 pr-4">
-            <div className="space-y-4">
-              {comments.length === 0 ? (
-                <div className="text-center py-8 text-muted-foreground">
-                  <MessageCircle className="h-12 w-12 mx-auto mb-4 opacity-50" />
-                  <p>아직 메시지가 없습니다.</p>
-                  <p className="text-sm">첫 번째 메시지를 보내보세요.</p>
+        <CardContent>
+          {loadingComments ? (
+            <div className="flex items-center justify-center p-8">
+              <div className="text-center">
+                <Skeleton className="h-4 w-32 mx-auto mb-2" />
+                <Skeleton className="h-4 w-48 mx-auto" />
+              </div>
+            </div>
+          ) : comments.length === 0 ? (
+            <div className="min-h-[200px] flex flex-col items-center justify-center text-center p-8">
+              <div className="max-w-md">
+                <div className="mx-auto bg-primary/10 rounded-full w-12 h-12 flex items-center justify-center mb-4">
+                  <MessageSquare className="h-6 w-6 text-primary" />
                 </div>
-              ) : (
-                comments.map((comment) => (
-                  <div key={comment.id} className="flex gap-3">
-                    <Avatar className="h-8 w-8 mt-1">
-                      <AvatarFallback className="text-xs">
-                        {getAuthorInitials(comment.author.name)}
-                      </AvatarFallback>
-                    </Avatar>
-                    <div className="flex-1 space-y-2">
-                      <div className="flex items-center gap-2">
-                        <span className="font-medium text-sm">{comment.author.name}</span>
-                        <Badge variant={getRoleBadgeVariant(comment.author.role)} className="text-xs">
-                          {comment.author.role}
-                        </Badge>
+                <h3 className="text-lg font-medium mb-2">아직 메시지가 없습니다</h3>
+                <p className="text-muted-foreground mb-4">
+                  견적서에 대한 질문이나 의견이 있으신가요? 구매자와 메시지를 주고받으세요.
+                </p>
+                <Button
+                  onClick={() => setCommunicationDrawerOpen(true)}
+                  className="mx-auto"
+                >
+                  메시지 보내기
+                </Button>
+              </div>
+            </div>
+          ) : (
+            <div className="space-y-4">
+              {/* 최근 메시지 3개 미리보기 */}
+              <div className="space-y-2">
+                <h3 className="text-sm font-medium">최근 메시지</h3>
+                <ScrollArea className="h-[250px] rounded-md border p-4">
+                  {comments.slice(-3).map(comment => (
+                    <div
+                      key={comment.id}
+                      className={`p-3 mb-3 rounded-lg ${!comment.isVendorComment && !comment.isRead
+                        ? 'bg-primary/10 border-l-4 border-primary'
+                        : 'bg-muted/50'
+                        }`}
+                    >
+                      <div className="flex justify-between items-center mb-1">
+                        <span className="text-sm font-medium">
+                          {comment.isVendorComment
+                            ? '나'
+                            : comment.userName || '구매 담당자'}
+                        </span>
                         <span className="text-xs text-muted-foreground">
-                          {formatDateTime(comment.createdAt)}
+                          {new Date(comment.createdAt).toLocaleDateString()}
                         </span>
                       </div>
-                      <div className="bg-muted p-3 rounded-lg text-sm">
-                        {comment.content}
-                      </div>
+                      <p className="text-sm line-clamp-2">{comment.content}</p>
+                      {comment.attachments.length > 0 && (
+                        <div className="mt-1 text-xs text-muted-foreground">
+                          <Paperclip className="h-3 w-3 inline mr-1" />
+                          첨부파일 {comment.attachments.length}개
+                        </div>
+                      )}
                     </div>
-                  </div>
-                ))
-              )}
-            </div>
-          </ScrollArea>
-
-          <Separator className="my-4" />
+                  ))}
+                </ScrollArea>
+              </div>
 
-          {/* 새 메시지 입력 */}
-          <div className="space-y-3">
-            <Textarea
-              placeholder="메시지를 입력하세요..."
-              value={newComment}
-              onChange={(e) => setNewComment(e.target.value)}
-              rows={3}
-              className="resize-none"
-              onKeyDown={(e) => {
-                if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
-                  e.preventDefault()
-                  handleSendComment()
-                }
-              }}
-            />
-            <div className="flex justify-between items-center">
-              <div className="text-xs text-muted-foreground">
-                Ctrl + Enter로 빠른 전송
+              <div className="flex justify-center">
+                <Button
+                  onClick={() => setCommunicationDrawerOpen(true)}
+                  className="w-full"
+                >
+                  전체 메시지 보기 ({comments.length}개)
+                </Button>
               </div>
-              <Button
-                onClick={handleSendComment}
-                disabled={isLoading || !newComment.trim()}
-                size="sm"
-              >
-                <Send className="h-4 w-4 mr-2" />
-                전송
-              </Button>
             </div>
-          </div>
+          )}
         </CardContent>
       </Card>
+
+      {/* 커뮤니케이션 드로어 */}
+      <BuyerCommunicationDrawer
+        open={communicationDrawerOpen}
+        onOpenChange={handleCommunicationDrawerChange}
+        quotation={{
+          id: quotation.id,
+          rfqId: quotation.rfqId,
+          vendorId: quotation.vendorId,
+          quotationCode: quotation.quotationCode,
+          rfq: quotation.rfq ? {
+            rfqCode: quotation.rfq.rfqCode
+          } : undefined
+        }}
+        onSuccess={loadCommunicationData}
+      />
     </div>
   )
 } 
\ No newline at end of file
-- 
cgit v1.2.3

