From a99513d75ae7b1b2f1a2edc36ecd778acefce6ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Tue, 10 Dec 2024 10:56:55 +0900
Subject: [PATCH] =?UTF-8?q?fix:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=EC=97=90?=
 =?UTF-8?q?=EC=84=9C=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=A0=84=EC=86=A1?=
 =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=9E=98=EB=A6=AC=EB=8A=94=20=EB=AC=B8?=
 =?UTF-8?q?=EC=A0=9C=ED=95=B4=EA=B2=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/ChattingDetail.jsx | 353 +++++++++++++++++-------------
 1 file changed, 206 insertions(+), 147 deletions(-)

diff --git a/src/components/ChattingDetail.jsx b/src/components/ChattingDetail.jsx
index 318a02a..f2ca049 100644
--- a/src/components/ChattingDetail.jsx
+++ b/src/components/ChattingDetail.jsx
@@ -1,11 +1,11 @@
-import { useParams, useLocation } from 'react-router-dom';
-import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
-import './ChattingDetail.css';
-import styled, { keyframes } from 'styled-components';
-import { FaSearch, FaArrowUp, FaArrowDown } from 'react-icons/fa';
+import { useParams, useLocation } from "react-router-dom";
+import { useEffect, useState, useRef, useCallback, useMemo } from "react";
+import "./ChattingDetail.css";
+import styled, { keyframes } from "styled-components";
+import { FaSearch, FaArrowUp, FaArrowDown } from "react-icons/fa";
 import Button from "../components/Button";
-import ChattingNoticeDetailModal from '../components/ChattingNoticeDetailModal';
-import ChattingNoticeListModal from '../components/ChattingNoticeListModal';
+import ChattingNoticeDetailModal from "../components/ChattingNoticeDetailModal";
+import ChattingNoticeListModal from "../components/ChattingNoticeListModal";
 
 // 웹소켓 서버 연결 URL
 const WS_URL = process.env.REACT_APP_WS_URL;
@@ -69,26 +69,28 @@ const ChatRoomMessages = styled.div`
 
 const MessageContainer = styled.div`
   display: flex;
-  justify-content: ${(props) => (props.isMine ? 'flex-end' : 'flex-start')};
+  justify-content: ${(props) => (props.isMine ? "flex-end" : "flex-start")};
   margin-bottom: 10px;
   padding: 10px;
   border-radius: 5px;
-  border: ${(props) => (props.highlighted ? '2px solid blue' : 'none')};
+  border: ${(props) => (props.highlighted ? "2px solid blue" : "none")};
   transition: background-color 0.3s ease, border 0.3s ease;
-  animation: ${(props) => (props.highlighted ? shakeAnimation : 'none')} 0.5s ease;
+  animation: ${(props) => (props.highlighted ? shakeAnimation : "none")} 0.5s
+    ease;
 `;
 
 const MessageBubble = styled.div`
   max-width: 60%;
   padding: 10px;
   border-radius: 10px;
-  background-color: ${(props) => (props.isMine ? '#dcf8c6' : '#f9f9f9')};
-  text-align: ${(props) => (props.isMine ? 'right' : 'left')};
-  border: ${(props) => (props.highlighted ? '2px solid blue' : 'none')};
+  background-color: ${(props) => (props.isMine ? "#dcf8c6" : "#f9f9f9")};
+  text-align: ${(props) => (props.isMine ? "right" : "left")};
+  border: ${(props) => (props.highlighted ? "2px solid blue" : "none")};
   word-wrap: break-word;
   transition: background-color 0.3s ease, border 0.3s ease;
   box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
-  animation: ${(props) => (props.highlighted ? shakeAnimation : 'none')} 0.5s ease;
+  animation: ${(props) => (props.highlighted ? shakeAnimation : "none")} 0.5s
+    ease;
 `;
 
 const CenteredMessage = styled.div`
@@ -107,7 +109,7 @@ const CenteredMessage = styled.div`
 const MessageTimestamp = styled.div`
   font-size: 0.8em;
   color: #888;
