frontPanel/src/ui_kit/FloatingSupportChat/Chat.tsx
2024-03-13 17:40:43 +03:00

391 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Box,
FormControl,
IconButton,
InputAdornment,
InputBase,
SxProps,
Theme,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import {
useTicketStore,
incrementUnauthMessage,
setUnauthIsPreventAutoscroll,
} from "@root/ticket";
import { useEffect, useMemo, useRef, useState } from "react";
import ChatMessage from "./ChatMessage";
import ChatVideo from "./ChatVideo";
import SendIcon from "@icons/SendIcon";
import UserCircleIcon from "./UserCircleIcon";
import { throttle } 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";
import {
checkAcceptableMediaType,
ACCEPT_SEND_MEDIA_TYPES_MAP,
} from "@utils/checkAcceptableMediaType";
import { enqueueSnackbar } from "notistack";
import type { WheelEvent, TouchEvent } from "react";
interface Props {
open: boolean;
sx?: SxProps<Theme>;
onclickArrow?: () => void;
sendMessage: (a: string) => Promise<boolean>;
sendFile: (a: File | undefined) => Promise<true>;
}
export default function Chat({
open = false,
sx,
onclickArrow,
sendMessage,
sendFile,
}: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const isMobile = useMediaQuery(theme.breakpoints.down(800));
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;
const chatBoxRef = useRef<HTMLDivElement>(null);
const sendMessageHC = async () => {
const successful = await sendMessage(messageField);
console.log("successful ", successful);
if (successful) {
setMessageField("");
}
};
const sendFileHC = async (file: File) => {
console.log(file);
const check = checkAcceptableMediaType(file);
if (check.length > 0) {
enqueueSnackbar(check);
return;
}
setDisableFileButton(true);
await sendFile(file);
setDisableFileButton(false);
console.log(disableFileButton);
};
const fileInputRef = useRef<HTMLInputElement>(null);
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) {
incrementUnauthMessage();
}
}, 200),
[fetchState],
);
useEffect(() => {
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
chatBoxRef.current.scrollTop = 1;
}, [messages]);
useEffect(
function scrollOnNewMessage() {
if (!chatBoxRef.current) return;
if (!isPreventAutoscroll) {
setTimeout(() => {
scrollToBottom();
}, 50);
}
},
[lastMessageId],
);
const loadNewMessages = (
event: WheelEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>,
) => {
event.stopPropagation();
throttledScrollHandler();
};
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();
}
};
return (
<>
{open && (
<Box
sx={{
display: "flex",
flexDirection: "column",
height: isMobile
? "100%"
: "clamp(250px, calc(100vh - 90px), 600px)",
backgroundColor: "#944FEE",
borderRadius: "8px",
...sx,
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "9px",
pl: "22px",
pt: "12px",
pb: "20px",
filter: "drop-shadow(0px 3px 12px rgba(37, 39, 52, 0.3))",
}}
>
{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
sx={{
flexGrow: 1,
backgroundColor: "white",
borderRadius: "8px",
display: "flex",
flexDirection: "column",
}}
>
<Box
onWheel={loadNewMessages}
onTouchMove={loadNewMessages}
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) => {
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) {
return ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some(
(fileType) =>
message.files[0].toLowerCase().endsWith(fileType),
);
}
};
const isFileDocument = () => {
if (message.files) {
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
}
/>
);
}
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
}
/>
);
}
if (message.files.length > 0 && isFileDocument()) {
return (
<ChatDocument
unAuthenticated
key={message.id}
file={message.files[0]}
createdAt={message.created_at}
isSelf={
(ticket.sessionData?.sessionId || user) ===
message.user_id
}
/>
);
}
return (
<ChatMessage
unAuthenticated
key={message.id}
text={message.message}
createdAt={message.created_at}
isSelf={
(ticket.sessionData?.sessionId || user) ===
message.user_id
}
/>
);
})}
</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">
<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"
/>
<IconButton
disabled={isMessageSending}
onClick={sendMessageHC}
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>
)}
</>
);
}