import { isDifferentDay } from "../../utils/helpers"; import { DateSeparator, MessageBubble } from "./MessageBubble"; import { MessageList as MessageListType, ThreadParticipant, } from "../../../src/api/types/models"; import { Loader2, ArrowDown } from "lucide-react"; import { useState, useRef, useEffect, forwardRef, ForwardedRef, useImperativeHandle, } from "react"; // Message List component const MessageList = forwardRef( ( { messages, isLoading, isFetching, userId, teamUserIds, onReply, onDelete, onScroll, otherParticipant, }: { messages: MessageListType[]; isLoading: boolean; isFetching: boolean; userId: string | undefined; // When present, any message sender in this set is treated as "our side" // (right-aligned, blue). Lets staff see coworkers' messages on the team // side of the store inbox instead of confusing them with customer messages. teamUserIds?: readonly string[]; onReply: (message: MessageListType) => void; onDelete?: (messageId: string) => void; onScroll?: (event: React.UIEvent) => void; otherParticipant: ThreadParticipant | undefined; }, ref: ForwardedRef<{ scrollToBottom: () => void; getContainer: () => HTMLDivElement | null; }> ) => { const [highlightedMessageId, setHighlightedMessageId] = useState< string | null >(null); const messageRefs = useRef>({}); const [showScrollButton, setShowScrollButton] = useState(false); const containerRef = useRef(null); // Expose methods to parent component useImperativeHandle(ref, () => ({ scrollToBottom: () => { if (containerRef.current) { containerRef.current.scrollTo({ top: containerRef.current.scrollHeight, }); } }, getContainer: () => containerRef.current, })); // Handle scroll events to show/hide the scroll button useEffect(() => { const container = containerRef.current; if (!container) return; const handleScroll = (e: Event) => { // Show button when scrolled up more than 300px from bottom const isScrolledUp = container.scrollHeight - container.scrollTop - container.clientHeight > 300; setShowScrollButton(isScrolledUp); // Call parent's onScroll handler if provided if (onScroll && e instanceof UIEvent) { onScroll(e as unknown as React.UIEvent); } }; container.addEventListener("scroll", handleScroll); return () => container.removeEventListener("scroll", handleScroll); }, [onScroll]); const scrollToBottom = () => { if (containerRef.current) { containerRef.current.scrollTo({ top: containerRef.current.scrollHeight + 100, behavior: "smooth", }); } }; if (isLoading && !messages.length) { return (

درحال بارگذاری پیام‌ها...

); } if (messages.length === 0) { return (

هنوز پیامی ارسال نشده. مکالمه را شروع کنید!

); } // Handle reply click to scroll to original message const handleReplyClick = (replyToId: string) => { const targetMessage = messages.find((m) => m.id === replyToId); if (targetMessage) { // Reset the highlighted message ID first to ensure the animation can trigger again setHighlightedMessageId(null); // Use setTimeout to ensure the state update has processed setTimeout(() => { // Then set the highlighted message ID setHighlightedMessageId(replyToId); // Scroll to the message const messageElement = messageRefs.current[replyToId]; if (messageElement) { messageElement.scrollIntoView({ behavior: "smooth", block: "center", }); } }, 300); } }; return (
{isFetching && (
)} {messages.map((message, index) => (
{ if (message.id) { messageRefs.current[message.id] = el; } }} > {/* Date separator - show when day changes between messages */} {(index === 0 || isDifferentDay( message.createdAt || "", messages[index - 1]?.createdAt )) && } {/* Message bubble */}
))} {/* Floating scroll to bottom button with animation */}
); } ); // Add display name MessageList.displayName = "MessageList"; export default MessageList;