-  text-align: ${(props) => (props.isMine ? 'right' : 'left')};
+  text-align: ${(props) => (props.isMine ? "right" : "left")};
 `;
 
 const ChatRoomInput = styled.div`
@@ -146,9 +148,11 @@ const FixedSearchBar = styled(SearchBar)`
 `;
 
 const NoticeContainer = styled.div`
-  background-color: ${({ isCollapsed }) => (isCollapsed ? "transparent" : "#f8f9fa")};
+  background-color: ${({ isCollapsed }) =>
+    isCollapsed ? "transparent" : "#f8f9fa"};
   padding: ${({ isCollapsed }) => (isCollapsed ? "0" : "10px")};
-  border-bottom: ${({ isCollapsed }) => (isCollapsed ? "none" : "1px solid #ddd")};
+  border-bottom: ${({ isCollapsed }) =>
+    isCollapsed ? "none" : "1px solid #ddd"};
   position: relative; /* 확성기 위치 조정을 위해 사용 */
 `;
 
@@ -196,17 +200,17 @@ function ChattingDetail() {
   const location = useLocation();
   const { nickname, chatRoomName } = location.state;
 
-  // const [notice, setNotice] = useState(null); // 공지 메시지  
+  // const [notice, setNotice] = useState(null); // 공지 메시지
   const [messages, setMessages] = useState([]);
-  const [input, setInput] = useState('');
-  const [searchTerm, setSearchTerm] = useState('');
+  const [input, setInput] = useState("");
+  const [searchTerm, setSearchTerm] = useState("");
   const [searchResults, setSearchResults] = useState([]);
   const [currentSearchIndex, setCurrentSearchIndex] = useState(0);
   const [isSearching, setIsSearching] = useState(false);
   const [loggedInUser, setLoggedInUser] = useState(nickname);
   const [chatUnread, setChatUnread] = useState({});
   // const [isScrolledToBottom, setIsScrolledToBottom] = useState(true);
-  const [notice, setNotice] = useState(null); // 공지 메시지  
+  const [notice, setNotice] = useState(null); // 공지 메시지
   const [isNoticeVisible, setIsNoticeVisible] = useState(true); // 공지 표시 여부
   const [isNoticeCollapsed, setIsNoticeCollapsed] = useState(false); // 공지 접힘 여부
   const [isNoticeModalOpen, setIsNoticeModalOpen] = useState(false);
@@ -220,7 +224,7 @@ function ChattingDetail() {
   const searchInputRef = useRef(null);
   const highlightedMessageRef = useRef(null);
   const isTabActiveRef = useRef(true); // useRef로 상태 초기화
-  
+
   let reconnectAttempts = 0;
   const MAX_RECONNECT_ATTEMPTS = 5;
   // let isTabActive = true; // 브라우저 탭 상태를 저장
@@ -241,7 +245,7 @@ function ChattingDetail() {
     (logId) => {
       if (chatUnreadSortArray.length === 0) {
         return 0;
-      } 
+      }
 
       for (let i = 0; i < chatUnreadSortArray.length; i++) {
         if (logId <= chatUnreadSortArray[i][1]) {
@@ -272,12 +276,16 @@ function ChattingDetail() {
   };
 
   const handleDismissNotice = () => {
-    const dismissedNotices = JSON.parse(localStorage.getItem('dismissedNotices')) || [];
-    
+    const dismissedNotices =
+      JSON.parse(localStorage.getItem("dismissedNotices")) || [];
+
     // 현재 공지사항이 로컬 스토리지에 추가되도록 처리
     if (notice && !dismissedNotices.includes(notice.message)) {
       dismissedNotices.push(notice.message);
-      localStorage.setItem('dismissedNotices', JSON.stringify(dismissedNotices));
+      localStorage.setItem(
+        "dismissedNotices",
+        JSON.stringify(dismissedNotices)
+      );
     }
 
     setIsNoticeVisible(false); // 공지를 숨김
@@ -285,21 +293,24 @@ function ChattingDetail() {
 
   const fetchUnreadCounts = async () => {
     try {
-      const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/unread-count/${chatRoomId}`, {
-        method: "GET",
-        credentials: "include", 
-        headers: {
-          "Content-Type": "application/json",
-        },
-      });
+      const response = await fetch(
+        `${process.env.REACT_APP_BASE_URL}/api/chat/unread-count/${chatRoomId}`,
+        {
+          method: "GET",
+          credentials: "include",
+          headers: {
+            "Content-Type": "application/json",
+          },
+        }
+      );
       if (response.ok) {
         const data = await response.json();
         setChatUnread(data);
       } else {
-        console.error('Failed to fetch unread counts');
+        console.error("Failed to fetch unread counts");
       }
     } catch (error) {
-      console.error('Error fetching unread counts:', error);
+      console.error("Error fetching unread counts:", error);
     }
   };
 
@@ -310,33 +321,38 @@ function ChattingDetail() {
     // isOnline이 false로 전환될 때 마지막 메시지가 없으면 가장 최근 메시지의 logId를 설정
     if (!status) {
       if (messages.length > 0) {
-        logId = messages[messages.length - 1]._id;  // 가장 최근 메시지의 logId로 설정
+        logId = messages[messages.length - 1]._id; // 가장 최근 메시지의 logId로 설정
       } else {
-        logId = lastReadLogIdRef.current;  // 마지막 읽은 logId를 그대로 유지
+        logId = lastReadLogIdRef.current; // 마지막 읽은 logId를 그대로 유지
       }
     }
 
     try {
-      const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/update-status-and-logid`, {
-        method: 'POST',
-        headers: {
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify({
-          chatRoomId,
-          nickname,
-          isOnline: status,
-          logId: logId,  // 상태에 따라 logId도 함께 보냄
-        }),
-      });
+      const response = await fetch(
+        `${process.env.REACT_APP_BASE_URL}/api/chat/update-status-and-logid`,
+        {
+          method: "POST",
+          headers: {
+            "Content-Type": "application/json",
+          },
+          body: JSON.stringify({
+            chatRoomId,
+            nickname,
+            isOnline: status,
+            logId: logId, // 상태에 따라 logId도 함께 보냄
+          }),
+        }
+      );
 
       if (!response.ok) {
-        throw new Error('Failed to update status and logId');
+        throw new Error("Failed to update status and logId");
       }
 
-      console.log(`${nickname} : 상태업데이트 to ${status}, logId updated to ${logId} for `);
+      console.log(
+        `${nickname} : 상태업데이트 to ${status}, logId updated to ${logId} for `
+      );
     } catch (err) {
-      console.error('Error updating status and logId:', err);
+      console.error("Error updating status and logId:", err);
     }
   };
 
@@ -344,25 +360,23 @@ function ChattingDetail() {
     e.preventDefault();
     if (window.confirm("이 메시지를 공지로 설정하시겠습니까?")) {
       try {
-
         const noticeMessage = {
-          type: 'notice',          // 메시지 유형: 공지사항 설정
+          type: "notice", // 메시지 유형: 공지사항 설정
           chatRoomId: chatRoomId, // 현재 채팅방 ID
-          nickname: loggedInUser,     // 공지 설정한 사용자 닉네임
-          text: messageData.message,          // 공지 내용
+          nickname: loggedInUser, // 공지 설정한 사용자 닉네임
+          text: messageData.message, // 공지 내용
         };
 
         // WebSocket을 통해 서버로 메시지 전송
         ws.current.send(JSON.stringify(noticeMessage));
-        console.log('공지사항 설정 메시지 전송:', noticeMessage);
-
+        console.log("공지사항 설정 메시지 전송:", noticeMessage);
 
         const response = await fetch(
           `${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices`,
           {
-            method: 'POST',
+            method: "POST",
             headers: {
-              'Content-Type': 'application/json',
+              "Content-Type": "application/json",
             },
             body: JSON.stringify({
               sender: messageData.sender,
@@ -375,10 +389,10 @@ function ChattingDetail() {
           const newNotice = await response.json();
           setNotice(newNotice); // 공지를 로컬 상태로 업데이트
         } else {
-          console.error('Failed to set notice');
+          console.error("Failed to set notice");
         }
       } catch (error) {
-        console.error('Error setting notice:', error);
+        console.error("Error setting notice:", error);
       }
     }
   };
@@ -402,11 +416,14 @@ function ChattingDetail() {
 
   const fetchLatestNotice = async () => {
     try {
-      const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices/latest`);
+      const response = await fetch(
+        `${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices/latest`
+      );
       if (response.ok) {
         const latestNotice = await response.json();
 
-        const dismissedNotices = JSON.parse(localStorage.getItem('dismissedNotices')) || [];
+        const dismissedNotices =
+          JSON.parse(localStorage.getItem("dismissedNotices")) || [];
         const isDismissed = dismissedNotices.includes(latestNotice.message);
 
         // 새로운 공지라면 표시하고, 숨겨진 공지가 아니라면 표시
@@ -417,18 +434,18 @@ function ChattingDetail() {
         setNotice(null);
         setIsNoticeVisible(false);
       } else {
-        console.error('Failed to fetch latest notice');
+        console.error("Failed to fetch latest notice");
       }
     } catch (error) {
-      console.error('Error fetching latest notice:', error);
+      console.error("Error fetching latest notice:", error);
     }
   };
 
   const scrollToHighlightedMessage = () => {
     if (highlightedMessageRef.current) {
-      highlightedMessageRef.current.scrollIntoView({ 
-        behavior: 'smooth', 
-        block: 'center' 
+      highlightedMessageRef.current.scrollIntoView({
+        behavior: "smooth",
+        block: "center",
       });
     }
   };
@@ -445,22 +462,24 @@ function ChattingDetail() {
         ws.current.close();
         return;
       }
-      
+
       reconnectAttempts = 0; // 재연결 성공 시 시도 횟수 초기화
       const joinMessage = JSON.stringify({
-        type: 'join',
+        type: "join",
         chatRoomId,
         nickname,
       });
       ws.current.send(joinMessage);
-      console.log(`(클라이언트) WebSocket 연결 완료 - 채팅방: ${chatRoomId}, 닉네임: ${nickname}`);
+      console.log(
+        `(클라이언트) WebSocket 연결 완료 - 채팅방: ${chatRoomId}, 닉네임: ${nickname}`
+      );
     };
 
     ws.current.onmessage = (event) => {
       const messageData = JSON.parse(event.data);
       console.log("받은 메시지", messageData);
 
-      if (messageData.type === 'status') {
+      if (messageData.type === "status") {
         const { nickname, isOnline } = messageData;
         if (!isOnline) {
           console.log(`${nickname}님이 오프라인 상태로 전환되었습니다.`);
@@ -469,27 +488,30 @@ function ChattingDetail() {
         return;
       }
 
-      if (messageData.type === 'notice') {
+      if (messageData.type === "notice") {
         // 공지사항 변경 이벤트 처리
-        console.log('새 공지사항 알림 수신:', messageData);
+        console.log("새 공지사항 알림 수신:", messageData);
         setNotice({
           sender: messageData.sender,
           message: messageData.message,
         });
         setIsNoticeVisible(true); // 공지를 표시하도록 설정
         fetchLatestNotice(); // 최신 공지사항 업데이트
-      } else if (messageData.type === 'previousMessages') {
+      } else if (messageData.type === "previousMessages") {
         // setMessages((prevMessages) => [...prevMessages, ...messageData.messages]);
         setMessages((prevMessages) => {
           const messageIds = new Set(prevMessages.map((msg) => msg._id)); // 기존 메시지의 ID 저장
-          const newMessages = messageData.messages.filter((msg) => !messageIds.has(msg._id)); // 중복 제거
+          const newMessages = messageData.messages.filter(
+            (msg) => !messageIds.has(msg._id)
+          ); // 중복 제거
           return [...prevMessages, ...newMessages];
         });
       } else {
         // setMessages((prevMessages) => [...prevMessages, messageData]);
         setMessages((prevMessages) => {
           const messageIds = new Set(prevMessages.map((msg) => msg._id)); // 기존 메시지의 ID 저장
-          if (!messageIds.has(messageData._id)) { // 새 메시지가 중복되지 않으면 추가
+          if (!messageIds.has(messageData._id)) {
+            // 새 메시지가 중복되지 않으면 추가
             return [...prevMessages, messageData];
           }
           return prevMessages;
@@ -497,11 +519,15 @@ function ChattingDetail() {
 
         lastReadLogIdRef.current = messageData._id;
         // messages 배열에 메시지가 추가된 후에 logId를 업데이트
-        const lastMessage = messageData;  // 새로 받은 메시지
+        const lastMessage = messageData; // 새로 받은 메시지
         lastReadLogIdRef.current = lastMessage._id;
 
-        console.log('Received message logId:', lastReadLogIdRef.current);
-        if (!chatUnreadSortArray.length || chatUnreadSortArray[chatUnreadSortArray.length - 1][1] <= messageData._id) {
+        console.log("Received message logId:", lastReadLogIdRef.current);
+        if (
+          !chatUnreadSortArray.length ||
+          chatUnreadSortArray[chatUnreadSortArray.length - 1][1] <=
+            messageData._id
+        ) {
           fetchUnreadCounts(chatRoomId);
         }
       }
@@ -509,46 +535,57 @@ function ChattingDetail() {
     };
 
     ws.current.onclose = async (event) => {
-      console.warn('(클라이언트)WebSocket 연결이 종료되었습니다.', event);
+      console.warn("(클라이언트)WebSocket 연결이 종료되었습니다.", event);
 
       // 탭이 활성화된 상태에서만 재연결 시도
-      if (isTabActiveRef.current && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
+      if (
+        isTabActiveRef.current &&
+        reconnectAttempts < MAX_RECONNECT_ATTEMPTS
+      ) {
         setTimeout(() => {
-          console.log(`WebSocket 재연결 시도 (${reconnectAttempts + 1}/${MAX_RECONNECT_ATTEMPTS})`);
+          console.log(
+            `WebSocket 재연결 시도 (${
+              reconnectAttempts + 1
+            }/${MAX_RECONNECT_ATTEMPTS})`
+          );
           reconnectAttempts++;
           joinRoom(); // 재연결 시도
         }, 2000); // 2초 후 재연결
       } else if (!isTabActiveRef.current) {
-        console.log('브라우저 탭이 비활성화 상태입니다. WebSocket 재연결을 시도하지 않습니다.');
+        console.log(
+          "브라우저 탭이 비활성화 상태입니다. WebSocket 재연결을 시도하지 않습니다."
+        );
       } else {
-        console.error('WebSocket 재연결 실패: 최대 시도 횟수를 초과했습니다.');
+        console.error("WebSocket 재연결 실패: 최대 시도 횟수를 초과했습니다.");
       }
     };
 
     ws.current.onerror = (error) => {
-      console.error('WebSocket 오류:', error);
+      console.error("WebSocket 오류:", error);
     };
   };
 
   const updateLastReadAt = async () => {
     try {
-      const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/update-read-status`, {
-        method: 'POST',
-        headers: {
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify({ chatRoomId, nickname }),
-      });
+      const response = await fetch(
+        `${process.env.REACT_APP_BASE_URL}/api/chat/update-read-status`,
+        {
+          method: "POST",
+          headers: {
+            "Content-Type": "application/json",
+          },
+          body: JSON.stringify({ chatRoomId, nickname }),
+        }
+      );
 
       if (!response.ok) {
-        throw new Error('Failed to update lastReadAt');
+        throw new Error("Failed to update lastReadAt");
       }
 
-      console.log('lastReadAt updated for', nickname);
+      console.log("lastReadAt updated for", nickname);
     } catch (err) {
-      console.error('Error updating lastReadAt:', err);
+      console.error("Error updating lastReadAt:", err);
     }
-    
   };
 
   // // 채팅방에서 퇴장
@@ -564,12 +601,14 @@ function ChattingDetail() {
   //     ws.current.close();  // 웹소켓 연결 종료
   //   } catch (error) {
   //     console.error('퇴장 메시지 전송 오류:', error);
-  //   } 
+  //   }
   // };
 
   const handleSearch = (e) => {
-    if (e.key === 'Enter') {
-      const results = messages.filter((message) => message.message.includes(searchTerm));
+    if (e.key === "Enter") {
+      const results = messages.filter((message) =>
+        message.message.includes(searchTerm)
+      );
       setSearchResults(results);
       setCurrentSearchIndex(results.length > 0 ? results.length - 1 : -1);
       setIsSearching(true);
@@ -580,13 +619,16 @@ function ChattingDetail() {
   };
 
   const handleArrowDown = () => {
-    if (searchResults.length > 0 && currentSearchIndex < searchResults.length - 1) {
+    if (
+      searchResults.length > 0 &&
+      currentSearchIndex < searchResults.length - 1
+    ) {
       setCurrentSearchIndex((prevIndex) => prevIndex + 1);
       setTimeout(() => {
         scrollToHighlightedMessage();
       }, 0);
     } else {
-      alert('더 이상 검색결과가 없습니다.');
+      alert("더 이상 검색결과가 없습니다.");
     }
   };
 
@@ -597,14 +639,14 @@ function ChattingDetail() {
         scrollToHighlightedMessage();
       }, 0);
     } else {
-      alert('더 이상 검색결과가 없습니다.');
+      alert("더 이상 검색결과가 없습니다.");
     }
   };
 
   const sendMessage = () => {
     if (input.trim()) {
       const message = JSON.stringify({
-        type: 'message',
+        type: "message",
         chatRoomId,
         sender: loggedInUser,
         nickname,
@@ -613,15 +655,15 @@ function ChattingDetail() {
 
       try {
         ws.current.send(message);
-        console.log('전송한 메시지:', message);
+        console.log("전송한 메시지:", message);
         updateLastReadAt();
       } catch (error) {
-        console.error('메시지 전송 오류:', error);
+        console.error("메시지 전송 오류:", error);
       }
 
-      setInput('');
+      setInput("");
     } else {
-      alert('메시지를 입력하세요.');
+      alert("메시지를 입력하세요.");
     }
   };
 
@@ -637,7 +679,7 @@ function ChattingDetail() {
     const sendHeartbeat = () => {
       if (ws.current && ws.current.readyState === WebSocket.OPEN) {
         const heartbeatMessage = JSON.stringify({
-          type: 'heartbeat',
+          type: "heartbeat",
           chatRoomId,
           nickname,
         });
@@ -661,11 +703,11 @@ function ChattingDetail() {
 
     updateLastReadAt();
 
-      // 탭 상태가 변경되면 Heartbeat 중단/재개
-    document.addEventListener('visibilitychange', () => {
-      if (document.visibilityState === 'hidden') {
+    // 탭 상태가 변경되면 Heartbeat 중단/재개
+    document.addEventListener("visibilitychange", () => {
+      if (document.visibilityState === "hidden") {
         stopHeartbeat();
-      } else if (document.visibilityState === 'visible') {
+      } else if (document.visibilityState === "visible") {
         startHeartbeat();
       }
     });
@@ -679,20 +721,22 @@ function ChattingDetail() {
   useEffect(() => {
     // eslint-disable-next-line react-hooks/exhaustive-deps
     const handleVisibilityChange = () => {
-      if (document.visibilityState === 'hidden') { // 브라우저 탭이 비활성화되었을 때
+      if (document.visibilityState === "hidden") {
+        // 브라우저 탭이 비활성화되었을 때
         // isTabActive = false; // 탭 비활성화 상태로 설정
         isTabActiveRef.current = false; // 탭 비활성화 상태로 설정
         updateUserStatusAndLogId(false);
         if (ws.current) {
           ws.current.close(); // WebSocket 연결 종료
-          console.log('브라우저 탭 비활성화: WebSocket 연결 종료');
+          console.log("브라우저 탭 비활성화: WebSocket 연결 종료");
         }
-      } else if (document.visibilityState === 'visible') { // 브라우저 탭이 다시 활성화되었을 때
+      } else if (document.visibilityState === "visible") {
+        // 브라우저 탭이 다시 활성화되었을 때
         // isTabActive = true; // 탭 활성화 상태로 설정
         isTabActiveRef.current = false; // 탭 비활성화 상태로 설정
         updateUserStatusAndLogId(true);
         if (!ws.current || ws.current.readyState === WebSocket.CLOSED) {
-          console.log('브라우저 탭 활성화: WebSocket 연결 재시작');
+          console.log("브라우저 탭 활성화: WebSocket 연결 재시작");
           joinRoom(); // WebSocket 재연결 시도
         }
       }
@@ -713,8 +757,8 @@ function ChattingDetail() {
   }, [currentSearchIndex, searchResults.length]);
 
   useEffect(() => {
-    console.log('chatRoomId:', chatRoomId);
-    console.log('nickname:', nickname);
+    console.log("chatRoomId:", chatRoomId);
+    console.log("nickname:", nickname);
     setLoggedInUser(nickname);
   }, [chatRoomId, nickname]);
 
@@ -727,15 +771,17 @@ function ChattingDetail() {
     // 공지사항 목록 가져오기
     const fetchNotices = async () => {
       try {
-        const response = await fetch(`${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices`);
+        const response = await fetch(
+          `${process.env.REACT_APP_BASE_URL}/api/chat/${chatRoomId}/notices`
+        );
         if (response.ok) {
           const data = await response.json();
           setNotices(data); // 공지사항 목록 업데이트
         } else {
-          console.error('Failed to fetch notices');
+          console.error("Failed to fetch notices");
         }
       } catch (error) {
-        console.error('Error fetching notices:', error);
+        console.error("Error fetching notices:", error);
       }
     };
     fetchNotices();
@@ -743,7 +789,7 @@ function ChattingDetail() {
 
   useEffect(() => {
     // 공지가 변경되면 렌더링
-    console.log('공지사항 업데이트:', notice);
+    console.log("공지사항 업데이트:", notice);
   }, [notice]);
 
   useEffect(() => {
@@ -754,12 +800,12 @@ function ChattingDetail() {
       }
     };
 
-    window.addEventListener('beforeunload', handleBeforeUnload); // 브라우저 종료/새로고침 이벤트 리스너 추가
+    window.addEventListener("beforeunload", handleBeforeUnload); // 브라우저 종료/새로고침 이벤트 리스너 추가
 
     return () => {
       console.log("페이지 이동시 상태 업데이트");
       updateUserStatusAndLogId(false); // 페이지 이동 시 상태 업데이트
-      window.removeEventListener('beforeunload', handleBeforeUnload); // 컴포넌트 언마운트 시 이벤트 리스너 제거
+      window.removeEventListener("beforeunload", handleBeforeUnload); // 컴포넌트 언마운트 시 이벤트 리스너 제거
       if (ws.current) {
         ws.current.close(); // WebSocket 연결 종료
       }
@@ -770,21 +816,26 @@ function ChattingDetail() {
   useEffect(() => {
     const lastMessage = messages[messages.length - 1];
 
-    localStorage.setItem(
-      `loadedPreviousMessages-${chatRoomId}`,
-      "true"
-    );
+    localStorage.setItem(`loadedPreviousMessages-${chatRoomId}`, "true");
 
     const hasLoadedPreviousMessages = localStorage.getItem(
       `loadedPreviousMessages-${chatRoomId}`
     );
 
-    if (!hasLoadedPreviousMessages && lastMessage && lastMessage.type === "previousMessages") {
+    if (
+      !hasLoadedPreviousMessages &&
+      lastMessage &&
+      lastMessage.type === "previousMessages"
+    ) {
       return; // 이전 메시지일 경우 자동 스크롤 방지
     }
 
     // 새 메시지 수신 시 자동으로 아래로 스크롤
-    if (lastMessage && lastMessage.type === "message" && messagesEndRef.current) {
+    if (
+      lastMessage &&
+      lastMessage.type === "message" &&
+      messagesEndRef.current
+    ) {
       messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
     }
   }, [messages, chatRoomId]);
@@ -792,12 +843,15 @@ function ChattingDetail() {
   const highlightSearchTerm = (message) => {
     if (!searchTerm) return message;
 
-    const regex = new RegExp(`(${searchTerm})`, 'gi');
+    const regex = new RegExp(`(${searchTerm})`, "gi");
     const parts = message.split(regex);
 
     return parts.map((part, index) =>
       part.toLowerCase() === searchTerm.toLowerCase() ? (
-        <span key={index} style={{ backgroundColor: 'yellow', fontWeight: 'bold' }}>
+        <span
+          key={index}
+          style={{ backgroundColor: "yellow", fontWeight: "bold" }}
+        >
           {part}
         </span>
       ) : (
@@ -809,7 +863,7 @@ function ChattingDetail() {
   return (
     <ChatRoomContainer>
       <ChatRoomHeader>
-        <h1 className="heading-1 text-gray-800">{chatRoomName}</h1>
+        <h1 className="text-gray-800 heading-1">{chatRoomName}</h1>
         {/* 검색 버튼 */}
         <Button
           size="lg"
@@ -937,13 +991,12 @@ function ChattingDetail() {
             const sameSenderAsNext =
               nextMessage && nextMessage.sender === messageData.sender;
 
-            const messageTime = new Date(messageData.timestamp).toLocaleTimeString(
-              [],
-              {
-                hour: "2-digit",
-                minute: "2-digit",
-              }
-            );
+            const messageTime = new Date(
+              messageData.timestamp
+            ).toLocaleTimeString([], {
+              hour: "2-digit",
+              minute: "2-digit",
+            });
 
             const prevMessageTime =
               prevMessage &&
@@ -959,7 +1012,9 @@ function ChattingDetail() {
 
             if (messageData.type === "join" || messageData.type === "leave") {
               return (
-                <CenteredMessage key={index}>{messageData.message}</CenteredMessage>
+                <CenteredMessage key={index}>
+                  {messageData.message}
+                </CenteredMessage>
               );
             }
 
@@ -983,7 +1038,9 @@ function ChattingDetail() {
 
                 <MessageContainer
                   isMine={isMine}
-                  highlighted={searchResults[currentSearchIndex] === messageData}
+                  highlighted={
+                    searchResults[currentSearchIndex] === messageData
+                  }
                   ref={
                     searchResults[currentSearchIndex] === messageData
                       ? highlightedMessageRef
@@ -1019,7 +1076,9 @@ function ChattingDetail() {
 
                   <MessageBubble
                     isMine={isMine}
-                    highlighted={searchResults[currentSearchIndex] === messageData}
+                    highlighted={
+                      searchResults[currentSearchIndex] === messageData
+                    }
                     onContextMenu={(e) => handleRightClick(e, messageData)}
                     style={{
                       textAlign: "left",
@@ -1087,7 +1146,7 @@ function ChattingDetail() {
           theme="pink"
           state={input.trim() ? "default" : "disable"}
           onClick={sendMessage}
-          className="ml-3 flex items-center justify-center whitespace-nowrap w-16" // w-32로 버튼 너비 설정
+          className="flex items-center justify-center flex-1 w-16 ml-3 tablet:flex-initial whitespace-nowrap" // w-32로 버튼 너비 설정
         >
           전송
         </Button>
@@ -1096,4 +1155,4 @@ function ChattingDetail() {
   );
 }
 
-export default ChattingDetail;
\ No newline at end of file
+export default ChattingDetail;
-- 
GitLab