import { Box, Fab, FormControl, IconButton, InputAdornment, InputBase, Typography, useMediaQuery, useTheme, } from "@mui/material"; import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; import { useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import CustomButton from "@components/CustomButton"; import SendIcon from "@components/icons/SendIcon"; import Message from "./Message"; import { throttle } from "@utils/decorators"; import { enqueueSnackbar } from "notistack"; import { useTicketStore } from "@root/stores/tickets"; import { addOrUpdateMessages, clearMessages, setMessages, useMessageStore } from "@root/stores/messages"; import { getTicketMessages, sendTicketMessage, subscribeToTicketMessages } from "@root/api/tickets"; import { GetMessagesRequest, TicketMessage } from "@root/model/ticket"; import { authStore } from "@root/stores/makeRequest"; export default function SupportChat() { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); const [messageField, setMessageField] = useState(""); const tickets = useTicketStore(state => state.tickets); const messages = useMessageStore(state => state.messages); const token = authStore(state => state.token); const ticketId = useParams().ticketId; const ticket = tickets.find(ticket => ticket.id === ticketId); const chatBoxRef = useRef(); const [isPreventAutoscroll, setIsPreventAutoscroll] = useState(false); useEffect(function fetchTicketMessages() { if (!ticketId) return; const getTicketsBody: GetMessagesRequest = { amt: 100, // TODO use pagination page: 0, ticket: ticketId, }; const controller = new AbortController(); getTicketMessages({ body: getTicketsBody, signal: controller.signal, }).then(result => { console.log("GetMessagesResponse", result); setMessages(result); }).catch(error => { console.log("Error fetching tickets", error); enqueueSnackbar(error.message); }); return () => { controller.abort(); clearMessages(); }; }, [ticketId]); useEffect(function subscribeToMessages() { if (!ticketId || !token) return; const unsubscribe = subscribeToTicketMessages({ ticketId: ticketId, accessToken: token, onMessage(event) { try { const newMessage = JSON.parse(event.data) as TicketMessage; console.log("SSE: parsed newMessage:", newMessage); addOrUpdateMessages([newMessage]); } catch (error) { console.log("SSE: couldn't parse:", event.data); console.log("Error parsing message SSE", error); } }, onError(event) { console.log("SSE Error:", event); }, }); return () => { unsubscribe(); }; }, [ticketId, token]); useEffect(function refreshChatScrollTop() { if (!chatBoxRef.current) return; const chatBox = chatBoxRef.current; const scrollHandler = () => setIsPreventAutoscroll(chatBox.scrollTop + chatBox.clientHeight * 2 < chatBox.scrollHeight); const throttledScrollHandler = throttle(scrollHandler, 200); chatBox.addEventListener("scroll", throttledScrollHandler); return () => { chatBox.removeEventListener("scroll", throttledScrollHandler); }; }, []); useEffect(function scrollOnMessage() { if (!chatBoxRef.current) return; if (!isPreventAutoscroll) scrollToBottom(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [messages]); async function handleSendMessage() { if (!ticket || !messageField) return; sendTicketMessage({ ticket: ticket.id, message: messageField, lang: "ru", files: [], }); setMessageField(""); } function scrollToBottom() { if (!chatBoxRef.current) return; chatBoxRef.current.scroll({ left: 0, top: chatBoxRef.current.scrollHeight, behavior: "smooth", }); } return ( Заголовок Создан: 15.09.22 08:39 {isPreventAutoscroll && ( )} {ticket && messages.map((message) => ( ))} setMessageField(e.target.value)} endAdornment={ !upMd && ( ) } /> {upMd && ( Отправить )} ); }