Proyectos de Subversion LeadersLinked - Backend

Rev

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

Rev Autor Línea Nro. Línea
15834 stevensc 1
import React, { useEffect, useRef, useState } from "react";
15801 stevensc 2
import { axios } from "../../../utils";
15834 stevensc 3
import { useForm } from "react-hook-form";
11347 nelberth 4
 
5
import Emojione from "./emojione/Emojione";
6
import FileModal from "./fileModal/FileModal";
15851 stevensc 7
import MessagesList from "./messages/MessagesList";
16253 stevensc 8
import ConferenceModal from "./components/ConferenceModal";
16226 stevensc 9
import IconButton from "@mui/material/IconButton";
15834 stevensc 10
import AttachFileIcon from "@mui/icons-material/AttachFile";
11
import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon";
15835 stevensc 12
import SendIcon from "@mui/icons-material/Send";
16226 stevensc 13
import MoreVertIcon from "@mui/icons-material/MoreVert";
11347 nelberth 14
 
15834 stevensc 15
import styles from "./chat.module.scss";
16
 
11347 nelberth 17
const permittedFiles =
18
  "video/mp4, video/mpeg, video/webm, application/pdf, image/jpeg, image/png, image/jpg";
19
 
16288 stevensc 20
const Chat = ({ entity, timezones }) => {
15839 stevensc 21
  const [oldMessages, setOldMessages] = useState([]);
15844 stevensc 22
  const [messages, setMessages] = useState([]);
15839 stevensc 23
  const [totalPages, setTotalPages] = useState(1);
24
  const [currentPage, setCurrentPage] = useState(1);
16004 stevensc 25
 
15839 stevensc 26
  const [loading, setLoading] = useState(false);
16004 stevensc 27
  const [isGettingMessages, setIsGettingMessages] = useState(false);
15839 stevensc 28
 
11347 nelberth 29
  const [showEmojione, setShowEmojione] = useState(false);
30
  const [selectedFile, setSelectedFile] = useState("");
31
 
16006 stevensc 32
  const { handleSubmit } = useForm();
15834 stevensc 33
 
16001 stevensc 34
  const scrollList = useRef(null);
11347 nelberth 35
  const inputTextEl = useRef(null);
36
  const fileInputEl = useRef(null);
15834 stevensc 37
 
38
  const {
39
    url_get_all_messages,
40
    url_send,
41
    url_upload,
42
    url_close,
43
    url_mark_seen,
16253 stevensc 44
    url_zoom,
15834 stevensc 45
    type,
46
  } = entity;
47
 
15844 stevensc 48
  // Get messages
49
  const getMessages = () => {
15839 stevensc 50
    setLoading(true);
51
    axios
52
      .get(url_get_all_messages)
53
      .then(({ data: response }) => {
54
        const { data, success } = response;
55
 
56
        if (!success) {
57
          return console.log("Ha ocurrido un error");
11347 nelberth 58
        }
15839 stevensc 59
 
60
        const messageResponse = [...data.items].reverse();
61
        const updatedMessages = messageResponse.reduce(
62
          (acum, updatedMessage) => {
63
            if (
64
              messages.findIndex(
65
                (message) => message.id === updatedMessage.id
66
              ) === -1
67
            ) {
68
              acum = [...acum, updatedMessage];
69
            }
70
            return acum;
71
          },
72
          []
73
        );
74
 
75
        if (updatedMessages.length > 0) {
15975 stevensc 76
          setMessages((prevMessages) => [...prevMessages, ...updatedMessages]);
15839 stevensc 77
          setTotalPages(data.pages);
16002 stevensc 78
          scrollTo(scrollList);
15839 stevensc 79
        }
80
      })
81
      .finally(() => setLoading(false));
11347 nelberth 82
  };
83
 
15851 stevensc 84
  const onIntersection = (entities) => {
15844 stevensc 85
    const target = entities[0];
15880 stevensc 86
    if (target.isIntersecting && currentPage < totalPages) {
16004 stevensc 87
      setIsGettingMessages(true);
15880 stevensc 88
      setCurrentPage((prevState) => prevState + 1);
16002 stevensc 89
      scrollTo(scrollList, 200);
15844 stevensc 90
    }
91
  };
92
 
15860 stevensc 93
  const getOldMessages = () => {
16004 stevensc 94
    setIsGettingMessages(true);
15844 stevensc 95
    axios
15860 stevensc 96
      .get(`${url_get_all_messages}?page=${currentPage}`)
15844 stevensc 97
      .then(({ data: response }) => {
98
        const { data, success } = response;
99
        if (success && data.page > 1) {
100
          setOldMessages([...data.items.slice().reverse(), ...oldMessages]);
101
        }
102
      })
16004 stevensc 103
      .finally(() => setIsGettingMessages(false));
15844 stevensc 104
  };
105
 
106
  //Utilitys
16001 stevensc 107
  const scrollTo = (element, distance) => {
108
    const divToScrollEl = element.current;
16003 stevensc 109
    const options = {
110
      top: distance,
111
      behavior: "smooth",
112
    };
113
 
16001 stevensc 114
    if (!distance) {
16003 stevensc 115
      divToScrollEl.scrollBy({ ...options, top: divToScrollEl.scrollHeight });
16001 stevensc 116
      return;
117
    }
16003 stevensc 118
    divToScrollEl.scrollBy(options);
11347 nelberth 119
  };
120
 
121
  const onClickEmoji = (event) => {
122
    const shortname = event.currentTarget.dataset.shortname;
15975 stevensc 123
    const currentText = inputTextEl.current.value;
15998 stevensc 124
    const cursorPosition = inputTextEl.current.selectionStart;
15975 stevensc 125
    const textBehind = currentText.substring(0, cursorPosition);
126
    const textForward = currentText.substring(cursorPosition);
15998 stevensc 127
    const unicode = emojione.shortnameToUnicode(shortname);
128
    inputTextEl.current.value = `${textBehind}${unicode}${textForward}`;
11347 nelberth 129
    inputTextEl.current.focus();
15975 stevensc 130
    inputTextEl.current.setSelectionRange(
15998 stevensc 131
      cursorPosition + unicode.length,
132
      cursorPosition + unicode.length
15975 stevensc 133
    );
11347 nelberth 134
  };
135
 
15880 stevensc 136
  const handleUploadFile = ({ target }) => {
137
    const file = target.files[0];
15975 stevensc 138
    if (!file) return;
15880 stevensc 139
    setSelectedFile(file);
11347 nelberth 140
  };
141
 
142
  const removeSelectedFile = () => {
143
    setSelectedFile("");
144
  };
145
 
15844 stevensc 146
  // On send
16001 stevensc 147
  const handleKeyDown = (e) => {
15999 stevensc 148
    if (e.key !== "Enter") return false;
16000 stevensc 149
    e.preventDefault();
15999 stevensc 150
    onHandleSubmit();
151
  };
152
 
15998 stevensc 153
  const onHandleSubmit = () => {
11347 nelberth 154
    const formData = new FormData();
15998 stevensc 155
    formData.append("message", emojione.toShort(inputTextEl.current.value));
156
    axios.post(url_send, formData).then(({ data: response }) => {
157
      const { data, success } = response;
158
      if (!success) {
159
        console.log("Ha ocurrido un error: " + data);
160
        return;
161
      }
162
      inputTextEl.current.value = "";
15943 stevensc 163
      setShowEmojione(false);
16002 stevensc 164
      scrollTo(scrollList);
15943 stevensc 165
    });
11347 nelberth 166
  };
167
 
168
  const handleSendFile = () => {
169
    const formData = new FormData();
170
    formData.append("file", selectedFile);
15880 stevensc 171
    axios.post(url_upload, formData).then(({ data: response }) => {
172
      const { success, data } = response;
173
      if (!success) {
174
        console.log("Ha ocurrido un error: " + data);
175
        return;
11347 nelberth 176
      }
15880 stevensc 177
      setSelectedFile("");
178
      setShowEmojione(false);
16002 stevensc 179
      scrollTo(scrollList);
11347 nelberth 180
    });
181
  };
182
 
15844 stevensc 183
  useEffect(() => {
184
    let timeInterval;
185
    if (loading) return;
186
    timeInterval = setTimeout(() => getMessages(), 2000);
187
 
188
    return () => {
189
      clearTimeout(timeInterval);
190
    };
15880 stevensc 191
  }, [loading]);
15844 stevensc 192
 
193
  useEffect(() => {
15880 stevensc 194
    setMessages([]);
195
    setOldMessages([]);
196
    setTotalPages(1);
197
    setCurrentPage(1);
198
  }, [entity]);
15844 stevensc 199
 
15880 stevensc 200
  useEffect(() => getOldMessages(), [currentPage]);
15844 stevensc 201
 
15880 stevensc 202
  useEffect(() => axios.post(url_mark_seen), []);
203
 
11347 nelberth 204
  return (
205
    <div className={styles.chat}>
16288 stevensc 206
      <Chat.Header
207
        name={entity.name}
208
        conferenceUrl={url_zoom}
209
        timezones={timezones}
210
      />
16001 stevensc 211
      <MessagesList
212
        isLastPage={currentPage >= totalPages}
213
        messages={[...oldMessages, ...messages]}
214
        onIntersection={onIntersection}
215
        scrollRef={scrollList}
16004 stevensc 216
        isLoading={isGettingMessages}
16001 stevensc 217
      />
15805 stevensc 218
      <div className={styles.chat__input__container}>
11347 nelberth 219
        {showEmojione && <Emojione onClickEmoji={onClickEmoji} />}
220
        <form
221
          onSubmit={handleSubmit(onHandleSubmit)}
222
          encType="multipart/form-data"
223
        >
224
          <button
225
            type="button"
15836 stevensc 226
            className={"btn " + styles.icon_btn}
15833 stevensc 227
            onClick={() => fileInputEl.current.click()}
228
          >
229
            <AttachFileIcon />
230
          </button>
11347 nelberth 231
          <button
232
            type="button"
15836 stevensc 233
            className={"btn " + styles.icon_btn}
15833 stevensc 234
            onClick={() => setShowEmojione(!showEmojione)}
235
          >
236
            <InsertEmoticonIcon />
237
          </button>
11347 nelberth 238
          <input
239
            type="file"
15833 stevensc 240
            ref={(e) => (fileInputEl.current = e)}
11347 nelberth 241
            accept={permittedFiles}
15833 stevensc 242
            onChange={handleUploadFile}
11347 nelberth 243
            hidden
244
          />
245
          <textarea
246
            className={styles.chatInput}
247
            placeholder="Escribe un mensaje"
16002 stevensc 248
            onKeyDown={handleKeyDown}
11347 nelberth 249
            ref={inputTextEl}
16002 stevensc 250
            rows="1"
15833 stevensc 251
          />
15836 stevensc 252
          <button type="submit" className={"btn " + styles.send_btn}>
15835 stevensc 253
            <SendIcon />
11347 nelberth 254
          </button>
255
        </form>
256
      </div>
257
      {selectedFile && (
258
        <FileModal
259
          file={selectedFile}
260
          onCancel={removeSelectedFile}
261
          onSend={handleSendFile}
262
        />
263
      )}
264
    </div>
265
  );
266
};
267
 
