vue-wiguet-chatweb 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-wiguet-chatweb",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -15,6 +15,7 @@
15
15
  :canLoadMoreMessages="messagesData.canLoadMoreMessages"
16
16
  @loadMore="getMessages"
17
17
  @retry="retryMessage"
18
+ @on-qualifying="(args) => onQualifying(args)"
18
19
  />
19
20
  </div>
20
21
 
@@ -28,7 +29,11 @@
28
29
  required
29
30
  ref="textAreaRef"
30
31
  @input="() => autoAdjustHeight()"
31
- @keydown.enter.prevent
32
+ @keydown.enter="
33
+ (event) => {
34
+ !isMovil && event.preventDefault();
35
+ }
36
+ "
32
37
  @keyup.enter="saltoDeLineaOEnviar"
33
38
  />
34
39
 
@@ -43,7 +48,15 @@
43
48
  </template>
44
49
 
45
50
  <script setup lang="ts">
46
- import { ref, onMounted, nextTick, PropType, watch, onUnmounted } from "vue";
51
+ import {
52
+ ref,
53
+ onMounted,
54
+ nextTick,
55
+ PropType,
56
+ watch,
57
+ onUnmounted,
58
+ computed,
59
+ } from "vue";
47
60
  import { v4 as uuidv4 } from "uuid";
48
61
 
