From 0257350f55c00735cadbd5b507ef5cc9cd3adb10 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 29 May 2025 08:17:25 +0000
Subject: (김준회) 기술영업 조선 RFQ - 캐시 문제 대응, 코멘트 기능 UX 향상
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../detail-table/vendor-communication-drawer.tsx   | 140 +++++++++++++++++----
 1 file changed, 119 insertions(+), 21 deletions(-)

(limited to 'lib/techsales-rfq/table/detail-table/vendor-communication-drawer.tsx')

diff --git a/lib/techsales-rfq/table/detail-table/vendor-communication-drawer.tsx b/lib/techsales-rfq/table/detail-table/vendor-communication-drawer.tsx
index 958cc8d1..4172ccd7 100644
--- a/lib/techsales-rfq/table/detail-table/vendor-communication-drawer.tsx
+++ b/lib/techsales-rfq/table/detail-table/vendor-communication-drawer.tsx
@@ -15,7 +15,6 @@ import {
   DrawerHeader,
   DrawerTitle,
 } from "@/components/ui/drawer"
-import { ScrollArea } from "@/components/ui/scroll-area"
 import { Badge } from "@/components/ui/badge"
 import { toast } from "sonner"
 import { 
@@ -143,6 +142,11 @@ export function VendorCommunicationDrawer({
   const fileInputRef = useRef<HTMLInputElement>(null);
   const messagesEndRef = useRef<HTMLDivElement>(null);
   
+  // 자동 새로고침 관련 상태
+  const [autoRefresh, setAutoRefresh] = useState(true);
+  const [lastMessageCount, setLastMessageCount] = useState(0);
+  const intervalRef = useRef<NodeJS.Timeout | null>(null);
+  
   // 첨부파일 관련 상태
   const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
   const [selectedAttachment, setSelectedAttachment] = useState<Attachment | null>(null);
@@ -151,8 +155,20 @@ export function VendorCommunicationDrawer({
   useEffect(() => {
     if (open && selectedRfq && selectedVendor) {
       loadComments();
+      // 자동 새로고침 시작
+      if (autoRefresh) {
+        startAutoRefresh();
+      }
+    } else {
+      // 드로어가 닫히면 자동 새로고침 중지
+      stopAutoRefresh();
     }
-  }, [open, selectedRfq, selectedVendor]);
+
+    // 컴포넌트 언마운트 시 정리
+    return () => {
+      stopAutoRefresh();
+    };
+  }, [open, selectedRfq, selectedVendor, autoRefresh]);
   
   // 스크롤 최하단으로 이동
   useEffect(() => {
@@ -160,25 +176,79 @@ export function VendorCommunicationDrawer({
       messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
     }
   }, [comments]);
+
+  // 자동 새로고침 시작
+  const startAutoRefresh = () => {
+    stopAutoRefresh(); // 기존 interval 정리
+    intervalRef.current = setInterval(() => {
+      if (open && selectedRfq && selectedVendor && !isSubmitting) {
+        loadComments(true); // 자동 새로고침임을 표시
+      }
+    }, 60000); // 60초마다 새로고침
+  };
+
+  // 자동 새로고침 중지
+  const stopAutoRefresh = () => {
+    if (intervalRef.current) {
+      clearInterval(intervalRef.current);
+      intervalRef.current = null;
+    }
+  };
+
+  // 자동 새로고침 토글
+  const toggleAutoRefresh = () => {
+    setAutoRefresh(prev => {
+      const newValue = !prev;
+      if (newValue && open) {
+        startAutoRefresh();
+      } else {
+        stopAutoRefresh();
+      }
+      return newValue;
+    });
+  };
   
-  // 코멘트 로드 함수
-  const loadComments = async () => {
+  // 코멘트 로드 함수 (자동 새로고침 여부 파라미터 추가)
+  const loadComments = async (isAutoRefresh = false) => {
     if (!selectedRfq || !selectedVendor) return;
     
     try {
-      setIsLoading(true);
+      // 자동 새로고침일 때는 로딩 표시하지 않음
+      if (!isAutoRefresh) {
+        setIsLoading(true);
+      }
       
       // Server Action을 사용하여 코멘트 데이터 가져오기
       const commentsData = await fetchTechSalesVendorComments(selectedRfq.id, selectedVendor.vendorId || 0);
+      
+      // 새 메시지가 있는지 확인 (자동 새로고침일 때만)
+      if (isAutoRefresh) {
+        const newMessageCount = commentsData.length;
+        if (newMessageCount > lastMessageCount && lastMessageCount > 0) {
+          // 새 메시지 알림 (선택사항)
+          toast.success(`새 메시지 ${newMessageCount - lastMessageCount}개가 도착했습니다`);
+        }
+        setLastMessageCount(newMessageCount);
+      } else {
+        setLastMessageCount(commentsData.length);
+      }
+      
       setComments(commentsData as Comment[]); // 구체적인 타입으로 캐스팅
       
       // Server Action을 사용하여 읽지 않은 메시지를 읽음 상태로 변경
       await markTechSalesMessagesAsRead(selectedRfq.id, selectedVendor.vendorId || 0);
     } catch (error) {
       console.error("코멘트 로드 오류:", error);
-      toast.error("메시지를 불러오는 중 오류가 발생했습니다");
+      if (!isAutoRefresh) { // 자동 새로고침일 때는 에러 토스트 표시하지 않음
+        toast.error("메시지를 불러오는 중 오류가 발생했습니다");
+      }
     } finally {
-      setIsLoading(false);
+      // 항상 로딩 상태를 해제하되, 최소 200ms는 유지하여 깜빡거림 방지
+      if (!isAutoRefresh) {
+        setTimeout(() => {
+          setIsLoading(false);
+        }, 200);
+      }
     }
   };
   
@@ -323,8 +393,8 @@ export function VendorCommunicationDrawer({
   
   return (
     <Drawer open={open} onOpenChange={onOpenChange}>
-      <DrawerContent className="max-h-[85vh]">
-        <DrawerHeader className="border-b">
+      <DrawerContent className="max-h-[80vh] flex flex-col">
+        <DrawerHeader className="border-b flex-shrink-0">
           <DrawerTitle className="flex items-center gap-2">
             <Avatar className="h-8 w-8">
               <AvatarFallback className="bg-primary/10">
@@ -341,10 +411,10 @@ export function VendorCommunicationDrawer({
           </DrawerDescription>
         </DrawerHeader>
         
-        <div className="p-0 flex flex-col h-[60vh]">
+        <div className="flex flex-col flex-1 min-h-0">
           {/* 메시지 목록 */}
-          <ScrollArea className="flex-1 p-4">
-            {isLoading ? (
+          <div className="flex-1 p-4 overflow-y-auto min-h-[300px]">
+            {isLoading && comments.length === 0 ? (
               <div className="flex h-full items-center justify-center">
                 <p className="text-muted-foreground">메시지 로딩 중...</p>
               </div>
@@ -356,7 +426,15 @@ export function VendorCommunicationDrawer({
                 </div>
               </div>
             ) : (
-              <div className="space-y-4">
+              <div className="space-y-4 relative">
+                {isLoading && (
+                  <div className="absolute top-0 right-0 z-10 bg-background/80 backdrop-blur-sm rounded-md px-2 py-1">
+                    <div className="flex items-center gap-2">
+                      <div className="w-2 h-2 bg-primary rounded-full animate-pulse" />
+                      <span className="text-xs text-muted-foreground">새로고침 중...</span>
+                    </div>
+                  </div>
+                )}
                 {comments.map(comment => (
                   <div 
                     key={comment.id} 
@@ -436,11 +514,11 @@ export function VendorCommunicationDrawer({
                 <div ref={messagesEndRef} />
               </div>
             )}
-          </ScrollArea>
+          </div>
           
           {/* 선택된 첨부파일 표시 */}
           {attachments.length > 0 && (
-            <div className="p-2 bg-muted mx-4 rounded-md mb-2">
+            <div className="p-2 bg-muted mx-4 rounded-md mb-2 flex-shrink-0">
               <div className="text-xs font-medium mb-1">첨부파일</div>
               <div className="flex flex-wrap gap-2">
                 {attachments.map((file, index) => (
@@ -466,7 +544,7 @@ export function VendorCommunicationDrawer({
           )}
           
           {/* 메시지 입력 영역 */}
-          <div className="p-4 border-t">
+          <div className="p-4 border-t flex-shrink-0">
             <div className="flex gap-2 items-end">
               <div className="flex-1">
                 <Textarea
@@ -503,11 +581,31 @@ export function VendorCommunicationDrawer({
           </div>
         </div>
         
-        <DrawerFooter className="border-t">
-          <div className="flex justify-between">
-            <Button variant="outline" onClick={() => loadComments()}>
-              새로고침
-            </Button>
+        <DrawerFooter className="border-t flex-shrink-0">
+          <div className="flex justify-between items-center">
+            <div className="flex items-center gap-2">
+              <Button variant="outline" onClick={() => loadComments()}>
+                새로고침
+              </Button>
+              <Button
+                variant={autoRefresh ? "default" : "outline"}
+                size="sm"
+                onClick={toggleAutoRefresh}
+                className="gap-2"
+              >
+                {autoRefresh ? (
+                  <>
+                    <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
+                    자동 새로고침 ON
+                  </>
+                ) : (
+                  <>
+                    <div className="w-2 h-2 bg-gray-400 rounded-full" />
+                    자동 새로고침 OFF
+                  </>
+                )}
+              </Button>
+            </div>
             <DrawerClose asChild>
               <Button variant="outline">닫기</Button>
             </DrawerClose>
-- 
cgit v1.2.3

