vue-wiguet-chatweb 0.1.28 → 0.1.29
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/README.md +68 -68
- package/dist/components/Chat.vue.d.ts +2 -5
- package/dist/components/DangerIcon.vue.d.ts +1 -1
- package/dist/components/IconAttach.vue.d.ts +1 -1
- package/dist/components/IconChat.vue.d.ts +1 -1
- package/dist/components/IconClose.vue.d.ts +1 -1
- package/dist/components/IconSend.vue.d.ts +1 -1
- package/dist/components/IconTelegram.vue.d.ts +1 -1
- package/dist/components/IconWhatsApp.vue.d.ts +1 -1
- package/dist/components/Loader.vue.d.ts +1 -1
- package/dist/components/MessageList.vue.d.ts +2 -3
- package/dist/components/ODialog/IPropsDialog.d.ts +1 -0
- package/dist/components/ODialog/ODialog.vue.d.ts +15 -13
- package/dist/components/Widget.vue.d.ts +2 -1
- package/dist/dto/app.dto.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/store/config.d.ts +1 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/style.css +1 -1
- package/dist/vue-wiguet-chatweb.js +1769 -1829
- package/dist/vue-wiguet-chatweb.umd.cjs +26 -26
- package/package.json +66 -66
- package/src/assets/emojis/AngryIcon.svg +4 -4
- package/src/assets/emojis/HappiestIcon.svg +4 -4
- package/src/assets/emojis/HappyIcon.svg +4 -4
- package/src/assets/emojis/NeutralIcon.svg +4 -4
- package/src/assets/emojis/SadIcon.svg +4 -4
- package/src/components/Chat.vue +691 -691
- package/src/components/ChatMessage.vue +102 -102
- package/src/components/DangerIcon.vue +12 -12
- package/src/components/IconAttach.vue +24 -24
- package/src/components/IconChat.vue +23 -23
- package/src/components/IconClose.vue +5 -5
- package/src/components/IconSend.vue +8 -8
- package/src/components/IconTelegram.vue +28 -28
- package/src/components/IconWhatsApp.vue +39 -39
- package/src/components/IconWidget.vue +45 -45
- package/src/components/Loader.vue +31 -31
- package/src/components/LoadingComponent.vue +111 -111
- package/src/components/MessageList.vue +357 -357
- package/src/components/ODialog/IPropsDialog.ts +4 -4
- package/src/components/ODialog/IPropsSidebar.ts +13 -13
- package/src/components/ODialog/ODialog.vue +106 -84
- package/src/components/Widget.vue +187 -205
- package/src/components/__tests__/Chat.spec.ts +5 -5
- package/src/components/__tests__/ChatMessage.spec.ts +5 -5
- package/src/components/__tests__/MessageList.spec.ts +47 -47
- package/dist/App.vue.d.ts +0 -2
- package/dist/components/ChatMessage.vue.d.ts +0 -12
package/src/components/Chat.vue
CHANGED
@@ -1,691 +1,691 @@
|
|
1
|
-
<template>
|
2
|
-
<div class="widget">
|
3
|
-
<div class="header-widget">
|
4
|
-
<h4 class="title-chat">{{ titlePrincipal }}</h4>
|
5
|
-
<button @click="() => toggleChat()" class="btn-close">
|
6
|
-
<IconClose class="pointer" />
|
7
|
-
</button>
|
8
|
-
</div>
|
9
|
-
<div class="messages-container" ref="messageContainerRef">
|
10
|
-
<div class="loader" v-if="isLoading">
|
11
|
-
<Loader />
|
12
|
-
</div>
|
13
|
-
<MessageList
|
14
|
-
v-if="messagesData.data.length > 0"
|
15
|
-
:messages="messagesData.data"
|
16
|
-
:canLoadMoreMessages="messagesData.canLoadMoreMessages"
|
17
|
-
@loadMore="getMessages"
|
18
|
-
@retry="retryMessage"
|
19
|
-
@on-qualifying="(args) => onQualifying(args)"
|
20
|
-
@see="(message: Message) => {
|
21
|
-
currentDialogView = DIALOG_VIEWS.SEE
|
22
|
-
urlFileMessage = message.urlFile
|
23
|
-
dialog.title = 'Imagen'
|
24
|
-
dialog.modelValue = true
|
25
|
-
}"
|
26
|
-
/>
|
27
|
-
<div class="fit" v-else>
|
28
|
-
<span class="center">No tienes mensajes</span>
|
29
|
-
</div>
|
30
|
-
</div>
|
31
|
-
|
32
|
-
<div class="w-full">
|
33
|
-
<form v-if="!isDisabledBoxMessage" class="message-send" @submit.prevent="(event) => submitMessage()">
|
34
|
-
<div class="form-message">
|
35
|
-
<div class="jl-inputgroup-chat">
|
36
|
-
<textarea
|
37
|
-
v-model="message"
|
38
|
-
class="jl2-input-chat"
|
39
|
-
placeholder="Escribe tu mensaje"
|
40
|
-
required
|
41
|
-
ref="textAreaRef"
|
42
|
-
@input="() => autoAdjustHeight()"
|
43
|
-
@keydown.enter="
|
44
|
-
(event) => {
|
45
|
-
!isMobile && event.preventDefault();
|
46
|
-
}
|
47
|
-
"
|
48
|
-
@keyup.enter="saltoDeLineaOEnviar"
|
49
|
-
/>
|
50
|
-
|
51
|
-
<input
|
52
|
-
type="file"
|
53
|
-
ref="fileInputRef"
|
54
|
-
@change="onFileSelect"
|
55
|
-
accept="image/*"
|
56
|
-
style="display: none;"
|
57
|
-
key="fileInputKey"
|
58
|
-
|
59
|
-
/>
|
60
|
-
<button
|
61
|
-
type="button"
|
62
|
-
class="pointer btn-primary"
|
63
|
-
title="Adjuntar archivo"
|
64
|
-
@click="
|
65
|
-
() => {
|
66
|
-
fileInputRef.value = '';
|
67
|
-
fileInputKey++;
|
68
|
-
fileInputRef?.click();
|
69
|
-
}
|
70
|
-
"
|
71
|
-
>
|
72
|
-
<IconAttach style="width: 20px; height: 20px" />
|
73
|
-
</button>
|
74
|
-
|
75
|
-
<button type="submit" class="pointer btn-primary">
|
76
|
-
<IconSend style="width: 20px; height: 20px" />
|
77
|
-
</button>
|
78
|
-
</div>
|
79
|
-
</div>
|
80
|
-
</form>
|
81
|
-
<span class="message-send-block" v-else>
|
82
|
-
Necesitamos que nos califique la atención para continuar
|
83
|
-
</span>
|
84
|
-
</div>
|
85
|
-
</div>
|
86
|
-
|
87
|
-
<ODialog v-bind="dialog">
|
88
|
-
<div v-if="currentDialogView === DIALOG_VIEWS.UPLOAD" class="flex flex-col gap-3 justify-center items-center">
|
89
|
-
<img v-for="(urlFile, i) in urlFiles" :key="i" :src="urlFile.toString()" alt="Image" width="400" />
|
90
|
-
|
91
|
-
<form
|
92
|
-
class="message-send"
|
93
|
-
@submit.prevent="
|
94
|
-
(event) => {
|
95
|
-
submitMessage();
|
96
|
-
dialog.modelValue = false;
|
97
|
-
}
|
98
|
-
"
|
99
|
-
>
|
100
|
-
<div class="form-message">
|
101
|
-
<div class="jl-inputgroup-chat">
|
102
|
-
<textarea
|
103
|
-
v-model="message"
|
104
|
-
autofocus
|
105
|
-
class="jl2-input-chat"
|
106
|
-
placeholder="Escribe tu mensaje"
|
107
|
-
ref="textAreaRef"
|
108
|
-
@input="() => autoAdjustHeight()"
|
109
|
-
@keydown.enter="
|
110
|
-
(event) => {
|
111
|
-
!isMobile && event.preventDefault();
|
112
|
-
}
|
113
|
-
"
|
114
|
-
@keyup.enter="(event)=>{
|
115
|
-
saltoDeLineaOEnviar(event, MESSAGE_TYPE_CODES.IMAGEN)
|
116
|
-
dialog.modelValue = false;
|
117
|
-
}"
|
118
|
-
/>
|
119
|
-
|
120
|
-
<button type="submit" class="pointer btn-primary">
|
121
|
-
<IconSend style="width: 20px; height: 20px" />
|
122
|
-
</button>
|
123
|
-
</div>
|
124
|
-
</div>
|
125
|
-
</form>
|
126
|
-
</div>
|
127
|
-
<div v-else>
|
128
|
-
<img v-if="urlFileMessage" :src="urlFileMessage" alt="Image" style="width: 55vw;" />
|
129
|
-
</div>
|
130
|
-
</ODialog>
|
131
|
-
</template>
|
132
|
-
|
133
|
-
<script setup lang="ts">
|
134
|
-
import {
|
135
|
-
ref,
|
136
|
-
onMounted,
|
137
|
-
nextTick,
|
138
|
-
PropType,
|
139
|
-
watch,
|
140
|
-
onUnmounted,
|
141
|
-
reactive,
|
142
|
-
} from "vue";
|
143
|
-
import { v4 as uuidv4 } from "uuid";
|
144
|
-
|
145
|
-
import {
|
146
|
-
type SendMessageBody,
|
147
|
-
ChatInformation,
|
148
|
-
ListMessageBody,
|
149
|
-
Message,
|
150
|
-
} from "../dto/app.dto";
|
151
|
-
import IconClose from "./IconClose.vue";
|
152
|
-
import IconSend from "./IconSend.vue";
|
153
|
-
import {
|
154
|
-
getMessagesApi,
|
155
|
-
sendMessageApi,
|
156
|
-
setVistoToTrueApi,
|
157
|
-
updateMessageApi,
|
158
|
-
} from "../store/index";
|
159
|
-
import { getInformationApi } from "../store";
|
160
|
-
import MessageList from "./MessageList.vue";
|
161
|
-
import Loader from "./Loader.vue";
|
162
|
-
import { searchFromLast } from "../resources/functions.helpers";
|
163
|
-
import { io, Socket } from "socket.io-client";
|
164
|
-
import { APP_TYPE } from "../dto/chat.dto";
|
165
|
-
import { MESSAGE_TYPE_CODES, TypeMessageTypeCodes } from "../resources/constants/message-type.constant";
|
166
|
-
import { useMobile } from "../hooks/useMobile";
|
167
|
-
import IconAttach from "./IconAttach.vue";
|
168
|
-
import ODialog from "./ODialog/ODialog.vue";
|
169
|
-
import { IPropsDialog } from "./ODialog/IPropsDialog";
|
170
|
-
|
171
|
-
const enum DIALOG_VIEWS {
|
172
|
-
UPLOAD,
|
173
|
-
SEE
|
174
|
-
}
|
175
|
-
|
176
|
-
//DATA
|
177
|
-
const message = ref("");
|
178
|
-
const notViewed = ref(0);
|
179
|
-
const fileInputRef = ref();
|
180
|
-
const fileInputKey = ref(0);
|
181
|
-
const currentDialogView = ref(DIALOG_VIEWS.SEE)
|
182
|
-
const urlFileMessage = ref<string>()
|
183
|
-
|
184
|
-
const messagesData = ref<{ data: Message[]; canLoadMoreMessages: boolean }>({
|
185
|
-
data: [],
|
186
|
-
canLoadMoreMessages: false,
|
187
|
-
});
|
188
|
-
|
189
|
-
const appChatId = ref("");
|
190
|
-
const isLoading = ref(false);
|
191
|
-
|
192
|
-
const emit = defineEmits([
|
193
|
-
"show-toast",
|
194
|
-
"show-confirm",
|
195
|
-
"new-message",
|
196
|
-
"clear-new-messages",
|
197
|
-
"not-viewed-total",
|
198
|
-
"onQualifying",
|
199
|
-
]);
|
200
|
-
|
201
|
-
const props = defineProps({
|
202
|
-
titlePrincipal: {
|
203
|
-
type: String,
|
204
|
-
default: "Comunicación en linea para consultas",
|
205
|
-
},
|
206
|
-
toggleChat: { type: Function, required: true },
|
207
|
-
tokenAuth: { type: String, required: true },
|
208
|
-
user: {
|
209
|
-
type: Object as PropType<{
|
210
|
-
nombreCompleto: string;
|
211
|
-
ci: string;
|
212
|
-
msPersonaId: number;
|
213
|
-
}>,
|
214
|
-
required: true,
|
215
|
-
},
|
216
|
-
visible: { type: Boolean, required: true },
|
217
|
-
});
|
218
|
-
|
219
|
-
const messageContainerRef = ref<HTMLElement | null>(null);
|
220
|
-
|
221
|
-
watch(
|
222
|
-
() => props.visible,
|
223
|
-
async (current) => {
|
224
|
-
if (!current) return;
|
225
|
-
|
226
|
-
emit("clear-new-messages");
|
227
|
-
scrollToBottom();
|
228
|
-
|
229
|
-
if (notViewed.value > 0) {
|
230
|
-
setVistoToTrueApi(appChatId.value, props.tokenAuth);
|
231
|
-
}
|
232
|
-
|
233
|
-
if (appChatId.value) return;
|
234
|
-
|
235
|
-
const resp = await getInformationApi(props.tokenAuth);
|
236
|
-
|
237
|
-
if (resp) {
|
238
|
-
appChatId.value = resp.appChat.id;
|
239
|
-
}
|
240
|
-
}
|
241
|
-
);
|
242
|
-
|
243
|
-
function saltoDeLineaOEnviar(event: KeyboardEvent, messageTypeCode?: TypeMessageTypeCodes) {
|
244
|
-
if(isMobile.value) {
|
245
|
-
autoAdjustHeight();
|
246
|
-
return;
|
247
|
-
}
|
248
|
-
|
249
|
-
if (event.key === "Enter" && event.shiftKey) {
|
250
|
-
const val = (event.target as any)?.value || "";
|
251
|
-
(event.target as any).value = val + "\n";
|
252
|
-
autoAdjustHeight();
|
253
|
-
return;
|
254
|
-
}
|
255
|
-
|
256
|
-
submitMessage({ codigoTipoMensaje: messageTypeCode });
|
257
|
-
}
|
258
|
-
|
259
|
-
function createMessage(message: string, codigoTipoMensaje?: TypeMessageTypeCodes) {
|
260
|
-
if (message?.length > 300) {
|
261
|
-
emit("show-toast", {
|
262
|
-
severity: "warn",
|
263
|
-
summary: "Error",
|
264
|
-
detail: "El mensaje no puede superar los 300 caracteres",
|
265
|
-
life: 5000,
|
266
|
-
});
|
267
|
-
return;
|
268
|
-
}
|
269
|
-
|
270
|
-
if (!message.trim() && codigoTipoMensaje !== MESSAGE_TYPE_CODES.IMAGEN) {
|
271
|
-
emit("show-toast", {
|
272
|
-
severity: "warn",
|
273
|
-
summary: "Error",
|
274
|
-
detail: "Por favor ingrese un mensaje",
|
275
|
-
life: 5000,
|
276
|
-
});
|
277
|
-
return;
|
278
|
-
}
|
279
|
-
|
280
|
-
const messageType = codigoTipoMensaje
|
281
|
-
? {
|
282
|
-
code: codigoTipoMensaje,
|
283
|
-
}
|
284
|
-
: undefined;
|
285
|
-
|
286
|
-
const newMessage: Message = {
|
287
|
-
id: uuidv4(),
|
288
|
-
chatId: information.value?.chat?.id,
|
289
|
-
message,
|
290
|
-
visto: true,
|
291
|
-
multimedia: false,
|
292
|
-
esCliente: true,
|
293
|
-
appChatId: appChatId.value,
|
294
|
-
createdAt: new Date().toISOString(),
|
295
|
-
updatedAt: new Date().toISOString(),
|
296
|
-
file: inputFiles.value?.[0],
|
297
|
-
urlFile: urlFiles.value?.[0].toString(),
|
298
|
-
messageType,
|
299
|
-
sender: {
|
300
|
-
nombreCompleto: props.user.nombreCompleto,
|
301
|
-
ci: props.user.ci,
|
302
|
-
msPersonaId: props.user.msPersonaId,
|
303
|
-
},
|
304
|
-
response: undefined
|
305
|
-
};
|
306
|
-
|
307
|
-
return newMessage
|
308
|
-
}
|
309
|
-
|
310
|
-
function updateMessageStatus(
|
311
|
-
newMessage: Message,
|
312
|
-
idxMessageToCommunicate?: number,
|
313
|
-
messageSaved?: Message,
|
314
|
-
tipoCalificacionId?: number
|
315
|
-
) {
|
316
|
-
if (idxMessageToCommunicate == null) throw new Error('idx is required')
|
317
|
-
|
318
|
-
if (!messageSaved) {
|
319
|
-
if (tipoCalificacionId) {
|
320
|
-
const messageAResponder = messagesData.value.data[idxMessageToCommunicate]
|
321
|
-
messageAResponder.response = undefined
|
322
|
-
} else {
|
323
|
-
messagesData.value.data[idxMessageToCommunicate].error = {
|
324
|
-
error: true,
|
325
|
-
id: newMessage.id,
|
326
|
-
};
|
327
|
-
}
|
328
|
-
|
329
|
-
emit("show-toast", {
|
330
|
-
severity: "error",
|
331
|
-
summary: "Error",
|
332
|
-
detail: "Ocurrio un error al enviar el mensaje, intente nuevamente",
|
333
|
-
life: 5000,
|
334
|
-
});
|
335
|
-
|
336
|
-
return
|
337
|
-
}
|
338
|
-
|
339
|
-
let messageUpdated = { ...messageSaved, chatId: newMessage?.chatId }
|
340
|
-
|
341
|
-
if (tipoCalificacionId) {
|
342
|
-
const messageAResponder = messagesData.value.data[idxMessageToCommunicate]
|
343
|
-
messageAResponder.response = messageSaved
|
344
|
-
|
345
|
-
messageUpdated = { ...messageAResponder, chatId: newMessage?.chatId }
|
346
|
-
} else {
|
347
|
-
messagesData.value.data[idxMessageToCommunicate] = messageUpdated;
|
348
|
-
}
|
349
|
-
|
350
|
-
return messageUpdated;
|
351
|
-
}
|
352
|
-
|
353
|
-
const submitMessage = async (
|
354
|
-
messageExtraData?: { mensajeARespondeId?: string, tipoCalificacionId?: number, codigoTipoMensaje?: TypeMessageTypeCodes}
|
355
|
-
) => {
|
356
|
-
|
357
|
-
const newMessage = createMessage(message.value, messageExtraData?.codigoTipoMensaje);
|
358
|
-
message.value = '';
|
359
|
-
if (!newMessage) return;
|
360
|
-
|
361
|
-
let idxMessageToCommunicate = -1;
|
362
|
-
if (messageExtraData?.tipoCalificacionId) {
|
363
|
-
idxMessageToCommunicate = messagesData.value.data.findIndex((val) => val.id === messageExtraData?.mensajeARespondeId )
|
364
|
-
const messageAResponder = messagesData.value.data[idxMessageToCommunicate]
|
365
|
-
|
366
|
-
if ( messageAResponder ) {
|
367
|
-
messageAResponder.response = newMessage
|
368
|
-
}
|
369
|
-
} else {
|
370
|
-
idxMessageToCommunicate = messagesData.value.data.push(newMessage) - 1;
|
371
|
-
}
|
372
|
-
|
373
|
-
const body = {
|
374
|
-
message: message.value,
|
375
|
-
appChatId: appChatId.value,
|
376
|
-
...messageExtraData,
|
377
|
-
codigoTipoMensaje: messageExtraData?.codigoTipoMensaje,
|
378
|
-
files: inputFiles.value,
|
379
|
-
}
|
380
|
-
sendApi(body).then((messageSaved) => {
|
381
|
-
let messageUpdated = updateMessageStatus(
|
382
|
-
newMessage,
|
383
|
-
idxMessageToCommunicate,
|
384
|
-
messageSaved,
|
385
|
-
messageExtraData?.tipoCalificacionId
|
386
|
-
)
|
387
|
-
|
388
|
-
if(!messageUpdated) return
|
389
|
-
|
390
|
-
isDisabledBoxMessage.value = !!messagesData.value.data.find((msg) => msg.messageType?.code === MESSAGE_TYPE_CODES.PREGUNTA && msg.response == null);
|
391
|
-
|
392
|
-
socketService.value?.emit(
|
393
|
-
"sendMessage",
|
394
|
-
{ roomId: information?.value?.appChat.id, message: messageUpdated },
|
395
|
-
(response: any) => {
|
396
|
-
console.log("🚀 ~ socketService.value.emit ~ response:", response);
|
397
|
-
}
|
398
|
-
);
|
399
|
-
});
|
400
|
-
|
401
|
-
message.value = "";
|
402
|
-
scrollToBottom();
|
403
|
-
textAreaRef.value && (textAreaRef.value.style.height = "20px");
|
404
|
-
};
|
405
|
-
|
406
|
-
const sendApi = async (bodyParam: Omit<SendMessageBody, 'esCliente'>) => {
|
407
|
-
const body: SendMessageBody = {
|
408
|
-
...bodyParam,
|
409
|
-
esCliente: true,
|
410
|
-
};
|
411
|
-
return sendMessageApi(body, props.tokenAuth);
|
412
|
-
};
|
413
|
-
|
414
|
-
const getMessages = async () => {
|
415
|
-
const lastMessagesId = messagesData.value.data[0]?.id;
|
416
|
-
const body: ListMessageBody = {
|
417
|
-
lastMessagesId,
|
418
|
-
appChatId: appChatId.value,
|
419
|
-
limit: 10,
|
420
|
-
};
|
421
|
-
|
422
|
-
isLoading.value = true;
|
423
|
-
const resp = await getMessagesApi({ body, token: props.tokenAuth });
|
424
|
-
isLoading.value = false;
|
425
|
-
|
426
|
-
isDisabledBoxMessage.value = !!resp.data.find((msg) => msg.messageType?.code === MESSAGE_TYPE_CODES.PREGUNTA && msg.response == null);
|
427
|
-
|
428
|
-
messagesData.value.data.unshift(
|
429
|
-
...resp.data.sort((a, b) => -b.createdAt.localeCompare(a.createdAt))
|
430
|
-
);
|
431
|
-
|
432
|
-
messagesData.value.canLoadMoreMessages =
|
433
|
-
resp.pagination.total > resp.pagination.size;
|
434
|
-
|
435
|
-
if (lastMessagesId && messageContainerRef.value?.scrollHeight) {
|
436
|
-
mantainElementsOnViewport(messageContainerRef.value?.scrollHeight);
|
437
|
-
}
|
438
|
-
|
439
|
-
if (!lastMessagesId) scrollToBottom();
|
440
|
-
};
|
441
|
-
|
442
|
-
const retryMessage = async (message: Message) => {
|
443
|
-
emit("show-confirm", async () => {
|
444
|
-
if (!message.error?.id) return;
|
445
|
-
|
446
|
-
const msg = await sendApi({
|
447
|
-
message: message.message,
|
448
|
-
appChatId: appChatId.value
|
449
|
-
});
|
450
|
-
|
451
|
-
if (!msg) {
|
452
|
-
emit("show-toast", {
|
453
|
-
severity: "error",
|
454
|
-
summary: "Error",
|
455
|
-
detail: "Ocurrio un error al enviar el mensaje, intente nuevamente",
|
456
|
-
life: 5000,
|
457
|
-
});
|
458
|
-
} else {
|
459
|
-
const idx = searchFromLast<Message>(
|
460
|
-
messagesData.value.data,
|
461
|
-
"id",
|
462
|
-
message.error.id
|
463
|
-
);
|
464
|
-
|
465
|
-
messagesData.value.data[idx] = { ...msg, error: undefined };
|
466
|
-
|
467
|
-
socketService.value?.emit("sendMessage", {
|
468
|
-
roomId: information?.value?.appChat.id,
|
469
|
-
message,
|
470
|
-
});
|
471
|
-
}
|
472
|
-
|
473
|
-
scrollToBottom();
|
474
|
-
});
|
475
|
-
};
|
476
|
-
|
477
|
-
const scrollToBottom = () => {
|
478
|
-
nextTick(() => {
|
479
|
-
if (messageContainerRef.value) {
|
480
|
-
messageContainerRef.value.scrollTop =
|
481
|
-
messageContainerRef.value.scrollHeight;
|
482
|
-
}
|
483
|
-
});
|
484
|
-
};
|
485
|
-
|
486
|
-
const mantainElementsOnViewport = (scrollHeightBeforeAdd: number) => {
|
487
|
-
nextTick(() => {
|
488
|
-
const objDiv = messageContainerRef.value;
|
489
|
-
if (objDiv) {
|
490
|
-
objDiv.scrollTop = objDiv.scrollHeight - scrollHeightBeforeAdd;
|
491
|
-
}
|
492
|
-
});
|
493
|
-
};
|
494
|
-
|
495
|
-
const textAreaRef = ref<HTMLTextAreaElement>();
|
496
|
-
|
497
|
-
const fontSpace = 14;
|
498
|
-
|
499
|
-
function autoAdjustHeight() {
|
500
|
-
if (!textAreaRef.value) return;
|
501
|
-
|
502
|
-
textAreaRef.value.style.height =
|
503
|
-
textAreaRef.value.scrollHeight - fontSpace + "px";
|
504
|
-
|
505
|
-
if (textAreaRef.value.scrollHeight === textAreaRef.value.clientHeight)
|
506
|
-
return;
|
507
|
-
|
508
|
-
textAreaRef.value.style.height = textAreaRef.value.scrollHeight + "px";
|
509
|
-
}
|
510
|
-
|
511
|
-
const socketService = ref<Socket>();
|
512
|
-
const information = ref<ChatInformation>();
|
513
|
-
|
514
|
-
function connectMsWebSocket(
|
515
|
-
userChat: ChatInformation,
|
516
|
-
app: APP_TYPE = APP_TYPE.WEBCHAT
|
517
|
-
) {
|
518
|
-
if (!userChat) throw new Error("user chat is required");
|
519
|
-
|
520
|
-
socketService.value = io(window.VITE_SOCKET_URI, {
|
521
|
-
query: {
|
522
|
-
// TODO: confirmar si se quita o no
|
523
|
-
usuarioId: `${userChat?.chat?.persona?.funcionarioId}`,
|
524
|
-
aplicacion: app,
|
525
|
-
},
|
526
|
-
extraHeaders: { Authorization: props.tokenAuth },
|
527
|
-
});
|
528
|
-
|
529
|
-
socketService.value.removeAllListeners();
|
530
|
-
|
531
|
-
socketService.value.on("connect", () => {
|
532
|
-
console.log("Conectado al servidor de sockets");
|
533
|
-
|
534
|
-
socketService.value?.emit("joinRoom", `${userChat?.appChat?.id}`);
|
535
|
-
});
|
536
|
-
|
537
|
-
socketService.value.on("disconnect", () => {
|
538
|
-
console.log("Desconectado del servidor de sockets");
|
539
|
-
});
|
540
|
-
|
541
|
-
socketService.value.on("receiveMessage", (data: any) => {
|
542
|
-
console.log("Mensaje recibido:", data.message, userChat);
|
543
|
-
const indexMessage = messagesData.value.data.findIndex((msg) => msg.id === data.message.id);
|
544
|
-
|
545
|
-
if (
|
546
|
-
data.message.sender.msPersonaId === userChat?.chat?.persona?.msPersonaId && indexMessage === -1
|
547
|
-
)
|
548
|
-
return;
|
549
|
-
|
550
|
-
if (indexMessage !== -1) {
|
551
|
-
messagesData.value.data[indexMessage].response = data.message.response;
|
552
|
-
} else {
|
553
|
-
messagesData.value.data.push(data.message);
|
554
|
-
}
|
555
|
-
|
556
|
-
isDisabledBoxMessage.value = !!messagesData.value.data.find((msg) => msg.messageType?.code === MESSAGE_TYPE_CODES.PREGUNTA && msg.response == null);
|
557
|
-
|
558
|
-
setVistoToTrueApi(data.message.appChatId, props.tokenAuth);
|
559
|
-
scrollToBottom();
|
560
|
-
!props.visible && emit("new-message");
|
561
|
-
});
|
562
|
-
}
|
563
|
-
|
564
|
-
const { isMobile } = useMobile()
|
565
|
-
|
566
|
-
function onQualifying({ message: messageParam, emoji }: { message: Message, emoji: { iconUnicode: string, value: number } }) {
|
567
|
-
const callback = async () => {
|
568
|
-
if (!messageParam.id || !emoji) return;
|
569
|
-
|
570
|
-
message.value = emoji.iconUnicode
|
571
|
-
|
572
|
-
submitMessage({
|
573
|
-
tipoCalificacionId: emoji.value,
|
574
|
-
mensajeARespondeId: messageParam.id
|
575
|
-
}).then();
|
576
|
-
}
|
577
|
-
|
578
|
-
emit('onQualifying', { message: emoji.iconUnicode , callback})
|
579
|
-
};
|
580
|
-
|
581
|
-
const isDisabledBoxMessage = ref(false);
|
582
|
-
|
583
|
-
// Adjuntar
|
584
|
-
function onFileSelect() {
|
585
|
-
inputFiles.value = [];
|
586
|
-
urlFiles.value = [];
|
587
|
-
|
588
|
-
const filesParam = fileInputRef.value?.files ?? []
|
589
|
-
|
590
|
-
const file = filesParam?.[0];
|
591
|
-
|
592
|
-
inputFiles.value = filesParam;
|
593
|
-
|
594
|
-
if (!file) return;
|
595
|
-
|
596
|
-
urlFiles.value.push(URL.createObjectURL(file))
|
597
|
-
|
598
|
-
currentDialogView.value = DIALOG_VIEWS.UPLOAD;
|
599
|
-
dialog.title = 'Preparar imagen';
|
600
|
-
dialog.modelValue = true;
|
601
|
-
}
|
602
|
-
|
603
|
-
// Dialog
|
604
|
-
|
605
|
-
const inputFiles = ref<(File & { objectURL: string })[]>([]);
|
606
|
-
const urlFiles = ref<Array<string>>([]);
|
607
|
-
|
608
|
-
const dialog = reactive<IPropsDialog>({
|
609
|
-
modelValue: false,
|
610
|
-
'onUpdate:modelValue': (args) => {
|
611
|
-
dialog.modelValue = args;
|
612
|
-
|
613
|
-
if (args) return;
|
614
|
-
|
615
|
-
urlFiles.value = [];
|
616
|
-
inputFiles.value = [];
|
617
|
-
},
|
618
|
-
title: 'Preparar imagen'
|
619
|
-
});
|
620
|
-
|
621
|
-
//
|
622
|
-
|
623
|
-
onMounted(async () => {
|
624
|
-
if (appChatId.value) return;
|
625
|
-
|
626
|
-
const resp = await getInformationApi(props.tokenAuth);
|
627
|
-
|
628
|
-
if (!resp) return;
|
629
|
-
|
630
|
-
information.value = resp;
|
631
|
-
appChatId.value = resp.appChat.id;
|
632
|
-
notViewed.value = resp.appChat.totalNoVistosCliente;
|
633
|
-
connectMsWebSocket(resp);
|
634
|
-
getMessages();
|
635
|
-
emit("not-viewed-total", resp.appChat.totalNoVistosCliente);
|
636
|
-
});
|
637
|
-
|
638
|
-
onUnmounted(() => {
|
639
|
-
socketService.value?.off();
|
640
|
-
});
|
641
|
-
</script>
|
642
|
-
|
643
|
-
<style scoped>
|
644
|
-
.btn-primary {
|
645
|
-
padding: 10px 12px;
|
646
|
-
&:hover {
|
647
|
-
background-color: rgb(242, 139, 12, 0.1);
|
648
|
-
}
|
649
|
-
}
|
650
|
-
|
651
|
-
.btn-close {
|
652
|
-
padding: 0;
|
653
|
-
background-color: transparent;
|
654
|
-
border: none;
|
655
|
-
display: flex;
|
656
|
-
align-items: center;
|
657
|
-
border-radius: 50%;
|
658
|
-
&:hover {
|
659
|
-
background-color: rgba(202, 202, 202, 0.534);
|
660
|
-
}
|
661
|
-
}
|
662
|
-
|
663
|
-
.messages-container {
|
664
|
-
position: relative;
|
665
|
-
}
|
666
|
-
.loader {
|
667
|
-
position: absolute;
|
668
|
-
top: 18px;
|
669
|
-
z-index: 5;
|
670
|
-
left: 50%;
|
671
|
-
transform: translate(-50%, -50%);
|
672
|
-
}
|
673
|
-
|
674
|
-
.fit {
|
675
|
-
width: 100%;
|
676
|
-
height: 100%;
|
677
|
-
position: relative;
|
678
|
-
}
|
679
|
-
.center {
|
680
|
-
position: absolute;
|
681
|
-
top: 50%;
|
682
|
-
left: 50%;
|
683
|
-
transform: translate(-50%, -50%);
|
684
|
-
}
|
685
|
-
|
686
|
-
.message-send-block {
|
687
|
-
display: block;
|
688
|
-
margin: 1.5rem;
|
689
|
-
text-align: center;
|
690
|
-
}
|
691
|
-
</style>
|
1
|
+
<template>
|
2
|
+
<div class="widget">
|
3
|
+
<div class="header-widget">
|
4
|
+
<h4 class="title-chat">{{ titlePrincipal }}</h4>
|
5
|
+
<button @click="() => toggleChat()" class="btn-close">
|
6
|
+
<IconClose class="pointer" />
|
7
|
+
</button>
|
8
|
+
</div>
|
9
|
+
<div class="messages-container" ref="messageContainerRef">
|
10
|
+
<div class="loader" v-if="isLoading">
|
11
|
+
<Loader />
|
12
|
+
</div>
|
13
|
+
<MessageList
|
14
|
+
v-if="messagesData.data.length > 0"
|
15
|
+
:messages="messagesData.data"
|
16
|
+
:canLoadMoreMessages="messagesData.canLoadMoreMessages"
|
17
|
+
@loadMore="getMessages"
|
18
|
+
@retry="retryMessage"
|
19
|
+
@on-qualifying="(args) => onQualifying(args)"
|
20
|
+
@see="(message: Message) => {
|
21
|
+
currentDialogView = DIALOG_VIEWS.SEE
|
22
|
+
urlFileMessage = message.urlFile
|
23
|
+
dialog.title = 'Imagen'
|
24
|
+
dialog.modelValue = true
|
25
|
+
}"
|
26
|
+
/>
|
27
|
+
<div class="fit" v-else>
|
28
|
+
<span class="center">No tienes mensajes</span>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div class="w-full">
|
33
|
+
<form v-if="!isDisabledBoxMessage" class="message-send" @submit.prevent="(event) => submitMessage()">
|
34
|
+
<div class="form-message">
|
35
|
+
<div class="jl-inputgroup-chat">
|
36
|
+
<textarea
|
37
|
+
v-model="message"
|
38
|
+
class="jl2-input-chat"
|
39
|
+
placeholder="Escribe tu mensaje"
|
40
|
+
required
|
41
|
+
ref="textAreaRef"
|
42
|
+
@input="() => autoAdjustHeight()"
|
43
|
+
@keydown.enter="
|
44
|
+
(event) => {
|
45
|
+
!isMobile && event.preventDefault();
|
46
|
+
}
|
47
|
+
"
|
48
|
+
@keyup.enter="saltoDeLineaOEnviar"
|
49
|
+
/>
|
50
|
+
|
51
|
+
<input
|
52
|
+
type="file"
|
53
|
+
ref="fileInputRef"
|
54
|
+
@change="onFileSelect"
|
55
|
+
accept="image/*"
|
56
|
+
style="display: none;"
|
57
|
+
key="fileInputKey"
|
58
|
+
|
59
|
+
/>
|
60
|
+
<button
|
61
|
+
type="button"
|
62
|
+
class="pointer btn-primary"
|
63
|
+
title="Adjuntar archivo"
|
64
|
+
@click="
|
65
|
+
() => {
|
66
|
+
fileInputRef.value = '';
|
67
|
+
fileInputKey++;
|
68
|
+
fileInputRef?.click();
|
69
|
+
}
|
70
|
+
"
|
71
|
+
>
|
72
|
+
<IconAttach style="width: 20px; height: 20px" />
|
73
|
+
</button>
|
74
|
+
|
75
|
+
<button type="submit" class="pointer btn-primary">
|
76
|
+
<IconSend style="width: 20px; height: 20px" />
|
77
|
+
</button>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
</form>
|
81
|
+
<span class="message-send-block" v-else>
|
82
|
+
Necesitamos que nos califique la atención para continuar
|
83
|
+
</span>
|
84
|
+
</div>
|
85
|
+
</div>
|
86
|
+
|
87
|
+
<ODialog v-bind="dialog">
|
88
|
+
<div v-if="currentDialogView === DIALOG_VIEWS.UPLOAD" class="flex flex-col gap-3 justify-center items-center">
|
89
|
+
<img v-for="(urlFile, i) in urlFiles" :key="i" :src="urlFile.toString()" alt="Image" width="400" />
|
90
|
+
|
91
|
+
<form
|
92
|
+
class="message-send"
|
93
|
+
@submit.prevent="
|
94
|
+
(event) => {
|
95
|
+
submitMessage();
|
96
|
+
dialog.modelValue = false;
|
97
|
+
}
|
98
|
+
"
|
99
|
+
>
|
100
|
+
<div class="form-message">
|
101
|
+
<div class="jl-inputgroup-chat">
|
102
|
+
<textarea
|
103
|
+
v-model="message"
|
104
|
+
autofocus
|
105
|
+
class="jl2-input-chat"
|
106
|
+
placeholder="Escribe tu mensaje"
|
107
|
+
ref="textAreaRef"
|
108
|
+
@input="() => autoAdjustHeight()"
|
109
|
+
@keydown.enter="
|
110
|
+
(event) => {
|
111
|
+
!isMobile && event.preventDefault();
|
112
|
+
}
|
113
|
+
"
|
114
|
+
@keyup.enter="(event)=>{
|
115
|
+
saltoDeLineaOEnviar(event, MESSAGE_TYPE_CODES.IMAGEN)
|
116
|
+
dialog.modelValue = false;
|
117
|
+
}"
|
118
|
+
/>
|
119
|
+
|
120
|
+
<button type="submit" class="pointer btn-primary">
|
121
|
+
<IconSend style="width: 20px; height: 20px" />
|
122
|
+
</button>
|
123
|
+
</div>
|
124
|
+
</div>
|
125
|
+
</form>
|
126
|
+
</div>
|
127
|
+
<div v-else>
|
128
|
+
<img v-if="urlFileMessage" :src="urlFileMessage" alt="Image" style="width: 55vw;" />
|
129
|
+
</div>
|
130
|
+
</ODialog>
|
131
|
+
</template>
|
132
|
+
|
133
|
+
<script setup lang="ts">
|
134
|
+
import {
|
135
|
+
ref,
|
136
|
+
onMounted,
|
137
|
+
nextTick,
|
138
|
+
PropType,
|
139
|
+
watch,
|
140
|
+
onUnmounted,
|
141
|
+
reactive,
|
142
|
+
} from "vue";
|
143
|
+
import { v4 as uuidv4 } from "uuid";
|
144
|
+
|
145
|
+
import {
|
146
|
+
type SendMessageBody,
|
147
|
+
ChatInformation,
|
148
|
+
ListMessageBody,
|
149
|
+
Message,
|
150
|
+
} from "../dto/app.dto";
|
151
|
+
import IconClose from "./IconClose.vue";
|
152
|
+
import IconSend from "./IconSend.vue";
|
153
|
+
import {
|
154
|
+
getMessagesApi,
|
155
|
+
sendMessageApi,
|
156
|
+
setVistoToTrueApi,
|
157
|
+
updateMessageApi,
|
158
|
+
} from "../store/index";
|
159
|
+
import { getInformationApi } from "../store";
|
160
|
+
import MessageList from "./MessageList.vue";
|
161
|
+
import Loader from "./Loader.vue";
|
162
|
+
import { searchFromLast } from "../resources/functions.helpers";
|
163
|
+
import { io, Socket } from "socket.io-client";
|
164
|
+
import { APP_TYPE } from "../dto/chat.dto";
|
165
|
+
import { MESSAGE_TYPE_CODES, TypeMessageTypeCodes } from "../resources/constants/message-type.constant";
|
166
|
+
import { useMobile } from "../hooks/useMobile";
|
167
|
+
import IconAttach from "./IconAttach.vue";
|
168
|
+
import ODialog from "./ODialog/ODialog.vue";
|
169
|
+
import { IPropsDialog } from "./ODialog/IPropsDialog";
|
170
|
+
|
171
|
+
const enum DIALOG_VIEWS {
|
172
|
+
UPLOAD,
|
173
|
+
SEE
|
174
|
+
}
|
175
|
+
|
176
|
+
//DATA
|
177
|
+
const message = ref("");
|
178
|
+
const notViewed = ref(0);
|
179
|
+
const fileInputRef = ref();
|
180
|
+
const fileInputKey = ref(0);
|
181
|
+
const currentDialogView = ref(DIALOG_VIEWS.SEE)
|
182
|
+
const urlFileMessage = ref<string>()
|
183
|
+
|
184
|
+
const messagesData = ref<{ data: Message[]; canLoadMoreMessages: boolean }>({
|
185
|
+
data: [],
|
186
|
+
canLoadMoreMessages: false,
|
187
|
+
});
|
188
|
+
|
189
|
+
const appChatId = ref("");
|
190
|
+
const isLoading = ref(false);
|
191
|
+
|
192
|
+
const emit = defineEmits([
|
193
|
+
"show-toast",
|
194
|
+
"show-confirm",
|
195
|
+
"new-message",
|
196
|
+
"clear-new-messages",
|
197
|
+
"not-viewed-total",
|
198
|
+
"onQualifying",
|
199
|
+
]);
|
200
|
+
|
201
|
+
const props = defineProps({
|
202
|
+
titlePrincipal: {
|
203
|
+
type: String,
|
204
|
+
default: "Comunicación en linea para consultas",
|
205
|
+
},
|
206
|
+
toggleChat: { type: Function, required: true },
|
207
|
+
tokenAuth: { type: String, required: true },
|
208
|
+
user: {
|
209
|
+
type: Object as PropType<{
|
210
|
+
nombreCompleto: string;
|
211
|
+
ci: string;
|
212
|
+
msPersonaId: number;
|
213
|
+
}>,
|
214
|
+
required: true,
|
215
|
+
},
|
216
|
+
visible: { type: Boolean, required: true },
|
217
|
+
});
|
218
|
+
|
219
|
+
const messageContainerRef = ref<HTMLElement | null>(null);
|
220
|
+
|
221
|
+
watch(
|
222
|
+
() => props.visible,
|
223
|
+
async (current) => {
|
224
|
+
if (!current) return;
|
225
|
+
|
226
|
+
emit("clear-new-messages");
|
227
|
+
scrollToBottom();
|
228
|
+
|
229
|
+
if (notViewed.value > 0) {
|
230
|
+
setVistoToTrueApi(appChatId.value, props.tokenAuth);
|
231
|
+
}
|
232
|
+
|
233
|
+
if (appChatId.value) return;
|
234
|
+
|
235
|
+
const resp = await getInformationApi(props.tokenAuth);
|
236
|
+
|
237
|
+
if (resp) {
|
238
|
+
appChatId.value = resp.appChat.id;
|
239
|
+
}
|
240
|
+
}
|
241
|
+
);
|
242
|
+
|
243
|
+
function saltoDeLineaOEnviar(event: KeyboardEvent, messageTypeCode?: TypeMessageTypeCodes) {
|
244
|
+
if(isMobile.value) {
|
245
|
+
autoAdjustHeight();
|
246
|
+
return;
|
247
|
+
}
|
248
|
+
|
249
|
+
if (event.key === "Enter" && event.shiftKey) {
|
250
|
+
const val = (event.target as any)?.value || "";
|
251
|
+
(event.target as any).value = val + "\n";
|
252
|
+
autoAdjustHeight();
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
|
256
|
+
submitMessage({ codigoTipoMensaje: messageTypeCode });
|
257
|
+
}
|
258
|
+
|
259
|
+
function createMessage(message: string, codigoTipoMensaje?: TypeMessageTypeCodes) {
|
260
|
+
if (message?.length > 300) {
|
261
|
+
emit("show-toast", {
|
262
|
+
severity: "warn",
|
263
|
+
summary: "Error",
|
264
|
+
detail: "El mensaje no puede superar los 300 caracteres",
|
265
|
+
life: 5000,
|
266
|
+
});
|
267
|
+
return;
|
268
|
+
}
|
269
|
+
|
270
|
+
if (!message.trim() && codigoTipoMensaje !== MESSAGE_TYPE_CODES.IMAGEN) {
|
271
|
+
emit("show-toast", {
|
272
|
+
severity: "warn",
|
273
|
+
summary: "Error",
|
274
|
+
detail: "Por favor ingrese un mensaje",
|
275
|
+
life: 5000,
|
276
|
+
});
|
277
|
+
return;
|
278
|
+
}
|
279
|
+
|
280
|
+
const messageType = codigoTipoMensaje
|
281
|
+
? {
|
282
|
+
code: codigoTipoMensaje,
|
283
|
+
}
|
284
|
+
: undefined;
|
285
|
+
|
286
|
+
const newMessage: Message = {
|
287
|
+
id: uuidv4(),
|
288
|
+
chatId: information.value?.chat?.id,
|
289
|
+
message,
|
290
|
+
visto: true,
|
291
|
+
multimedia: false,
|
292
|
+
esCliente: true,
|
293
|
+
appChatId: appChatId.value,
|
294
|
+
createdAt: new Date().toISOString(),
|
295
|
+
updatedAt: new Date().toISOString(),
|
296
|
+
file: inputFiles.value?.[0],
|
297
|
+
urlFile: urlFiles.value?.[0].toString(),
|
298
|
+
messageType,
|
299
|
+
sender: {
|
300
|
+
nombreCompleto: props.user.nombreCompleto,
|
301
|
+
ci: props.user.ci,
|
302
|
+
msPersonaId: props.user.msPersonaId,
|
303
|
+
},
|
304
|
+
response: undefined
|
305
|
+
};
|
306
|
+
|
307
|
+
return newMessage
|
308
|
+
}
|
309
|
+
|
310
|
+
function updateMessageStatus(
|
311
|
+
newMessage: Message,
|
312
|
+
idxMessageToCommunicate?: number,
|
313
|
+
messageSaved?: Message,
|
314
|
+
tipoCalificacionId?: number
|
315
|
+
) {
|
316
|
+
if (idxMessageToCommunicate == null) throw new Error('idx is required')
|
317
|
+
|
318
|
+
if (!messageSaved) {
|
319
|
+
if (tipoCalificacionId) {
|
320
|
+
const messageAResponder = messagesData.value.data[idxMessageToCommunicate]
|
321
|
+
messageAResponder.response = undefined
|
322
|
+
} else {
|
323
|
+
messagesData.value.data[idxMessageToCommunicate].error = {
|
324
|
+
error: true,
|
325
|
+
id: newMessage.id,
|
326
|
+
};
|
327
|
+
}
|
328
|
+
|
329
|
+
emit("show-toast", {
|
330
|
+
severity: "error",
|
331
|
+
summary: "Error",
|
332
|
+
detail: "Ocurrio un error al enviar el mensaje, intente nuevamente",
|
333
|
+
life: 5000,
|
334
|
+
});
|
335
|
+
|
336
|
+
return
|
337
|
+
}
|
338
|
+
|
339
|
+
let messageUpdated = { ...messageSaved, chatId: newMessage?.chatId }
|
340
|
+
|
341
|
+
if (tipoCalificacionId) {
|
342
|
+
const messageAResponder = messagesData.value.data[idxMessageToCommunicate]
|
343
|
+
messageAResponder.response = messageSaved
|
344
|
+
|
345
|
+
messageUpdated = { ...messageAResponder, chatId: newMessage?.chatId }
|
346
|
+
} else {
|
347
|
+
messagesData.value.data[idxMessageToCommunicate] = messageUpdated;
|
348
|
+
}
|
349
|
+
|
350
|
+
return messageUpdated;
|
351
|
+
}
|
352
|
+
|
353
|
+
const submitMessage = async (
|
354
|
+
messageExtraData?: { mensajeARespondeId?: string, tipoCalificacionId?: number, codigoTipoMensaje?: TypeMessageTypeCodes}
|
355
|
+
) => {
|
356
|
+
|
357
|
+
const newMessage = createMessage(message.value, messageExtraData?.codigoTipoMensaje);
|
358
|
+
message.value = '';
|
359
|
+
if (!newMessage) return;
|
360
|
+
|
361
|
+
let idxMessageToCommunicate = -1;
|
362
|
+
if (messageExtraData?.tipoCalificacionId) {
|
363
|
+
idxMessageToCommunicate = messagesData.value.data.findIndex((val) => val.id === messageExtraData?.mensajeARespondeId )
|
364
|
+
const messageAResponder = messagesData.value.data[idxMessageToCommunicate]
|
365
|
+
|
366
|
+
if ( messageAResponder ) {
|
367
|
+
messageAResponder.response = newMessage
|
368
|
+
}
|
369
|
+
} else {
|
370
|
+
idxMessageToCommunicate = messagesData.value.data.push(newMessage) - 1;
|
371
|
+
}
|
372
|
+
|
373
|
+
const body = {
|
374
|
+
message: message.value,
|
375
|
+
appChatId: appChatId.value,
|
376
|
+
...messageExtraData,
|
377
|
+
codigoTipoMensaje: messageExtraData?.codigoTipoMensaje,
|
378
|
+
files: inputFiles.value,
|
379
|
+
}
|
380
|
+
sendApi(body).then((messageSaved) => {
|
381
|
+
let messageUpdated = updateMessageStatus(
|
382
|
+
newMessage,
|
383
|
+
idxMessageToCommunicate,
|
384
|
+
messageSaved,
|
385
|
+
messageExtraData?.tipoCalificacionId
|
386
|
+
)
|
387
|
+
|
388
|
+
if(!messageUpdated) return
|
389
|
+
|
390
|
+
isDisabledBoxMessage.value = !!messagesData.value.data.find((msg) => msg.messageType?.code === MESSAGE_TYPE_CODES.PREGUNTA && msg.response == null);
|
391
|
+
|
392
|
+
socketService.value?.emit(
|
393
|
+
"sendMessage",
|
394
|
+
{ roomId: information?.value?.appChat.id, message: messageUpdated },
|
395
|
+
(response: any) => {
|
396
|
+
console.log("🚀 ~ socketService.value.emit ~ response:", response);
|
397
|
+
}
|
398
|
+
);
|
399
|
+
});
|
400
|
+
|
401
|
+
message.value = "";
|
402
|
+
scrollToBottom();
|
403
|
+
textAreaRef.value && (textAreaRef.value.style.height = "20px");
|
404
|
+
};
|
405
|
+
|
406
|
+
const sendApi = async (bodyParam: Omit<SendMessageBody, 'esCliente'>) => {
|
407
|
+
const body: SendMessageBody = {
|
408
|
+
...bodyParam,
|
409
|
+
esCliente: true,
|
410
|
+
};
|
411
|
+
return sendMessageApi(body, props.tokenAuth);
|
412
|
+
};
|
413
|
+
|
414
|
+
const getMessages = async () => {
|
415
|
+
const lastMessagesId = messagesData.value.data[0]?.id;
|
416
|
+
const body: ListMessageBody = {
|
417
|
+
lastMessagesId,
|
418
|
+
appChatId: appChatId.value,
|
419
|
+
limit: 10,
|
420
|
+
};
|
421
|
+
|
422
|
+
isLoading.value = true;
|
423
|
+
const resp = await getMessagesApi({ body, token: props.tokenAuth });
|
424
|
+
isLoading.value = false;
|
425
|
+
|
426
|
+
isDisabledBoxMessage.value = !!resp.data.find((msg) => msg.messageType?.code === MESSAGE_TYPE_CODES.PREGUNTA && msg.response == null);
|
427
|
+
|
428
|
+
messagesData.value.data.unshift(
|
429
|
+
...resp.data.sort((a, b) => -b.createdAt.localeCompare(a.createdAt))
|
430
|
+
);
|
431
|
+
|
432
|
+
messagesData.value.canLoadMoreMessages =
|
433
|
+
resp.pagination.total > resp.pagination.size;
|
434
|
+
|
435
|
+
if (lastMessagesId && messageContainerRef.value?.scrollHeight) {
|
436
|
+
mantainElementsOnViewport(messageContainerRef.value?.scrollHeight);
|
437
|
+
}
|
438
|
+
|
439
|
+
if (!lastMessagesId) scrollToBottom();
|
440
|
+
};
|
441
|
+
|
442
|
+
const retryMessage = async (message: Message) => {
|
443
|
+
emit("show-confirm", async () => {
|
444
|
+
if (!message.error?.id) return;
|
445
|
+
|
446
|
+
const msg = await sendApi({
|
447
|
+
message: message.message,
|
448
|
+
appChatId: appChatId.value
|
449
|
+
});
|
450
|
+
|
451
|
+
if (!msg) {
|
452
|
+
emit("show-toast", {
|
453
|
+
severity: "error",
|
454
|
+
summary: "Error",
|
455
|
+
detail: "Ocurrio un error al enviar el mensaje, intente nuevamente",
|
456
|
+
life: 5000,
|
457
|
+
});
|
458
|
+
} else {
|
459
|
+
const idx = searchFromLast<Message>(
|
460
|
+
messagesData.value.data,
|
461
|
+
"id",
|
462
|
+
message.error.id
|
463
|
+
);
|
464
|
+
|
465
|
+
messagesData.value.data[idx] = { ...msg, error: undefined };
|
466
|
+
|
467
|
+
socketService.value?.emit("sendMessage", {
|
468
|
+
roomId: information?.value?.appChat.id,
|
469
|
+
message,
|
470
|
+
});
|
471
|
+
}
|
472
|
+
|
473
|
+
scrollToBottom();
|
474
|
+
});
|
475
|
+
};
|
476
|
+
|
477
|
+
const scrollToBottom = () => {
|
478
|
+
nextTick(() => {
|
479
|
+
if (messageContainerRef.value) {
|
480
|
+
messageContainerRef.value.scrollTop =
|
481
|
+
messageContainerRef.value.scrollHeight;
|
482
|
+
}
|
483
|
+
});
|
484
|
+
};
|
485
|
+
|
486
|
+
const mantainElementsOnViewport = (scrollHeightBeforeAdd: number) => {
|
487
|
+
nextTick(() => {
|
488
|
+
const objDiv = messageContainerRef.value;
|
489
|
+
if (objDiv) {
|
490
|
+
objDiv.scrollTop = objDiv.scrollHeight - scrollHeightBeforeAdd;
|
491
|
+
}
|
492
|
+
});
|
493
|
+
};
|
494
|
+
|
495
|
+
const textAreaRef = ref<HTMLTextAreaElement>();
|
496
|
+
|
497
|
+
const fontSpace = 14;
|
498
|
+
|
499
|
+
function autoAdjustHeight() {
|
500
|
+
if (!textAreaRef.value) return;
|
501
|
+
|
502
|
+
textAreaRef.value.style.height =
|
503
|
+
textAreaRef.value.scrollHeight - fontSpace + "px";
|
504
|
+
|
505
|
+
if (textAreaRef.value.scrollHeight === textAreaRef.value.clientHeight)
|
506
|
+
return;
|
507
|
+
|
508
|
+
textAreaRef.value.style.height = textAreaRef.value.scrollHeight + "px";
|
509
|
+
}
|
510
|
+
|
511
|
+
const socketService = ref<Socket>();
|
512
|
+
const information = ref<ChatInformation>();
|
513
|
+
|
514
|
+
function connectMsWebSocket(
|
515
|
+
userChat: ChatInformation,
|
516
|
+
app: APP_TYPE = APP_TYPE.WEBCHAT
|
517
|
+
) {
|
518
|
+
if (!userChat) throw new Error("user chat is required");
|
519
|
+
|
520
|
+
socketService.value = io(window.VITE_SOCKET_URI, {
|
521
|
+
query: {
|
522
|
+
// TODO: confirmar si se quita o no
|
523
|
+
usuarioId: `${userChat?.chat?.persona?.funcionarioId}`,
|
524
|
+
aplicacion: app,
|
525
|
+
},
|
526
|
+
extraHeaders: { Authorization: props.tokenAuth },
|
527
|
+
});
|
528
|
+
|
529
|
+
socketService.value.removeAllListeners();
|
530
|
+
|
531
|
+
socketService.value.on("connect", () => {
|
532
|
+
console.log("Conectado al servidor de sockets");
|
533
|
+
|
534
|
+
socketService.value?.emit("joinRoom", `${userChat?.appChat?.id}`);
|
535
|
+
});
|
536
|
+
|
537
|
+
socketService.value.on("disconnect", () => {
|
538
|
+
console.log("Desconectado del servidor de sockets");
|
539
|
+
});
|
540
|
+
|
541
|
+
socketService.value.on("receiveMessage", (data: any) => {
|
542
|
+
console.log("Mensaje recibido:", data.message, userChat);
|
543
|
+
const indexMessage = messagesData.value.data.findIndex((msg) => msg.id === data.message.id);
|
544
|
+
|
545
|
+
if (
|
546
|
+
data.message.sender.msPersonaId === userChat?.chat?.persona?.msPersonaId && indexMessage === -1
|
547
|
+
)
|
548
|
+
return;
|
549
|
+
|
550
|
+
if (indexMessage !== -1) {
|
551
|
+
messagesData.value.data[indexMessage].response = data.message.response;
|
552
|
+
} else {
|
553
|
+
messagesData.value.data.push(data.message);
|
554
|
+
}
|
555
|
+
|
556
|
+
isDisabledBoxMessage.value = !!messagesData.value.data.find((msg) => msg.messageType?.code === MESSAGE_TYPE_CODES.PREGUNTA && msg.response == null);
|
557
|
+
|
558
|
+
setVistoToTrueApi(data.message.appChatId, props.tokenAuth);
|
559
|
+
scrollToBottom();
|
560
|
+
!props.visible && emit("new-message");
|
561
|
+
});
|
562
|
+
}
|
563
|
+
|
564
|
+
const { isMobile } = useMobile()
|
565
|
+
|
566
|
+
function onQualifying({ message: messageParam, emoji }: { message: Message, emoji: { iconUnicode: string, value: number } }) {
|
567
|
+
const callback = async () => {
|
568
|
+
if (!messageParam.id || !emoji) return;
|
569
|
+
|
570
|
+
message.value = emoji.iconUnicode
|
571
|
+
|
572
|
+
submitMessage({
|
573
|
+
tipoCalificacionId: emoji.value,
|
574
|
+
mensajeARespondeId: messageParam.id
|
575
|
+
}).then();
|
576
|
+
}
|
577
|
+
|
578
|
+
emit('onQualifying', { message: emoji.iconUnicode , callback})
|
579
|
+
};
|
580
|
+
|
581
|
+
const isDisabledBoxMessage = ref(false);
|
582
|
+
|
583
|
+
// Adjuntar
|
584
|
+
function onFileSelect() {
|
585
|
+
inputFiles.value = [];
|
586
|
+
urlFiles.value = [];
|
587
|
+
|
588
|
+
const filesParam = fileInputRef.value?.files ?? []
|
589
|
+
|
590
|
+
const file = filesParam?.[0];
|
591
|
+
|
592
|
+
inputFiles.value = filesParam;
|
593
|
+
|
594
|
+
if (!file) return;
|
595
|
+
|
596
|
+
urlFiles.value.push(URL.createObjectURL(file))
|
597
|
+
|
598
|
+
currentDialogView.value = DIALOG_VIEWS.UPLOAD;
|
599
|
+
dialog.title = 'Preparar imagen';
|
600
|
+
dialog.modelValue = true;
|
601
|
+
}
|
602
|
+
|
603
|
+
// Dialog
|
604
|
+
|
605
|
+
const inputFiles = ref<(File & { objectURL: string })[]>([]);
|
606
|
+
const urlFiles = ref<Array<string>>([]);
|
607
|
+
|
608
|
+
const dialog = reactive<IPropsDialog>({
|
609
|
+
modelValue: false,
|
610
|
+
'onUpdate:modelValue': (args) => {
|
611
|
+
dialog.modelValue = args;
|
612
|
+
|
613
|
+
if (args) return;
|
614
|
+
|
615
|
+
urlFiles.value = [];
|
616
|
+
inputFiles.value = [];
|
617
|
+
},
|
618
|
+
title: 'Preparar imagen'
|
619
|
+
});
|
620
|
+
|
621
|
+
//
|
622
|
+
|
623
|
+
onMounted(async () => {
|
624
|
+
if (appChatId.value) return;
|
625
|
+
|
626
|
+
const resp = await getInformationApi(props.tokenAuth);
|
627
|
+
|
628
|
+
if (!resp) return;
|
629
|
+
|
630
|
+
information.value = resp;
|
631
|
+
appChatId.value = resp.appChat.id;
|
632
|
+
notViewed.value = resp.appChat.totalNoVistosCliente;
|
633
|
+
connectMsWebSocket(resp);
|
634
|
+
getMessages();
|
635
|
+
emit("not-viewed-total", resp.appChat.totalNoVistosCliente);
|
636
|
+
});
|
637
|
+
|
638
|
+
onUnmounted(() => {
|
639
|
+
socketService.value?.off();
|
640
|
+
});
|
641
|
+
</script>
|
642
|
+
|
643
|
+
<style scoped>
|
644
|
+
.btn-primary {
|
645
|
+
padding: 10px 12px;
|
646
|
+
&:hover {
|
647
|
+
background-color: rgb(242, 139, 12, 0.1);
|
648
|
+
}
|
649
|
+
}
|
650
|
+
|
651
|
+
.btn-close {
|
652
|
+
padding: 0;
|
653
|
+
background-color: transparent;
|
654
|
+
border: none;
|
655
|
+
display: flex;
|
656
|
+
align-items: center;
|
657
|
+
border-radius: 50%;
|
658
|
+
&:hover {
|
659
|
+
background-color: rgba(202, 202, 202, 0.534);
|
660
|
+
}
|
661
|
+
}
|
662
|
+
|
663
|
+
.messages-container {
|
664
|
+
position: relative;
|
665
|
+
}
|
666
|
+
.loader {
|
667
|
+
position: absolute;
|
668
|
+
top: 18px;
|
669
|
+
z-index: 5;
|
670
|
+
left: 50%;
|
671
|
+
transform: translate(-50%, -50%);
|
672
|
+
}
|
673
|
+
|
674
|
+
.fit {
|
675
|
+
width: 100%;
|
676
|
+
height: 100%;
|
677
|
+
position: relative;
|
678
|
+
}
|
679
|
+
.center {
|
680
|
+
position: absolute;
|
681
|
+
top: 50%;
|
682
|
+
left: 50%;
|
683
|
+
transform: translate(-50%, -50%);
|
684
|
+
}
|
685
|
+
|
686
|
+
.message-send-block {
|
687
|
+
display: block;
|
688
|
+
margin: 1.5rem;
|
689
|
+
text-align: center;
|
690
|
+
}
|
691
|
+
</style>
|