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

330 lines
12 KiB
TypeScript
Raw Normal View History

2023-08-10 06:53:12 +00:00
import {
Box,
Button,
Fab,
FormControl,
IconButton,
InputAdornment,
InputBase,
Typography,
useMediaQuery,
useTheme,
2023-08-10 06:53:12 +00:00
} 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 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-08-10 06:53:12 +00:00
import {
addOrUpdateMessages,
clearMessageState,
incrementMessageApiPage,
setIsPreventAutoscroll,
2023-08-29 11:54:35 +00:00
setTicketMessageFetchState,
useMessageStore,
2023-08-10 06:53:12 +00:00
} from "@root/stores/messages";
2023-06-06 13:13:58 +00:00
import { TicketMessage } from "@frontend/kitui";
2023-04-13 16:48:17 +00:00
import ChatMessage from "@root/components/ChatMessage";
2023-08-22 10:28:22 +00:00
import { cardShadow } from "@root/utils/theme";
2023-08-10 06:53:12 +00:00
import {
getMessageFromFetchError,
useEventListener,
useSSESubscription,
useTicketMessages,
2023-08-10 06:53:12 +00:00
} from "@frontend/kitui";
2023-03-30 12:33:28 +00:00
2022-12-15 14:09:07 +00:00
export default function SupportChat() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.up(460));
const [messageField, setMessageField] = useState<string>("");
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<HTMLDivElement>(null);
2023-08-29 11:54:35 +00:00
const fetchState = useMessageStore(state => state.ticketMessageFetchState);
2023-06-06 13:13:58 +00:00
2023-08-29 11:54:35 +00:00
useTicketMessages({
url: "https://admin.pena.digital/heruvym/getMessages",
ticketId,
messagesPerPage,
messageApiPage,
2023-08-29 11:54:35 +00:00
onSuccess: (messages) => {
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
chatBoxRef.current.scrollTop = 1;
addOrUpdateMessages(messages);
2023-08-29 11:54:35 +00:00
},
onError: (error: Error) => {
const message = getMessageFromFetchError(error);
if (message) enqueueSnackbar(message);
2023-08-29 11:54:35 +00:00
},
onFetchStateChange: setTicketMessageFetchState,
});
2023-03-31 15:01:43 +00:00
useSSESubscription<TicketMessage>({
enabled: Boolean(token) && Boolean(ticketId),
url: `https://admin.pena.digital/heruvym/ticket?ticket=${ticketId}&Authorization=${token}`,
onNewData: addOrUpdateMessages,
onDisconnect: useCallback(() => {
clearMessageState();
setIsPreventAutoscroll(false);
}, []),
marker: "ticket message",
});
2022-12-24 10:41:12 +00:00
const throttledScrollHandler = useMemo(
() =>
throttle(() => {
const chatBox = chatBoxRef.current;
if (!chatBox) return;
2023-04-06 15:10:22 +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
if (fetchState !== "idle") return;
2022-12-24 10:41:12 +00:00
if (chatBox.scrollTop < chatBox.clientHeight) {
incrementMessageApiPage();
}
}, 200),
[fetchState]
);
2022-12-24 10:41:12 +00:00
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
2022-12-24 10:41:12 +00:00
useEffect(
function scrollOnNewMessage() {
if (!chatBoxRef.current) return;
2022-12-24 10:41:12 +00:00
if (!isPreventAutoscroll) {
setTimeout(() => {
scrollToBottom();
}, 50);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[lastMessageId]
);
2023-04-06 15:10:22 +00:00
useEffect(() => {
if (ticket)
makeRequest({
url: "https://admin.pena.digital/heruvym/shown",
method: "POST",
useToken: true,
body: { id: ticket.top_message.id },
});
}, [ticket]);
2022-12-24 10:41:12 +00:00
function handleSendMessage() {
if (!ticket || !messageField) return;
2023-03-31 15:01:43 +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-31 15:01:43 +00:00
function scrollToBottom(behavior?: ScrollBehavior) {
if (!chatBoxRef.current) return;
2023-08-10 06:53:12 +00:00
const chatBox = chatBoxRef.current;
chatBox.scroll({
left: 0,
top: chatBox.scrollHeight,
behavior,
});
}
2022-12-15 14:09:07 +00:00
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",
});
2023-04-06 08:47:28 +00:00
return (
2023-08-10 06:53:12 +00:00
<Box
sx={{
backgroundColor: upMd ? "white" : undefined,
display: "flex",
flexGrow: 1,
maxHeight: upMd ? "443px" : undefined,
borderRadius: "12px",
p: upMd ? "20px" : undefined,
gap: "40px",
height: !upMd ? `calc(100% - ${isMobile ? 90 : 115}px)` : null,
boxShadow: upMd ? cardShadow : undefined,
2023-08-10 06:53:12 +00:00
}}
>
<Box
2022-12-15 14:09:07 +00:00
sx={{
display: "flex",
alignItems: "start",
flexDirection: "column",
flexGrow: 1,
2022-12-15 14:09:07 +00:00
}}
>
<Typography variant={upMd ? "h5" : "body2"} mb={"4px"}>
{ticket?.title}
</Typography>
<Typography
sx={{
fontWeight: 400,
fontSize: "14px",
lineHeight: "17px",
color: theme.palette.gray.main,
mb: upMd ? "9px" : "20px",
}}
>
Создан: {createdAtString}
</Typography>
<Box
sx={{
backgroundColor: "#ECECF3",
border: `1px solid ${theme.palette.gray.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.gray.main}`,
height: "200px",
}}
>
{isPreventAutoscroll && (
<Fab
size="small"
onClick={() => scrollToBottom("smooth")}
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%",
}}
>
{ticket &&
messages.map((message) => (
<ChatMessage
key={message.id}
text={message.message}
createdAt={message.created_at}
isSelf={ticket.user === message.user_id}
/>
))}
</Box>
</Box>
<FormControl>
<InputBase
value={messageField}
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) => setMessageField(e.target.value)}
endAdornment={
!upMd && (
<InputAdornment position="end">
<IconButton
onClick={handleSendMessage}
sx={{
height: "45px",
width: "45px",
mr: "13px",
p: 0,
}}
>
<SendIcon />
</IconButton>
</InputAdornment>
)
}
/>
</FormControl>
</Box>
</Box>
{upMd && (
<Box sx={{ alignSelf: "end" }}>
<Button
variant="pena-contained-dark"
onClick={handleSendMessage}
disabled={!messageField}
>Отправить</Button>
</Box>
)}
2022-12-15 14:09:07 +00:00
</Box>
);
2023-08-10 06:53:12 +00:00
}