frontPanel/src/ui_kit/FloatingSupportChat/Chat.tsx

234 lines
6.4 KiB
TypeScript
Raw Normal View History

2024-02-06 14:39:02 +00:00
import {
Box,
IconButton,
SxProps,
Theme,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import {
addOrUpdateUnauthMessages,
2024-03-13 08:54:52 +00:00
incrementUnauthMessage,
2024-02-06 14:39:02 +00:00
setUnauthIsPreventAutoscroll,
useTicketStore,
} from "@root/ticket";
import type { TouchEvent, WheelEvent } from "react";
2025-07-11 16:21:36 +00:00
import { useEffect, useMemo, useRef } from "react";
import ChatMessageRenderer from "./ChatMessageRenderer";
import ChatInput from "./ChatInput";
2024-02-06 14:39:02 +00:00
import UserCircleIcon from "./UserCircleIcon";
import { throttle, TicketMessage } from "@frontend/kitui";
import ArrowLeft from "@icons/questionsPage/arrowLeft";
import { useUserStore } from "@root/user";
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>;
2025-07-05 00:27:58 +00:00
sendFile: (a: File | undefined) => Promise<void>;
2024-02-06 14:39:02 +00:00
}
2025-07-11 16:21:36 +00:00
const greetingMessage = "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут";
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));
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);
useEffect(() => {
if (open) {
scrollToBottom();
}
}, [open]);
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) {
2024-03-13 08:54:52 +00:00
incrementUnauthMessage();
2024-02-06 14:39:02 +00:00
}
}, 200),
[fetchState],
);
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],
);
2024-03-13 14:40:43 +00:00
const loadNewMessages = (
event: WheelEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>,
) => {
event.stopPropagation();
throttledScrollHandler();
};
2024-02-06 14:39:02 +00:00
function scrollToBottom(behavior?: ScrollBehavior) {
if (!chatBoxRef.current) return;
const chatBox = chatBoxRef.current;
chatBox.scroll({
left: 0,
top: chatBox.scrollHeight,
behavior,
});
}
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>
2024-02-14 16:47:30 +00:00
<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
2024-03-13 14:40:43 +00:00
onWheel={loadNewMessages}
onTouchMove={loadNewMessages}
2024-02-14 16:47:30 +00:00
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) => {
2025-07-11 16:21:36 +00:00
const isSelf = useMemo(() =>
(ticket.sessionData?.sessionId || user) === message.user_id,
[ticket.sessionData?.sessionId, user, message.user_id]
);
return (
2025-07-11 16:21:36 +00:00
<ChatMessageRenderer
key={message.id}
2025-07-11 16:21:36 +00:00
message={message}
isSelf={isSelf}
/>
);
})}
{!ticket.sessionData?.ticketId && (
2025-07-11 16:21:36 +00:00
<ChatMessageRenderer
message={greetingMessage}
isSelf={useMemo(() =>
(ticket.sessionData?.sessionId || user) === greetingMessage.user_id,
[ticket.sessionData?.sessionId, user, greetingMessage.user_id]
)}
/>
)}
2024-02-14 16:47:30 +00:00
</Box>
2025-07-11 16:21:36 +00:00
<ChatInput
sendMessage={sendMessage}
sendFile={sendFile}
isMessageSending={isMessageSending}
/>
2024-02-14 16:47:30 +00:00
</Box>
</Box>
)}
</>
2024-02-06 14:39:02 +00:00
);
}