"use client"; import { useState, useEffect, useRef, Suspense } from "react"; import Secure from "../components/Secure/Secure"; import Alert from "@components/Alert/Alert"; import Header from "@components/Header/Header"; import { SystemNotificationsProvider } from "@components/SystemNotificationsProvider/SystemNotificationsProvider"; import UserMenu from "@components/UserMenu/UserMenu"; import style from "./style.module.css"; import useWindowSize from "../hooks/WindowSize"; import classNames from "classnames"; import Textbox from "../components/Textbox/Textbox"; import { FindConversationItem, IMessage, ISendMessageResult, MESSAGE_CONVERSATION_TYPES, SendMessageParams, } from "@core/Models/Messages.model"; import { markConversationAsRead, messagesFind, messagesFindContacts, messagesFindConversations, sendMessage, } from "./Messages.service"; import { useSessionStore } from "@core/Store/Sesion.Store"; import Avatar from "../components/Avatar/Avatar"; import SendIcon from "@mui/icons-material/Send"; import BackIcon from "@mui/icons-material/ArrowBack"; import { motion } from "motion/react"; import { IconButton } from "@mui/material"; import { EVENT_TYPES, useEventHandlerStore } from "@core/Store/EventHandler.Store"; import { useAlert } from "@core/Store/Alert.Store"; import { getChatClientSocket } from "./Messages.socket"; import { Socket } from "socket.io-client"; import dayjs from "dayjs"; import "dayjs/locale/es"; import { blurElementsOnLoad } from "@core/helpers/blur"; import HeaderConfProvider from "../components/HeaderConfProvider/HeaderConfProvider"; import SuspenseLoading from "../components/SuspendeLoading/SuspenseLoading"; dayjs.locale("es"); /** * TODO: * En la columna de conversaciones, vamos a poner en la parte superior una caja de texto * que permita buscar organizaciones, y en el caso de las organizaciones que el usuario es cliente * se tienen que mostrar los colaboradores de esa organizacion. * Si el usuario es colaborador de la organizacion, se tienen que mostrar los clientes de esa organizacion * y los otros colaboradores de la organizacion. */ enum VIEW_FOCUS { CONVERSATIONS = "CONVERSATIONS", MESSAGES = "MESSAGES", } enum MESSENGER_VIEWS { CONVERSATIONS = "CONVERSATIONS", MESSAGES = "MESSAGES", BOTH = "BOTH", } type ViewClasses = | "MessengerContainer" | "MessengerBody" | "ConversationsContainer" | "ConversationsHeader" | "ConversationsBody" | "MessagesContainer" | "MessagesHeader" | "MessagesBody"; const classesMap = { CONVERSATIONS: { MessengerContainer: style.MessengerContainer_CONVERSATIONS, MessengerBody: style.MessengerBody_CONVERSATIONS, ConversationsContainer: style.ConversationsContainer_CONVERSATIONS, ConversationsHeader: style.ConversationsHeader_CONVERSATIONS, ConversationsBody: style.ConversationsBody_CONVERSATIONS, MessagesContainer: style.MessagesContainer_CONVERSATIONS, MessagesHeader: style.MessagesHeader_CONVERSATIONS, MessagesBody: style.MessagesBody_CONVERSATIONS, }, MESSAGES: { MessengerContainer: style.MessengerContainer_MESSAGES, MessengerBody: style.MessengerBody_MESSAGES, ConversationsContainer: style.ConversationsContainer_MESSAGES, ConversationsHeader: style.ConversationsHeader_MESSAGES, ConversationsBody: style.ConversationsBody_MESSAGES, MessagesContainer: style.MessagesContainer_MESSAGES, MessagesHeader: style.MessagesHeader_MESSAGES, MessagesBody: style.MessagesBody_MESSAGES, }, BOTH: { MessengerContainer: style.MessengerContainer_BOTH, MessengerBody: style.MessengerBody_BOTH, ConversationsContainer: style.ConversationsContainer_BOTH, ConversationsHeader: style.ConversationsHeader_BOTH, ConversationsBody: style.ConversationsBody_BOTH, MessagesContainer: style.MessagesContainer_BOTH, MessagesHeader: style.MessagesHeader_BOTH, MessagesBody: style.MessagesBody_BOTH, }, }; interface FindConversationParams { selectConversationId?: string; page?: number; } export default function Messages() { const SessionInfo = useSessionStore(); const alert = useAlert(); const eventHandler = useEventHandlerStore(); const [width, height] = useWindowSize(); const [view, setView] = useState(MESSENGER_VIEWS.BOTH); const [viewFocus, setViewFocus] = useState(VIEW_FOCUS.CONVERSATIONS); const [conversations, setConversations] = useState([]); const [messages, setMessages] = useState([]); const [selectedConversation, setSelectedConversation] = useState< FindConversationItem | undefined >(undefined); const [filter, setFilter] = useState(""); const [chatText, setChatText] = useState(""); const chatBodyRef = useRef(null); const [chatSocket, setChatSocket] = useState(undefined); const [newMessageReceived, setNewMessageReceived] = useState(null); const [loadingChatNextPage, setLoadingChatNextPage] = useState(false); const [chatPage, setChatPage] = useState(1); const getClassName = (name: ViewClasses) => { return classNames(style[name], classesMap[view][name]); }; const getMessageClassName = (message: IMessage) => { if (message.userId === SessionInfo.userId) { return classNames(style.MessageContainerSender); } else { return classNames(style.MessageContainerReceiver); } }; const viewManager = () => { if (width < 800) { if (viewFocus === VIEW_FOCUS.CONVERSATIONS) { setView(MESSENGER_VIEWS.CONVERSATIONS); } else if (viewFocus === VIEW_FOCUS.MESSAGES) { setView(MESSENGER_VIEWS.MESSAGES); } } else { setView(MESSENGER_VIEWS.BOTH); } }; const findContacts = (query: string) => { messagesFindContacts({ page: 1, query: query, sessionUser: SessionInfo.userId, limit: 20, }) .then((result) => { setConversations(result.data); }) .finally(() => { eventHandler.setEventType(EVENT_TYPES.SLEEP); }); }; const findConversations = ({ page = 1, selectConversationId = "" }: FindConversationParams) => { eventHandler.setEventType(EVENT_TYPES.LOADING); messagesFindConversations({ page: page, sessionUser: SessionInfo.userId, limit: 20, }) .then((result) => { setConversations(result.data); if (selectConversationId != "") { setSelectedConversation( result.data.find((item) => item.conversation.id === selectConversationId) ); } }) .finally(() => { eventHandler.setEventType(EVENT_TYPES.SLEEP); }); }; const loadConversationChat = (markAsRead: boolean = false) => { if (!selectedConversation) { return; } if (!selectedConversation.conversation.id) { setMessages([]); return; } messagesFind({ conversationId: selectedConversation.conversation.id, sessionUser: SessionInfo.userId, page: 1, limit: 20, }) .then((result) => { const messages = result.data; const orderedMessages = messages.sort((a, b) => { return new Date(a.messageDate).getTime() - new Date(b.messageDate).getTime(); }); setMessages(orderedMessages); setChatPage(1); if (markAsRead) { setTimeout(() => { markCurrentConversationAsReadHandler(); markConversationsAsReadHandler(); }, 2000); } }) .finally(() => { chatScrollToEnd(); }); }; const loadConversationChatNextPage = () => { if (!selectedConversation) { return; } if (!selectedConversation.conversation.id) { setMessages([]); return; } if (loadingChatNextPage) { return; } setLoadingChatNextPage(true); const chatBody = chatBodyRef.current; const previousScrollHeight = chatBody?.scrollHeight || 0; const previousScrollTop = chatBody?.scrollTop || 0; messagesFind({ conversationId: selectedConversation.conversation.id, sessionUser: SessionInfo.userId, page: chatPage + 1, limit: 20, }) .then((result) => { if (result.page > chatPage && result.data.length > 0) { setChatPage(result.page); const messagesResult = [...messages, ...result.data]; const orderedMessages = messagesResult.sort((a, b) => { return ( new Date(a.messageDate).getTime() - new Date(b.messageDate).getTime() ); }); setMessages(orderedMessages); //Lleva dobre requestAnimationFrame porque sino no se entera del nuevo tamanio del div. requestAnimationFrame(() => { requestAnimationFrame(() => { const chatBody = chatBodyRef.current; if (chatBody) { const newScrollHeight = chatBody.scrollHeight; const delta = newScrollHeight - previousScrollHeight; chatBody.scrollTop = previousScrollTop + delta; } }); }); } }) .finally(() => { setLoadingChatNextPage(false); }); }; const send = () => { if (!selectedConversation) { return; } eventHandler.setEventType(EVENT_TYPES.LOADING); const conversationType = selectedConversation.conversation.type; const sendMessageData: SendMessageParams = { conversationId: selectedConversation.conversation.id ? selectedConversation.conversation.id : undefined, type: conversationType, userIdFrom: SessionInfo.userId, userIdTo: conversationType == MESSAGE_CONVERSATION_TYPES.USER_TO_USER ? selectedConversation.conversation.userIdTo : undefined, companyId: conversationType == MESSAGE_CONVERSATION_TYPES.USER_TO_ORG ? selectedConversation.conversation.companyId : undefined, message: chatText, sessionUser: SessionInfo.userId, }; sendMessage(sendMessageData) .then((result) => { if (!sendMessageData.conversationId) { findConversations({ page: 1, selectConversationId: result.conversation.id, }); return; } loadConversationChat(); }) .catch((error) => { alert.showError(error.format()); }) .finally(() => { chatScrollToEnd(); setChatText(""); eventHandler.setEventType(EVENT_TYPES.SLEEP); }); }; const conversationSelectedHandler = (item: FindConversationItem) => { if (view != MESSENGER_VIEWS.BOTH) { setView(MESSENGER_VIEWS.MESSAGES); setViewFocus(VIEW_FOCUS.MESSAGES); } setSelectedConversation(item); setChatText(""); setFilter(""); }; const chatScrollToEnd = () => { requestAnimationFrame(() => { const chatBody = chatBodyRef.current; if (chatBody) { chatBody.scrollTop = chatBody.scrollHeight; } }); }; const chatScrollHandler = () => { const chatBody = chatBodyRef.current; if (chatBody && chatBody.scrollTop <= 50) { loadConversationChatNextPage(); } }; const updateOtherConversationsReadedCounter = (data: ISendMessageResult) => { const conversation = conversations.find( (c) => c.conversation.id === data.message.conversationId ); if (conversation) { const newMessagesCount = conversation.conversation.newMessages ? conversation.conversation.newMessages + 1 : 1; conversation.conversation.newMessages = newMessagesCount; const indexConversation = conversations.indexOf(conversation); const conversationsUpdated = [...conversations]; conversationsUpdated[indexConversation] = conversation; setConversations(conversationsUpdated); } }; const newMessageReceivedHandler = (data: ISendMessageResult) => { if (!selectedConversation) { updateOtherConversationsReadedCounter(data); return; } if (selectedConversation.conversation.id != data.message.conversationId) { updateOtherConversationsReadedCounter(data); return; } const updatedMessages = [...messages, ...[data.message]]; setMessages(updatedMessages); chatScrollToEnd(); }; const markConversationsAsReadHandler = () => { if (!selectedConversation || !selectedConversation.conversation.id) { return; } if (!SessionInfo.userId) { return; } markConversationAsRead({ conversationId: selectedConversation.conversation.id, sessionUser: SessionInfo.userId, }); }; const markCurrentConversationAsReadHandler = () => { if (!selectedConversation || !selectedConversation.conversation.id) { return; } const conversation = conversations.find( (c) => c.conversation.id === selectedConversation.conversation.id ); if (conversation) { conversation.conversation.newMessages = 0; const indexConversation = conversations.indexOf(conversation); const conversationsUpdated = [...conversations]; conversationsUpdated[indexConversation] = conversation; setConversations(conversationsUpdated); } }; useEffect(() => { eventHandler.setEventType(EVENT_TYPES.LOADING); setSelectedConversation(undefined); blurElementsOnLoad(); }, []); useEffect(() => { if (!SessionInfo.userId) { return; } if (filter != "") { findContacts(filter); return; } findConversations({ page: 1 }); }, [SessionInfo.userId, filter]); useEffect(() => { if (!SessionInfo.userId) { if (chatSocket) { chatSocket.off("new_message"); chatSocket.disconnect(); } return; } loadConversationChat(true); if (chatSocket) { return; } const socket = getChatClientSocket(SessionInfo.userId); setChatSocket(socket); }, [SessionInfo.userId, selectedConversation]); useEffect(() => { if (!chatSocket) { return; } chatSocket.on("new_message", (message: ISendMessageResult) => { setNewMessageReceived(message); }); return () => { chatSocket.off("new_message"); }; }, [chatSocket]); useEffect(() => { if (!newMessageReceived) { return; } newMessageReceivedHandler(newMessageReceived); }, [newMessageReceived]); useEffect(() => { viewManager(); }, [width, height]); return ( }>
setFilter(e.target.value)} />
{conversations.length === 0 && (
No hay conversaciones activas. Puede buscar contactos en el cuadro de busqueda superior.
)} {conversations.map((item, index) => { return (
{ conversationSelectedHandler(item); }} >
{item.name}
{item.description}
{item.conversation.newMessages > 0 && (
{item.conversation.newMessages > 0 ? item.conversation.newMessages : "-1"}
)}
); })}
{!selectedConversation && (
Seleccione un conversación
)} {selectedConversation && ( <>
{selectedConversation && (
{view === MESSENGER_VIEWS.MESSAGES && (
{ setView( MESSENGER_VIEWS.CONVERSATIONS ); setViewFocus( VIEW_FOCUS.CONVERSATIONS ); }} >
)}
{selectedConversation.name}
{selectedConversation.description}
)}
{messages.map((item, index) => { const showDate = index === 0 || !dayjs(item.messageDate) .startOf("day") .isSame( dayjs( messages[ index - 1 ].messageDate.toString() ).startOf("day") ); return (
{showDate && (
{dayjs( item.messageDate ).format( "dddd DD [de] MMMM, YYYY" )}
)}
{item.message}
{dayjs( item.messageDate ).format("HH:mm")}
); })}
{ setChatText(e.target.value); }} onKeyUp={(e) => { if (e.key === "Enter") { send(); } }} />
)}
); }