import { FC, useEffect, useRef, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; import { setMenuActiveGroupPage } from '../../../../redux/slices/store'; import { fetchGroupMessages, sendGroupMessage, setGroupChatStatus, } from '../../../../redux/slices/groupChat'; import { SearchInput } from '../../../../components/input/SearchInput'; import { MessageItem } from './MessageItem'; import { Send } from '../../../../assets/icons/input'; interface GroupChatProps { groupId: number; } const CHUNK_SIZE = 10; export const Chat: FC = ({ groupId }) => { const dispatch = useAppDispatch(); const messages = useAppSelector((s) => s.groupchat.messages[groupId] || []); const messagesState = useAppSelector( (state) => state.groupchat.fetchMessages.status, ); const lastMessageId = useAppSelector( (state) => state.groupchat.lastMessage[groupId] || 0, ); const user = useAppSelector((state) => state.auth); const [text, setText] = useState(''); const [firstMessagesFetch, setFirctMessagesFetch] = useState(true); const scrollRef = useRef(null); // добавлено: ref для хранения предыдущей высоты const prevHeightRef = useRef(0); // активируем таб useEffect(() => { dispatch(setMenuActiveGroupPage('chat')); }, []); // первичная загрузка useEffect(() => { dispatch( fetchGroupMessages({ groupId, limit: CHUNK_SIZE, }), ); }, [groupId]); // автоскролл вниз после начальной загрузки (но не при догрузке) useEffect(() => { const div = scrollRef.current; if (!div) return; // если prevHeightRef == 0 — значит это не догрузка, а обычная загрузка if (prevHeightRef.current === 0) { div.scrollTop = div.scrollHeight; } }, [messages.length]); // добавлено: компенсирование скролла при догрузке useEffect(() => { const div = scrollRef.current; if (!div) return; if (prevHeightRef.current > 0) { const diff = div.scrollHeight - prevHeightRef.current; div.scrollTop = diff; // компенсируем смещение prevHeightRef.current = 0; // сбрасываем } }, [messages]); useEffect(() => { if (messagesState == 'successful') { dispatch( setGroupChatStatus({ key: 'fetchMessages', status: 'idle' }), ); } if (messagesState == 'failed') { dispatch( setGroupChatStatus({ key: 'fetchMessages', status: 'idle' }), ); } }, [messagesState]); const lastMessageIdRef = useRef(null); useEffect(() => { lastMessageIdRef.current = lastMessageId; if (firstMessagesFetch) { setFirctMessagesFetch(false); dispatch( fetchGroupMessages({ groupId, afterMessageId: lastMessageIdRef.current, timeoutSeconds: 10, }), ); } }, [messages]); useEffect(() => { const interval = setInterval(() => { if (lastMessageIdRef.current === null) return; dispatch( fetchGroupMessages({ groupId, afterMessageId: lastMessageIdRef.current, timeoutSeconds: 10, }), ); }, 10000); return () => clearInterval(interval); }, [groupId]); const handleSend = () => { if (!text.trim()) return; dispatch( sendGroupMessage({ groupId, content: text.trim(), }), ).then(() => { setText(''); setTimeout(() => { const div = scrollRef.current; if (div) div.scrollTop = div.scrollHeight; }, 0); }); }; const handleEnter = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); handleSend(); } }; // догрузка старых сообщений при скролле вверх const handleScroll = () => { const div = scrollRef.current; if (!div) return; // если скролл в верхней точке if (div.scrollTop === 0) { prevHeightRef.current = div.scrollHeight; // запоминаем высоту до загрузки const first = messages[0]; if (!first || first.id == 1) return; const beforeId = first.id - CHUNK_SIZE; dispatch( fetchGroupMessages({ groupId, limit: CHUNK_SIZE, afterMessageId: beforeId, }), ); } }; return (
{}} placeholder="Поиск сообщений" />
{messages.map((msg, i) => ( ))}
); };