Skip to content

Commit a08f6af

Browse files
committed
Fix: UI 및 기능 수정
1 parent afdaefe commit a08f6af

File tree

3 files changed

+210
-185
lines changed

3 files changed

+210
-185
lines changed

src/components/ChatRoom.jsx

Lines changed: 151 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import ChatService from '../services/ChatService';
44
import webSocketService from '../services/WebSocketService';
55
import './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

Comments
 (0)