Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

Rev 6974 | Rev 6976 | Ir a la última revisión | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
6932 stevensc 1
import React, { memo, useEffect, useRef, useState } from 'react'
6911 stevensc 2
import { axios } from '../../utils'
3
import { useForm } from 'react-hook-form'
4
import { useDispatch } from 'react-redux'
5
import { addNotification } from '../../redux/notification/notification.actions'
6
import parse from 'html-react-parser'
6927 stevensc 7
import styled, { css } from 'styled-components'
6911 stevensc 8
import SendIcon from '@mui/icons-material/Send'
9
import IconButton from '@mui/material/IconButton'
10
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
11
import AttachFileIcon from '@mui/icons-material/AttachFile'
12
import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon'
13
 
14
import Options from '../UI/Option'
15
import Emojione from './emojione/Emojione'
16
import FileModal from '../modals/FileModal'
6965 stevensc 17
import LoaderContainer from '../UI/LoaderContainer'
18
import Spinner from '../UI/Spinner'
6911 stevensc 19
 
20
const StyledChatContainer = styled.div`
6921 stevensc 21
  background-color: var(--bg-color);
22
  border-radius: var(--border-radius);
23
  border: 1px solid var(--border-primary);
6911 stevensc 24
  height: 80vh;
25
  display: flex;
26
  flex-direction: column;
6922 stevensc 27
  flex-grow: 1;
6911 stevensc 28
`
29
 
6921 stevensc 30
const StyledChatHeader = styled.div`
6926 stevensc 31
  align-items: center;
6921 stevensc 32
  border-bottom: 1px solid var(--border-primary);
6926 stevensc 33
  display: flex;
34
  justify-content: center;
6921 stevensc 35
  padding: 1rem 0.5rem;
36
  position: relative;
37
 
6941 stevensc 38
  & > button:first-child {
6921 stevensc 39
    position: absolute;
40
    left: 1rem;
41
    top: 50%;
42
    transform: translateY(-50%);
43
    display: none;
44
  }
45
`
46
 
47
const StyledTitle = styled.h2`
48
  font-size: 1.5rem;
49
  font-weight: 500;
50
  width: fit-content;
51
  max-width: 30ch;
52
  text-align: center;
53
`
54
 
6922 stevensc 55
const StyledMessageList = styled.div`
56
  gap: 0.5rem;
6921 stevensc 57
  display: flex;
6922 stevensc 58
  flex-direction: column-reverse;
6921 stevensc 59
  height: -webkit-fill-available;
60
  padding: 0.5rem 0;
61
  overflow: auto;
62
`
63
 
6927 stevensc 64
const StyledMessage = styled.div`
65
  box-shadow: var(--light-shadow);
66
  display: inline-flex;
67
  flex-direction: column;
68
  gap: 0.5rem;
69
  margin-bottom: 0.5rem;
70
  max-width: 70%;
71
  min-width: 4rem;
72
  padding: 0.5rem;
73
  position: relative;
74
 
75
  & > p {
76
    color: var(--chat-color);
77
    max-width: 100%;
78
    overflow: hidden;
79
    text-overflow: ellipsis;
80
    word-break: break-word;
81
  }
82
 
6975 stevensc 83
  & > img,
84
  & > video {
6927 stevensc 85
    max-width: 250px;
86
    max-height: 250px;
87
    object-fit: contain;
88
  }
89
 
90
  &:first-child {
91
    margin-top: 0.5rem;
92
  }
93
 
94
  .time {
95
    color: $subtitle-color;
96
    font-size: 0.8rem;
97
  }
98
 
99
  .emojione {
100
    width: 1rem;
101
    height: 1rem;
102
  }
103
 
6930 stevensc 104
  ${(props) => {
6932 stevensc 105
    if (props.send) {
6930 stevensc 106
      return css`
107
        align-self: flex-end;
108
        background-color: var(--chat-send);
109
        border-radius: 10px 0 10px 10px;
6934 stevensc 110
        margin-right: 0.5rem;
6930 stevensc 111
      `
112
    }
113
    return css`
114
      align-self: flex-start;
115
      background-color: var(--chat-received);
116
      border-radius: 0 10px 10px 10px;
6934 stevensc 117
      margin-left: 0.5rem;
6930 stevensc 118
    `
119
  }}
6927 stevensc 120
`
121
 
