diff --git a/src/pages/dashboard/Content/Support/Chat.tsx b/src/pages/dashboard/Content/Support/Chat.tsx index eaa5dad..e66e138 100644 --- a/src/pages/dashboard/Content/Support/Chat.tsx +++ b/src/pages/dashboard/Content/Support/Chat.tsx @@ -1,16 +1,190 @@ -import { Box, useTheme } from "@mui/material"; +import { Box, IconButton, InputAdornment, TextField, useTheme } from "@mui/material"; +import { addMessages, setMessages, useMessageStore } from "@root/stores/messages"; +import Message from "./Message"; +import SendIcon from "@mui/icons-material/Send"; +import AttachFileIcon from "@mui/icons-material/AttachFile"; +import { KeyboardEvent, useEffect, useRef, useState } from "react"; +import { useParams } from "react-router-dom"; +import { GetMessagesRequest, TicketMessage } from "@root/model/ticket"; +import { getTicketMessages, sendTicketMessage, subscribeToTicketMessages } from "@root/api/tickets"; +import { enqueueSnackbar } from "notistack"; export default function Chat() { - const theme = useTheme() - + const theme = useTheme(); + const messages = useMessageStore(state => state.messages); + const [messageField, setMessageField] = useState(""); + const ticketId = useParams().ticketId; + const chatBoxRef = useRef(null); + + useEffect(function scrollOnNewMessage() { + scrollToBottom(); + }, [messages]); + + useEffect(function fetchTicketMessages() { + if (!ticketId) return; + + const getTicketsBody: GetMessagesRequest = { + amt: 10, + page: 0, + srch: "", + ticket: ticketId, + }; + const controller = new AbortController(); + + getTicketMessages({ + body: getTicketsBody, + signal: controller.signal, + }).then(result => { + console.log("GetTicketsResponse", result); + setMessages(result); + }).catch(error => { + console.log("Error fetching tickets", error); + enqueueSnackbar(error.message); + }); + + return () => controller.abort(); + }, [ticketId]); + + useEffect(function subscribeToMessages() { + if (!ticketId) return; + + const token = localStorage.getItem("AT"); + if (!token) return; + + const unsubscribe = subscribeToTicketMessages({ + ticketId, + accessToken: token, + onMessage(event) { + console.log("SSE: message received:", event.data); + if (event.data === "no tickets 4 user") return; + + try { + const newMessage = JSON.parse(event.data) as TicketMessage; + addMessages([newMessage]); + } catch (error) { + console.log("Error parsing message SSE", error); + } + }, + onError(event) { + console.log("SSE Error:", event); + }, + }); + + return () => { + unsubscribe(); + }; + }, [ticketId]); + + function scrollToBottom() { + if (!chatBoxRef.current) return; + + chatBoxRef.current.scroll({ + left: 0, + top: chatBoxRef.current.scrollHeight, + behavior: "smooth", + }); + } + + function handleSendMessage() { + if (!ticketId) return; + + sendTicketMessage({ + body: { + files: [], + lang: "ru", + message: messageField, + ticket: ticketId, + } + }); + setMessageField(""); + } + + function handleAddAttachment() { + + } + + function handleTextfieldKeyPress(e: KeyboardEvent) { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + } + return ( - ) + borderRadius: "3px", + p: "8px", + display: "flex", + flexDirection: "column", + gap: "8px", + }}> + + {messages.map((message, index) => + + )} + + setMessageField(e.target.value)} + onKeyPress={handleTextfieldKeyPress} + id="message-input" + placeholder="Написать сообщение" + fullWidth + multiline + maxRows={8} + InputProps={{ + style: { + backgroundColor: theme.palette.content.main, + color: theme.palette.secondary.main, + }, + endAdornment: ( + + + + + + + + + ) + }} + InputLabelProps={{ + style: { + color: theme.palette.secondary.main, + } + }} + /> + + ); } \ No newline at end of file diff --git a/src/pages/dashboard/Content/Support/Message.tsx b/src/pages/dashboard/Content/Support/Message.tsx new file mode 100644 index 0000000..e08b357 --- /dev/null +++ b/src/pages/dashboard/Content/Support/Message.tsx @@ -0,0 +1,45 @@ +import { Box, Typography, useTheme } from "@mui/material"; +import { TicketMessage } from "@root/model/ticket"; + + +interface Props { + message: TicketMessage; + isSelf?: boolean; +} + +export default function Message({ message, isSelf }: Props) { + const theme = useTheme(); + + const time = ( + + {new Date(message.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} + + ); + + return ( + + {isSelf && time} + + + {message.message} + + + {!isSelf && time} + + ); +} \ No newline at end of file