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