frontPanel/src/ui_kit/FloatingSupportChat/index.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

205 lines
5.7 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 { useCallback, useEffect, useMemo, useState } from "react";
import {
TicketMessage,
createTicket,
shownMessage,
useSSESubscription,
useTicketMessages,
useTicketsFetcher,
sendFile as sf
} from "@frontend/kitui";
import FloatingSupportChat from "./FloatingSupportChat";
import { useUserStore } from "@root/user";
import { useSSETab } from "../../utils/hooks/useSSETab";
import {
addOrUpdateUnauthMessages,
cleanAuthTicketData,
cleanUnauthTicketData,
setIsMessageSending,
setTicketData,
setUnauthIsPreventAutoscroll,
setUnauthTicketMessageFetchState,
useTicketStore,
} from "@root/ticket";
import { enqueueSnackbar } from "notistack";
import { parseAxiosError } from "@utils/parse-error";
import { selectSendingMethod } from "./utils";
type ModalWarningType =
| "errorType"
| "errorSize"
| "picture"
| "video"
| "audio"
| "document"
| null;
const MAX_FILE_SIZE = 419430400;
const ACCEPT_SEND_FILE_TYPES_MAP = [
".jpeg",
".jpg",
".png",
".mp4",
".doc",
".docx",
".pdf",
".txt",
".xlsx",
".csv",
] as const;
export default () => {
const user = useUserStore((state) => state.user?._id);
const ticket = useTicketStore(state => state[user ? "authData" : "unauthData"]);
//Запись SEE в учётник
const { isActiveSSETab, updateSSEValue } = useSSETab<TicketMessage[]>(
"ticket",
addOrUpdateUnauthMessages,
);
const [modalWarningType, setModalWarningType] =
useState<ModalWarningType>(null);
const [isChatOpened, setIsChatOpened] = useState<boolean>(false);
const [sseEnabled, setSseEnabled] = useState(true);
const handleChatClickOpen = () => {
setIsChatOpened(true);
};
const handleChatClickClose = () => {
setIsChatOpened(false);
};
const handleChatClickSwitch = () => {
setIsChatOpened((state) => !state);
};
useTicketMessages({
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getMessages`,
isUnauth: true,
ticketId: ticket.sessionData?.ticketId,
messagesPerPage: ticket.messagesPerPage,
messageApiPage: ticket.apiPage,
onSuccess: useCallback((messages) => {
addOrUpdateUnauthMessages(messages);
}, []),
onError: useCallback((error: Error) => {
if (error.name === "CanceledError") {
return;
}
const [message] = parseAxiosError(error);
if (message) enqueueSnackbar(message);
}, []),
onFetchStateChange: setUnauthTicketMessageFetchState,
});
useSSESubscription<TicketMessage>({
enabled:
sseEnabled && isActiveSSETab && Boolean(ticket.sessionData?.sessionId),
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/ticket?ticket=${ticket.sessionData?.ticketId}&s=${ticket.sessionData?.sessionId}`,
onNewData: (ticketMessages) => {
const isTicketClosed = ticketMessages.some(
(message) => message.session_id === "close",
);
if (isTicketClosed) {
cleanAuthTicketData();
if (!user) {
cleanUnauthTicketData();
localStorage.removeItem("unauth-ticket");
}
return;
}
updateSSEValue(ticketMessages);
addOrUpdateUnauthMessages(ticketMessages);
},
onDisconnect: useCallback(() => {
setUnauthIsPreventAutoscroll(false);
setSseEnabled(false);
}, []),
marker: "ticket",
});
useEffect(() => {
cleanAuthTicketData();
setSseEnabled(true);
}, [user]);
useEffect(() => {
if (isChatOpened) {
const newMessages = ticket.messages.filter(
({ shown }) => shown?.me !== 1,
);
// Находим последнее сообщение, которое не от пользователя
const lastNonUserMessage = newMessages
.filter(({ user_id }) => (ticket.sessionData?.sessionId || user) !== user_id)
.pop();
// Отправляем shown только на последнее сообщение, которое не от пользователя
if (lastNonUserMessage) {
shownMessage(lastNonUserMessage.id);
}
}
}, [isChatOpened, ticket.messages]);
const sendMessage = async (messageField: string) => {
if (!messageField || ticket.isMessageSending) return false;
setSseEnabled(true);
setIsMessageSending(true);
let successful = await selectSendingMethod({ messageField });
setIsMessageSending(false);
return successful;
};
const sendFile = async (file: File | undefined): Promise<void> => {
if (file === undefined) return;
let ticketId = ticket.sessionData?.ticketId;
if (!ticket.sessionData?.ticketId) {
const [data, createError] = await createTicket({
message: "",
useToken: Boolean(user),
systemError: false
});
ticketId = data?.Ticket;
if (createError || !data) {
enqueueSnackbar(`Не удалось создать диалог ${parseAxiosError(createError)}`);
return;
} else {
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
}
setIsMessageSending(false);
}
if (ticketId !== undefined) {
if (file.size > MAX_FILE_SIZE) {
setModalWarningType("errorSize");
return;
}
const [_, sendFileError] = await sf({ticketId, file});
if (sendFileError) {
enqueueSnackbar(`Не удалось отправить файл ${parseAxiosError(sendFileError)}`);
}
}
};
return (
<FloatingSupportChat
isChatOpened={isChatOpened}
handleChatClickOpen={handleChatClickOpen}
handleChatClickClose={handleChatClickClose}
handleChatClickSwitch={handleChatClickSwitch}
sendMessage={sendMessage}
sendFile={sendFile}
modalWarningType={modalWarningType}
setModalWarningType={setModalWarningType}
/>
);
};