49
62
  import {
@@ -58,6 +71,7 @@ import {
58
71
  getMessagesApi,
59
72
  sendMessageApi,
60
73
  setVistoToTrueApi,
74
+ updateMessageApi,
61
75
  } from "../store/index";
62
76
  import { getInformationApi } from "../store";
63
77
  import MessageList from "./MessageList.vue";
@@ -84,6 +98,7 @@ const emit = defineEmits([
84
98
  "new-message",
85
99
  "clear-new-messages",
86
100
  "not-viewed-total",
101
+ "onQualifying",
87
102
  ]);
88
103
 
89
104
  const props = defineProps({
@@ -109,8 +124,8 @@ const messageContainerRef = ref<HTMLElement | null>(null);
109
124
  watch(
110
125
  () => props.visible,
111
126
  async (current) => {
112
- if(!current) return
113
-
127
+ if (!current) return;
128
+
114
129
  emit("clear-new-messages");
115
130
  scrollToBottom();
116
131
 
@@ -122,18 +137,23 @@ watch(
122
137
 
123
138
  const resp = await getInformationApi(props.tokenAuth);
124
139
 
125
- if(resp) {
140
+ if (resp) {
126
141
  appChatId.value = resp.appChat.id;
127
142
  }
128
143
  }
129
144
  );
130
145
 
131
146
  function saltoDeLineaOEnviar(event: KeyboardEvent) {
147
+ if(isMovil.value) {
148
+ autoAdjustHeight();
149
+ return;
150
+ }
151
+
132
152
  if (event.key === "Enter" && event.shiftKey) {
133
- const val = (event.target as any)?.value || '';
153
+ const val = (event.target as any)?.value || "";
134
154
  (event.target as any).value = val + "\n";
135
- autoAdjustHeight()
136
- return
155
+ autoAdjustHeight();
156
+ return;
137
157
  }
138
158
 
139
159
  submitMessage(event);
@@ -149,7 +169,7 @@ const submitMessage = async (event: Event) => {
149
169
  });
150
170
  return;
151
171
  }
152
-
172
+
153
173
  if (!message.value.trim()) {
154
174
  emit("show-toast", {
155
175
  severity: "warn",
@@ -176,11 +196,11 @@ const submitMessage = async (event: Event) => {
176
196
  msPersonaId: props.user.msPersonaId,
177
197
  },
178
198
  };
179
-
199
+
180
200
  const idx = messagesData.value.data.push(newMessage) - 1;
181
-
182
- sendApi(message.value, appChatId.value).then((newMsg)=>{
183
- if(!newMsg) {
201
+
202
+ sendApi(message.value, appChatId.value).then((newMsg) => {
203
+ if (!newMsg) {
184
204
  messagesData.value.data[idx].error = {
185
205
  error: true,
186
206
  id: newMessage.id,
@@ -194,13 +214,13 @@ const submitMessage = async (event: Event) => {
194
214
  });
195
215
  } else {
196
216
  messagesData.value.data[idx] = newMsg;
197
-
198
- const message = {...newMsg, chatId:newMessage.chatId }
217
+
218
+ const message = { ...newMsg, chatId: newMessage.chatId };
199
219
  socketService.value?.emit(
200
- 'sendMessage',
220
+ "sendMessage",
201
221
  { roomId: information?.value?.appChat.id, message },
202
222
  (response: any) => {
203
- console.log('🚀 ~ socketService.value.emit ~ response:', response);
223
+ console.log("🚀 ~ socketService.value.emit ~ response:", response);
204
224
  }
205
225
  );
206
226
  }
@@ -208,7 +228,7 @@ const submitMessage = async (event: Event) => {
208
228
 
209
229
  message.value = "";
210
230
  scrollToBottom();
211
- textAreaRef.value && (textAreaRef.value.style.height = "20px")
231
+ textAreaRef.value && (textAreaRef.value.style.height = "20px");
212
232
  };
213
233
 
214
234
  const sendApi = async (message: string, appChatId: string) => {
@@ -221,37 +241,35 @@ const sendApi = async (message: string, appChatId: string) => {
221
241
  };
222
242
 
223
243
  const getMessages = async () => {
224
-
225
244
  const lastMessagesId = messagesData.value.data[0]?.id;
226
245
  const body: ListMessageBody = {
227
246
  lastMessagesId,
228
247
  appChatId: appChatId.value,
229
- limit: 10
248
+ limit: 10,
230
249
  };
231
250
 
232
251
  isLoading.value = true;
233
252
  const resp = await getMessagesApi({ body, token: props.tokenAuth });
234
253
  isLoading.value = false;
235
-
254
+
236
255
  messagesData.value.data.unshift(
237
256
  ...resp.data.sort((a, b) => -b.createdAt.localeCompare(a.createdAt))
238
257
  );
239
-
258
+
240
259
  messagesData.value.canLoadMoreMessages =
241
260
  resp.pagination.total > resp.pagination.size;
242
-
261
+
243
262
  if (lastMessagesId && messageContainerRef.value?.scrollHeight) {
244
263
  mantainElementsOnViewport(messageContainerRef.value?.scrollHeight);
245
264
  }
246
-
265
+
247
266
  if (!lastMessagesId) scrollToBottom();
248
267
  };
249
268
 
250
269
  const retryMessage = async (message: Message) => {
251
270
  emit("show-confirm", async () => {
252
-
253
271
  if (!message.error?.id) return;
254
-
272
+
255
273
  const msg = await sendApi(message.message, appChatId.value);
256
274
 
257
275
  if (!msg) {
@@ -267,13 +285,13 @@ const retryMessage = async (message: Message) => {
267
285
  "id",
268
286
  message.error.id
269
287
  );
270
-
288
+
271
289
  messagesData.value.data[idx] = { ...msg, error: undefined };
272
290
 
273
- socketService.value?.emit(
274
- 'sendMessage',
275
- { roomId: information?.value?.appChat.id, message },
276
- );
291
+ socketService.value?.emit("sendMessage", {
292
+ roomId: information?.value?.appChat.id,
293
+ message,
294
+ });
277
295
  }
278
296
 
279
297
  scrollToBottom();
@@ -305,23 +323,28 @@ const fontSpace = 14;
305
323
  function autoAdjustHeight() {
306
324
  if (!textAreaRef.value) return;
307
325
 
308
- textAreaRef.value.style.height = textAreaRef.value.scrollHeight - fontSpace + 'px'
326
+ textAreaRef.value.style.height =
327
+ textAreaRef.value.scrollHeight - fontSpace + "px";
328
+
329
+ if (textAreaRef.value.scrollHeight === textAreaRef.value.clientHeight)
330
+ return;
309
331
 
310
- if(textAreaRef.value.scrollHeight === textAreaRef.value.clientHeight) return;
311
-
312
- textAreaRef.value.style.height = textAreaRef.value.scrollHeight + "px"
332
+ textAreaRef.value.style.height = textAreaRef.value.scrollHeight + "px";
313
333
  }
314
334
 
315
335
  const socketService = ref<Socket>();
316
336
  const information = ref<ChatInformation>();
317
337
 
318
- function connectMsWebSocket (userChat: ChatInformation ,app: APP_TYPE = APP_TYPE.WEBCHAT) {
319
- if (!userChat) throw new Error('user chat is required')
338
+ function connectMsWebSocket(
339
+ userChat: ChatInformation,
340
+ app: APP_TYPE = APP_TYPE.WEBCHAT
341
+ ) {
342
+ if (!userChat) throw new Error("user chat is required");
320
343
 
321
344
  socketService.value = io(window.VITE_SOCKET_URI, {
322
345
  query: {
323
346
  // TODO: confirmar si se quita o no
324
- usuarioId: `${userChat?.chat?.persona?.funcionarioId}`,
347
+ usuarioId: `${userChat?.chat?.persona?.funcionarioId}`,
325
348
  aplicacion: app,
326
349
  },
327
350
  extraHeaders: { Authorization: props.tokenAuth },
@@ -329,24 +352,24 @@ function connectMsWebSocket (userChat: ChatInformation ,app: APP_TYPE = APP_TYPE
329
352
 
330
353
  socketService.value.removeAllListeners();
331
354
 
332
- socketService.value.on('connect', () => {
333
- console.log('Conectado al servidor de sockets');
355
+ socketService.value.on("connect", () => {
356
+ console.log("Conectado al servidor de sockets");
334
357
 
335
- socketService.value?.emit(
336
- 'joinRoom',
337
- `${userChat?.appChat?.id}`
338
- );
339
- })
358
+ socketService.value?.emit("joinRoom", `${userChat?.appChat?.id}`);
359
+ });
340
360
 
341
- socketService.value.on('disconnect', () => {
342
- console.log('Desconectado del servidor de sockets');
361
+ socketService.value.on("disconnect", () => {
362
+ console.log("Desconectado del servidor de sockets");
343
363
  });
344
364
 
345
- socketService.value.on('receiveMessage', (data: any) => {
346
- console.log('Mensaje recibido:', data.message, userChat);
347
-
348
- if (data.message.sender.msPersonaId === userChat?.chat?.persona?.msPersonaId) return
349
-
365
+ socketService.value.on("receiveMessage", (data: any) => {
366
+ console.log("Mensaje recibido:", data.message, userChat);
367
+
368
+ if (
369
+ data.message.sender.msPersonaId === userChat?.chat?.persona?.msPersonaId
370
+ )
371
+ return;
372
+
350
373
  messagesData.value.data.push(data.message);
351
374
  setVistoToTrueApi(data.message.appChatId, props.tokenAuth);
352
375
  scrollToBottom();
@@ -354,17 +377,92 @@ function connectMsWebSocket (userChat: ChatInformation ,app: APP_TYPE = APP_TYPE
354
377
  });
355
378
  }
356
379
 
380
+ const isMovil = computed(() => {
381
+ return [OS.ANDROID, OS.IOS].includes(getOS() as OS);
382
+ });
383
+
384
+ const enum OS {
385
+ ANDROID = "Android",
386
+ IOS = "iPhone",
387
+ }
388
+
389
+ function getOS() {
390
+ const userAgent = window.navigator.userAgent;
391
+ const platform = (window.navigator as any)?.userAgentData?.platform || window.navigator.platform;
392
+ const macosPlatforms = [
393
+ "macOS",
394
+ "Macintosh",
395
+ "MacIntel",
396
+ "MacPPC",
397
+ "Mac68K",
398
+ ];
399
+ const windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"];
400
+ const iosPlatforms = ["iPhone", "iPad", "iPod"];
401
+
402
+ let os = null;
403
+
404
+ if (macosPlatforms.indexOf(platform) !== -1) {
405
+ os = "Mac OS";
406
+ } else if (iosPlatforms.indexOf(platform) !== -1) {
407
+ os = OS.IOS;
408
+ } else if (windowsPlatforms.indexOf(platform) !== -1) {
409
+ os = "Windows";
410
+ } else if (/Android/.test(userAgent)) {
411
+ os = OS.ANDROID;
412
+ } else if (/Linux/.test(platform)) {
413
+ os = "Linux";
414
+ }
415
+
416
+ return os;
417
+ }
418
+
419
+ function onQualifying({ message, emoji }: { message: Message, emoji: {icon: string, value: number } }) {
420
+ const callback = async () => {
421
+ if (!message.id || !emoji) return;
422
+
423
+ const idx = searchFromLast<Message>(
424
+ messagesData.value.data,
425
+ "id",
426
+ message.id
427
+ );
428
+
429
+ let prevMessage = messagesData.value?.data?.[idx].message
430
+ messagesData.value?.data?.[idx] && (messagesData.value.data[idx].message = emoji.icon);
431
+
432
+ const msg = await updateMessageApi(message.id, { message: emoji.icon, tipoCalificacionId: emoji.value }, props.tokenAuth);
433
+
434
+ if (!msg) {
435
+ messagesData.value?.data?.[idx] && (messagesData.value.data[idx].message = prevMessage);
436
+ emit("show-toast", {
437
+ severity: "error",
438
+ summary: "Error",
439
+ detail: "Ocurrio un error al enviar el mensaje, intente nuevamente",
440
+ life: 5000,
441
+ });
442
+ return
443
+ }
444
+
445
+ socketService.value?.emit("sendMessage", {
446
+ roomId: information?.value?.appChat.id,
447
+ message,
448
+ });
449
+ }
450
+
451
+ emit('onQualifying', callback)
452
+ };
453
+
454
+
357
455
  onMounted(async () => {
358
456
  if (appChatId.value) return;
359
-
457
+
360
458
  const resp = await getInformationApi(props.tokenAuth);
361
-
362
- if(!resp) return;
363
-
459
+
460
+ if (!resp) return;
461
+
364
462
  information.value = resp;
365
463
  appChatId.value = resp.appChat.id;
366
464
  notViewed.value = resp.appChat.totalNoVistosCliente;
367
- connectMsWebSocket(resp)
465
+ connectMsWebSocket(resp);
368
466
  getMessages();
369
467
  emit("not-viewed-total", resp.appChat.totalNoVistosCliente);
370
468
  });
@@ -26,7 +26,25 @@
26
26
  <div class="chat-message">
27
27
  <div class="bubble" :class="message.esCliente ? 'right' : 'left'">
28
28
  <div :class="message.esCliente ? 'content-right' : 'content-left'">
29
- <div class="message-text" style="white-space: pre-line" v-html="convertUrlsToLinks(message.message)"></div>
29
+ <div v-if="message.message === '😊😄🙂😐🙁'" class="flex gap-2" >
30
+ <div>
31
+ <strong style="display: block; margin-bottom: 0.5rem;">Ayúdanos a mejorar nuestro servicio.</strong>
32
+ <span>Que le pareció la atención:</span>
33
+ </div>
34
+ <a
35
+ v-for="emoji in emojis"
36
+ href="javascript:"
37
+ class="btn-icon"
38
+ :key="emoji.value"
39
+ @click="emit('onQualifying', { message, emoji })"
40
+ >
41
+ <div class="flex flex-col items-center">
42
+ <div class="icon">{{ emoji.icon }}</div>
43
+ <span>{{ emoji.label }}</span>
44
+ </div>
45
+ </a>
46
+ </div>
47
+ <div v-else class="message-text" style="white-space: pre-line" v-html="textToRichFormat(message.message)"></div>
30
48
  <div class="detail-message flex justify-content-between">
31
49
  <span class="mr-5" v-if="message.sender?.nombreCompleto">
32
50
  {{
@@ -53,13 +71,13 @@
53
71
  </template>
54
72
 
55
73
  <script setup lang="ts">
56
- import { PropType, onBeforeMount, ref, watch } from "vue";
74
+ import { PropType, onBeforeMount, ref, watch, h } from 'vue';
57
75
  import { useIntersectionObserver } from "@vueuse/core";
58
76
  import { type Message } from "../dto/app.dto";
59
77
  import { DateTime } from "luxon";
60
78
  import DangerIcon from "./DangerIcon.vue";
61
79
 
62
- const emit = defineEmits(["loadMore", "retry"]);
80
+ const emit = defineEmits(["loadMore", "retry", "onQualifying"]);
63
81
  const props = defineProps({
64
82
  messages: {
65
83
  type: Array as PropType<Message[]>,
@@ -98,15 +116,51 @@ watch(
98
116
  }
99
117
  );
100
118
 
101
- function convertUrlsToLinks(content:string) {
102
- // Expresión regular para detectar URLs
103
- const urlRegex = /(https?:\/\/[^\s]+)/g;
119
+ type RichTextType = {
120
+ regex: RegExp;
121
+ tag: string;
122
+ };
123
+
124
+ const richText: { [key in string]: RichTextType } = {
125
+ BOLD: { regex: /\*(.+?)\*(?!\*)/g, tag: '<strong--custom-tag>{val}</strong--custom-tag>' },
126
+ ITALIC: { regex: /_(.+?)_/, tag: '<i--custom-tag>{val}</i--custom-tag>' },
127
+ CROSSED_OUT: { regex: /~(.+?)~(?!~)/g, tag: '<del--custom-tag>{val}</del--custom-tag>' },
128
+ URL: {
129
+ regex: /(http?s?:\/\/[^\s]+)/g,
130
+ tag: '<a--custom-tag href="{val}" target="_blank">{val}</a--custom-tag>',
131
+ },
132
+ };
133
+
134
+ function textToRichFormat(text: string) {
135
+ const richTextValues = Object.values(richText);
136
+
137
+ if (!richTextValues || (Array.isArray(richTextValues) && richTextValues.length === 0))
138
+ return text;
139
+
140
+ const newMessage = () => {
141
+
142
+ return richTextValues.reduce((textPrev, rtv) => {
143
+ return textPrev.replace(rtv.regex, (middleValueAssert, middleValue) => {
144
+ const regexVerifyExistTag = /--custom-tag/;
104
145
 
105
- // Reemplazar las URLs con etiquetas <a>
106
- return content.replace(urlRegex, (url) => {
107
- return `<a href="${url}" target="_blank">${url}</a>`;
108
- });
146
+ if (regexVerifyExistTag.test(middleValueAssert)) return middleValueAssert;
147
+
148
+ return rtv.tag.replace(/{val}/g, middleValue);
149
+ });
150
+ }, text);
151
+ };
152
+
153
+ return newMessage().replace(/--custom-tag/g, '');
109
154
  }
155
+
156
+ const emojis = [
157
+ { label: 'Excelente', value: 1, icon: '😊' },
158
+ { label: 'Buena', value: 2, icon: '😄' },
159
+ { label: 'Aceptable', value: 3, icon: '🙂' },
160
+ { label: 'Mala', value: 4, icon: '😐' },
161
+ { label: 'Muy Mala', value: 5, icon: '🙁' },
162
+ ]
163
+
110
164
  </script>
111
165
 
112
166
  <style scoped>
@@ -232,4 +286,32 @@ function convertUrlsToLinks(content:string) {
232
286
  .messages-container-list {
233
287
  width: 100%;
234
288
  }
289
+
290
+ .btn-icon {
291
+ text-decoration: none;
292
+ border: none;
293
+ }
294
+ .btn-icon span {
295
+ color: currentColor;
296
+ text-wrap: nowrap;
297
+ }
298
+
299
+ .btn-icon .icon {
300
+ font-size: 2rem;
301
+ }
302
+
303
+ .flex {
304
+ display: flex;
305
+ flex-wrap: wrap;
306
+ }
307
+ .flex-col {
308
+ flex-direction: column;
309
+ }
310
+ .items-center {
311
+ align-items: center;
312
+ }
313
+ .gap-2 {
314
+ gap: 0.5rem;
315
+ }
316
+
235
317
  </style>
@@ -50,6 +50,7 @@
50
50
  @clear-new-messages="newMessages = 0"
51
51
  @new-message="() => newMessages++"
52
52
  @not-viewed-total="(val) => (newMessages = val)"
53
+ @on-qualifying="(args)=> emit('onQualifying', args)"
53
54
  />
54
55
  </div>
55
56
  </div>
@@ -63,7 +64,7 @@ import IconTelegram from "./IconTelegram.vue";
63
64
  import IconWhatsApp from "./IconWhatsApp.vue";
64
65
  import IconChat from "./IconChat.vue";
65
66
 
66
- const emit = defineEmits(["show-toast", "show-confirm"]);
67
+ const emit = defineEmits(["show-toast", "show-confirm", "onQualifying"]);
67
68
 
68
69
  const enum MeansCommunication{
69
70
  WHATSAPP,