16288 stevensc 268
const Header = ({ name, conferenceUrl, timezones }) => {
16226 stevensc 269
  const [isShowConferenceModal, setisShowConferenceModal] = useState(false);
270
 
271
  const toggleConferenceModal = () =>
272
    setisShowConferenceModal(!isShowConferenceModal);
273
 
274
  const options = [
275
    { label: "Crear Conferencia", action: toggleConferenceModal },
276
  ];
277
 
278
  return (
16274 stevensc 279
    <div className={styles.chat_header}>
280
      <h2>{name}</h2>
281
      <Header.Options options={options} />
16266 stevensc 282
      <ConferenceModal
16274 stevensc 283
        isShow={isShowConferenceModal}
284
        onClose={toggleConferenceModal}
16279 stevensc 285
        zoomUrl={conferenceUrl}
16288 stevensc 286
        timezones={timezones}
16266 stevensc 287
      />
16274 stevensc 288
    </div>
16226 stevensc 289
  );
290
};
291
 
292
const Options = ({ options }) => {
293
  const [isShowMenu, setIsShowMenu] = useState(false);
294
 
295
  const toggleOptions = () => {
296
    setIsShowMenu(!isShowMenu);
297
  };
298
 
299
  return (
16279 stevensc 300
    <div className="header-options">
16226 stevensc 301
      <IconButton onClick={toggleOptions}>
302
        <MoreVertIcon />
303
      </IconButton>
304
      <div className="position-relative">
305
        <div className={`feed-options ${isShowMenu ? "active" : ""}`}>
306
          <ul>
307
            {options.map((option, index) => (
308
              <li key={index}>
16253 stevensc 309
                <button
310
                  className="btn option-btn"
311
                  onClick={() => {
312
                    toggleOptions();
313
                    option.action();
314
                  }}
315
                >
16226 stevensc 316
                  {option.label}
317
                </button>
318
              </li>
319
            ))}
320
          </ul>
321
        </div>
322
      </div>
323
    </div>
324
  );
325
};
326
 
327
Chat.Header = Header;
328
Header.Options = Options;
329
 
11347 nelberth 330
export default Chat;