front-hub/src/pages/Support/SupportChat.tsx

269 lines
10 KiB
TypeScript
Raw Normal View History

2022-12-24 10:41:12 +00:00
import { Box, Fab, FormControl, IconButton, InputAdornment, InputBase, Typography, useMediaQuery, useTheme } from "@mui/material";
import { useEffect, useRef, useState } from "react";
2022-12-15 14:09:07 +00:00
import CustomButton from "../../components/CustomButton";
import Message from "./Message";
import SendIcon from "../../components/icons/SendIcon";
2022-12-24 10:41:12 +00:00
import { apiRequestHandler } from "../../utils/api/apiRequestHandler";
import { useParams } from "react-router-dom";
import { ApiError, TicketMessage } from "../../utils/api/types";
import { useSnackbar } from "notistack";
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
import { throttle } from "../../utils/decorators";
2022-12-15 14:09:07 +00:00
export default function SupportChat() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const [messageText, setMessageText] = useState<string>("");
2022-12-24 10:41:12 +00:00
const [messages, setMessages] = useState<TicketMessage[]>([]);
const ticketId = useParams().ticketId;
const { enqueueSnackbar } = useSnackbar();
const chatBoxRef = useRef<HTMLDivElement>();
const [isPreventAutoscroll, setIsPreventAutoscroll] = useState<boolean>(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("");
}
}
2022-12-15 14:09:07 +00:00
return (
<Box
sx={{
backgroundColor: upMd ? "white" : undefined,
display: "flex",
flexGrow: 1,
maxHeight: upMd ? "443px" : undefined,
borderRadius: "12px",
p: upMd ? "20px" : undefined,
gap: "40px",
boxShadow: upMd ?
`0px 100px 309px rgba(210, 208, 225, 0.24),
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`
:
undefined,
}}
>
<Box
sx={{
display: "flex",
alignItems: "start",
flexDirection: "column",
flexGrow: 1,
}}
>
<Typography variant={upMd ? "h5" : "body2"} mb={"4px"}>Заголовок</Typography>
<Typography sx={{
fontWeight: 400,
fontSize: "14px",
lineHeight: "17px",
color: theme.palette.grey2.main,
mb: upMd ? "9px" : "20px",
}}>Создан: 15.09.22 08:39</Typography>
<Box
sx={{
backgroundColor: "#ECECF3",
border: `1px solid ${theme.palette.grey2.main}`,
borderRadius: "10px",
overflow: "hidden",
width: "100%",
minHeight: "345px",
display: "flex",
flexGrow: 1,
flexDirection: "column",
}}
>
2022-12-24 10:41:12 +00:00
<Box sx={{
position: "relative",
width: "100%",
flexGrow: 1,
borderBottom: `1px solid ${theme.palette.grey2.main}`,
height: "200px",
}}>
{isPreventAutoscroll &&
<Fab
size="small"
onClick={scrollToBottom}
sx={{
position: "absolute",
left: "10px",
bottom: "10px",
}}
>
<ArrowDownwardIcon />
</Fab>
}
<Box
ref={chatBoxRef}
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
gap: upMd ? "20px" : "16px",
px: upMd ? "20px" : "5px",
py: upMd ? "20px" : "13px",
overflowY: "auto",
height: "100%",
}}
>
{messages.map(message =>
<Message
key={message.id}
text={message.message}
time={new Date(message.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
isSelf={true}
/>
)}
</Box>
2022-12-15 14:09:07 +00:00
</Box>
<FormControl>
<InputBase
value={messageText}
fullWidth
placeholder="Текст обращения"
id="message"
multiline
sx={{
width: "100%",
p: 0,
}}
inputProps={{
sx: {
fontWeight: 400,
fontSize: "16px",
lineHeight: "19px",
pt: upMd ? "13px" : "28px",
pb: upMd ? "13px" : "24px",
px: "19px",
maxHeight: "calc(19px * 5)",
}
}}
onChange={e => setMessageText(e.target.value)}
endAdornment={!upMd &&
<InputAdornment position="end">
<IconButton
2022-12-24 10:41:12 +00:00
onClick={handleSendMessage}
2022-12-15 14:09:07 +00:00
sx={{
height: "45px",
width: "45px",
mr: "13px",
p: 0,
}}
>
<SendIcon />
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</Box>
</Box>
{upMd &&
<Box sx={{ alignSelf: "end" }}>
<CustomButton
2022-12-24 10:41:12 +00:00
onClick={handleSendMessage}
2022-12-15 14:09:07 +00:00
variant="contained"
sx={{
backgroundColor: theme.palette.brightPurple.main,
}}
>Отправить</CustomButton>
</Box>
}
</Box>
);
}