frontPanel/src/ui_kit/FloatingSupportChat/Chat.tsx
Nastya 2fecf6ca14
All checks were successful
Deploy / CreateImage (push) Successful in 14m11s
Deploy / DeployService (push) Successful in 27s
fix floating support chat
2025-07-23 14:10:43 +03:00

239 lines
6.3 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,
IconButton,
SxProps,
Theme,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import {
addOrUpdateUnauthMessages,
incrementUnauthMessage,
setUnauthIsPreventAutoscroll,
useTicketStore,
} from "@root/ticket";
import type { TouchEvent, WheelEvent } from "react";
import { useEffect, useMemo, useRef } from "react";
import ChatMessageRenderer from "./ChatMessageRenderer";
import ChatInput from "./ChatInput";
import UserCircleIcon from "./UserCircleIcon";
import { throttle, TicketMessage } from "@frontend/kitui";
import ArrowLeft from "@icons/questionsPage/arrowLeft";
import { useUserStore } from "@root/user";
interface Props {
open: boolean;
sx?: SxProps<Theme>;
onclickArrow?: () => void;
sendMessage: (a: string) => Promise<boolean>;
sendFile: (a: File | undefined) => Promise<void>;
}
const greetingMessage: TicketMessage = {
id: "greeting",
ticket_id: "",
user_id: "system",
session_id: "",
message: "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут",
files: [],
shown: {},
request_screenshot: "",
created_at: new Date().toISOString(),
system: false
};
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 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);
useEffect(() => {
if (open) {
scrollToBottom();
}
}, [open]);
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,
});
}
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 isSelf = (ticket.sessionData?.sessionId || user) === message.user_id;
return (
<ChatMessageRenderer
key={message.id}
message={message}
isSelf={isSelf}
/>
);
})}
{!ticket.sessionData?.ticketId && (
<ChatMessageRenderer
message={greetingMessage}
isSelf={false}
/>
)}
</Box>
<ChatInput
sendMessage={sendMessage}
sendFile={sendFile}
isMessageSending={isMessageSending}
/>
</Box>
</Box>
)}
</>
);
}