"use client"; import React, { useEffect, useLayoutEffect, useRef, useState } from "react"; import { MessageSquare, Pencil, Check, X, ChevronsDown } from "lucide-react"; import { useMessages, useMarkAsRead, useUpdateRoom } from "@/hooks/useMessenger"; import { useAuth } from "@/hooks/useAuth"; import { useMessengerContext } from "@/contexts/MessengerContext"; import { useMessengerSocket } from "@/hooks/useMessengerSocket"; import { MessageItem } from "./MessageItem"; import { MessageInput } from "./MessageInput"; import type { MessageInputHandle } from "./MessageInput"; import type { Room } from "@/hooks/useMessenger"; interface ChatPanelProps { room: Room | null; messageInputRef?: React.RefObject; onCaptureClick?: () => void; } export function ChatPanel({ room, messageInputRef, onCaptureClick }: ChatPanelProps) { const { user } = useAuth(); const { selectedRoomId } = useMessengerContext(); const { data: messages } = useMessages(selectedRoomId); const markAsRead = useMarkAsRead(); const updateRoom = useUpdateRoom(); const { emitTypingStart, emitTypingStop, typingUsers } = useMessengerSocket(); const scrollRef = useRef(null); const [isAtBottom, setIsAtBottom] = useState(true); const [isEditingName, setIsEditingName] = useState(false); const [editName, setEditName] = useState(""); const editInputRef = useRef(null); useEffect(() => { if (selectedRoomId) { markAsRead.mutate(selectedRoomId); } }, [selectedRoomId, messages?.length]); const lastMessageId = messages?.[messages.length - 1]?.id; useLayoutEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [lastMessageId, selectedRoomId]); useEffect(() => { const el = scrollRef.current; if (!el) return; const onScroll = () => { setIsAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 60); }; el.addEventListener("scroll", onScroll); return () => el.removeEventListener("scroll", onScroll); }, []); if (!room) { return (

대화를 선택하세요

); } const roomTyping = selectedRoomId ? typingUsers.get(selectedRoomId) : undefined; // First message in a time group (shows avatar + name) const isFirstInGroup = (idx: number) => { 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; // Gap > 5 minutes → new time group const gap = new Date(next.createdAt).getTime() - new Date(curr.createdAt).getTime(); return gap > 5 * 60 * 1000; }; // Date separator helper const shouldShowDate = (idx: number) => { 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.participants.length}명 )}
{/* Messages */}
{messages?.map((msg, idx) => (
{shouldShowDate(idx) && (
{new Date(msg.createdAt).toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric", weekday: "short", })}
)}
))}
{roomTyping && roomTyping.length > 0 ? `${roomTyping.join(", ")}님이 입력 중...` : ""}
{!isAtBottom && ( )}
{/* Input */} emitTypingStart(room.id)} onTypingStop={() => emitTypingStop(room.id)} onCaptureClick={onCaptureClick} />
); }