Rev 6345 | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
import React, { useEffect, useRef, useState } from 'react'
import { axios, scrollToBottom } from '../../../utils'
import { addNotification } from '../../../redux/notification/notification.actions'
import { useDispatch } from 'react-redux'
import SendIcon from '@mui/icons-material/Send'
import IconButton from '@mui/material/IconButton'
import MoreVertIcon from '@mui/icons-material/MoreVert'
import AttachFileIcon from '@mui/icons-material/AttachFile'
import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
// Components
import Message from '../../../components/chat/Message'
import Emojione from '../emojione/Emojione'
import { ConferenceModal } from '../../../chat/chat/personal-chat/PersonalChat'
import SendFileModal from '../../../chat/chat/personal-chat/send-file-modal/SendFileModal'
const CHAT_TABS = {
CHAT: 'CHAT',
DEFAULT: 'DEFAULT',
GROUP_MEMBERS: 'GROUP_MEMBERS',
ADD_GROUP_MEMBER: 'ADD_GROUP_MEMBER',
}
const Chat = ({ entity, timezones, changeTab }) => {
const {
url_get_all_messages,
url_mark_received,
not_received_messages,
not_seen_messages,
url_zoom,
url_send,
url_upload,
url_mark_seen,
url_close,
url_add_user_to_group, // Group url
url_delete, // Group url
url_get_contact_group_list, // Group url
url_leave, // Group url
name,
profile,
type,
} = entity
const [oldMessages, setOldMessages] = useState([])
const [newMessages, setNewMessages] = useState([])
const [currentPage, setCurrentPage] = useState(1)
const [pages, setPages] = useState(1)
const [showEmojione, setShowEmojione] = useState(false)
const [isShowFileModal, setIsShowFileModal] = useState(false)
const [isShowConferenceModal, setisShowConferenceModal] = useState(false)
const [loading, setLoading] = useState(false)
const [isSending, setIsSending] = useState(false)
const [isGetting, setIsGetting] = useState(false)
const dispatch = useDispatch()
const scrollRef = useRef(null)
const inputTextEl = useRef(null)
// Messages getters
const getMessages = async () => {
setLoading(true)
axios
.get(url_get_all_messages)
.then(({ data: response }) => {
const { data, success } = response
if (!success) {
return
}
const messageResponse = [...data.items].reverse()
const updatedMessages = messageResponse.reduce(
(acum, updatedMessage) => {
if (
newMessages.findIndex(
(message) => message.id === updatedMessage.id
) === -1
) {
acum = [...acum, updatedMessage]
}
return acum
},
[]
)
if (updatedMessages.length > 0) {
setNewMessages((prevMessages) => [
...prevMessages,
...updatedMessages,
])
setPages(data.pages)
scrollRef.current.scrollBy(0, 200)
}
})
.finally(() => setLoading(false))
}
const loadOldMessages = () => {
setIsGetting(true)
axios
.get(`${url_get_all_messages}?page=${currentPage}`)
.then(({ data: response }) => {
const { data, success } = response
if (success && data.page > 1) {
setOldMessages([...data.items.slice().reverse(), ...oldMessages])
scrollRef.current.scrollBy(0, 200)
}
})
.finally(() => setIsGetting(false))
}
// Sender functions
const onHandleSubmit = (e) => {
e.preventDefault()
const formData = new FormData()
// eslint-disable-next-line no-undef
formData.append('message', emojione.toShort(inputTextEl.current.value))
inputTextEl.current.value = ''
axios.post(url_send, formData).then(({ data }) => {
if (!data.success) {
setShowEmojione(false)
dispatch(
addNotification({
style: 'danger',
msg:
typeof data.data === 'string'
? data.data
: 'Ha ocurrido un error',
})
)
return
}
setShowEmojione(false)
scrollToBottom(scrollRef)
})
}
const sendFile = (file) => {
setIsSending(true)
const formData = new FormData()
formData.append('file', file)
axios
.post(url_upload, formData)
.then(({ data: response }) => {
const { success, data } = response
if (!success) {
const errorMessage =
typeof data === 'string' ? data : 'Ha ocurrido un error'
dispatch(addNotification({ style: 'success', msg: errorMessage }))
return
}
toggleFileModal()
scrollToBottom(scrollRef)
})
.finally(() => setIsSending(false))
}
const handleKeyDown = (e) => {
if (e.key !== 'Enter') return false
}
// Modals handlers
const toggleFileModal = () => {
setIsShowFileModal(!isShowFileModal)
}
const toggleEmojione = () => {
setShowEmojione(!showEmojione)
}
const toggleConferenceModal = () => {
setisShowConferenceModal(!isShowConferenceModal)
}
// On select emoji
const onClickEmoji = (event) => {
const shortname = event.currentTarget.dataset.shortname
const currentText = inputTextEl.current.value
const cursorPosition = inputTextEl.current.selectionStart
const textBehind = currentText.substring(0, cursorPosition)
const textForward = currentText.substring(cursorPosition)
// eslint-disable-next-line no-undef
const unicode = emojione.shortnameToUnicode(shortname)
inputTextEl.current.value = `${textBehind}${unicode}${textForward}`
inputTextEl.current.focus()
inputTextEl.current.setSelectionRange(
cursorPosition + shortname.length,
cursorPosition + shortname.length
)
}
// On interception handler
const onIntersection = (entities) => {
const target = entities[0]
if (target.isIntersecting && currentPage < pages) {
setIsGetting(true)
setCurrentPage((prevState) => prevState + 1)
}
}
const deleteGroup = async (url) => {
setLoading(true)
axios
.post(url)
.then(({ data: response }) => {
const { data, success } = response
if (!success) {
const errorMessage =
typeof data.data === 'string' ? data.data : 'Ha ocurrido un error'
dispatch(addNotification({ style: 'danger', msg: errorMessage }))
return
}
changeTab(CHAT_TABS.DEFAULT)
})
.finally(() => setLoading(false))
}
const options = [
{
url: url_zoom,
label: 'Crear Conferencia',
action: toggleConferenceModal,
},
]
const groupOptions = [
{
url: url_zoom,
label: 'Crear Conferencia',
action: toggleConferenceModal,
},
{
url: url_get_contact_group_list,
label: 'Integrantes',
action: () => changeTab(CHAT_TABS.GROUP_MEMBERS),
},
{
url: url_add_user_to_group,
label: 'Agregar Contactos',
action: () => changeTab(CHAT_TABS.ADD_GROUP_MEMBER),
},
{
url: url_delete,
label: 'Eliminar Grupo',
action: () => deleteGroup(url_delete),
},
{
url: url_leave,
label: 'Dejar Grupo',
action: () => deleteGroup(url_leave),
},
]
useEffect(() => {
let getInterval = null
if (loading) return
getInterval = setTimeout(() => getMessages(), 2000)
return () => {
clearTimeout(getInterval)
}
}, [loading])
useEffect(() => {
loadOldMessages()
}, [currentPage])
useEffect(() => {
if (not_seen_messages) axios.post(url_mark_seen)
if (not_received_messages) axios.post(url_mark_received)
return () => {
axios.post(url_close)
}
}, [])
useEffect(() => {
setNewMessages([])
setOldMessages([])
setPages(1)
setCurrentPage(1)
}, [entity])
return (
<>
<div className="chat">
<Chat.Header
name={name}
profile={profile}
options={type === 'group' ? groupOptions : options}
changeTab={() => changeTab(CHAT_TABS.DEFAULT)}
/>
{![...newMessages, ...oldMessages].length ? (
<div className="message-select-conversation">
<div className="msgs-select-container">
<i className="fas fa-inbox icon" />
<h3>No hay mensajes en esta conversación</h3>
</div>
</div>
) : (
<Chat.List
isLastPage={currentPage >= pages}
messages={[...oldMessages, ...newMessages]}
onIntersection={onIntersection}
scrollRef={scrollRef}
isLoading={isGetting}
/>
)}
<div className="chat__input-container">
<form onSubmit={onHandleSubmit}>
{showEmojione && <Emojione onClickEmoji={onClickEmoji} />}
<button
type="button"
className="icon_btn"
onClick={toggleFileModal}
>
<AttachFileIcon />
</button>
<button type="button" className="icon_btn" onClick={toggleEmojione}>
<InsertEmoticonIcon />
</button>
<input
type="text"
className="chatInput"
placeholder="Escribe un mensaje"
onKeyDown={handleKeyDown}
ref={inputTextEl}
/>
<button type="submit" className="send_btn">
<SendIcon />
</button>
</form>
</div>
<SendFileModal
isShow={isShowFileModal}
onHide={toggleFileModal}
onComplete={sendFile}
loading={isSending}
/>
<ConferenceModal
show={isShowConferenceModal}
zoomUrl={url_zoom}
timezones={timezones}
onCreate={toggleConferenceModal}
/>
</div>
</>
)
}
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()
useEffect(() => {
const observer = new IntersectionObserver(onIntersection)
if (loadMoreEl.current) {
observer.observe(loadMoreEl.current)
}
return () => {
observer.disconnect()
}
}, [messages])
return (
<div className="messages_container" ref={scrollRef}>
<div className="message_wrapper">
{!isLastPage && !isLoading && <p ref={loadMoreEl}>Cargando...</p>}
{messages.map((message) => (
<Message message={message} key={message.id} />
))}
</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>
)
}
Chat.Header = Header
Chat.List = List
Header.Options = Options
export default Chat