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

283 lines
11 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-24 18:16:02 +00:00
import { makeRequest, throttle, useToken } 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-16 19:04:59 +00:00
import { cardShadow } from "@root/utils/themes/shadow";
2023-06-06 13:13:58 +00:00
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-08-02 08:31:58 +00:00
const isMobile = useMediaQuery(theme.breakpoints.up(460));
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-24 18:16:02 +00:00
const token = useToken();
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({
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-06-24 18:16:02 +00:00
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
2023-06-06 13:13:58 +00:00
}, []),
});
2023-03-31 15:01:43 +00:00
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",
2023-08-02 08:31:58 +00:00
height: !upMd ? `calc(100% - ${isMobile ? 90 : 115}px)` : null,
2023-04-06 08:47:28 +00:00
boxShadow: upMd
2023-06-16 19:04:59 +00:00
? cardShadow
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
}