From 32a3965a5beaefea5f1393ae59aab5ca8a38cf03 Mon Sep 17 00:00:00 2001 From: ahnjongin <80513276+ahnjongin@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:15:43 +0900 Subject: [PATCH] =?UTF-8?q?[Feat]=20QFEED-130=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B0=81=EC=A2=85=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=EA=B3=BC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: qfeed-130 본인 메세지 확인 후 입력 처리 * feat: qfeed-130 메세지 전송 후 저장, 읽음 처리 완료 * fix: qfeed-130 스크롤 밑에 고정된 채로 채팅내역 띄우기 * fix: qfeed-130 채팅 기능 테스트 전 완성 --- src/pages/ChatList/index.tsx | 56 ++++++- src/pages/ChatRoom/api/fetchChatRoom.tsx | 13 ++ src/pages/ChatRoom/api/socket.ts | 19 +-- src/pages/ChatRoom/component/InputBar.tsx | 13 +- src/pages/ChatRoom/index.tsx | 178 ++++++++++++++-------- src/pages/ChatRoom/styles.ts | 6 +- src/pages/ChatRoom/type/messageType.tsx | 1 + 7 files changed, 194 insertions(+), 92 deletions(-) diff --git a/src/pages/ChatList/index.tsx b/src/pages/ChatList/index.tsx index 78ac213..1c77eb2 100644 --- a/src/pages/ChatList/index.tsx +++ b/src/pages/ChatList/index.tsx @@ -18,6 +18,15 @@ import { userNameStyle, } from '@/pages/ChatList/styles'; +const formatDate = (dateString: string): string => { + const date = new Date(dateString); + return date.toLocaleString('ko-KR', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }); +}; // 채팅 리스트 아이템 컴포넌트 const ChatItem = ({ chatRoomId, @@ -29,20 +38,26 @@ const ChatItem = ({ }: ChatData) => { const navigate = useNavigate(); + // 채팅방 클릭 시 해당 채팅방으로 이동 const handleClick = () => { navigate(`/chatroom/${chatRoomId}`); // 클릭 시 채팅방으로 이동 }; return (
+ {/* 프로필 이미지 */}
+ {/* 유저 닉네임 */} {otherUserNickname} - {lastMessageCreatedAt} + {/* 마지막 메시지 시간 */} + {formatDate(lastMessageCreatedAt)}
+ {/* 마지막 메시지 내용 */} {lastMessageContent} + {/* 읽지 않은 메시지 수 */} {unreadMessageCount && unreadMessageCount > 0 ? ( {unreadMessageCount} ) : null} @@ -55,13 +70,29 @@ const ChatItem = ({ // 채팅 리스트 메인 컴포넌트 const ChatList = () => { const [searchTerm, setSearchTerm] = useState(''); // 검색어 상태 - const { data: chatData, isLoading } = useQuery({ - queryKey: ['chatList'], - queryFn: fetchChatList, - refetchOnWindowFocus: false, - staleTime: 5 * 60 * 1000, + const { + data: chatData, + isLoading, + refetch, + } = useQuery({ + queryKey: ['chatList'], // React Query 키 + queryFn: fetchChatList, // 데이터를 가져오는 함수 + refetchOnWindowFocus: true, // 창 포커스 시 자동 리페치 + staleTime: 5 * 60 * 1000, // 캐시 데이터 유효 시간 }); + const navigate = useNavigate(); + + // 채팅방 클릭 핸들러 + const handleChatRoomClick = (chatRoomId: string, otherUserNickname: string) => { + console.log('ChatRoomId:', chatRoomId); // 확인 + console.log('OtherUserNickname:', otherUserNickname); // 확인 + + navigate(`/chatroom/${chatRoomId}`, { + state: { otherUserNickname, refetchChatList: refetch }, // 닉네임 전달 + }); + }; + // 검색어를 기준으로 채팅 리스트 필터링 const filteredChatData = Array.isArray(chatData) ? chatData.filter((chat) => @@ -69,23 +100,32 @@ const ChatList = () => { ) : []; + // 검색어 입력 핸들러 const handleSearchChange = (value: string) => { setSearchTerm(value); // 검색어 상태 업데이트 }; + // 데이터 로딩 중 처리 if (isLoading) { return
Loading...
; } return (
- {/* 검색 인풋 */} + {/* 상단 헤더 */}
+ {/* 검색 인풋 */} {/* 채팅 리스트 */}
{filteredChatData.map((chat) => ( - +
handleChatRoomClick(chat.chatRoomId, chat.otherUserNickname)} // 채팅방 클릭 이벤트 추가 + > + +
))}
diff --git a/src/pages/ChatRoom/api/fetchChatRoom.tsx b/src/pages/ChatRoom/api/fetchChatRoom.tsx index 96af742..b43d196 100644 --- a/src/pages/ChatRoom/api/fetchChatRoom.tsx +++ b/src/pages/ChatRoom/api/fetchChatRoom.tsx @@ -17,3 +17,16 @@ export const fetchMessages = async (chatRoomId: string): Promise return []; } }; + +export const markAsRead = async (chatRoomId: string): Promise => { + try { + const response = await apiClient.put(`/chats/${chatRoomId}/markasread`); + if (response.status === 200) { + console.log('읽음 처리 완료'); + } else { + console.error('읽음 처리 실패', response); + } + } catch (error) { + console.error('읽음 처리 중 오류 발생:', error); + } +}; diff --git a/src/pages/ChatRoom/api/socket.ts b/src/pages/ChatRoom/api/socket.ts index a7fb51f..b2acffe 100644 --- a/src/pages/ChatRoom/api/socket.ts +++ b/src/pages/ChatRoom/api/socket.ts @@ -5,33 +5,22 @@ const SOCKET_URL = 'wss://q-feed.n-e.kr/ws'; // STOMP 클라이언트 생성 export const stompClient = new Client({ - brokerURL: SOCKET_URL, // WebSocket URL - reconnectDelay: 5000, // 재연결 대기 시간 (ms) + brokerURL: SOCKET_URL, + reconnectDelay: 5000, debug: (str) => { console.log('STOMP Debug:', str); }, connectHeaders: { - Authorization: 'Token', // 토큰 인증 (필요한 경우) + Authorization: 'Bearer token', }, }); -// STOMP 연결 함수 export const connectStomp = () => { - stompClient.onConnect = (frame) => { - console.log('STOMP 연결 성공:', frame); - }; - - stompClient.onStompError = (frame) => { - console.error('STOMP 에러:', frame.headers['message']); - }; - - stompClient.activate(); // STOMP 활성화 + stompClient.activate(); }; -// STOMP 연결 해제 함수 export const disconnectStomp = () => { if (stompClient.active) { stompClient.deactivate(); - console.log('STOMP 연결 해제'); } }; diff --git a/src/pages/ChatRoom/component/InputBar.tsx b/src/pages/ChatRoom/component/InputBar.tsx index 1a9da8d..b76f89e 100644 --- a/src/pages/ChatRoom/component/InputBar.tsx +++ b/src/pages/ChatRoom/component/InputBar.tsx @@ -1,14 +1,15 @@ /** @jsxImportSource @emotion/react */ import { - iconButtonStyle, - iconStyle, + /* iconButtonStyle, */ + /* iconStyle, */ inputContainerStyle, inputStyle, inputWrap, sendButtonStyle, } from '@/pages/ChatRoom/component/InputBar.styles'; import { useState } from 'react'; -import { MdOutlineAddAPhoto } from 'react-icons/md'; + +/* import { MdOutlineAddAPhoto } from 'react-icons/md'; */ import { SendButton } from '@/components/ui/SendButton/SendButton'; interface InputBarProps { @@ -54,9 +55,9 @@ const ChatInputBar: React.FC = ({
{/* 카메라 아이콘 */} - + {/* */} {/* 텍스트 입력 */} { const { id: chatRoomId } = useParams<{ id: string }>(); const navigate = useNavigate(); + const location = useLocation(); + const refetchChatList = location.state?.refetchChatList; + const otherUserNickname = location.state?.otherUserNickname || "채팅방"; // 기본값 설정 const [isNotificationEnabled, setIsNotificationEnabled] = useState(true); - const [messages, setMessages] = useState([]); // 메시지 상태 관리 + const [messages, setMessages] = useState([]); const messagesEndRef = useRef(null); + const myId = '83974189-a749-4a24-bd5a-8ca2577fac73'; + const messagesContainerRef = useRef(null); // 메시지 컨테이너 참조 추가 - const toggleNotification = () => { - setIsNotificationEnabled((prevState) => !prevState); + console.log('Location State:', location.state); // 전체 state 확인 + console.log('Other User Nickname:', otherUserNickname); // 닉네임 확인 + + // 뒤로가기 시 채팅 리스트 새로고침 + const handleBack = () => { + if (refetchChatList) refetchChatList(); + navigate(-1); }; - useEffect(() => { - if (messagesEndRef.current) { - messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }); - } - }, [messages]); - useEffect(() => { + // 읽음 처리 + const handleMarkAsRead = useCallback(async () => { if (!chatRoomId) return; - - // 초기 메시지 로드 - const fetchInitialMessages = async () => { - try { - const response = await fetch(`/api/chats/${chatRoomId}/messages`, { - headers: { - Authorization: 'Token', // Postman에서 사용한 토큰 - }, - }); - - if (response.ok) { - const data: MessageType[] = await response.json(); - setMessages(data); - console.log('초기 메시지 로드:', data); - } else { - console.error('초기 메시지 로드 실패:', response.status, await response.text()); - } - } catch (error) { - console.error('초기 메시지 로드 중 오류:', error); - } - }; - - fetchInitialMessages(); - - // STOMP 연결 설정 - connectStomp(); - - stompClient.onConnect = () => { - console.log(`STOMP 연결 성공 (ChatRoom ID: ${chatRoomId})`); - const subscription = stompClient.subscribe(`/sub/chat/${chatRoomId}`, (message) => { - const receivedMessage: MessageType = JSON.parse(message.body); - setMessages((prevMessages) => [...prevMessages, receivedMessage]); // 실시간 메시지 추가 - console.log('새 메시지:', receivedMessage); + try { + await markAsRead(chatRoomId); + console.log('읽음 처리 완료'); + } catch (error) { + console.error('읽음 처리 중 오류:', error); + } + }, [chatRoomId]); + // 메시지 불러오기 함수 + const fetchInitialMessages = useCallback(async () => { + try { + const response = await fetch(`/api/chats/${chatRoomId}/messages`, { + headers: { + Authorization: + 'Bearer token', + }, }); - return () => subscription.unsubscribe(); - }; - - stompClient.onStompError = (error) => { - console.error('STOMP 연결 에러:', error); - }; - - return () => { - disconnectStomp(); - }; + if (response.ok) { + const data: MessageType[] = await response.json(); + const sortedData = [...data].sort( + (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() + ); + setMessages(sortedData); + + // 스크롤을 가장 아래로 이동 + setTimeout(() => { + if (messagesContainerRef.current) { + messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight; + } + }, 0); + } else { + console.error('초기 메시지 로드 실패:', response.status, await response.text()); + } + } catch (error) { + console.error('초기 메시지 로드 중 오류:', error); + } }, [chatRoomId]); + // 메시지 전송 함수 const handleSendMessage = (message: string) => { if (!chatRoomId) return; const payload = { roomId: Number(chatRoomId), - senderId: '83974189-a749-4a24-bd5a-8ca2577fac73', // 본인 ID - message, // 메시지 내용 + senderId: myId, + message, }; stompClient.publish({ destination: `/pub/chat/message`, body: JSON.stringify(payload), }); - console.log('메시지 전송:', payload); }; + useEffect(() => { + if (!chatRoomId) return; + + fetchInitialMessages(); // 초기 메시지 로드 + handleMarkAsRead(); // 읽음 처리 + + // STOMP 설정 + connectStomp(); + stompClient.onConnect = () => { + console.log(`STOMP 연결 성공 (ChatRoom ID: ${chatRoomId})`); + + const subscription = stompClient.subscribe(`/sub/chat/${chatRoomId}`, (message) => { + try { + const receivedMessage: MessageType = JSON.parse(message.body); + + setMessages((prevMessages) => { + const updatedMessages = [ + ...prevMessages, + { ...receivedMessage, isMine: receivedMessage.senderId === myId }, + ]; + return updatedMessages.sort( + (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() + ); + }); + + setTimeout(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'instant' }); + }, 0); + } catch (error) { + console.error('메시지 파싱 오류:', error); + } + }); + + return () => subscription.unsubscribe(); + }; + + return () => disconnectStomp(); + }, [chatRoomId, fetchInitialMessages, handleMarkAsRead]); + + useEffect(() => { + // 메시지가 업데이트될 때 스크롤 즉시 맨 아래로 이동 + if (messagesContainerRef.current) { + messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight; + } + }, [messages]); + return (
- navigate(-1)} /> - 채팅방 ID: {chatRoomId} -
- - +
+ +
+ +
); }; diff --git a/src/pages/ChatRoom/styles.ts b/src/pages/ChatRoom/styles.ts index 36168ee..e4d58b9 100644 --- a/src/pages/ChatRoom/styles.ts +++ b/src/pages/ChatRoom/styles.ts @@ -41,7 +41,11 @@ export const messageListStyle = css` overflow-y: auto; background-color: ${theme.colors.background}; `; - +/* export const messageContainerStyle = css` + flex: 1; + overflow-y: 'auto'; + background-color: ${theme.colors .background}; +`; */ export const otherMessageStyle = css` display: flex; align-items: flex-start; diff --git a/src/pages/ChatRoom/type/messageType.tsx b/src/pages/ChatRoom/type/messageType.tsx index cbc7f7f..345cb44 100644 --- a/src/pages/ChatRoom/type/messageType.tsx +++ b/src/pages/ChatRoom/type/messageType.tsx @@ -9,4 +9,5 @@ export interface MessageType { userNickName: string; userProfileImage: string; isMine: boolean; + senderId: string; }