Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

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