"use client"; import React, { useEffect, useRef, useState } from "react"; import { MessageSquare, Pencil, Check, X, ChevronsDown, Bell, BellOff } from "lucide-react"; import { useMessages, useMarkAsRead, useUpdateRoom } from "@/hooks/useMessenger"; import { useAuth } from "@/hooks/useAuth"; import { useMessengerContext } from "@/contexts/MessengerContext"; import { MessageItem } from "./MessageItem"; import { MessageInput } from "./MessageInput"; import type { Room } from "@/hooks/useMessenger"; interface ChatPanelProps { room: Room | null; typingUsers: Map; emitTypingStart: (roomId: string) => void; emitTypingStop: (roomId: string) => void; onNewRoom: () => void; } const formatDateLabel = (dateStr: string) => { const d = new Date(dateStr); const today = new Date(); const yesterday = new Date(); yesterday.setDate(today.getDate() - 1); if (d.toDateString() === today.toDateString()) return "오늘"; if (d.toDateString() === yesterday.toDateString()) return "어제"; return d.toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric", weekday: "short" }); }; export function ChatPanel({ room, typingUsers, emitTypingStart, emitTypingStop, onNewRoom }: ChatPanelProps) { const { user } = useAuth(); const { selectedRoomId, isOpen, mutedRooms, toggleRoomMute } = useMessengerContext(); const { data: messages } = useMessages(selectedRoomId); const markAsRead = useMarkAsRead(); const updateRoom = useUpdateRoom(); const scrollRef = useRef(null); const [isAtBottom, setIsAtBottom] = useState(true); const [isEditingName, setIsEditingName] = useState(false); const [editName, setEditName] = useState(""); const editInputRef = useRef(null); useEffect(() => { if (isOpen && selectedRoomId) { markAsRead.mutate(selectedRoomId); } }, [isOpen, selectedRoomId, messages?.length]); // Re-attach scroll listener whenever room changes useEffect(() => { const el = scrollRef.current; if (!el) return; const onScroll = () => { // In flex-col-reverse, scrollTop=0 means bottom (newest messages) setIsAtBottom(Math.abs(el.scrollTop) < 60); }; el.addEventListener("scroll", onScroll); return () => el.removeEventListener("scroll", onScroll); }, [room?.id]); if (!room) { return (

대화를 선택하세요

); } const roomTyping = selectedRoomId ? typingUsers.get(selectedRoomId) : undefined; // messages is oldest-first; flex-col-reverse CSS pins scroll to bottom (newest visible) const isFirstInGroup = (idx: number) => { if (!messages) return true; if (idx === 0) return true; const prev = messages[idx - 1]; const curr = messages[idx]; if (prev.senderId !== curr.senderId || curr.isDeleted || prev.isDeleted) return true; const gap = new Date(curr.createdAt).getTime() - new Date(prev.createdAt).getTime(); return gap > 5 * 60 * 1000; }; const isLastInGroup = (idx: number) => { if (!messages) return true; if (idx === messages.length - 1) return true; const curr = messages[idx]; const next = messages[idx + 1]; if (curr.senderId !== next.senderId || next.isDeleted || curr.isDeleted) return true; const gap = new Date(next.createdAt).getTime() - new Date(curr.createdAt).getTime(); return gap > 5 * 60 * 1000; }; const shouldShowDate = (idx: number) => { if (!messages) return false; if (idx === 0) return true; const prev = new Date(messages[idx - 1].createdAt).toDateString(); const curr = new Date(messages[idx].createdAt).toDateString(); return prev !== curr; }; // Compute display name based on room type const displayName = (() => { if (room.type === "dm") { const other = room.participants.find((p) => p.userId !== user?.userId); return other?.userName ?? room.name; } if (room.name) return room.name; const others = room.participants.filter((p) => p.userId !== user?.userId); if (others.length <= 2) { return others.map((p) => p.userName).join(", "); } return `${others[0].userName}, ${others[1].userName} 외 ${others.length - 2}명`; })(); return (
{/* Header */}
{isEditingName ? (
{ e.preventDefault(); const trimmed = editName.trim(); if (trimmed && trimmed !== room.name) { updateRoom.mutate({ roomId: room.id, name: trimmed }); } setIsEditingName(false); }} > setEditName(e.target.value)} className="font-semibold text-sm bg-transparent border-b border-primary outline-none flex-1 min-w-0" autoFocus />
) : ( <>

{displayName}

{room.type !== "dm" && ( )} {room.participants.length}명 )}
{/* Messages — flex-col-reverse keeps scroll pinned to bottom (newest visible) */}
{/* Typing indicator at top of inner div = visually just above messages */}
{roomTyping && roomTyping.length > 0 && ( <> {roomTyping.join(", ")}님이 입력 중 )}
{messages?.map((msg, idx) => (
{shouldShowDate(idx) && (
{formatDateLabel(msg.createdAt)}
)}
))}
{!isAtBottom && ( )} {/* Input */} emitTypingStart(room.id)} onTypingStop={() => emitTypingStop(room.id)} />
); }