205 lines
5.7 KiB
TypeScript
205 lines
5.7 KiB
TypeScript
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}
|
||
/>
|
||
);
|
||
};
|