frontPanel/src/ui_kit/FloatingSupportChat/Chat.tsx

384 lines
12 KiB
TypeScript
Raw Normal View History

2024-02-06 14:39:02 +00:00
import {
Box,
FormControl,
IconButton,
InputAdornment,
InputBase,
InputLabel,
2024-02-06 14:39:02 +00:00
SxProps,
Theme,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { makeRequest } from "@frontend/kitui";
2024-02-06 14:39:02 +00:00
import {
useTicketStore,
2024-02-06 14:39:02 +00:00
incrementUnauthMessageApiPage,
setUnauthIsPreventAutoscroll,
} from "@root/ticket";
import { useEffect, useMemo, useRef, useState } from "react";
2024-02-06 14:39:02 +00:00
import ChatMessage from "./ChatMessage";
2024-03-10 14:40:48 +00:00
import ChatVideo from "./ChatVideo";
2024-02-06 14:39:02 +00:00
import SendIcon from "@icons/SendIcon";
import UserCircleIcon from "./UserCircleIcon";
import { throttle, getAuthToken } from "@frontend/kitui";
import { useEventListener } from "@frontend/kitui";
import ArrowLeft from "@icons/questionsPage/arrowLeft";
import { useUserStore } from "@root/user";
import AttachFileIcon from "@mui/icons-material/AttachFile";
import ChatImage from "./ChatImage";
import ChatDocument from "@ui_kit/FloatingSupportChat/ChatDocument";
import * as React from "react";
2024-03-10 14:40:48 +00:00
import { ACCEPT_SEND_MEDIA_TYPES_MAP, MAX_FILE_SIZE, MAX_PHOTO_SIZE, MAX_VIDEO_SIZE } from "./fileUpload";
import { enqueueSnackbar } from "notistack";
2024-02-06 14:39:02 +00:00
interface Props {
2024-02-14 16:47:30 +00:00
open: boolean;
2024-02-06 14:39:02 +00:00
sx?: SxProps<Theme>;
onclickArrow?: () => void;
sendMessage: (a: string) => Promise<boolean>;
sendFile: (a: File | undefined) => Promise<true>;
2024-02-06 14:39:02 +00:00
}
2024-03-10 14:40:48 +00:00
const tooLarge = "Файл слишком большой"
const checkAcceptableMediaType = (file: File) => {
if (file === null) return ""
const segments = file?.name.split('.');
const extension = segments[segments.length - 1];
const type = extension.toLowerCase();
console.log(type)
switch (type) {
case ACCEPT_SEND_MEDIA_TYPES_MAP.document.find(name => name === type):
if (file.size > MAX_FILE_SIZE) return tooLarge
return ""
case ACCEPT_SEND_MEDIA_TYPES_MAP.picture.find(name => name === type):
if (file.size > MAX_PHOTO_SIZE) return tooLarge
return ""
case ACCEPT_SEND_MEDIA_TYPES_MAP.video.find(name => name === type):
if (file.size > MAX_VIDEO_SIZE) return tooLarge
return ""
default:
return "Не удалось отправить файл. Недопустимый тип"
}
}
export default function Chat({
open = false,
sx,
onclickArrow,
sendMessage,
sendFile,
}: Props) {
2024-02-06 14:39:02 +00:00
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(800));
2024-02-06 14:39:02 +00:00
const [messageField, setMessageField] = useState<string>("");
const [disableFileButton, setDisableFileButton] = useState(false);
const user = useUserStore((state) => state.user?._id);
const ticket = useTicketStore(
(state) => state[user ? "authData" : "unauthData"],
);
const messages = ticket.messages;
const isMessageSending = ticket.isMessageSending;
const isPreventAutoscroll = ticket.isPreventAutoscroll;
const lastMessageId = ticket.lastMessageId;
const fetchState = ticket.unauthTicketMessageFetchState;
2024-02-06 14:39:02 +00:00
const chatBoxRef = useRef<HTMLDivElement>(null);
const sendMessageHC = async () => {
const successful = await sendMessage(messageField);
console.log("successful ", successful);
if (successful) {
setMessageField("");
}
};
2024-03-10 14:40:48 +00:00
const sendFileHC = async (file: File) => {
console.log(file)
const check = checkAcceptableMediaType(file)
if (check.length > 0) {
enqueueSnackbar(check)
return
}
setDisableFileButton(true)
2024-03-10 14:40:48 +00:00
await sendFile(file)
setDisableFileButton(false)
console.log(disableFileButton)
};
2024-02-06 14:39:02 +00:00
const fileInputRef = useRef<HTMLInputElement>(null);
2024-02-06 14:39:02 +00:00
const throttledScrollHandler = useMemo(
() =>
throttle(() => {
const chatBox = chatBoxRef.current;
if (!chatBox) return;
const scrollBottom =
chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight;
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight;
setUnauthIsPreventAutoscroll(isPreventAutoscroll);
if (fetchState !== "idle") return;
if (chatBox.scrollTop < chatBox.clientHeight) {
incrementUnauthMessageApiPage();
}
}, 200),
[fetchState],
);
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
useEffect(() => {
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
chatBoxRef.current.scrollTop = 1;
}, [messages]);
2024-02-06 14:39:02 +00:00
useEffect(
function scrollOnNewMessage() {
if (!chatBoxRef.current) return;
if (!isPreventAutoscroll) {
setTimeout(() => {
scrollToBottom();
}, 50);
}
},
[lastMessageId],
);
function scrollToBottom(behavior?: ScrollBehavior) {
if (!chatBoxRef.current) return;
const chatBox = chatBoxRef.current;
chatBox.scroll({
left: 0,
top: chatBox.scrollHeight,
behavior,
});
}
const handleTextfieldKeyPress: React.KeyboardEventHandler<
HTMLInputElement | HTMLTextAreaElement
> = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessageHC();
2024-02-06 14:39:02 +00:00
}
};
return (
2024-02-14 16:47:30 +00:00
<>
{open && (
2024-02-06 14:39:02 +00:00
<Box
sx={{
display: "flex",
flexDirection: "column",
2024-02-14 16:47:30 +00:00
height: isMobile
? "100%"
: "clamp(250px, calc(100vh - 90px), 600px)",
backgroundColor: "#944FEE",
borderRadius: "8px",
...sx,
2024-02-06 14:39:02 +00:00
}}
>
2024-02-14 16:47:30 +00:00
<Box
2024-02-06 14:39:02 +00:00
sx={{
2024-02-14 16:47:30 +00:00
display: "flex",
alignItems: "center",
gap: "9px",
pl: "22px",
pt: "12px",
pb: "20px",
filter: "drop-shadow(0px 3px 12px rgba(37, 39, 52, 0.3))",
2024-02-06 14:39:02 +00:00
}}
>
2024-02-14 16:47:30 +00:00
{isMobile && (
<IconButton onClick={onclickArrow}>
<ArrowLeft color="white" />
</IconButton>
)}
<UserCircleIcon />
<Box
sx={{
mt: "5px",
display: "flex",
flexDirection: "column",
gap: "3px",
color: theme.palette.common.white,
}}
>
<Typography>Мария</Typography>
<Typography
sx={{
fontSize: "16px",
lineHeight: "19px",
}}
>
онлайн-консультант
</Typography>
</Box>
</Box>
<Box
2024-02-06 14:39:02 +00:00
sx={{
2024-02-14 16:47:30 +00:00
flexGrow: 1,
backgroundColor: "white",
borderRadius: "8px",
display: "flex",
flexDirection: "column",
2024-02-06 14:39:02 +00:00
}}
2024-02-14 16:47:30 +00:00
>
<Box
ref={chatBoxRef}
sx={{
display: "flex",
width: "100%",
flexBasis: 0,
flexDirection: "column",
gap: upMd ? "20px" : "16px",
px: upMd ? "20px" : "5px",
py: upMd ? "20px" : "13px",
overflowY: "auto",
flexGrow: 1,
}}
>
{ticket.sessionData?.ticketId &&
messages.map((message) => {
2024-03-10 14:40:48 +00:00
const isFileVideo = () => {
if (message.files) {
return (ACCEPT_SEND_MEDIA_TYPES_MAP.video.some((fileType) =>
message.files[0].toLowerCase().endsWith(fileType),
))
}
};
const isFileImage = () => {
if (message.files) {
2024-03-10 14:40:48 +00:00
return (ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some((fileType) =>
message.files[0].toLowerCase().endsWith(fileType),
))
}
};
const isFileDocument = () => {
if (message.files) {
2024-03-10 14:40:48 +00:00
return (ACCEPT_SEND_MEDIA_TYPES_MAP.document.some((fileType) =>
message.files[0].toLowerCase().endsWith(fileType),
))
}
};
if (message.files.length > 0 && isFileImage()) {
return <ChatImage
unAuthenticated
key={message.id}
file={message.files[0]}
createdAt={message.created_at}
isSelf={(ticket.sessionData?.sessionId || user) === message.user_id}
/>
}
2024-03-10 14:40:48 +00:00
if (message.files.length > 0 && isFileVideo()) {
return <ChatVideo
unAuthenticated
key={message.id}
file={message.files[0]}
createdAt={message.created_at}
isSelf={(ticket.sessionData?.sessionId || user) === message.user_id}
/>
2024-03-10 14:40:48 +00:00
}
if (message.files.length > 0 && isFileDocument()) {
return <ChatDocument
unAuthenticated
key={message.id}
2024-03-10 14:40:48 +00:00
file={message.files[0]}
createdAt={message.created_at}
isSelf={(ticket.sessionData?.sessionId || user) === message.user_id}
/>
}
2024-03-10 14:40:48 +00:00
return <ChatMessage
unAuthenticated
key={message.id}
text={message.message}
createdAt={message.created_at}
isSelf={(ticket.sessionData?.sessionId || user) === message.user_id}
/>
})}
2024-02-14 16:47:30 +00:00
</Box>
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
<InputBase
value={messageField}
fullWidth
placeholder="Введите сообщение..."
id="message"
multiline
onKeyDown={handleTextfieldKeyPress}
sx={{
width: "100%",
p: 0,
}}
inputProps={{
sx: {
fontWeight: 400,
fontSize: "16px",
lineHeight: "19px",
pt: upMd ? "30px" : "28px",
pb: upMd ? "30px" : "24px",
px: "19px",
maxHeight: "calc(19px * 5)",
color: "black",
},
}}
onChange={(e) => setMessageField(e.target.value)}
endAdornment={
<InputAdornment position="end">
2024-03-10 14:40:48 +00:00
<IconButton
disabled={disableFileButton}
onClick={() => {
console.log(disableFileButton)
if (!disableFileButton) fileInputRef.current?.click()
}}>
<AttachFileIcon />
</IconButton>
<input
ref={fileInputRef}
id="fileinput"
onChange={(e) => {
if (e.target.files?.[0]) sendFileHC(e.target.files?.[0]);
}}
style={{ display: "none" }}
type="file"
/>
2024-02-14 16:47:30 +00:00
<IconButton
disabled={isMessageSending}
onClick={sendMessageHC}
2024-02-14 16:47:30 +00:00
sx={{
height: "53px",
width: "53px",
mr: "13px",
p: 0,
opacity: isMessageSending ? 0.3 : 1,
}}
>
<SendIcon
style={{
width: "100%",
height: "100%",
}}
/>
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</Box>
</Box>
)}
</>
2024-02-06 14:39:02 +00:00
);
}