6937 stevensc 122
const StyledForm = styled.form`
123
  border-top: 1px solid var(--border-primary);
124
  padding: 0.5rem;
125
  position: relative;
126
  display: flex;
127
  justify-content: center;
128
  align-items: center;
129
  gap: 0.5rem;
130
`
131
 
132
const StyledInput = styled.input`
133
  border: none;
134
  outline: none;
135
  flex: 1;
136
  padding: 0.5rem 1rem;
137
  border-radius: 30px;
138
  background: $bg-color-secondary;
139
 
140
  &:focus {
141
    background: $bg-color-secondary;
142
  }
143
`
144
 
6965 stevensc 145
const StyledLoader = styled(LoaderContainer)`
146
  max-height: 50px;
147
  max-width: 50px;
148
`
149
 
6933 stevensc 150
function messageAreEqual(oldProps, newProps) {
6968 stevensc 151
  return oldProps.message.id
152
    ? oldProps.message.id === newProps.message.id
153
    : oldProps.message.uuid === newProps.message.uuid
6932 stevensc 154
}
155
 
6911 stevensc 156
const Chat = ({ children }) => {
157
  return <StyledChatContainer>{children}</StyledChatContainer>
158
}
159
 
6956 stevensc 160
const Header = ({ children, options = [], onClose }) => {
6911 stevensc 161
  return (
6921 stevensc 162
    <StyledChatHeader>
6911 stevensc 163
      <IconButton onClick={onClose}>
164
        <ArrowBackIcon />
165
      </IconButton>
166
      {children}
6956 stevensc 167
      {!!options.length && <Options options={options} right="1rem" />}
6921 stevensc 168
    </StyledChatHeader>
6911 stevensc 169
  )
170
}
171
 
6921 stevensc 172
const Title = ({ children, url }) => {
6911 stevensc 173
  if (!url) {
6921 stevensc 174
    return <StyledTitle>{children}</StyledTitle>
6911 stevensc 175
  }
176
 
177
  return (
6921 stevensc 178
    <a href={url} style={{ width: 'fit-content' }}>
179
      <StyledTitle>{children}</StyledTitle>
6911 stevensc 180
    </a>
181
  )
182
}
183
 
6956 stevensc 184
const List = ({ messages = [], onPagination, scrollRef, loading }) => {
6921 stevensc 185
  const loadMoreEl = useRef(null)
6911 stevensc 186
 
187
  useEffect(() => {
188
    const observer = new IntersectionObserver(onPagination)
189
 
190
    if (loadMoreEl.current) {
191
      observer.observe(loadMoreEl.current)
192
    }
193
 
194
    return () => {
195
      observer.disconnect()
196
    }
197
  }, [messages])
198
 
199
  return (
6922 stevensc 200
    <StyledMessageList ref={scrollRef}>
201
      {messages.map((message) => (
6935 stevensc 202
        <List.Message message={message} key={message.id} />
6922 stevensc 203
      ))}
6965 stevensc 204
      <span ref={loadMoreEl}>.</span>
205
      {loading && (
206
        <StyledLoader>
207
          <Spinner />
208
        </StyledLoader>
209
      )}
6922 stevensc 210
    </StyledMessageList>
6911 stevensc 211
  )
6935 stevensc 212
}
6911 stevensc 213
 
6935 stevensc 214
// eslint-disable-next-line react/display-name
215
const Message = memo(({ message }) => {
6911 stevensc 216
  const senderName = (message) => {
6968 stevensc 217
    if (message.type === 'group' && !message.u === 1) {
218
      return <span className="user_name">{message.user_name}</span>
219
    }
6911 stevensc 220
  }
221
 
222
  const messagesContent = {
6962 stevensc 223
    text: (
224
      // eslint-disable-next-line no-undef
225
      <p>{parse(emojione.shortnameToImage(message.m || message.message))}</p>
226
    ),
6964 stevensc 227
    image: <img src={message.m || message.filename} alt="chat_image" />,
228
    video: (
229
      <video src={message.m || message.filename} preload="none" controls />
230
    ),
6911 stevensc 231
    document: (
6964 stevensc 232
      <a href={message.m || message.filename} download>
6974 stevensc 233
        <img className="pdf" src="/images/extension/pdf.png" alt="pdf" />
6964 stevensc 234
      </a>
6911 stevensc 235
    ),
236
  }
237
 
238
  return (
6927 stevensc 239
    <>
6968 stevensc 240
      {senderName(message)}
6962 stevensc 241
      <StyledMessage
242
        send={message.u ? message.u === 1 : message.side === 'left'}
243
      >
244
        {messagesContent[message.mtype || message.type]}
6927 stevensc 245
        <label className="time">
6911 stevensc 246
          {!message.not_received && (
247
            <i
248
              className="fa fa-check"
249
              style={message.seen ? { color: 'blue' } : { color: 'gray' }}
250
            />
251
          )}
6962 stevensc 252
          {message.time || message.date}
6911 stevensc 253
        </label>
6927 stevensc 254
      </StyledMessage>
255
    </>
6911 stevensc 256
  )
6935 stevensc 257
}, messageAreEqual)
6911 stevensc 258
 
