Skip to content
Snippets Groups Projects
Commit 57de5d9a authored by 석찬 윤's avatar 석찬 윤
Browse files

Merge branch 'develop' into 'main'

fix: 모바일 뷰 오류 해결

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