vue-wiguet-chatweb 0.1.28 → 0.1.30
Sign up to get free protection for your applications and to get access to all the features.
- 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 -1830
- 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]
|
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],
|
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>
|