Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

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