frontPanel/src/ui_kit/FloatingSupportChat/index.tsx

205 lines
5.7 KiB
TypeScript
Raw Normal View History

2025-07-05 00:27:58 +00:00
import { useCallback, useEffect, useMemo, useState } from "react";
import {
TicketMessage,
2025-07-05 00:27:58 +00:00
createTicket,
shownMessage,
useSSESubscription,
useTicketMessages,
useTicketsFetcher,
2025-07-05 00:27:58 +00:00
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";
2024-05-15 12:37:42 +00:00
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({
2024-06-02 23:29:40 +00:00
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) => {
2024-05-16 13:40:52 +00:00
if (error.name === "CanceledError") {
return;
}
2024-05-15 12:37:42 +00:00
const [message] = parseAxiosError(error);
if (message) enqueueSnackbar(message);
}, []),
onFetchStateChange: setUnauthTicketMessageFetchState,
});
useSSESubscription<TicketMessage>({
enabled:
sseEnabled && isActiveSSETab && Boolean(ticket.sessionData?.sessionId),
2024-06-02 23:29:40 +00:00
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,
);
2025-07-23 09:05:33 +00:00
// Находим последнее сообщение, которое не от пользователя
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;
2025-07-05 00:27:58 +00:00
setSseEnabled(true);
setIsMessageSending(true);
2024-05-15 11:44:10 +00:00
2025-07-05 00:27:58 +00:00
let successful = await selectSendingMethod({ messageField });
setIsMessageSending(false);
return successful;
};
2025-07-05 00:27:58 +00:00
const sendFile = async (file: File | undefined): Promise<void> => {
if (file === undefined) return;
2024-05-15 11:44:10 +00:00
let ticketId = ticket.sessionData?.ticketId;
if (!ticket.sessionData?.ticketId) {
2025-07-05 00:27:58 +00:00
const [data, createError] = await createTicket({
message: "",
useToken: Boolean(user),
systemError: false
});
2024-05-15 11:44:10 +00:00
ticketId = data?.Ticket;
if (createError || !data) {
2025-07-05 00:27:58 +00:00
enqueueSnackbar(`Не удалось создать диалог ${parseAxiosError(createError)}`);
return;
2024-05-15 11:44:10 +00:00
} else {
setTicketData({ ticketId: data.Ticket, sessionId: data.sess });
}
2024-05-15 11:44:10 +00:00
setIsMessageSending(false);
}
if (ticketId !== undefined) {
2025-07-05 00:27:58 +00:00
if (file.size > MAX_FILE_SIZE) {
setModalWarningType("errorSize");
return;
}
2025-07-05 00:27:58 +00:00
const [_, sendFileError] = await sf({ticketId, file});
2024-05-15 11:44:10 +00:00
if (sendFileError) {
2025-07-05 00:27:58 +00:00
enqueueSnackbar(`Не удалось отправить файл ${parseAxiosError(sendFileError)}`);
}
}
};
return (
<FloatingSupportChat
isChatOpened={isChatOpened}
handleChatClickOpen={handleChatClickOpen}
handleChatClickClose={handleChatClickClose}
handleChatClickSwitch={handleChatClickSwitch}
sendMessage={sendMessage}
sendFile={sendFile}
modalWarningType={modalWarningType}
setModalWarningType={setModalWarningType}
/>
);
};