@@ -4,8 +4,9 @@ import ChatService from '../services/ChatService';
44import  webSocketService  from  '../services/WebSocketService' ; 
55import  './ChatStyles.css' ; 
66
7- const  ChatRoom  =  ( )  =>  { 
8-  const  {  roomId }  =  useParams ( ) ; 
7+ const  ChatRoom  =  ( {  roomId : propRoomId ,  onLeaveRoom,  refreshRooms } )  =>  { 
8+  const  {  roomId : paramRoomId  }  =  useParams ( ) ; 
9+  const  roomId  =  propRoomId  ||  paramRoomId ;  // 속성으로 받은 값 우선, 없으면 URL 파라미터 사용 
910 const  navigate  =  useNavigate ( ) ; 
1011 const  [ messages ,  setMessages ]  =  useState ( [ ] ) ; 
1112 const  [ newMessage ,  setNewMessage ]  =  useState ( '' ) ; 
@@ -235,42 +236,24 @@ const ChatRoom = () => {
235236
236237 setActionInProgress ( true ) ;  // 액션 시작 
237238
239+  // 먼저 UI 업데이트 
240+  if  ( onLeaveRoom )  { 
241+  onLeaveRoom ( ) ; 
242+  } 
243+ 
238244 try  { 
239-  const  response  =  await  ChatService . leaveRoom ( roomId ) ; 
240-  if  ( response  &&  response . success )  { 
241-  // 웹소켓 연결 해제 
242-  webSocketService . unsubscribeFromRoom ( roomIdInt ) ; 
245+  // 백그라운드에서 서버 요청 처리 
246+  await  ChatService . leaveRoom ( roomId ) ; 
243247
244-  // 방 목록 업데이트 이벤트 트리거 
245-  localStorage . setItem ( 'chatRoomChanged' ,  Date . now ( ) . toString ( ) ) ; 
246-  localStorage . setItem ( 'refreshJoinedOnly' ,  'true' ) ;  // 참여 목록만 갱신하도록 플래그 설정 
248+  // 웹소켓 연결 해제 
249+  webSocketService . unsubscribeFromRoom ( roomIdInt ) ; 
247250
248-  // 리다이렉트 방법 강화: React Router와 window.location 모두 사용 
249-  try  { 
250-  // 1. React Router의 navigate 사용 시도 
251-  navigate ( '/chat-rooms' ,  {  replace : true  } ) ; 
252- 
253-  // 2. 페이지 이동이 실패할 경우를 대비해 직접 URL 이동 
254-  setTimeout ( ( )  =>  { 
255-  // 현재 URL과 이동할 URL을 비교하여 이동이 안 된 경우에만 실행 
256-  if  ( ! window . location . pathname . includes ( '/chat-rooms' ) )  { 
257-  console . log ( 'React Router 이동 실패, window.location 사용' ) ; 
258-  window . location . href  =  '/chat-rooms' ; 
259-  } 
260-  } ,  300 ) ; 
261-  }  catch  ( navError )  { 
262-  console . error ( 'Navigation error:' ,  navError ) ; 
263-  // 어떤 이유로든 navigate가 실패하면 window.location 사용 
264-  window . location . href  =  '/chat-rooms' ; 
265-  } 
266-  }  else  { 
267-  alert ( response ?. message  ||  '채팅방을 나가는데 실패했습니다.' ) ; 
268-  setActionInProgress ( false ) ;  // 실패 시 액션 상태 초기화 
251+  // 목록 새로고침 
252+  if  ( refreshRooms )  { 
253+  refreshRooms ( ) ; 
269254 } 
270255 }  catch  ( err )  { 
271256 console . error ( 'Error leaving room:' ,  err ) ; 
272-  alert ( err ?. message  ||  '채팅방을 나가는데 실패했습니다.' ) ; 
273-  setActionInProgress ( false ) ;  // 실패 시 액션 상태 초기화 
274257 } 
275258 } ; 
276259
@@ -286,44 +269,27 @@ const ChatRoom = () => {
286269
287270 setActionInProgress ( true ) ;  // 액션 시작 
288271
272+  // 먼저 UI 업데이트 
273+  if  ( onLeaveRoom )  { 
274+  onLeaveRoom ( ) ; 
275+  } 
276+ 
289277 try  { 
290-  const  response  =  await  ChatService . deleteRoom ( roomId ) ; 
291-  if  ( response  &&  response . success )  { 
292-  // 웹소켓 연결 해제 
293-  webSocketService . unsubscribeFromRoom ( roomIdInt ) ; 
278+  // 백그라운드에서 서버 요청 처리 
279+  await  ChatService . deleteRoom ( roomId ) ; 
294280
295-    // 채팅방 삭제 시 로컬 스토리지의 메시지도 삭제  
296-    localStorage . removeItem ( getChatStorageKey ( roomId ) ) ; 
281+  // 웹소켓 연결 해제  
282+  webSocketService . unsubscribeFromRoom ( roomIdInt ) ; 
297283
298-    // 방 목록 업데이트 이벤트 트리거 - 모든 목록 갱신 (삭제이므로)  
299-    localStorage . setItem ( 'chatRoomChanged' ,   Date . now ( ) . toString ( ) ) ; 
284+  // 채팅방 삭제 시 로컬 스토리지의 메시지도 삭제  
285+  localStorage . removeItem ( getChatStorageKey ( roomId ) ) ; 
300286
301-  // 리다이렉트 방법 강화: React Router와 window.location 모두 사용 
302-  try  { 
303-  // 1. React Router의 navigate 사용 시도 
304-  navigate ( '/chat-rooms' ,  {  replace : true  } ) ; 
305- 
306-  // 2. 페이지 이동이 실패할 경우를 대비해 직접 URL 이동 
307-  setTimeout ( ( )  =>  { 
308-  // 현재 URL과 이동할 URL을 비교하여 이동이 안 된 경우에만 실행 
309-  if  ( ! window . location . pathname . includes ( '/chat-rooms' ) )  { 
310-  console . log ( 'React Router 이동 실패, window.location 사용' ) ; 
311-  window . location . href  =  '/chat-rooms' ; 
312-  } 
313-  } ,  300 ) ; 
314-  }  catch  ( navError )  { 
315-  console . error ( 'Navigation error:' ,  navError ) ; 
316-  // 어떤 이유로든 navigate가 실패하면 window.location 사용 
317-  window . location . href  =  '/chat-rooms' ; 
318-  } 
319-  }  else  { 
320-  alert ( response ?. message  ||  '채팅방 삭제에 실패했습니다.' ) ; 
321-  setActionInProgress ( false ) ;  // 실패 시 액션 상태 초기화 
287+  // 목록 새로고침 
288+  if  ( refreshRooms )  { 
289+  refreshRooms ( ) ; 
322290 } 
323291 }  catch  ( err )  { 
324292 console . error ( 'Error deleting room:' ,  err ) ; 
325-  alert ( err ?. message  ||  '채팅방 삭제에 실패했습니다.' ) ; 
326-  setActionInProgress ( false ) ;  // 실패 시 액션 상태 초기화 
327293 } 
328294 } ; 
329295
@@ -477,18 +443,43 @@ const ChatRoom = () => {
477443 fetchRoomDetails ( ) ; 
478444 } ,  [ roomId ] ) ; 
479445
480-  // 메시지 목록이 바뀔 때마다 스크롤을 최하단으로 이동 
446+ 
447+  // 새 메시지 추가 시 조건부 스크롤 처리 
481448 useEffect ( ( )  =>  { 
482-  messagesEndRef . current ?. scrollIntoView ( {  behavior : 'smooth'  } ) ; 
449+  if  ( messageListRef . current )  { 
450+  // 스크롤이 맨 아래에 있는지 확인 
451+  const  {  scrollTop,  scrollHeight,  clientHeight }  =  messageListRef . current ; 
452+  const  isScrolledToBottom  =  scrollHeight  -  scrollTop  -  clientHeight  <  50 ; 
453+ 
454+  // 본인이 보낸 새 메시지이거나 스크롤이 이미 맨 아래에 있는 경우만 자동 스크롤 
455+  const  isOwnNewMessage  =  messages . length  >  0  && 
456+  messages [ messages . length  -  1 ] . userId  ===  userInfo . id  && 
457+  messages [ messages . length  -  1 ] . chatMsgId ?. toString ( ) . startsWith ( 'local-' ) ; 
458+ 
459+  if  ( isScrolledToBottom  ||  isOwnNewMessage )  { 
460+  messagesEndRef . current ?. scrollIntoView ( {  behavior : 'smooth'  } ) ; 
461+  } 
462+  } 
483463 } ,  [ messages ] ) ; 
484464
465+  const  prevMessagesRef  =  useRef ( [ ] ) ; 
466+ 
485467 // 메시지 목록 상단에 도달하면 이전 메시지 로드 
486468 const  handleScroll  =  ( )  =>  { 
487469 if  ( messageListRef . current )  { 
488470 const  {  scrollTop }  =  messageListRef . current ; 
489471 if  ( scrollTop  ===  0  &&  ! loading  &&  messages . length  >  0 )  { 
472+  // 스크롤 위치 기억 
473+  const  scrollHeight  =  messageListRef . current . scrollHeight ; 
474+ 
490475 // 이전 메시지 로드 
491-  loadMessages ( ) ; 
476+  loadMessages ( ) . then ( ( )  =>  { 
477+  // 이전 위치 유지 (새 메시지가 위에 추가되면 스크롤 위치 조정) 
478+  if  ( messageListRef . current )  { 
479+  const  newScrollHeight  =  messageListRef . current . scrollHeight ; 
480+  messageListRef . current . scrollTop  =  newScrollHeight  -  scrollHeight ; 
481+  } 
482+  } ) ; 
492483 } 
493484 } 
494485 } ; 
@@ -612,119 +603,108 @@ const ChatRoom = () => {
612603 } ) ; 
613604
614605 return  ( 
615-  < div  className = "chat-layout" > 
616-  { /* 사이드바는 상위 컴포넌트에서 렌더링한다고 가정 */ } 
617- 
618-  { /* 채팅 창 */ } 
619-  < div  className = "chat-window" > 
620-  { /* 채팅 헤더 */ } 
621-  < div  className = "chat-header" > 
622-  < div  className = "room-title" > { roomInfo . title } </ div > 
623-  < div  className = "actions" > 
624-  < div  className = "connection-status" > 
625-  < div  className = { `status-indicator ${ connected  ? 'connected'  : 'disconnected' }  } > </ div > 
626-  < span > { connected  ? '연결됨'  : '연결 중...' } </ span > 
627-  </ div > 
606+  < div  className = "chat-window" > 
607+  { /* 채팅 헤더 */ } 
608+  < div  className = "chat-header" > 
609+  < div  className = "room-title" > { roomInfo . title } </ div > 
610+  < div  className = "actions" > 
611+  < div  className = "connection-status" > 
612+  < div  className = { `status-indicator ${ connected  ? 'connected'  : 'disconnected' }  } > </ div > 
613+  < span > { connected  ? '연결됨'  : '연결 중...' } </ span > 
614+  </ div > 
615+  < button 
616+  className = { `action-btn ${ actionInProgress  ? 'disabled'  : '' }  } 
617+  onClick = { leaveRoom } 
618+  disabled = { actionInProgress } 
619+  > 
620+  < i  className = "fa-solid fa-right-from-bracket" > </ i > 
621+  { actionInProgress  ? '처리 중...'  : '나가기' } 
622+  </ button > 
623+  { isRoomCreator  &&  ( 
628624 < button 
629-  className = { `action-btn ${ actionInProgress  ? 'disabled'  : '' }  } 
630-  onClick = { leaveRoom } 
625+  className = { `action-btn delete  ${ actionInProgress  ? 'disabled'  : '' }  } 
626+  onClick = { deleteRoom } 
631627 disabled = { actionInProgress } 
632628 > 
633-  < i  className = "fa-solid fa-right-from-bracket " > </ i > 
634-  { actionInProgress  ? '처리 중...'  : '나가기 ' } 
629+  < i  className = "fa-solid fa-trash " > </ i > 
630+  { actionInProgress  ? '처리 중...'  : '삭제하기 ' } 
635631 </ button > 
636-  { isRoomCreator  &&  ( 
637-  < button 
638-  className = { `action-btn delete ${ actionInProgress  ? 'disabled'  : '' }  } 
639-  onClick = { deleteRoom } 
640-  disabled = { actionInProgress } 
641-  > 
642-  < i  className = "fa-solid fa-trash" > </ i > 
643-  { actionInProgress  ? '처리 중...'  : '삭제하기' } 
644-  </ button > 
645-  ) } 
646-  </ div > 
632+  ) } 
647633 </ div > 
634+  </ div > 
648635
649-  { /* 채팅 메시지 영역 */ } 
650-  { loading  &&  messages . length  ===  0  ? ( 
651-  < div  className = "loading" > 
652-  < div  className = "loading-spinner" > </ div > 
653-  < p > 로딩 중...</ p > 
654-  </ div > 
655-  )  : error  ? ( 
656-  < div  className = "empty-chat" > 
657-  < p  className = "text-red-500" > { error } </ p > 
658-  </ div > 
659-  )  : ( 
660-  < div 
661-  className = "chat-messages" 
662-  ref = { messageListRef } 
663-  onScroll = { handleScroll } 
664-  > 
665-  { messages . length  ===  0  ? ( 
666-  < div  className = "empty-chat" > 
667-  < p  className = "main-message" > 아직 메시지가 없습니다.</ p > 
668-  < p  className = "sub-message" > 첫 메시지를 보내보세요!</ p > 
669-  </ div > 
670-  )  : ( 
671-  < > 
672-  { sortedDates . map ( date  =>  ( 
673-  < div  key = { date } > 
674-  < div  className = "date-divider" > 
675-  < span > { date } </ span > 
676-  </ div > 
677-  { groupedMessages [ date ] . map ( ( msg )  =>  ( 
678-  < div 
679-  key = { msg . chatMsgId  ||  `${ msg . userId } ${ Date . now ( ) } ${ Math . random ( ) }  } 
680-  className = { `message ${ msg . userId  ===  userInfo . id  ? 'sent'  : 'received' } ${ msg . failed  ? 'failed'  : '' }  } 
681-  > 
682-  < div  className = "avatar" > 
683-  { /* 실제 사용자 아바타가 있으면 추가 */ } 
684-  </ div > 
685-  < div  className = "content" > 
686-  < div  className = "sender" > { msg . nickname } </ div > 
687-  < div  className = "bubble" > { msg . message } </ div > 
688-  < div  className = "time" > 
689-  { msg . sendAt  ? formatTime ( msg . sendAt )  : '' } 
690-  { msg . failed  &&  < span  className = "error-badge"  title = { msg . failReason } > !</ span > } 
691-  </ div > 
636+  { /* 채팅 메시지 영역 */ } 
637+  { loading  &&  messages . length  ===  0  ? ( 
638+  < div  className = "loading" > 
639+  < div  className = "loading-spinner" > </ div > 
640+  < p > 로딩 중...</ p > 
641+  </ div > 
642+  )  : error  ? ( 
643+  < div  className = "empty-chat" > 
644+  < p  className = "text-red-500" > { error } </ p > 
645+  </ div > 
646+  )  : ( 
647+  < div 
648+  className = "chat-messages" 
649+  ref = { messageListRef } 
650+  onScroll = { handleScroll } 
651+  > 
652+  { messages . length  ===  0  ? ( 
653+  < div  className = "empty-chat" > 
654+  < p  className = "main-message" > 아직 메시지가 없습니다.</ p > 
655+  < p  className = "sub-message" > 첫 메시지를 보내보세요!</ p > 
656+  </ div > 
657+  )  : ( 
658+  < > 
659+  { sortedDates . map ( date  =>  ( 
660+  < div  key = { date } > 
661+  < div  className = "date-divider" > 
662+  < span > { date } </ span > 
663+  </ div > 
664+  { groupedMessages [ date ] . map ( ( msg )  =>  ( 
665+  < div 
666+  key = { msg . chatMsgId  ||  `${ msg . userId } ${ Date . now ( ) } ${ Math . random ( ) }  } 
667+  className = { `message ${ msg . userId  ===  userInfo . id  ? 'sent'  : 'received' } ${ msg . failed  ? 'failed'  : '' }  } 
668+  > 
669+  < div  className = "avatar" > 
670+  { /* 실제 사용자 아바타가 있으면 추가 */ } 
671+  </ div > 
672+  < div  className = "content" > 
673+  < div  className = "sender" > { msg . nickname } </ div > 
674+  < div  className = "bubble" > { msg . message } </ div > 
675+  < div  className = "time" > 
676+  { msg . sendAt  ? formatTime ( msg . sendAt )  : '' } 
677+  { msg . failed  &&  < span  className = "error-badge"  title = { msg . failReason } > !</ span > } 
692678 </ div > 
693679 </ div > 
694-  ) ) } 
695-  </ div > 
696-  ) ) } 
697-  < div  ref = { messagesEndRef }  /> 
698-  </ > 
699-  ) } 
700-  </ div > 
701-  ) } 
702- 
703-  { /* 채팅 입력 영역 */ } 
704-  < form  onSubmit = { sendMessage }  className = "chat-input" > 
705-  < button  type = "button"  className = "emoji-btn" > 
706-  < i  className = "fa-regular fa-face-smile" > </ i > 
707-  </ button > 
708-  < button  type = "button"  className = "attach-btn" > 
709-  < i  className = "fa-solid fa-paperclip" > </ i > 
710-  </ button > 
711-  < input 
712-  type = "text" 
713-  value = { newMessage } 
714-  onChange = { ( e )  =>  setNewMessage ( e . target . value ) } 
715-  placeholder = "메시지를 입력하세요" 
716-  maxLength = { 300 } 
717-  disabled = { actionInProgress  ||  ! connected } 
718-  /> 
719-  < button 
720-  type = "submit" 
721-  className = "send-btn" 
722-  disabled = { ! newMessage . trim ( )  ||  actionInProgress  ||  ! connected } 
723-  > 
724-  < i  className = "fa-solid fa-paper-plane" > </ i > 
725-  </ button > 
726-  </ form > 
727-  </ div > 
680+  </ div > 
681+  ) ) } 
682+  </ div > 
683+  ) ) } 
684+  < div  ref = { messagesEndRef }  /> 
685+  </ > 
686+  ) } 
687+  </ div > 
688+  ) } 
689+ 
690+  { /* 채팅 입력 영역 */ } 
691+  < form  onSubmit = { sendMessage }  className = "chat-input" > 
692+  < input 
693+  type = "text" 
694+  value = { newMessage } 
695+  onChange = { ( e )  =>  setNewMessage ( e . target . value ) } 
696+  placeholder = "메시지를 입력하세요" 
697+  maxLength = { 300 } 
698+  disabled = { actionInProgress  ||  ! connected } 
699+  /> 
700+  < button 
701+  type = "submit" 
702+  className = "send-btn" 
703+  disabled = { ! newMessage . trim ( )  ||  actionInProgress  ||  ! connected } 
704+  > 
705+  < i  className = "fa-solid fa-paper-plane" > 전송</ i > 
706+  </ button > 
707+  </ form > 
728708 </ div > 
729709 ) ; 
730710} ; 
0 commit comments