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

287 lines
12 KiB
TypeScript
Raw Normal View History

2023-03-30 12:33:28 +00:00
import { Box, Fab, FormControl, IconButton, InputAdornment, InputBase, Typography, useMediaQuery, useTheme, } from "@mui/material";
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
2023-06-06 13:13:58 +00:00
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2022-12-24 10:41:12 +00:00
import { useParams } from "react-router-dom";
import CustomButton from "@components/CustomButton";
import SendIcon from "@components/icons/SendIcon";
2023-06-06 13:13:58 +00:00
import { throttle } from "@frontend/kitui";
2023-03-31 15:01:43 +00:00
import { enqueueSnackbar } from "notistack";
import { useTicketStore } from "@root/stores/tickets";
2023-06-06 13:13:58 +00:00
import { addOrUpdateMessages, clearMessageState, incrementMessageApiPage, setIsPreventAutoscroll, useMessageStore } from "@root/stores/messages";
import { TicketMessage } from "@frontend/kitui";
2023-04-13 16:48:17 +00:00
import ChatMessage from "@root/components/ChatMessage";
2023-06-06 13:13:58 +00:00
import { useAuthStore } from "@root/stores/auth";
import { makeRequest } from "@root/api/makeRequest";
import { getMessageFromFetchError, useEventListener, useSSESubscription, useTicketMessages } from "@frontend/kitui";
2022-12-15 14:09:07 +00:00
2023-03-30 12:33:28 +00:00
2022-12-15 14:09:07 +00:00
export default function SupportChat() {
2023-03-30 12:33:28 +00:00
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
2023-03-31 15:01:43 +00:00
const [messageField, setMessageField] = useState<string>("");
const tickets = useTicketStore(state => state.tickets);
const messages = useMessageStore(state => state.messages);
2023-04-06 15:10:22 +00:00
const messageApiPage = useMessageStore(state => state.apiPage);
const lastMessageId = useMessageStore(state => state.lastMessageId);
const messagesPerPage = useMessageStore(state => state.messagesPerPage);
2023-04-07 13:32:30 +00:00
const isPreventAutoscroll = useMessageStore(state => state.isPreventAutoscroll);
2023-06-06 13:13:58 +00:00
const token = useAuthStore(state => state.token);
2023-03-30 12:33:28 +00:00
const ticketId = useParams().ticketId;
2023-03-31 15:01:43 +00:00
const ticket = tickets.find(ticket => ticket.id === ticketId);
2023-06-06 13:13:58 +00:00
const chatBoxRef = useRef<HTMLDivElement>(null);
const fetchState = useTicketMessages({
makeRequest,
url: "https://admin.pena.digital/heruvym/getMessages",
ticketId,
messagesPerPage,
messageApiPage,
onNewMessages: useCallback(messages => {
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1) chatBoxRef.current.scrollTop = 1;
addOrUpdateMessages(messages);
}, []),
onError: useCallback((error: Error) => {
2023-03-31 15:01:43 +00:00
enqueueSnackbar(error.message);
2023-06-06 13:13:58 +00:00
}, []),
});
useSSESubscription<TicketMessage>({
enabled: Boolean(token) && Boolean(ticketId),
url: `https://admin.pena.digital/heruvym/ticket?ticket=${ticketId}&Authorization=${token}`,
onNewData: addOrUpdateMessages,
onDisconnect: useCallback(() => {
2023-04-06 15:10:22 +00:00
clearMessageState();
2023-06-06 13:13:58 +00:00
setIsPreventAutoscroll(false);
}, []),
marker: "ticket message"
});
2022-12-24 10:41:12 +00:00
2023-06-06 13:13:58 +00:00
const throttledScrollHandler = useMemo(() => throttle(() => {
2023-03-30 12:33:28 +00:00
const chatBox = chatBoxRef.current;
2023-06-06 13:13:58 +00:00
if (!chatBox) return;
2023-04-06 15:10:22 +00:00
2023-06-06 13:13:58 +00:00
const scrollBottom = chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight;
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight * 20;
setIsPreventAutoscroll(isPreventAutoscroll);
2023-04-06 15:10:22 +00:00
2023-06-06 13:13:58 +00:00
if (fetchState !== "idle") return;
2022-12-24 10:41:12 +00:00
2023-06-06 13:13:58 +00:00
if (chatBox.scrollTop < chatBox.clientHeight) {
incrementMessageApiPage();
}
}, 200), [fetchState]);
2022-12-24 10:41:12 +00:00
2023-06-06 13:13:58 +00:00
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
2022-12-24 10:41:12 +00:00
2023-04-06 15:10:22 +00:00
useEffect(function scrollOnNewMessage() {
2023-03-30 12:33:28 +00:00
if (!chatBoxRef.current) return;
2022-12-24 10:41:12 +00:00
2023-04-06 15:10:22 +00:00
if (!isPreventAutoscroll) {
setTimeout(() => {
scrollToBottom();
}, 50);
}
2023-03-30 12:33:28 +00:00
// eslint-disable-next-line react-hooks/exhaustive-deps
2023-04-06 15:10:22 +00:00
}, [lastMessageId]);
2023-04-07 13:32:30 +00:00
function handleSendMessage() {
2023-03-31 15:01:43 +00:00
if (!ticket || !messageField) return;
2022-12-24 10:41:12 +00:00
2023-06-06 13:13:58 +00:00
makeRequest({
url: "https://hub.pena.digital/heruvym/send",
method: "POST",
useToken: true,
body: {
ticket: ticket.id,
message: messageField,
lang: "ru",
files: [],
},
}).then(() => {
setMessageField("");
}).catch(error => {
const errorMessage = getMessageFromFetchError(error);
if (errorMessage) enqueueSnackbar(errorMessage);
2023-03-30 12:33:28 +00:00
});
2023-03-31 15:01:43 +00:00
}
2023-04-06 15:10:22 +00:00
function scrollToBottom(behavior?: ScrollBehavior) {
2023-03-31 15:01:43 +00:00
if (!chatBoxRef.current) return;
2023-04-06 15:10:22 +00:00
const chatBox = chatBoxRef.current;
chatBox.scroll({
2023-03-31 15:01:43 +00:00
left: 0,
2023-04-06 15:10:22 +00:00
top: chatBox.scrollHeight,
behavior,
2023-03-31 15:01:43 +00:00
});
2022-12-24 10:41:12 +00:00
}
2022-12-15 14:09:07 +00:00
2023-04-06 15:10:22 +00:00
const createdAt = ticket && new Date(ticket.created_at);
const createdAtString = createdAt && createdAt.toLocaleDateString(undefined, {
2023-04-06 08:47:28 +00:00
year: "numeric",
month: "2-digit",
day: "2-digit",
}) + " " + createdAt.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
});
2023-03-30 12:33:28 +00:00
return (
2023-04-06 08:47:28 +00:00
<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),
2022-12-15 14:09:07 +00:00
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)`
2023-04-06 08:47:28 +00:00
: undefined,
}}>
2023-03-30 12:33:28 +00:00
<Box
2022-12-15 14:09:07 +00:00
sx={{
2023-03-30 12:33:28 +00:00
display: "flex",
alignItems: "start",
flexDirection: "column",
flexGrow: 1,
2022-12-15 14:09:07 +00:00
}}
>
2023-03-30 12:33:28 +00:00
<Typography variant={upMd ? "h5" : "body2"} mb={"4px"}>
2023-04-06 15:10:22 +00:00
{ticket?.title}
2023-03-30 12:33:28 +00:00
</Typography>
<Typography
sx={{
fontWeight: 400,
fontSize: "14px",
lineHeight: "17px",
color: theme.palette.grey2.main,
mb: upMd ? "9px" : "20px",
}}
>
2023-04-06 08:47:28 +00:00
Создан: {createdAtString}
2023-03-30 12:33:28 +00:00
</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",
}}
>
<Box
sx={{
position: "relative",
width: "100%",
flexGrow: 1,
borderBottom: `1px solid ${theme.palette.grey2.main}`,
height: "200px",
}}
>
{isPreventAutoscroll && (
<Fab
size="small"
2023-04-06 15:10:22 +00:00
onClick={() => scrollToBottom("smooth")}
2023-03-30 12:33:28 +00:00
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%",
}}
>
2023-04-06 15:10:22 +00:00
{ticket && messages.map((message) => (
2023-04-13 16:48:17 +00:00
<ChatMessage
2023-03-30 12:33:28 +00:00
key={message.id}
text={message.message}
2023-05-09 16:28:52 +00:00
createdAt={message.created_at}
2023-03-31 15:01:43 +00:00
isSelf={ticket.user === message.user_id}
2023-03-30 12:33:28 +00:00
/>
))}
</Box>
</Box>
<FormControl>
<InputBase
2023-03-31 15:01:43 +00:00
value={messageField}
2023-03-30 12:33:28 +00:00
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)",
},
}}
2023-03-31 15:01:43 +00:00
onChange={(e) => setMessageField(e.target.value)}
2023-03-30 12:33:28 +00:00
endAdornment={
!upMd && (
<InputAdornment position="end">
<IconButton
onClick={handleSendMessage}
sx={{
height: "45px",
width: "45px",
mr: "13px",
p: 0,
}}
>
<SendIcon />
</IconButton>
</InputAdornment>
)
}
/>
</FormControl>
</Box>
2022-12-15 14:09:07 +00:00
</Box>
2023-03-30 12:33:28 +00:00
{upMd && (
<Box sx={{ alignSelf: "end" }}>
<CustomButton
onClick={handleSendMessage}
2023-03-31 15:01:43 +00:00
disabled={!messageField}
2023-03-30 12:33:28 +00:00
variant="contained"
sx={{
backgroundColor: theme.palette.brightPurple.main,
}}
>
2023-03-30 12:33:28 +00:00
Отправить
</CustomButton>
</Box>
)}
2022-12-15 14:09:07 +00:00
</Box>
2023-03-30 12:33:28 +00:00
);
2023-04-06 08:47:28 +00:00
}