import { Box, Button, Fab, FormControl, IconButton, InputAdornment, InputBase, Typography, useMediaQuery, useTheme, } from "@mui/material"; import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import SendIcon from "@components/icons/SendIcon"; import { throttle, useToken } from "@frontend/kitui"; import { enqueueSnackbar } from "notistack"; import { useTicketStore } from "@root/stores/tickets"; import { addOrUpdateMessages, clearMessageState, incrementMessageApiPage, setIsPreventAutoscroll, setTicketMessageFetchState, useMessageStore, } from "@root/stores/messages"; import { TicketMessage } from "@frontend/kitui"; import ChatMessage from "@root/components/ChatMessage"; import { cardShadow } from "@root/utils/theme"; import { getMessageFromFetchError, useEventListener, useSSESubscription, useTicketMessages, } from "@frontend/kitui"; import { shownMessage, sendTicketMessage } from "@root/api/ticket"; import { withErrorBoundary } from "react-error-boundary"; import { handleComponentError } from "@root/utils/handleComponentError"; function SupportChat() { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); const isMobile = useMediaQuery(theme.breakpoints.up(460)); const [messageField, setMessageField] = useState(""); const tickets = useTicketStore((state) => state.tickets); const messages = useMessageStore((state) => state.messages); const messageApiPage = useMessageStore((state) => state.apiPage); const lastMessageId = useMessageStore((state) => state.lastMessageId); const messagesPerPage = useMessageStore((state) => state.messagesPerPage); const isPreventAutoscroll = useMessageStore( (state) => state.isPreventAutoscroll ); const token = useToken(); const ticketId = useParams().ticketId; const ticket = tickets.find((ticket) => ticket.id === ticketId); const chatBoxRef = useRef(null); const fetchState = useMessageStore((state) => state.ticketMessageFetchState); useTicketMessages({ url: "https://hub.pena.digital/heruvym/getMessages", ticketId, messagesPerPage, messageApiPage, onSuccess: (messages) => { if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1) chatBoxRef.current.scrollTop = 1; addOrUpdateMessages(messages); }, onError: (error: Error) => { const message = getMessageFromFetchError(error); if (message) enqueueSnackbar(message); }, onFetchStateChange: setTicketMessageFetchState, }); useSSESubscription({ enabled: Boolean(token) && Boolean(ticketId), url: `https://hub.pena.digital/heruvym/ticket?ticket=${ticketId}&Authorization=${token}`, onNewData: addOrUpdateMessages, onDisconnect: useCallback(() => { clearMessageState(); setIsPreventAutoscroll(false); }, []), marker: "ticket message", }); const throttledScrollHandler = useMemo( () => throttle(() => { const chatBox = chatBoxRef.current; if (!chatBox) return; const scrollBottom = chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight; const isPreventAutoscroll = scrollBottom > chatBox.clientHeight * 20; setIsPreventAutoscroll(isPreventAutoscroll); if (fetchState !== "idle") return; if (chatBox.scrollTop < chatBox.clientHeight) { incrementMessageApiPage(); } }, 200), [fetchState] ); useEventListener("scroll", throttledScrollHandler, chatBoxRef); useEffect( function scrollOnNewMessage() { if (!chatBoxRef.current) return; if (!isPreventAutoscroll) { setTimeout(() => { scrollToBottom(); }, 50); } }, // eslint-disable-next-line react-hooks/exhaustive-deps [lastMessageId] ); useEffect(() => { if (ticket) { shownMessage(ticket.top_message.id); } }, [ticket]); async function handleSendMessage() { if (!ticket || !messageField) return; const [, sendTicketMessageError] = await sendTicketMessage( ticket.id, messageField ); if (sendTicketMessageError) { return enqueueSnackbar(sendTicketMessageError); } setMessageField(""); } function scrollToBottom(behavior?: ScrollBehavior) { if (!chatBoxRef.current) return; const chatBox = chatBoxRef.current; chatBox.scroll({ left: 0, top: chatBox.scrollHeight, behavior, }); } const createdAt = ticket && new Date(ticket.created_at); const createdAtString = createdAt && createdAt.toLocaleDateString(undefined, { year: "numeric", month: "2-digit", day: "2-digit", }) + " " + createdAt.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", }); return ( {ticket?.title} Создан: {createdAtString} {isPreventAutoscroll && ( scrollToBottom("smooth")} sx={{ position: "absolute", left: "10px", bottom: "10px", }} > )} {ticket && messages.map((message) => ( ))} setMessageField(e.target.value)} endAdornment={ !upMd && ( ) } /> {upMd && ( )} ); } export default withErrorBoundary(SupportChat, { fallback: Не удалось отобразить чат, onError: handleComponentError, })