Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

Rev 6345 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
6345 stevensc 1
import React, { useEffect, useRef, useState } from 'react'
2
import { axios, scrollToBottom } from '../../../utils'
3
import { addNotification } from '../../../redux/notification/notification.actions'
4
import { useDispatch } from 'react-redux'
5
import SendIcon from '@mui/icons-material/Send'
6
import IconButton from '@mui/material/IconButton'
7
import MoreVertIcon from '@mui/icons-material/MoreVert'
8
import AttachFileIcon from '@mui/icons-material/AttachFile'
9
import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon'
10
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
11
 
12
// Components
13
import Message from '../../../components/chat/Message'
14
import Emojione from '../emojione/Emojione'
15
import { ConferenceModal } from '../../../chat/chat/personal-chat/PersonalChat'
16
import SendFileModal from '../../../chat/chat/personal-chat/send-file-modal/SendFileModal'
17
 
18
const CHAT_TABS = {
19
  CHAT: 'CHAT',
20
  DEFAULT: 'DEFAULT',
21
  GROUP_MEMBERS: 'GROUP_MEMBERS',
22
  ADD_GROUP_MEMBER: 'ADD_GROUP_MEMBER',
23
}
24
 
25
const Chat = ({ entity, timezones, changeTab }) => {
26
  const {
27
    url_get_all_messages,
28
    url_mark_received,
29
    not_received_messages,
30
    not_seen_messages,
31
    url_zoom,
32
    url_send,
33
    url_upload,
34
    url_mark_seen,
35
    url_close,
36
    url_add_user_to_group, // Group url
37
    url_delete, // Group url
38
    url_get_contact_group_list, // Group url
39
    url_leave, // Group url
40
    name,
41
    profile,
42
    type,
43
  } = entity
44
 
45
  const [oldMessages, setOldMessages] = useState([])
46
  const [newMessages, setNewMessages] = useState([])
47
  const [currentPage, setCurrentPage] = useState(1)
48
  const [pages, setPages] = useState(1)
49
 
50
  const [showEmojione, setShowEmojione] = useState(false)
51
  const [isShowFileModal, setIsShowFileModal] = useState(false)
52
  const [isShowConferenceModal, setisShowConferenceModal] = useState(false)
53
 
54
  const [loading, setLoading] = useState(false)
55
  const [isSending, setIsSending] = useState(false)
56
  const [isGetting, setIsGetting] = useState(false)
57
 
58
  const dispatch = useDispatch()
59
 
60
  const scrollRef = useRef(null)
61
  const inputTextEl = useRef(null)
62
 
63
  // Messages getters
64
  const getMessages = async () => {
65
    setLoading(true)
66
    axios
67
      .get(url_get_all_messages)
68
      .then(({ data: response }) => {
69
        const { data, success } = response
70
 
71
        if (!success) {
72
          return
73
        }
74
 
75
        const messageResponse = [...data.items].reverse()
76
        const updatedMessages = messageResponse.reduce(
77
          (acum, updatedMessage) => {
78
            if (
79
              newMessages.findIndex(
80
                (message) => message.id === updatedMessage.id
81
              ) === -1
82
            ) {
83
              acum = [...acum, updatedMessage]
84
            }
85
            return acum
86
          },
87
          []
88
        )
89
 
90
        if (updatedMessages.length > 0) {
91
          setNewMessages((prevMessages) => [
92
            ...prevMessages,
93
            ...updatedMessages,
94
          ])
95
          setPages(data.pages)
96
          scrollRef.current.scrollBy(0, 200)
97
        }
98
      })
99
      .finally(() => setLoading(false))
100
  }
101
 
102
  const loadOldMessages = () => {
103
    setIsGetting(true)
104
    axios
105
      .get(`${url_get_all_messages}?page=${currentPage}`)
106
      .then(({ data: response }) => {
107
        const { data, success } = response
108
        if (success && data.page > 1) {
109
          setOldMessages([...data.items.slice().reverse(), ...oldMessages])
110
          scrollRef.current.scrollBy(0, 200)
111
        }
112
      })
113
      .finally(() => setIsGetting(false))
114
  }
115
 
116
  // Sender functions
117
  const onHandleSubmit = (e) => {
118
    e.preventDefault()
119
    const formData = new FormData()
120
    // eslint-disable-next-line no-undef
121
    formData.append('message', emojione.toShort(inputTextEl.current.value))
122
    inputTextEl.current.value = ''
123
 
124
    axios.post(url_send, formData).then(({ data }) => {
125
      if (!data.success) {
126
        setShowEmojione(false)
127
        dispatch(
128
          addNotification({
129
            style: 'danger',
130
            msg:
131
              typeof data.data === 'string'
132
                ? data.data
133
                : 'Ha ocurrido un error',
134
          })
135
        )
136
        return
137
      }
138
      setShowEmojione(false)
139
      scrollToBottom(scrollRef)
140
    })
141
  }
142
 
143
  const sendFile = (file) => {
144
    setIsSending(true)
145
    const formData = new FormData()
146
    formData.append('file', file)
147
 
148
    axios
149
      .post(url_upload, formData)
150
      .then(({ data: response }) => {
151
        const { success, data } = response
152
        if (!success) {
153
          const errorMessage =
154
            typeof data === 'string' ? data : 'Ha ocurrido un error'
155
          dispatch(addNotification({ style: 'success', msg: errorMessage }))
156
          return
157
        }
158
 
159
        toggleFileModal()
160
        scrollToBottom(scrollRef)
161
      })
162
      .finally(() => setIsSending(false))
163
  }
164
 
165
  const handleKeyDown = (e) => {
166
    if (e.key !== 'Enter') return false
167
  }
168
 
169
  // Modals handlers
170
  const toggleFileModal = () => {
171
    setIsShowFileModal(!isShowFileModal)
172
  }
173
 
174
  const toggleEmojione = () => {
175
    setShowEmojione(!showEmojione)
176
  }
177
 
178
  const toggleConferenceModal = () => {
179
    setisShowConferenceModal(!isShowConferenceModal)
180
  }
181
 
182
  // On select emoji
183
  const onClickEmoji = (event) => {
184
    const shortname = event.currentTarget.dataset.shortname
185
    const currentText = inputTextEl.current.value
186
    const cursorPosition = inputTextEl.current.selectionStart
187
    const textBehind = currentText.substring(0, cursorPosition)
188
    const textForward = currentText.substring(cursorPosition)
189
    // eslint-disable-next-line no-undef
190
    const unicode = emojione.shortnameToUnicode(shortname)
191
    inputTextEl.current.value = `${textBehind}${unicode}${textForward}`
192
    inputTextEl.current.focus()
193
    inputTextEl.current.setSelectionRange(
194
      cursorPosition + shortname.length,
195
      cursorPosition + shortname.length
196
    )
197
  }
198
 
199
  // On interception handler
200
  const onIntersection = (entities) => {
201
    const target = entities[0]
202
    if (target.isIntersecting && currentPage < pages) {
203
      setIsGetting(true)
204
      setCurrentPage((prevState) => prevState + 1)
205
    }
206
  }
207
 
208
  const deleteGroup = async (url) => {
209
    setLoading(true)
210
    axios
211
      .post(url)
212
      .then(({ data: response }) => {
213
        const { data, success } = response
214
        if (!success) {
215
          const errorMessage =
216
            typeof data.data === 'string' ? data.data : 'Ha ocurrido un error'
217
          dispatch(addNotification({ style: 'danger', msg: errorMessage }))
218
          return
219
        }
220
 
221
        changeTab(CHAT_TABS.DEFAULT)
222
      })
223
      .finally(() => setLoading(false))
224
  }
225
 
226
  const options = [
227
    {
228
      url: url_zoom,
229
      label: 'Crear Conferencia',
230
      action: toggleConferenceModal,
231
    },
232
  ]
233
 
234
  const groupOptions = [
235
    {
236
      url: url_zoom,
237
      label: 'Crear Conferencia',
238
      action: toggleConferenceModal,
239
    },
240
    {
241
      url: url_get_contact_group_list,
242
      label: 'Integrantes',
243
      action: () => changeTab(CHAT_TABS.GROUP_MEMBERS),
244
    },
245
    {
246
      url: url_add_user_to_group,
247
      label: 'Agregar Contactos',
248
      action: () => changeTab(CHAT_TABS.ADD_GROUP_MEMBER),
249
    },
250
    {
251
      url: url_delete,
252
      label: 'Eliminar Grupo',
253
      action: () => deleteGroup(url_delete),
254
    },
255
    {
256
      url: url_leave,
257
      label: 'Dejar Grupo',
258
      action: () => deleteGroup(url_leave),
259
    },
260
  ]
261
 
262
  useEffect(() => {
263
    let getInterval = null
264
    if (loading) return
265
    getInterval = setTimeout(() => getMessages(), 2000)
266
 
267
    return () => {
268
      clearTimeout(getInterval)
269
    }
270
  }, [loading])
271
 
272
  useEffect(() => {
273
    loadOldMessages()
274
  }, [currentPage])
275
 
276
  useEffect(() => {
277
    if (not_seen_messages) axios.post(url_mark_seen)
278
    if (not_received_messages) axios.post(url_mark_received)
279
 
280
    return () => {
281
      axios.post(url_close)
282
    }
283
  }, [])
284
 
285
  useEffect(() => {
286
    setNewMessages([])
287
    setOldMessages([])
288
    setPages(1)
289
    setCurrentPage(1)
290
  }, [entity])
291
 
292
  return (
293
    <>
294
      <div className="chat">
295
        <Chat.Header
296
          name={name}
297
          profile={profile}
298
          options={type === 'group' ? groupOptions : options}
299
          changeTab={() => changeTab(CHAT_TABS.DEFAULT)}
300
        />
6753 stevensc 301
        {![...newMessages, ...oldMessages].length ? (
6345 stevensc 302
          <div className="message-select-conversation">
303
            <div className="msgs-select-container">
304
              <i className="fas fa-inbox icon" />
305
              <h3>No hay mensajes en esta conversación</h3>
306
            </div>
307
          </div>
308
        ) : (
309
          <Chat.List
310
            isLastPage={currentPage >= pages}
311
            messages={[...oldMessages, ...newMessages]}
312
            onIntersection={onIntersection}
313
            scrollRef={scrollRef}
314
            isLoading={isGetting}
315
          />
316
        )}
317
        <div className="chat__input-container">
318
          <form onSubmit={onHandleSubmit}>
319
            {showEmojione && <Emojione onClickEmoji={onClickEmoji} />}
320
            <button
321
              type="button"
322
              className="icon_btn"
323
              onClick={toggleFileModal}
324
            >
325
              <AttachFileIcon />
326
            </button>
327
            <button type="button" className="icon_btn" onClick={toggleEmojione}>
328
              <InsertEmoticonIcon />
329
            </button>
330
            <input
331
              type="text"
332
              className="chatInput"
333
              placeholder="Escribe un mensaje"
334
              onKeyDown={handleKeyDown}
335
              ref={inputTextEl}
336
            />
337
            <button type="submit" className="send_btn">
338
              <SendIcon />
339
            </button>
340
          </form>
341
        </div>
342
        <SendFileModal
343
          isShow={isShowFileModal}
344
          onHide={toggleFileModal}
345
          onComplete={sendFile}
346
          loading={isSending}
347
        />
348
        <ConferenceModal
349
          show={isShowConferenceModal}
350
          zoomUrl={url_zoom}
351
          timezones={timezones}
352
          onCreate={toggleConferenceModal}
353
        />
354
      </div>
355
    </>
356
  )
357
}
358
const Header = ({ name, profile, options, changeTab }) => {
359
  return (
360
    <>
361
      <div className="chat_header">
362
        <IconButton onClick={changeTab}>
363
          <ArrowBackIcon />
364
        </IconButton>
365
        <a href={profile}>
366
          <h2>{name}</h2>
367
        </a>
368
        <Header.Options options={options} />
369
      </div>
370
    </>
371
  )
372
}
373
 
