'use client'; import { useState, useRef, useEffect, useCallback, type KeyboardEvent } from 'react'; import useAuth from '@/hooks/useAuth'; import useChat from '@/hooks/useChat'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUsers, faEllipsisVertical, faPaperPlane, faTrashCan, faMagnifyingGlassPlus, faMagnifyingGlassMinus, faRotateRight, faClock } from '@fortawesome/free-solid-svg-icons'; import './chat-sidebar.scss'; const MIN_FONT_SIZE = 11; const MAX_FONT_SIZE = 19; const DEFAULT_FONT_SIZE = 13; export default function ChatSidebar() { const { isAuthenticated } = useAuth(); const { messages, systemMessages, participantCount, participants, sendMessage, clearMessages, refreshChat, requestParticipants, chatConnected } = useChat(); const [inputValue, setInputValue] = useState(''); const [fontSize, setFontSize] = useState(DEFAULT_FONT_SIZE); const [showMenu, setShowMenu] = useState(false); const [showTime, setShowTime] = useState(false); const messagesRef = useRef(null); const isAutoScrollRef = useRef(true); const [showParticipants, setShowParticipants] = useState(false); const menuRef = useRef(null); // 메시지 + 시스템 메시지를 시간순 병합 const mergedMessages = (() => { const items: Array< | { type: 'chat'; data: typeof messages[number] } | { type: 'system'; data: typeof systemMessages[number] } > = []; messages.forEach((m) => items.push({ type: 'chat', data: m })); systemMessages.forEach((m) => items.push({ type: 'system', data: m })); items.sort((a, b) => { const timeA = a.type === 'chat' ? a.data.sentAt : a.data.receivedAt; const timeB = b.type === 'chat' ? b.data.sentAt : b.data.receivedAt; return new Date(timeA).getTime() - new Date(timeB).getTime(); }); return items; })(); // 자동 스크롤 useEffect(() => { const el = messagesRef.current; if (!el || !isAutoScrollRef.current) return; el.scrollTop = el.scrollHeight; }, [mergedMessages.length]); // 스크롤 이벤트로 자동 스크롤 제어 const handleScroll = useCallback(() => { const el = messagesRef.current; if (!el) return; const threshold = 50; isAutoScrollRef.current = el.scrollTop + el.clientHeight >= el.scrollHeight - threshold; }, []); // 메뉴 외부 클릭 닫기 useEffect(() => { if (!showMenu) return; const handleClick = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { setShowMenu(false); } }; document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [showMenu]); const handleSend = useCallback(() => { if (!inputValue.trim()) return; sendMessage(inputValue); setInputValue(''); }, [inputValue, sendMessage]); const handleKeyDown = useCallback((e: KeyboardEvent) => { if (e.key === 'Enter' && !e.nativeEvent.isComposing) { e.preventDefault(); handleSend(); } }, [handleSend]); const handleClear = useCallback(() => { clearMessages(); setShowMenu(false); }, [clearMessages]); const handleFontIncrease = useCallback(() => { setFontSize((prev) => Math.min(prev + 2, MAX_FONT_SIZE)); setShowMenu(false); }, []); const handleFontDecrease = useCallback(() => { setFontSize((prev) => Math.max(prev - 2, MIN_FONT_SIZE)); setShowMenu(false); }, []); const handleRefresh = useCallback(() => { setShowMenu(false); refreshChat(); }, [refreshChat]); const handleToggleTime = useCallback(() => { setShowTime((prev) => !prev); setShowMenu(false); }, []); const handleShowParticipants = useCallback(() => { requestParticipants(); setShowParticipants(true); }, [requestParticipants]); const handleCloseParticipants = useCallback(() => { setShowParticipants(false); }, []); const formatTime = (dateStr: string) => { const date = new Date(dateStr); return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`; }; return (
{/* 헤더 */}
실시간 채팅
{showMenu && (
)}
{/* 메시지 영역 */}
{!chatConnected && (
채팅 서버에 연결 중...
)} {mergedMessages.map((item, index) => { if (item.type === 'system') { return (
{item.data.content}
); } const msg = item.data; return (
{showTime && {formatTime(msg.sentAt)}} {msg.memberName || msg.memberSID} {msg.content}
); })}
{/* 입력 영역 */}
{isAuthenticated ? (
setInputValue(e.target.value)} onKeyDown={handleKeyDown} maxLength={500} disabled={!chatConnected} />
) : (
로그인 후 채팅에 참여하세요
)}
{/* 참여자 목록 패널 */} {showParticipants && ( <>

참여자 ({participantCount}명)

    {participants.length === 0 && participantCount === 0 ? (
  • 참여자가 없습니다
  • ) : ( <> {participants.map((p) => (
  • {p.memberName}
  • ))} {participantCount - participants.length > 0 && (
  • 비회원 {participantCount - participants.length}명
  • )} )}
)}
); }