Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

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