Rev 5933 | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
import React, { useEffect, useRef, useState } from 'react'
import { axios, scrollToBottom } from '../../utils'
import { useDispatch } from 'react-redux'
import { addNotification } from '../../redux/notification/notification.actions'
import IconButton from '@mui/material/IconButton'
import MoreVertIcon from '@mui/icons-material/MoreVert'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import QuestionAnswerRoundedIcon from '@mui/icons-material/QuestionAnswerRounded'
import MessageBox from './MessageBox'
import MessageTemplate from './MessageTemplate'
import EmptySection from '../../shared/empty-section/EmptySection'
import ConfirmModal from '../../shared/confirm-modal/ConfirmModal'
import LoaderContainer from '../../app/components/UI/LoaderContainer'
import Spinner from '../../shared/loading-spinner/Spinner'
import { getMessagesDifferences } from '../../services/chat'
const DEFAULT_PAGES = { current: 1, last: 1 }
const Chatmail = ({
messagesUrl = '',
selectedConversation = null,
setConversation = () => null,
}) => {
const [oldMessages, setOldMessages] = useState([])
const [messages, setMessages] = useState([])
const [pages, setPages] = useState(DEFAULT_PAGES)
const [loading, setLoading] = useState(false)
const [isGetting, setIsGetting] = useState(false)
const [isShowConfirm, setIsShowConfirm] = useState(false)
const messagesList = useRef(null)
const dispatch = useDispatch()
const getMessages = () => {
try {
const controller = new AbortController()
const query = (url = '', page = pages.current) =>
axios
.get(`${url}?page=${page}`, {
signal: controller.signal,
})
.then((response) => response.data)
return { query, controller }
} catch (error) {
dispatch(addNotification({ style: 'danger', message: 'Error: ' + error }))
throw new Error(error)
}
}
const getMoreMessages = () => {
setLoading(true)
const { query } = getMessages()
query(messagesUrl, pages.current)
.then(({ success, data, pagination }) => {
if (!success) {
const errorMessage = data ?? 'Ha ocurrido un error'
dispatch(addNotification({ style: 'danger', msg: errorMessage }))
return
}
if (pagination.current > 1) {
setOldMessages((prevOldMessages) => [
...data.reverse(),
...prevOldMessages,
])
}
setPages((prevPages) => ({
...prevPages,
last: pagination.last,
}))
})
.catch((error) => {
const errorMessage = 'Error: ' + error
dispatch(addNotification({ style: 'danger', message: errorMessage }))
throw new Error(error)
})
.finally(() => setLoading(false))
}
const hearbeat = () => {
setIsGetting(true)
const { query } = getMessages()
query(messagesUrl, 1)
.then(({ success, data, pagination }) => {
if (!success) {
const errorMessage = data ?? 'Ha ocurrido un error'
dispatch(addNotification({ style: 'danger', msg: errorMessage }))
return
}
const newMessages = getMessagesDifferences(messages, data)
console.log(newMessages)
if (newMessages.length) {
setMessages((prevMessages) => [
...prevMessages,
...newMessages.reverse(),
])
}
setPages((prevPages) => ({
...prevPages,
last: pagination.last,
}))
})
.catch((err) => {
const errorMessage = 'Error: ' + err
dispatch(addNotification({ style: 'danger', message: errorMessage }))
throw new Error(err)
})
.finally(() => setIsGetting(false))
}
const loadMore = () => {
setLoading(true)
setPages((prevPages) => ({ ...prevPages, current: prevPages.current + 1 }))
}
const toggleConfirmModal = () => {
setIsShowConfirm(!isShowConfirm)
}
const sendMessage = async (sendUrl = '', message = {}) => {
try {
const formData = new FormData()
Object.entries(message).forEach(([key, value]) =>
formData.append(key, value)
)
await axios.post(sendUrl, formData)
scrollToBottom(messagesList)
} catch (error) {
const errorMessage = new Error(error)
dispatch(addNotification({ style: 'danger', msg: errorMessage.message }))
}
}
const deleteConversation = () => {
axios.post(selectedConversation.delete_link).then(({ data: response }) => {
const { success, data } = response
if (!success) {
dispatch(addNotification({ style: 'danger', msg: data }))
return
}
dispatch(addNotification({ style: 'success', msg: data }))
toggleConfirmModal()
setConversation(null)
})
}
useEffect(() => {
getMoreMessages()
}, [pages.current])
useEffect(() => {
setMessages([])
setOldMessages([])
setPages(DEFAULT_PAGES)
}, [selectedConversation])
useEffect(() => {
if (isGetting) return
const messagesInterval = setTimeout(() => {
hearbeat()
}, 2000)
return () => {
clearTimeout(messagesInterval)
}
}, [isGetting])
return (
<>
<div className="chat">
<Chatmail.Header
name={selectedConversation.name}
profile={selectedConversation.profile}
options={[
{
url: selectedConversation.delete_link,
label: 'Borrar convesación',
action: toggleConfirmModal,
},
]}
changeTab={() => setConversation(null)}
/>
{![...messages, ...oldMessages].length ? (
<EmptySection
message="No hay mensajes en esta conversación"
Icon={<QuestionAnswerRoundedIcon />}
/>
) : (
<Chatmail.List
isLastPage={pages.current >= pages.last}
messages={[...oldMessages, ...messages]}
onIntersection={loadMore}
scrollRef={messagesList}
isLoading={loading}
/>
)}
<MessageBox
onSend={sendMessage}
sendUrl={selectedConversation.send_link}
/>
</div>
<ConfirmModal
show={isShowConfirm}
onClose={toggleConfirmModal}
onAccept={deleteConversation}
acceptLabel="Aceptar"
/>
</>
)
}
const Header = ({ name, profile, options, changeTab }) => {
return (
<>
<div className="chat_header">
<IconButton onClick={changeTab}>
<ArrowBackIcon />
</IconButton>
<a href={profile}>
<h2>{name}</h2>
</a>
<Header.Options options={options} />
</div>
</>
)
}
const List = ({
messages,
onIntersection,
isLastPage,
scrollRef,
isLoading,
}) => {
const loadMoreEl = useRef(null)
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && !isLoading) {
onIntersection()
}
})
if (loadMoreEl.current === null) {
return
}
observer.observe(loadMoreEl.current)
return () => {
observer.disconnect()
}
}, [isLoading, onIntersection])
return (
<div className="messages_container" ref={scrollRef}>
<div className="message_wrapper">
{!isLastPage && <p ref={loadMoreEl}>Cargando...</p>}
{isLoading && (
<LoaderContainer>
<Spinner />
</LoaderContainer>
)}
{messages.map((message, index) => (
<MessageTemplate message={message} key={JSON.stringify(message)} />
))}
</div>
</div>
)
}
const Options = ({ options }) => {
const [isShowMenu, setIsShowMenu] = useState(false)
const toggleOptions = () => {
setIsShowMenu(!isShowMenu)
}
return (
<div className="header-options">
<IconButton onClick={toggleOptions}>
<MoreVertIcon />
</IconButton>
<div className="position-relative">
<div className={`feed-options ${isShowMenu ? 'active' : ''}`}>
<ul>
{options.map((option, index) => {
if (!option.url) {
return null
}
return (
<li key={index}>
<button
className="btn option-btn"
onClick={() => {
option.action()
toggleOptions()
}}
>
{option.label}
</button>
</li>
)
})}
</ul>
</div>
</div>
</div>
)
}
Chatmail.Header = Header
Chatmail.List = List
Header.Options = Options
export default Chatmail