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 { apiRequestHandler } from "@utils/api/apiRequestHandler"; import { ApiError, TicketMessage } from "@utils/api/types"; import { throttle } from "@utils/decorators"; import { useSnackbar } from "notistack"; export default function SupportChat() { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); const [messageText, setMessageText] = useState(""); const [messages, setMessages] = useState([]); const ticketId = useParams().ticketId; const { enqueueSnackbar } = useSnackbar(); const chatBoxRef = useRef(); const [isPreventAutoscroll, setIsPreventAutoscroll] = useState(false); function scrollToBottom() { if (!chatBoxRef.current) return; chatBoxRef.current.scroll({ left: 0, top: chatBoxRef.current.scrollHeight, behavior: "smooth", }); } 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); }; }, []); // TODO При подписке на SSE сервер уже отправляет все сообщения тикета useEffect( function getMessages() { if (!ticketId) return; const abortController = new AbortController(); apiRequestHandler .getMessages( { amt: 100, page: 0, srch: "", ticket: ticketId, }, abortController.signal ) .then((result) => { if (result instanceof ApiError) { enqueueSnackbar(`Api error: ${result.message}`); } else if (result instanceof Error) { enqueueSnackbar(`Error: ${result.message}`); } else { setMessages(result); } }); return () => { abortController.abort(); }; }, [enqueueSnackbar, ticketId] ); useEffect( function scrollOnMessage() { if (!chatBoxRef.current) return; if (!isPreventAutoscroll) scrollToBottom(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [messages] ); useEffect( function subscribeToMessages() { if (!ticketId) return; const unsubscribe = apiRequestHandler.subscribeToTicket({ ticketId, onMessage(event) { // console.log("SSE received:", event.data); try { const newMessage = JSON.parse(event.data) as TicketMessage; setMessages((prev) => prev.findIndex((message) => message.id === newMessage.id) === -1 ? [...prev.slice(), newMessage] : prev ); } catch (error) { console.log("SSE is not JSON", error); } }, onError(event) { console.log("SSE Error:", event); }, }); return () => { unsubscribe(); }; }, [ticketId] ); async function handleSendMessage() { if (!ticketId) return; const result = await apiRequestHandler.sendTicketMessage({ ticket: ticketId, message: messageText, lang: "ru", files: [], }); if (result instanceof ApiError) { enqueueSnackbar(`Api error: ${result.message}`); } else if (result instanceof Error) { enqueueSnackbar(`Error: ${result.message}`); } else { setMessageText(""); } } return ( Заголовок Создан: 15.09.22 08:39 {isPreventAutoscroll && ( )} {messages.map((message) => ( ))} setMessageText(e.target.value)} endAdornment={ !upMd && ( ) } /> {upMd && ( Отправить )} ); }