259
const SubmitForm = ({ sendUrl, uploadUrl, onSubmit: onComplete }) => {
260
  const [showEmojione, setShowEmojione] = useState(false)
261
  const [isShowFileModal, setIsShowFileModal] = useState(false)
262
  const [isSending, setIsSending] = useState(false)
263
  const dispatch = useDispatch()
264
 
265
  const { handleSubmit, setValue, register, reset, getValues } = useForm()
266
 
267
  const onSubmit = handleSubmit(({ message }) => {
268
    const formData = new FormData()
269
    // eslint-disable-next-line no-undef
270
    formData.append('message', emojione.toShort(message))
271
 
272
    axios.post(sendUrl, formData).then((response) => {
273
      const { success, data } = response.data
274
 
275
      if (!success) {
276
        const errorMessage =
277
          typeof data === 'string' ? data : 'Ha ocurrido un error'
278
 
279
        setShowEmojione(false)
280
        dispatch(addNotification({ style: 'danger', msg: errorMessage }))
281
        return
282
      }
283
 
284
      setShowEmojione(false)
285
      onComplete()
286
      reset()
287
    })
288
  })
289
 
290
  const sendFile = (file) => {
291
    setIsSending(true)
292
    const formData = new FormData()
293
    formData.append('file', file)
294
 
295
    axios
296
      .post(uploadUrl, formData)
297
      .then(({ data: response }) => {
298
        const { success, data } = response
299
        if (!success) {
300
          const errorMessage =
301
            typeof data === 'string' ? data : 'Ha ocurrido un error'
302
          dispatch(addNotification({ style: 'success', msg: errorMessage }))
303
          return
304
        }
305
 
306
        toggleFileModal()
307
        onComplete()
308
      })
309
      .finally(() => setIsSending(false))
310
  }
311
 
312
  const toggleEmojione = () => {
313
    setShowEmojione(!showEmojione)
314
  }
315
 
316
  const toggleFileModal = () => {
317
    setIsShowFileModal(!isShowFileModal)
318
  }
319
 
320
  const onClickEmoji = (event) => {
321
    const shortname = event.currentTarget.dataset.shortname
322
    const currentMessage = getValues('message')
323
    // eslint-disable-next-line no-undef
324
    const unicode = emojione.shortnameToUnicode(shortname)
325
    setValue('message', `${currentMessage}${unicode}`)
326
  }
327
 
328
  return (
329
    <>
6937 stevensc 330
      <StyledForm onSubmit={onSubmit}>
6911 stevensc 331
        {showEmojione && <Emojione onClickEmoji={onClickEmoji} />}
332
        <IconButton onClick={toggleFileModal}>
333
          <AttachFileIcon />
334
        </IconButton>
335
        <IconButton onClick={toggleEmojione}>
336
          <InsertEmoticonIcon />
337
        </IconButton>
6937 stevensc 338
        <StyledInput
6911 stevensc 339
          type="text"
340
          name="message"
341
          placeholder="Escribe un mensaje"
342
          ref={register({ required: true })}
343
        />
344
        <IconButton type="submit">
345
          <SendIcon />
346
        </IconButton>
6937 stevensc 347
      </StyledForm>
6911 stevensc 348
      <FileModal
349
        isShow={isShowFileModal}
350
        onHide={toggleFileModal}
351
        onComplete={sendFile}
352
        loading={isSending}
353
      />
354
    </>
355
  )
356
}
357
 
358
Chat.Header = Header
359
Chat.Title = Title
6936 stevensc 360
Chat.List = List
361
List.Message = Message
6911 stevensc 362
Chat.SubmitForm = SubmitForm
363
 
364
export default Chat