374
const List = ({
375
  messages,
376
  onIntersection,
377
  isLastPage,
378
  scrollRef,
379
  isLoading,
380
}) => {
381
  const loadMoreEl = useRef()
382
 
383
  useEffect(() => {
384
    const observer = new IntersectionObserver(onIntersection)
385
 
386
    if (loadMoreEl.current) {
387
      observer.observe(loadMoreEl.current)
388
    }
389
 
390
    return () => {
391
      observer.disconnect()
392
    }
393
  }, [messages])
394
 
395
  return (
396
    <div className="messages_container" ref={scrollRef}>
397
      <div className="message_wrapper">
398
        {!isLastPage && !isLoading && <p ref={loadMoreEl}>Cargando...</p>}
399
        {messages.map((message) => (
400
          <Message message={message} key={message.id} />
401
        ))}
402
      </div>
403
    </div>
404
  )
405
}
406
 
407
const Options = ({ options }) => {
408
  const [isShowMenu, setIsShowMenu] = useState(false)
409
 
410
  const toggleOptions = () => {
411
    setIsShowMenu(!isShowMenu)
412
  }
413
 
414
  return (
415
    <div className="header-options">
416
      <IconButton onClick={toggleOptions}>
417
        <MoreVertIcon />
418
      </IconButton>
419
      <div className="position-relative">
420
        <div className={`feed-options ${isShowMenu ? 'active' : ''}`}>
421
          <ul>
422
            {options.map((option, index) => {
423
              if (!option.url) {
424
                return null
425
              }
426
 
427
              return (
428
                <li key={index}>
429
                  <button
430
                    className="btn option-btn"
431
                    onClick={() => {
432
                      option.action()
433
                      toggleOptions()
434
                    }}
435
                  >
436
                    {option.label}
437
                  </button>
438
                </li>
439
              )
440
            })}
441
          </ul>
442
        </div>
443
      </div>
444
    </div>
445
  )
446
}
447
 
448
Chat.Header = Header
449
Chat.List = List
450
Header.Options = Options
451
 
452
export default Chat