first commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import ApiRequest from "@services/Api.Service";
|
||||
import {
|
||||
FindConversationsResults,
|
||||
ISendMessageResult,
|
||||
MarkConversationAsReadParams,
|
||||
PaginateFindContactsParams,
|
||||
PaginateFindContactsResults,
|
||||
PaginateMessageParams,
|
||||
PaginateMessageResults,
|
||||
SendMessageParams,
|
||||
} from "@core/Models/Messages.model";
|
||||
import { ApiVoidResult } from "@core/Models/Api.VoidResult.type";
|
||||
|
||||
export const messagesFindConversations = async (
|
||||
data: PaginateFindContactsParams
|
||||
): Promise<FindConversationsResults> => {
|
||||
return ApiRequest.post<FindConversationsResults>("messages/find-conversations", data);
|
||||
};
|
||||
|
||||
export const messagesFindContacts = async (
|
||||
data: PaginateFindContactsParams
|
||||
): Promise<PaginateFindContactsResults> => {
|
||||
return ApiRequest.post<PaginateFindContactsResults>("messages/find-contacts", data);
|
||||
};
|
||||
|
||||
export const messagesFind = async (
|
||||
data: PaginateMessageParams
|
||||
): Promise<PaginateMessageResults> => {
|
||||
return ApiRequest.post<PaginateMessageResults>("messages/paginate", data);
|
||||
};
|
||||
|
||||
export const sendMessage = async (data: SendMessageParams): Promise<ISendMessageResult> => {
|
||||
return ApiRequest.post<ISendMessageResult>("messages/send", data);
|
||||
};
|
||||
|
||||
export const markConversationAsRead = async (
|
||||
data: MarkConversationAsReadParams
|
||||
): Promise<ApiVoidResult> => {
|
||||
return ApiRequest.post<ApiVoidResult>("messages/conversation-read", data);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// socket.ts
|
||||
import { io, Socket } from "socket.io-client";
|
||||
|
||||
export let chatClientSocket: Socket | undefined;
|
||||
|
||||
export const getChatClientSocket = (userId: string) => {
|
||||
const serverUrl = process.env.NEXT_PUBLIC_CHAT_URL;
|
||||
chatClientSocket = io(serverUrl, {
|
||||
auth: {
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
return chatClientSocket;
|
||||
};
|
||||
@@ -0,0 +1,722 @@
|
||||
"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>(MESSENGER_VIEWS.BOTH);
|
||||
const [viewFocus, setViewFocus] = useState<VIEW_FOCUS>(VIEW_FOCUS.CONVERSATIONS);
|
||||
const [conversations, setConversations] = useState<FindConversationItem[]>([]);
|
||||
const [messages, setMessages] = useState<IMessage[]>([]);
|
||||
const [selectedConversation, setSelectedConversation] = useState<
|
||||
FindConversationItem | undefined
|
||||
>(undefined);
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [chatText, setChatText] = useState<string>("");
|
||||
const chatBodyRef = useRef<HTMLDivElement>(null);
|
||||
const [chatSocket, setChatSocket] = useState<Socket | undefined>(undefined);
|
||||
const [newMessageReceived, setNewMessageReceived] = useState<ISendMessageResult | null>(null);
|
||||
|
||||
const [loadingChatNextPage, setLoadingChatNextPage] = useState<boolean>(false);
|
||||
const [chatPage, setChatPage] = useState<number>(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 (
|
||||
<Suspense fallback={<SuspenseLoading />}>
|
||||
<div>
|
||||
<Header />
|
||||
<HeaderConfProvider />
|
||||
<SystemNotificationsProvider />
|
||||
<Secure>
|
||||
<div className={getClassName("MessengerContainer")}>
|
||||
<div className={getClassName("MessengerBody")}>
|
||||
<div className={getClassName("ConversationsContainer")}>
|
||||
<div className={getClassName("ConversationsHeader")}>
|
||||
<Textbox
|
||||
type="text"
|
||||
value={filter}
|
||||
width="100%"
|
||||
onChange={(e) => setFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={getClassName("ConversationsBody")}>
|
||||
{conversations.length === 0 && (
|
||||
<div className={style.NoContacts}>
|
||||
No hay conversaciones activas. Puede buscar contactos en
|
||||
el cuadro de busqueda superior.
|
||||
</div>
|
||||
)}
|
||||
{conversations.map((item, index) => {
|
||||
return (
|
||||
<motion.div
|
||||
key={"conversation." + index}
|
||||
className="w-64 h-40 rounded-2xl cursor-pointer"
|
||||
initial={{ backgroundColor: "var(--white)" }}
|
||||
whileHover={{
|
||||
backgroundColor: "var(--gray-lighter)",
|
||||
color: "var(--black)",
|
||||
}} // Azul claro en hover
|
||||
whileTap={{
|
||||
backgroundColor: "var(--wine-dark)",
|
||||
color: "white",
|
||||
}} // Azul más fuerte en tap
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<div
|
||||
className={style.ConversationItem}
|
||||
onClick={() => {
|
||||
conversationSelectedHandler(item);
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Avatar
|
||||
src={item.image}
|
||||
alt={item.name}
|
||||
size="medium"
|
||||
border="none"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ width: "100%" }}>
|
||||
<div
|
||||
className={style.ConversationItemTitle}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
style.ConversationItemDescription
|
||||
}
|
||||
>
|
||||
{item.description}
|
||||
</div>
|
||||
</div>
|
||||
{item.conversation.newMessages > 0 && (
|
||||
<div
|
||||
className={
|
||||
style.ConversationItemNewMessages
|
||||
}
|
||||
>
|
||||
{item.conversation.newMessages > 0
|
||||
? item.conversation.newMessages
|
||||
: "-1"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className={getClassName("MessagesContainer")}>
|
||||
{!selectedConversation && (
|
||||
<div className={style.NoConversation}>
|
||||
Seleccione un conversación
|
||||
</div>
|
||||
)}
|
||||
{selectedConversation && (
|
||||
<>
|
||||
<div className={getClassName("MessagesHeader")}>
|
||||
{selectedConversation && (
|
||||
<div className={style.MessageHeaderLeft}>
|
||||
{view === MESSENGER_VIEWS.MESSAGES && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setView(
|
||||
MESSENGER_VIEWS.CONVERSATIONS
|
||||
);
|
||||
setViewFocus(
|
||||
VIEW_FOCUS.CONVERSATIONS
|
||||
);
|
||||
}}
|
||||
>
|
||||
<BackIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Avatar
|
||||
src={selectedConversation.image}
|
||||
alt={selectedConversation.name}
|
||||
size="small"
|
||||
border="none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className={style.MessageHeaderLeftTitle}
|
||||
>
|
||||
{selectedConversation.name}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
style.MessageHeaderLeftDescription
|
||||
}
|
||||
>
|
||||
{selectedConversation.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
ref={chatBodyRef}
|
||||
className={getClassName("MessagesBody")}
|
||||
onScroll={chatScrollHandler}
|
||||
>
|
||||
<div className={style.MessageChatContainer}>
|
||||
{messages.map((item, index) => {
|
||||
const showDate =
|
||||
index === 0 ||
|
||||
!dayjs(item.messageDate)
|
||||
.startOf("day")
|
||||
.isSame(
|
||||
dayjs(
|
||||
messages[
|
||||
index - 1
|
||||
].messageDate.toString()
|
||||
).startOf("day")
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={"message." + index}>
|
||||
{showDate && (
|
||||
<div
|
||||
className={
|
||||
style.MessageChatDateContainer
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
style.MessageChatDate
|
||||
}
|
||||
>
|
||||
{dayjs(
|
||||
item.messageDate
|
||||
).format(
|
||||
"dddd DD [de] MMMM, YYYY"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={getMessageClassName(
|
||||
item
|
||||
)}
|
||||
>
|
||||
<div
|
||||
key={"message." + index}
|
||||
className={
|
||||
style.MessageChatItem
|
||||
}
|
||||
>
|
||||
<div>{item.message}</div>
|
||||
<div>
|
||||
{dayjs(
|
||||
item.messageDate
|
||||
).format("HH:mm")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className={style.MessageBottomBar}>
|
||||
<Textbox
|
||||
type="text"
|
||||
value={chatText}
|
||||
width="100%"
|
||||
onChange={(e) => {
|
||||
setChatText(e.target.value);
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
send();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<IconButton color="success" onClick={send}>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Secure>
|
||||
|
||||
<UserMenu />
|
||||
<Alert />
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
.MessengerContainer {
|
||||
border: solid 1px var(--gray-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.MessengerHeader {
|
||||
border-bottom: solid 1px var(--gray-light);
|
||||
background-color: var(--white-dark);
|
||||
}
|
||||
|
||||
.MessengerBody {
|
||||
background-image: url("/messenger-pattern.jpg");
|
||||
}
|
||||
|
||||
.ConversationsContainer {
|
||||
background-color: var(--white);
|
||||
border-right: solid 1px var(--gray-light);
|
||||
}
|
||||
|
||||
.MessagesContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.MessagesHeader {
|
||||
border-bottom: solid 1px var(--gray-light);
|
||||
background-color: var(--white-dark);
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.MessagesBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.ConversationsHeader {
|
||||
border-bottom: solid 1px var(--gray-light);
|
||||
background-color: var(--white-dark);
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.ConversationsBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.ConversationItem {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
border-bottom: solid 1px var(--white-dark);
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ConversationItemTitle {
|
||||
padding-left: 10px;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.ConversationItemDescription {
|
||||
padding-left: 10px;
|
||||
font-weight: 400;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.MessageHeaderLeft {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.MessageHeaderLeftTitle {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
padding-left: 5px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.MessageHeaderLeftDescription {
|
||||
font-weight: 400;
|
||||
font-size: 0.8rem;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.MessageChatContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.MessageBottomBar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 50px;
|
||||
border-top: solid 1px var(--gray-light);
|
||||
background-color: var(--white-dark);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.MessageContainerSender {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.MessageContainerSender > div {
|
||||
border: solid 1px var(--green-darkest);
|
||||
background-color: var(--green-light);
|
||||
}
|
||||
|
||||
.MessageContainerSender :nth-child(2) {
|
||||
font-size: 0.8rem;
|
||||
color: var(--green-darkest);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.MessageContainerReceiver {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.MessageContainerReceiver > div {
|
||||
border: solid 1px var(--gray-lighter);
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.MessageContainerReceiver :nth-child(2) {
|
||||
font-size: 0.8rem;
|
||||
color: var(--gray-light);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.MessageChatItem {
|
||||
margin-top: 10px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.ConversationItemNewMessages {
|
||||
background-color: var(--green);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
height: 24px;
|
||||
color: var(--white);
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.NoConversation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
text-align: center;
|
||||
padding-bottom: 30px;
|
||||
font-weight: 700;
|
||||
color: var(--wine-red);
|
||||
height: 100%;
|
||||
background-image: url("/no-conversation.webp");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.NoContacts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
text-align: center;
|
||||
padding-bottom: 80px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
font-weight: 700;
|
||||
color: var(--wine-red);
|
||||
height: 100%;
|
||||
background-image: url("/no-contacts.webp");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center calc(50% - 60px);
|
||||
}
|
||||
|
||||
.MessageChatDateContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.MessageChatDateContainer div {
|
||||
border-radius: 5px;
|
||||
background-color: var(--gray-light);
|
||||
color: var(--black);
|
||||
padding: 2px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
/*****************************************************/
|
||||
|
||||
.MessengerContainer_BOTH {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 0px;
|
||||
top: 60px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.MessengerHeader_BOTH {
|
||||
min-height: 50px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.MessengerBody_BOTH {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsContainer_BOTH {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.MessagesContainer_BOTH {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.MessagesHeader_BOTH {
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MessagesBody_BOTH {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsHeader_BOTH {
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsBody_BOTH {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/******************************************/
|
||||
|
||||
.MessengerContainer_CONVERSATIONS {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 0px;
|
||||
top: 60px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.MessengerHeader_CONVERSATIONS {
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MessengerBody_CONVERSATIONS {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsContainer_CONVERSATIONS {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.MessagesContainer_CONVERSATIONS {
|
||||
width: 0px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MessagesHeader_CONVERSATIONS {
|
||||
min-height: 0px;
|
||||
height: 0px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MessagesBody_CONVERSATIONS {
|
||||
height: 0px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ConversationsHeader_CONVERSATIONS {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsBody_CONVERSATIONS {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/******************************************/
|
||||
|
||||
.MessengerContainer_MESSAGES {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 0px;
|
||||
top: 60px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.MessengerHeader_MESSAGES {
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MessengerBody_MESSAGES {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsContainer_MESSAGES {
|
||||
min-width: 0px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MessagesContainer_MESSAGES {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.MessagesHeader_MESSAGES {
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MessagesBody_MESSAGES {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsHeader_MESSAGES {
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ConversationsBody_MESSAGES {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
Reference in New Issue
Block a user