@minhnd1010/chat-widget 1.0.0

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.
Files changed (2) hide show
  1. package/index.js +1489 -0
  2. package/package.json +13 -0
package/index.js ADDED
@@ -0,0 +1,1489 @@
1
+ class ChatWidget extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ this.attachShadow({ mode: 'open' });
5
+ this.connection = null;
6
+ }
7
+
8
+ async connectedCallback() {
9
+ this.render();
10
+
11
+ // Đảm bảo SignalR đã được tải trước khi chạy initLogic
12
+ if (typeof signalR === "undefined") {
13
+ await this.loadSignalR();
14
+ }
15
+
16
+ this.initLogic();
17
+
18
+ if (!customElements.get('emoji-picker')) {
19
+ // Nếu chưa load thư viện → load từ CDN
20
+ const script = document.createElement('script');
21
+ script.type = 'module';
22
+ script.src = 'https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js';
23
+ script.onload = () => {
24
+ console.log('emoji-picker-element loaded');
25
+ // Sau khi load xong, khởi tạo picker (nếu cần)
26
+ this.setupEmojiPickerAfterLoad();
27
+ };
28
+ script.onerror = () => console.error('Failed to load emoji-picker-element');
29
+ document.head.appendChild(script);
30
+ } else {
31
+ // Nếu đã load rồi (trang có nhiều widget) → khởi tạo luôn
32
+ this.setupEmojiPickerAfterLoad();
33
+ }
34
+ }
35
+
36
+ render() {
37
+ const position = this.getAttribute('position') || 'bottom-left';
38
+
39
+ let containerStyles = 'bottom: 20px; left: 20px;';
40
+ let boxStyles = 'top: -78vh; left: 42px;';
41
+
42
+ switch (position) {
43
+ case 'bottom-right':
44
+ containerStyles = 'bottom: 20px; right: 20px; left: auto;';
45
+ boxStyles = 'top: -78vh; right: 42px; left: auto; transform: translateX(-100%);';
46
+ break;
47
+ case 'top-left':
48
+ containerStyles = 'top: 20px; left: 20px; bottom: auto;';
49
+ boxStyles = 'bottom: 80px; top: auto; left: 42px; transform: translateY(100%);';
50
+ break;
51
+ case 'top-right':
52
+ containerStyles = 'top: 20px; right: 20px; left: auto; bottom: auto;';
53
+ boxStyles = 'bottom: 80px; top: auto; right: 42px; left: auto; transform: translateX(-100%) translateY(100%);';
54
+ break;
55
+ default:
56
+ // bottom-left
57
+ }
58
+
59
+ const template = document.createElement('template');
60
+ template.innerHTML = `
61
+ <style>
62
+ * {
63
+ margin: 0;
64
+ padding: 0;
65
+ font-family: Arial, sans-serif;
66
+ box-sizing: border-box;
67
+ }
68
+
69
+ /* ================================================================================
70
+ CHAT CONTAINER & MAIN BOX LAYOUT
71
+ ================================================================================ */
72
+ .chat-container {
73
+ position: fixed;
74
+ bottom: 20px;
75
+ left: 20px;
76
+ }
77
+
78
+ #chat-icon {
79
+ display: flex;
80
+ width: 60px;
81
+ height: 60px;
82
+ justify-content: center;
83
+ align-items: center;
84
+ }
85
+
86
+ #chat-box {
87
+ position: absolute;
88
+ top: -78vh;
89
+ left: 42px;
90
+ height: 80vh;
91
+ width: 500px;
92
+ border-radius: 16px;
93
+ overflow: hidden;
94
+ box-shadow: 0 4px 8px rgba(63, 63, 63, 0.5);
95
+ display: flex;
96
+ flex-direction: column;
97
+ flex: 1;
98
+ }
99
+
100
+ .chat-box-container {
101
+ display: flex;
102
+ flex-direction: column;
103
+ background-color: #ffffffff;
104
+ flex: 1;
105
+ overflow: hidden;
106
+ }
107
+
108
+ /* ================================================================================
109
+ CHAT HEADER & FILTER
110
+ ================================================================================ */
111
+ #chat-box-header {
112
+ background-color: #ffffffff;
113
+ border-bottom: 1px solid #e7e7e7ff;
114
+ box-shadow: 0 2px 4px rgba(165, 165, 165, 0.1);
115
+ padding: 10px;
116
+ }
117
+
118
+ .chat-filter {
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: start;
122
+ padding: 5px;
123
+ gap: 10px;
124
+ border: 1px solid #9e9e9eff;
125
+ width: 80%;
126
+ margin: 0 auto;
127
+ border-radius: 220px;
128
+ background-color: #eeeeeeff;
129
+ padding-left: 15px;
130
+ }
131
+
132
+ .chat-filter svg {
133
+ width: 24px;
134
+ height: 24px;
135
+ cursor: pointer;
136
+ }
137
+
138
+ .chat-filter input {
139
+ border: none;
140
+ background-color: inherit;
141
+ width: 100%;
142
+ font-size: 1rem;
143
+ }
144
+
145
+ .chat-filter input:focus {
146
+ outline: none;
147
+ background-color: inherit;
148
+ }
149
+
150
+ .chat-filter input:-internal-autofill-selected {
151
+ background-color: #eeeeeeff !important;
152
+ }
153
+
154
+ /* ================================================================================
155
+ CHAT LIST & LIST ITEMS
156
+ ================================================================================ */
157
+ #list-items {
158
+ position: relative;
159
+ background-color: #ffffffff;
160
+ display: flex;
161
+ flex-direction: column;
162
+ width: 100%;
163
+ overflow-y: auto;
164
+ }
165
+
166
+ #list-items ul {
167
+ list-style-type: none;
168
+ }
169
+
170
+ #list-items ul li {
171
+ padding: 10px;
172
+ border-bottom: 1px solid #ddd;
173
+ white-space: nowrap;
174
+ overflow: hidden;
175
+ text-overflow: ellipsis;
176
+ cursor: pointer;
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 10px;
180
+ }
181
+
182
+ #list-items ul li:hover {
183
+ background-color: #f0f0f0;
184
+ }
185
+
186
+ .logo-item {
187
+ width: 45px;
188
+ height: 45px;
189
+ border-radius: 50%;
190
+ background-color: #ffffffff;
191
+ display: flex;
192
+ justify-content: center;
193
+ align-items: center;
194
+ padding: 5px;
195
+ border: 1px solid #ccc;
196
+ }
197
+
198
+ .readed {
199
+ color: gray;
200
+ }
201
+
202
+ /* ================================================================================
203
+ CHAT INFO INPUT (User Information Dialog)
204
+ ================================================================================ */
205
+ #info-input {
206
+ background-color: #ffffffff;
207
+ display: flex;
208
+ flex-direction: column;
209
+ gap: 10px;
210
+ justify-content: center;
211
+ align-items: center;
212
+ height: 100%;
213
+ }
214
+
215
+ #info-input .info-header {
216
+ padding: 1% 3%;
217
+ background-color: #dadadaff;
218
+ border-radius: 12px;
219
+ box-shadow: 0 5px 10px rgba(109, 109, 109, 0.1);
220
+ }
221
+
222
+ #info-input input {
223
+ width: 70%;
224
+ padding: 10px 10px;
225
+ border: 1px solid #ccc;
226
+ border-radius: 50px;
227
+ font-size: 1rem;
228
+ }
229
+
230
+ #info-input input:focus {
231
+ outline: none;
232
+ }
233
+
234
+ #info-input input.error {
235
+ border: 2px solid #ff4444;
236
+ animation: shake 0.3s;
237
+ }
238
+
239
+ #info-input-buttons {
240
+ display: flex;
241
+ gap: 0.825rem;
242
+ }
243
+
244
+ #info-input #turn-back-button,
245
+ #continue-button {
246
+ display: flex;
247
+ align-items: center;
248
+ padding: 10px 20px;
249
+ border: none;
250
+ border-radius: 50px;
251
+ background-color: #aac96f;
252
+ color: white;
253
+ font-size: 1rem;
254
+ cursor: pointer;
255
+ }
256
+
257
+ #info-input svg {
258
+ fill: white;
259
+ width: 20px;
260
+ height: 20px;
261
+ margin-right: 8px;
262
+ }
263
+
264
+ #info-input #error-info-input {
265
+ color: red;
266
+ font-size: 1rem;
267
+ }
268
+
269
+ @keyframes shake {
270
+ 0%,
271
+ 100% {
272
+ transform: translateX(0);
273
+ }
274
+ 25% {
275
+ transform: translateX(-5px);
276
+ }
277
+ 75% {
278
+ transform: translateX(5px);
279
+ }
280
+ }
281
+
282
+ /* ================================================================================
283
+ CHAT DETAILS & MESSAGE CONTAINER
284
+ ================================================================================ */
285
+ #chat-details {
286
+ position: absolute;
287
+ top: 0;
288
+ left: 0;
289
+ width: 100%;
290
+ background-color: #ffffff;
291
+ height: 100%;
292
+ display: flex;
293
+ flex-direction: column;
294
+ flex: 1;
295
+ }
296
+
297
+ #chat-details .details-header {
298
+ padding: 12px 8px;
299
+ display: flex;
300
+ gap: 0.825rem;
301
+ align-items: center;
302
+ justify-content: space-between;
303
+ font-size: 1rem;
304
+ background-color: #ffffffff;
305
+ border-bottom: 1px solid #e7e7e7ff;
306
+ box-shadow: 0 2px 4px rgba(165, 165, 165, 0.1);
307
+ }
308
+
309
+ .details-header .title-header {
310
+ background-color: #fff;
311
+ color: black;
312
+ border-radius: 16px;
313
+ padding: 5px 12px;
314
+ font-weight: 600;
315
+ }
316
+
317
+ .details-header #btn-details-back {
318
+ background-color: #e0e0e0ff;
319
+ border: none;
320
+ border-radius: 50px;
321
+ cursor: pointer;
322
+ display: flex;
323
+ align-items: center;
324
+ justify-content: center;
325
+ font-size: 1rem;
326
+ color: black;
327
+ fill: black;
328
+ gap: 0.5rem;
329
+ white-space: nowrap;
330
+ padding: 0.25rem 0.55rem;
331
+ }
332
+
333
+ .details-header #btn-x {
334
+ background-color: #e0e0e0ff;
335
+ border: none;
336
+ border-radius: 50px;
337
+ height: 28px;
338
+ width: 28px;
339
+ cursor: pointer;
340
+ display: flex;
341
+ align-items: center;
342
+ justify-content: center;
343
+ font-size: 1rem;
344
+ color: black;
345
+ fill: black;
346
+ gap: 0.5rem;
347
+ white-space: nowrap;
348
+ }
349
+
350
+ .details-header button svg {
351
+ width: 20px;
352
+ height: 20px;
353
+ }
354
+
355
+ .details-header #btn-details-back:hover,
356
+ #btn-x:hover {
357
+ background-color: #cececeff;
358
+ }
359
+
360
+ #message-container {
361
+ scroll-behavior: smooth;
362
+ display: flex;
363
+ flex-direction: column;
364
+ padding: 10px 10px;
365
+ gap: 15px;
366
+ width: 100%;
367
+ height: 100%;
368
+ overflow-y: auto;
369
+ }
370
+
371
+ /* ================================================================================
372
+ MESSAGE STYLING
373
+ ================================================================================ */
374
+ .message {
375
+ display: flex;
376
+ }
377
+
378
+ .message p {
379
+ white-space: pre-wrap;
380
+ max-width: 80%;
381
+ padding: 10px;
382
+ border-radius: 12px;
383
+ margin: 0;
384
+ }
385
+
386
+ .partner .message {
387
+ margin-left: 10%;
388
+ justify-content: flex-start;
389
+ }
390
+
391
+ .me .message {
392
+ margin-right: 10%;
393
+ justify-content: flex-end;
394
+ }
395
+
396
+ .partner .message p {
397
+ background-color: rgb(240, 240, 240);
398
+ }
399
+
400
+ .me .message p {
401
+ background-color: rgba(75, 75, 218, 1);
402
+ color: white;
403
+ }
404
+
405
+ .avatar {
406
+ width: 35px;
407
+ height: 35px;
408
+ border-radius: 50%;
409
+ background-color: #cccccc;
410
+ display: flex;
411
+ justify-content: center;
412
+ align-items: center;
413
+ }
414
+
415
+ .info {
416
+ display: flex;
417
+ gap: 10px;
418
+ align-items: center;
419
+ }
420
+
421
+ .info .message-time {
422
+ font-size: 0.725rem;
423
+ color: gray;
424
+ font-style: italic;
425
+ }
426
+
427
+ .partner .info {
428
+ justify-content: flex-start;
429
+ }
430
+
431
+ .me .info {
432
+ justify-content: flex-end;
433
+ }
434
+
435
+ .partner .message-title {
436
+ display: flex;
437
+ flex-direction: column;
438
+ align-items: flex-start;
439
+ }
440
+
441
+ .me .message-title {
442
+ display: flex;
443
+ flex-direction: column;
444
+ align-items: flex-end;
445
+ }
446
+
447
+ /* ================================================================================
448
+ MESSAGE INPUT & SENDING
449
+ ================================================================================ */
450
+ .message-sending {
451
+ display: flex;
452
+ align-items: center;
453
+ padding: 10px;
454
+ gap: 10px;
455
+ border-top: 1px solid #e7e7e7ff;
456
+ }
457
+
458
+ .message-sending .message-content {
459
+ flex: 1;
460
+ display: flex;
461
+ align-items: center;
462
+ background-color: #e0e0e0ff;
463
+ border-radius: 50px;
464
+ padding: 10px 20px;
465
+ overflow-y: hidden;
466
+ }
467
+
468
+ .message-sending #message-input {
469
+ flex-grow: 1;
470
+ background-color: transparent;
471
+ border: none;
472
+ outline: none;
473
+ resize: none;
474
+ overflow: hidden;
475
+ font-size: 16px;
476
+ white-space: normal;
477
+ word-wrap: break-word;
478
+ }
479
+
480
+ .message-sending #message-input:focus {
481
+ outline: none;
482
+ }
483
+
484
+ .message-sending #btn-send-message {
485
+ background-color: #5b5bdfff;
486
+ border: none;
487
+ border-radius: 50px;
488
+ padding: 10px 10px;
489
+ font-size: 1rem;
490
+ cursor: pointer;
491
+ display: flex;
492
+ justify-content: center;
493
+ align-items: center;
494
+ }
495
+
496
+ .message-sending #btn-send-message svg {
497
+ width: 20px;
498
+ height: 20px;
499
+ fill: white;
500
+ }
501
+
502
+ .message-sending button:hover {
503
+ background-color: #4b4bda;
504
+ }
505
+
506
+ /* ================================================================================
507
+ EMOJI PICKER STYLING
508
+ ================================================================================ */
509
+ #emoji-button {
510
+ background: none;
511
+ border: none;
512
+ font-size: 24px;
513
+ cursor: pointer;
514
+ padding: 8px;
515
+ border-radius: 50%;
516
+ display: flex;
517
+ align-items: center;
518
+ justify-content: center;
519
+ }
520
+
521
+ #emoji-button:hover {
522
+ background-color: rgba(0, 0, 0, 0.1);
523
+ }
524
+
525
+ emoji-picker {
526
+ position: absolute;
527
+ bottom: 70px;
528
+ right: 15px;
529
+ display: none;
530
+ }
531
+
532
+ emoji-picker.show {
533
+ display: block;
534
+ }
535
+
536
+ /* ================================================================================
537
+ RESPONSIVE DESIGN - TABLET (768px and below)
538
+ ================================================================================ */
539
+ @media (max-width: 768px) {
540
+ .chat-container {
541
+ bottom: 15px;
542
+ left: 50%;
543
+ transform: translateX(-50%);
544
+ }
545
+
546
+ #chat-icon {
547
+ width: 55px;
548
+ height: 55px;
549
+ }
550
+
551
+ #chat-box {
552
+ left: 50%;
553
+ transform: translateX(-50%);
554
+ width: 80vw;
555
+ max-width: 500px;
556
+ height: 85vh;
557
+ top: auto;
558
+ bottom: 75px;
559
+ }
560
+
561
+ .chat-filter {
562
+ width: 90%;
563
+ }
564
+
565
+ .chat-filter input {
566
+ font-size: 0.95rem;
567
+ }
568
+
569
+ #info-input input {
570
+ width: 80%;
571
+ font-size: 1rem;
572
+ }
573
+
574
+ #info-input #turn-back-button,
575
+ #info-input #continue-button {
576
+ padding: 8px 16px;
577
+ font-size: 0.95rem;
578
+ }
579
+
580
+ .message p {
581
+ max-width: 85%;
582
+ font-size: 0.95rem;
583
+ }
584
+
585
+ .message-sending .message-content {
586
+ padding: 8px 15px;
587
+ }
588
+
589
+ .message-sending #message-input {
590
+ font-size: 0.95rem;
591
+ }
592
+
593
+ .details-header button {
594
+ padding: 0.2rem 0.45rem;
595
+ }
596
+
597
+ .details-header #btn-x {
598
+ height: 26px;
599
+ width: 26px;
600
+ }
601
+ }
602
+
603
+ /* ================================================================================
604
+ RESPONSIVE DESIGN - MOBILE (480px and below)
605
+ ================================================================================ */
606
+ @media (max-width: 480px) {
607
+ #chat-icon {
608
+ width: 50px;
609
+ height: 50px;
610
+ }
611
+
612
+ #chat-icon svg {
613
+ width: 30px;
614
+ height: 30px;
615
+ }
616
+
617
+ #chat-box {
618
+ width: 98vw;
619
+ height: 90vh;
620
+ bottom: 65px;
621
+ }
622
+
623
+ .chat-filter {
624
+ width: 95%;
625
+ padding-left: 10px;
626
+ }
627
+
628
+ .chat-filter input {
629
+ font-size: 0.9rem;
630
+ }
631
+
632
+ #info-input h3 {
633
+ font-size: 1.2rem;
634
+ }
635
+
636
+ #info-input input {
637
+ width: 85%;
638
+ font-size: 0.95rem;
639
+ padding: 8px 10px;
640
+ }
641
+
642
+ .message p {
643
+ max-width: 80%;
644
+ padding: 8px;
645
+ font-size: 0.9rem;
646
+ }
647
+
648
+ .avatar {
649
+ width: 30px;
650
+ height: 30px;
651
+ font-size: 0.8rem;
652
+ }
653
+
654
+ .info h5 {
655
+ font-size: 0.9rem;
656
+ }
657
+
658
+ .message-sending {
659
+ padding: 8px;
660
+ }
661
+
662
+ .message-sending #btn-send-message {
663
+ padding: 8px;
664
+ }
665
+ }
666
+ </style>
667
+
668
+ <div class="chat-container">
669
+ <div id="chat-icon">
670
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
671
+ <path
672
+ d="M64 304C64 358.4 83.3 408.6 115.9 448.9L67.1 538.3C65.1 542 64 546.2 64 550.5C64 564.6 75.4 576 89.5 576C93.5 576 97.3 575.4 101 573.9L217.4 524C248.8 536.9 283.5 544 320 544C461.4 544 576 436.5 576 304C576 171.5 461.4 64 320 64C178.6 64 64 171.5 64 304zM158 471.9C167.3 454.8 165.4 433.8 153.2 418.7C127.1 386.4 112 346.8 112 304C112 200.8 202.2 112 320 112C437.8 112 528 200.8 528 304C528 407.2 437.8 496 320 496C289.8 496 261.3 490.1 235.7 479.6C223.8 474.7 210.4 474.8 198.6 479.9L140 504.9L158 471.9zM208 336C225.7 336 240 321.7 240 304C240 286.3 225.7 272 208 272C190.3 272 176 286.3 176 304C176 321.7 190.3 336 208 336zM352 304C352 286.3 337.7 272 320 272C302.3 272 288 286.3 288 304C288 321.7 302.3 336 320 336C337.7 336 352 321.7 352 304zM432 336C449.7 336 464 321.7 464 304C464 286.3 449.7 272 432 272C414.3 272 400 286.3 400 304C400 321.7 414.3 336 432 336z"
673
+ />
674
+ </svg>
675
+ </div>
676
+ <div id="chat-box">
677
+ <div id="chat-box-header">
678
+ <div class="chat-filter">
679
+ <input
680
+ id="search-chat-input"
681
+ type="text"
682
+ placeholder="Tìm kiếm đối tác..."
683
+ />
684
+ <svg
685
+ id="search-chat-icon"
686
+ xmlns="http://www.w3.org/2000/svg"
687
+ viewBox="0 0 640 640"
688
+ >
689
+ <path
690
+ d="M480 272C480 317.9 465.1 360.3 440 394.7L566.6 521.4C579.1 533.9 579.1 554.2 566.6 566.7C554.1 579.2 533.8 579.2 521.3 566.7L394.7 440C360.3 465.1 317.9 480 272 480C157.1 480 64 386.9 64 272C64 157.1 157.1 64 272 64C386.9 64 480 157.1 480 272zM272 416C351.5 416 416 351.5 416 272C416 192.5 351.5 128 272 128C192.5 128 128 192.5 128 272C128 351.5 192.5 416 272 416z"
691
+ />
692
+ </svg>
693
+ </div>
694
+ </div>
695
+ <div class="chat-box-container">
696
+ <!-- Chat list items -->
697
+ <div id="list-items">
698
+ <ul></ul>
699
+ </div>
700
+
701
+ <!-- Chat info input -->
702
+ <div id="info-input">
703
+ <div class="info-header">
704
+ <h4></h4>
705
+ </div>
706
+ <h3>Nhập thông tin chat</h3>
707
+ <input id="input-shsbl" type="text" placeholder="Nhập số HSBL" />
708
+ <input
709
+ id="input-username"
710
+ type="text"
711
+ placeholder="Nhập họ và tên"
712
+ />
713
+ <div id="info-input-buttons">
714
+ <button id="turn-back-button">
715
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
716
+ <path
717
+ d="M73.4 297.4C60.9 309.9 60.9 330.2 73.4 342.7L233.4 502.7C245.9 515.2 266.2 515.2 278.7 502.7C291.2 490.2 291.2 469.9 278.7 457.4L173.3 352L544 352C561.7 352 576 337.7 576 320C576 302.3 561.7 288 544 288L173.3 288L278.7 182.6C291.2 170.1 291.2 149.8 278.7 137.3C266.2 124.8 245.9 124.8 233.4 137.3L73.4 297.3z"
718
+ />
719
+ </svg>
720
+ Quay lại
721
+ </button>
722
+
723
+ <button id="continue-button">
724
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
725
+ <path
726
+ d="M566.6 342.6C579.1 330.1 579.1 309.8 566.6 297.3L406.6 137.3C394.1 124.8 373.8 124.8 361.3 137.3C348.8 149.8 348.8 170.1 361.3 182.6L466.7 288L96 288C78.3 288 64 302.3 64 320C64 337.7 78.3 352 96 352L466.7 352L361.3 457.4C348.8 469.9 348.8 490.2 361.3 502.7C373.8 515.2 394.1 515.2 406.6 502.7L566.6 342.7z"
727
+ />
728
+ </svg>
729
+ Tiếp tục
730
+ </button>
731
+ </div>
732
+ <div>
733
+ <p id="error-info-input"></p>
734
+ </div>
735
+ </div>
736
+ </div>
737
+
738
+ <!-- Chat message detail -->
739
+ <div id="chat-details">
740
+ <div class="details-header">
741
+ <button id="btn-details-back">
742
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
743
+ <path
744
+ d="M73.4 297.4C60.9 309.9 60.9 330.2 73.4 342.7L233.4 502.7C245.9 515.2 266.2 515.2 278.7 502.7C291.2 490.2 291.2 469.9 278.7 457.4L173.3 352L544 352C561.7 352 576 337.7 576 320C576 302.3 561.7 288 544 288L173.3 288L278.7 182.6C291.2 170.1 291.2 149.8 278.7 137.3C266.2 124.8 245.9 124.8 233.4 137.3L73.4 297.3z"
745
+ />
746
+ </svg>
747
+ Quay lại
748
+ </button>
749
+ <button id="btn-x">X</button>
750
+ </div>
751
+
752
+ <!-- Message chat details -->
753
+ <div id="message-container"></div>
754
+
755
+ <!-- Message input -->
756
+ <div class="message-sending">
757
+ <div class="message-content">
758
+ <textarea
759
+ id="message-input"
760
+ rows="1"
761
+ placeholder="Nhập tin nhắn..."
762
+ ></textarea>
763
+ </div>
764
+ <button id="emoji-button">😊</button>
765
+ <button id="btn-send-message">
766
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
767
+ <path
768
+ d="M322.5 351.7L523.4 150.9L391 520.3L322.5 351.7zM489.4 117L288.6 317.8L120 249.3L489.4 117zM70.1 280.8L275.9 364.4L359.5 570.2C364.8 583.3 377.6 591.9 391.8 591.9C406.5 591.9 419.6 582.7 424.6 568.8L602.6 72C606.1 62.2 603.6 51.4 596.3 44C589 36.6 578.1 34.2 568.3 37.7L71.4 215.7C57.5 220.7 48.3 233.8 48.3 248.5C48.3 262.7 56.9 275.5 70 280.8z"
769
+ />
770
+ </svg>
771
+ </button>
772
+ </div>
773
+ </div>
774
+ </div>
775
+ </div>
776
+ `;
777
+
778
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
779
+ }
780
+
781
+ initLogic() {
782
+ // Toàn bộ logic từ script.js, điều chỉnh querySelector thành shadowRoot
783
+ const shadowRoot = this.shadowRoot;
784
+
785
+ // ================== CONFIGURATION ==================
786
+ const BASE_URL = this.getAttribute('base-url');
787
+ const PAGE_SIZE = 10;
788
+ const SCROLL_THRESHOLD = 20;
789
+ const SCROLL_LOAD_MORE_THRESHOLD = 100;
790
+ const DEBOUNCE_DELAY = 200;
791
+
792
+ // ================== USER INPUT ==================
793
+ let token = prompt("Nhập access token của bạn:", "");
794
+ if (!token) token = "Anonymous";
795
+
796
+ let userId = prompt("Nhập mã của bạn:", "");
797
+ if (!userId) userId = "Anonymous";
798
+
799
+ let fullName = prompt("Nhập tên đầy đủ của bạn:");
800
+ if (!fullName) fullName = "Khách hàng";
801
+
802
+ let ePartnerCode = prompt("Nhập ePartnerCode của bạn:", "BENH_VIEN");
803
+ if (!ePartnerCode) ePartnerCode = "PORTAL";
804
+
805
+ // ================== STATE MANAGEMENT ==================
806
+ let selectedChat = null;
807
+ let currentPage = 1;
808
+ let isLoading = false;
809
+ let hasMoreMessages = true;
810
+
811
+ let chatListCurrentPage = 1;
812
+ let chatListIsLoading = false;
813
+ let chatListHasMore = true;
814
+ let currentSearchTerm = "";
815
+
816
+ let chatBoxContainerDisplay = true;
817
+ let chatBoxListContainerDisplay = false;
818
+ let chatBoxInputInfoDisplay = true;
819
+ let chatDetailsDisplay = true;
820
+
821
+ // ================== DOM ELEMENTS ==================
822
+ const listEl = shadowRoot.querySelector("#list-items ul");
823
+ const infoTitle = shadowRoot.querySelector('#info-input .info-header h4');
824
+ const chatBoxIcon = shadowRoot.querySelector('#chat-icon');
825
+ const chatBoxContainer = shadowRoot.querySelector('#chat-box');
826
+ const chatBoxFilter = shadowRoot.querySelector('#chat-box-header');
827
+ const searchChatInput = shadowRoot.querySelector('#search-chat-input');
828
+ const searchChatIcon = shadowRoot.querySelector('#search-chat-icon');
829
+ const chatBoxListContainer = shadowRoot.querySelector('#list-items');
830
+ const chatBoxInputInfo = shadowRoot.querySelector('#info-input');
831
+ const turnBackButton = shadowRoot.querySelector('#turn-back-button');
832
+ const continueButton = shadowRoot.querySelector('#continue-button');
833
+ const chatDetails = shadowRoot.querySelector('#chat-details');
834
+ const btnX = shadowRoot.querySelector('#btn-x');
835
+ const detailsBackButton = shadowRoot.querySelector('#btn-details-back');
836
+ const messageContainer = shadowRoot.querySelector('#message-container');
837
+ const hsblInput = shadowRoot.querySelector('#input-shsbl');
838
+ const userNameInput = shadowRoot.querySelector('#input-username');
839
+ const errorInfoInput = shadowRoot.querySelector('#error-info-input');
840
+ const btnSendMessage = shadowRoot.querySelector('#btn-send-message');
841
+ const textarea = shadowRoot.getElementById('message-input');
842
+ const emojiButton = shadowRoot.getElementById('emoji-button');
843
+ const messageInput = shadowRoot.getElementById('message-input');
844
+
845
+ userNameInput.value = fullName;
846
+
847
+ // ================== UTILITY FUNCTIONS ==================
848
+ function parseCustomDateString(str) {
849
+ // Expected format: dd/MM/yyyy HH:mm:ss
850
+ const [date, time] = str.split(' ');
851
+ const [day, month, year] = date.split('/');
852
+ return new Date(`${year}-${month}-${day}T${time}`);
853
+ }
854
+
855
+ function debounce(func, wait) {
856
+ let timeout;
857
+ return function executedFunction(...args) {
858
+ const later = () => {
859
+ clearTimeout(timeout);
860
+ func(...args);
861
+ };
862
+ clearTimeout(timeout);
863
+ timeout = setTimeout(later, wait);
864
+ };
865
+ }
866
+
867
+ function autoResize() {
868
+ this.style.height = 'auto';
869
+ this.style.height = this.scrollHeight + 'px';
870
+ }
871
+
872
+ function scrollToBottom() {
873
+ if (messageContainer) {
874
+ messageContainer.scrollTop = messageContainer.scrollHeight;
875
+ }
876
+ }
877
+
878
+ // ================== CHAT LIST FUNCTIONS ==================
879
+ function renderChatList(items, append = false) {
880
+ const htmlContent = items
881
+ .map(item => `
882
+ <li data-id="${item.MA}">
883
+ <img class="logo-item" src="${item.LINK_LOGO}" alt="Logo" />
884
+ <h4 class="title-item">${item.TEN}</h4>
885
+ </li>
886
+ `)
887
+ .join("");
888
+
889
+ if (append) {
890
+ listEl.innerHTML += htmlContent;
891
+ } else {
892
+ listEl.innerHTML = htmlContent;
893
+ }
894
+ }
895
+
896
+ async function ensureChatListFilled() {
897
+ const scrollHeight = chatBoxListContainer.scrollHeight;
898
+ const clientHeight = chatBoxListContainer.clientHeight;
899
+
900
+ if (scrollHeight <= clientHeight && chatListHasMore && !chatListIsLoading) {
901
+ console.log('Danh sách chưa đủ để scroll, load thêm...');
902
+ await loadMoreChatList();
903
+ setTimeout(() => ensureChatListFilled(), 100);
904
+ }
905
+ }
906
+
907
+ async function loadChatList(searchTerm, page = 1, append = false) {
908
+ try {
909
+ const body = {
910
+ trang: page,
911
+ so_dong: PAGE_SIZE,
912
+ nd_tim: searchTerm || ""
913
+ };
914
+
915
+ const header = {
916
+ ConnectionId: connection.connectionId || "",
917
+ eAction: "OO78LLZ0T7NLEZ3",
918
+ };
919
+
920
+ const response = await fetch(`${BASE_URL}api/chat/execute`, {
921
+ method: 'POST',
922
+ headers: header,
923
+ body: JSON.stringify(body)
924
+ });
925
+
926
+ if (!response.ok) throw new Error('Failed to fetch chat list');
927
+
928
+ const data = await response.json();
929
+
930
+ // Check if there are more items
931
+ if (data.data.length === 0 || data.data.length >= data.output.tong_so_dong) {
932
+ chatListHasMore = false;
933
+ } else {
934
+ chatListHasMore = true;
935
+ }
936
+
937
+ renderChatList(data.data, append);
938
+ wireChatItemClicks();
939
+
940
+ // Kiểm tra và load thêm nếu cần (chỉ ở lần load đầu tiên)
941
+ if (page === 1 && !append) {
942
+ setTimeout(() => ensureChatListFilled(), 50);
943
+ }
944
+
945
+ return data.data.length;
946
+ } catch (error) {
947
+ console.error('Error loading chat list:', error);
948
+ if (!append) {
949
+ listEl.innerHTML = '<li style="padding: 20px; text-align: center;">Không thể tải danh sách chat</li>';
950
+ }
951
+ return 0;
952
+ }
953
+ }
954
+
955
+ async function loadMoreChatList() {
956
+ if (chatListIsLoading || !chatListHasMore) {
957
+ return;
958
+ }
959
+
960
+ chatListIsLoading = true;
961
+
962
+ try {
963
+ chatListCurrentPage++;
964
+ const count = await loadChatList(currentSearchTerm, chatListCurrentPage, true);
965
+
966
+ if (count === 0) {
967
+ chatListHasMore = false;
968
+ console.log('Đã tải hết danh sách chat');
969
+ }
970
+ } catch (error) {
971
+ console.error('Lỗi khi load thêm chat list:', error);
972
+ chatListCurrentPage--;
973
+ } finally {
974
+ chatListIsLoading = false;
975
+ }
976
+ }
977
+
978
+ function wireChatItemClicks() {
979
+ const chatBoxListItem = shadowRoot.querySelectorAll('#list-items li');
980
+ chatBoxListItem.forEach((item) => {
981
+ item.addEventListener('click', function () {
982
+ if (chatBoxInputInfo) {
983
+ chatBoxListContainerDisplay = !chatBoxListContainerDisplay;
984
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
985
+ chatBoxFilter.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
986
+
987
+ chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
988
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
989
+
990
+ const { id } = item.dataset;
991
+ const name = item.querySelector('.title-item')?.textContent?.trim() || '';
992
+
993
+ selectedChat = { id, name };
994
+ infoTitle.textContent = name || 'Không tìm thấy cuộc trò chuyện!';
995
+ userNameInput.value = fullName;
996
+ }
997
+ });
998
+ });
999
+ }
1000
+
1001
+ // ================== MESSAGE HISTORY FUNCTIONS ==================
1002
+ async function loadMessageHistory(groupName, page = 1) {
1003
+ try {
1004
+ const body = {
1005
+ trang: page,
1006
+ so_dong: PAGE_SIZE,
1007
+ nhom: groupName || ""
1008
+ };
1009
+
1010
+ const header = {
1011
+ ConnectionId: connection.connectionId || "",
1012
+ eAction: "P75EVW99A84IGNK",
1013
+ };
1014
+
1015
+ const response = await fetch(`${BASE_URL}api/chat/execute`, {
1016
+ method: 'POST',
1017
+ headers: header,
1018
+ body: JSON.stringify(body)
1019
+ });
1020
+
1021
+ if (!response.ok) throw new Error('Failed to fetch chat list');
1022
+
1023
+ const data = await response.json();
1024
+
1025
+ // Sort messages by NGAY_GUI ascending
1026
+ console.log('Loaded messages:', data.data);
1027
+ const sortedMessages = data.data.sort((a, b) => {
1028
+ const dateA = parseCustomDateString(a.NGAY_GUI);
1029
+ const dateB = parseCustomDateString(b.NGAY_GUI);
1030
+ return dateA - dateB;
1031
+ });
1032
+ console.log('Sorted messages:', sortedMessages);
1033
+
1034
+ return sortedMessages;
1035
+ } catch {
1036
+ return [];
1037
+ }
1038
+ }
1039
+
1040
+ async function appendMessageHistory(messages, prepend = false) {
1041
+ const fragment = document.createDocumentFragment();
1042
+
1043
+ for (const msg of messages) {
1044
+ const isMe = userId === msg.MA_DOI_TAC_NSD;
1045
+ const msgDiv = document.createElement("div");
1046
+ msgDiv.classList.add(isMe ? 'me' : 'partner');
1047
+ msgDiv.innerHTML = `
1048
+ <div class="info">
1049
+ ${isMe ?
1050
+ `<div class="message-title">
1051
+ <h5>${msg.TEN}</h5>
1052
+ <p class="message-time">${msg.NGAY_GUI}</p>
1053
+ </div>
1054
+ <img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />`
1055
+ :
1056
+ `<img class="logo-item" src="${msg.LINK_LOGO}" alt="Logo" />
1057
+ <div class="message-title">
1058
+ <h5>${msg.TEN}</h5>
1059
+ <p class="message-time">${msg.NGAY_GUI}</p>
1060
+ </div>`
1061
+ }
1062
+ </div>
1063
+ <div class="message">
1064
+ <p>${msg.NOI_DUNG_TIN_NHAN}</p>
1065
+ </div>
1066
+ `;
1067
+ fragment.appendChild(msgDiv);
1068
+ }
1069
+
1070
+ if (prepend) {
1071
+ messageContainer.insertBefore(fragment, messageContainer.firstChild);
1072
+ } else {
1073
+ messageContainer.appendChild(fragment);
1074
+ }
1075
+ }
1076
+
1077
+ async function loadMoreMessages() {
1078
+ if (isLoading || !hasMoreMessages || !selectedChat) {
1079
+ return;
1080
+ }
1081
+
1082
+ isLoading = true;
1083
+
1084
+ const oldScrollHeight = messageContainer.scrollHeight;
1085
+ const oldScrollTop = messageContainer.scrollTop;
1086
+
1087
+ try {
1088
+ currentPage++;
1089
+ const messages = await loadMessageHistory(selectedChat.hsbl, currentPage);
1090
+
1091
+ if (messages.length === 0) {
1092
+ hasMoreMessages = false;
1093
+ console.log('Đã tải hết tin nhắn');
1094
+ return;
1095
+ }
1096
+
1097
+ await appendMessageHistory(messages, true);
1098
+
1099
+ const newScrollHeight = messageContainer.scrollHeight;
1100
+ messageContainer.scrollTop = newScrollHeight - oldScrollHeight + oldScrollTop;
1101
+
1102
+ } catch (error) {
1103
+ console.error('Lỗi khi load thêm tin nhắn:', error);
1104
+ currentPage--;
1105
+ } finally {
1106
+ isLoading = false;
1107
+ }
1108
+ }
1109
+
1110
+ // ================== VALIDATION FUNCTIONS ==================
1111
+ function validateInfoInputs() {
1112
+ let isValid = true;
1113
+
1114
+ if (!hsblInput.value.trim()) {
1115
+ hsblInput.classList.add('error');
1116
+ isValid = false;
1117
+ } else {
1118
+ hsblInput.classList.remove('error');
1119
+ }
1120
+
1121
+ if (!userNameInput.value.trim()) {
1122
+ userNameInput.classList.add('error');
1123
+ isValid = false;
1124
+ } else {
1125
+ userNameInput.classList.remove('error');
1126
+ }
1127
+
1128
+ return isValid;
1129
+ }
1130
+
1131
+ function validateContractId(contractId) {
1132
+ return fetch(`${BASE_URL}api/chat/execute`)
1133
+ .then(response => {
1134
+ if (!response.ok) {
1135
+ errorInfoInput.textContent = `Mã hợp đồng không tồn tại.`;
1136
+ return false;
1137
+ }
1138
+ return true;
1139
+ })
1140
+ .catch(() => {
1141
+ errorInfoInput.textContent = `Hệ thống lỗi.`;
1142
+ return false;
1143
+ });
1144
+ }
1145
+
1146
+ // ================== EVENT LISTENERS ==================
1147
+ // Auto resize textarea
1148
+ textarea.addEventListener('input', autoResize, false);
1149
+
1150
+ // Scroll handlers
1151
+ const handleScroll = debounce(function () {
1152
+ if (messageContainer.scrollTop < SCROLL_THRESHOLD) {
1153
+ loadMoreMessages();
1154
+ }
1155
+ }, DEBOUNCE_DELAY);
1156
+
1157
+ messageContainer.addEventListener('scroll', handleScroll);
1158
+
1159
+ const handleChatListScroll = debounce(function () {
1160
+ console.log('Scroll chat list:', chatBoxListContainer.scrollTop);
1161
+ const scrollTop = chatBoxListContainer.scrollTop;
1162
+ const scrollHeight = chatBoxListContainer.scrollHeight;
1163
+ const clientHeight = chatBoxListContainer.clientHeight;
1164
+
1165
+ if (scrollTop + clientHeight >= scrollHeight - SCROLL_LOAD_MORE_THRESHOLD) {
1166
+ loadMoreChatList();
1167
+ }
1168
+ }, DEBOUNCE_DELAY);
1169
+
1170
+ chatBoxListContainer.addEventListener('scroll', handleChatListScroll);
1171
+
1172
+ // Input validation listeners
1173
+ hsblInput.addEventListener('input', function () {
1174
+ if (this.value.trim()) {
1175
+ this.classList.remove('error');
1176
+ }
1177
+ });
1178
+
1179
+ userNameInput.addEventListener('input', function () {
1180
+ if (this.value.trim()) {
1181
+ this.classList.remove('error');
1182
+ }
1183
+ });
1184
+
1185
+ // Display initialization
1186
+ chatBoxContainer.style.display = chatBoxContainerDisplay ? 'none' : 'flex';
1187
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1188
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1189
+ chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1190
+
1191
+ // Chat box icon click
1192
+ chatBoxIcon.addEventListener('click', function () {
1193
+ if (chatBoxContainer) {
1194
+ chatListCurrentPage = 1;
1195
+ chatListIsLoading = false;
1196
+ currentSearchTerm = "";
1197
+ searchChatInput.value = '';
1198
+
1199
+ loadChatList();
1200
+ chatBoxContainerDisplay = !chatBoxContainerDisplay;
1201
+ chatBoxContainer.style.display = chatBoxContainerDisplay ? 'none' : 'flex';
1202
+ }
1203
+ });
1204
+
1205
+ // Close button
1206
+ btnX.addEventListener('click', function () {
1207
+ if (chatBoxContainer) {
1208
+ chatBoxContainerDisplay = true;
1209
+ chatBoxContainer.style.display = 'none';
1210
+ }
1211
+ });
1212
+
1213
+ // Search handlers
1214
+ searchChatIcon.addEventListener('click', function () {
1215
+ const searchTerm = searchChatInput.value.trim();
1216
+ currentSearchTerm = searchTerm;
1217
+
1218
+ chatListCurrentPage = 1;
1219
+ chatListIsLoading = false;
1220
+
1221
+ loadChatList(currentSearchTerm);
1222
+ });
1223
+
1224
+ searchChatInput.addEventListener('keypress', function (event) {
1225
+ if (event.key === 'Enter') {
1226
+ const searchTerm = searchChatInput.value.trim();
1227
+ currentSearchTerm = searchTerm;
1228
+
1229
+ chatListCurrentPage = 1;
1230
+ chatListIsLoading = false;
1231
+
1232
+ loadChatList(currentSearchTerm);
1233
+ }
1234
+ });
1235
+
1236
+ // Turn back button
1237
+ turnBackButton.addEventListener('click', function () {
1238
+ if (chatBoxInputInfo) {
1239
+ chatBoxListContainerDisplay = !chatBoxListContainerDisplay;
1240
+ chatBoxListContainer.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1241
+ chatBoxFilter.style.display = chatBoxListContainerDisplay ? 'none' : 'flex';
1242
+
1243
+ chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1244
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1245
+
1246
+ userNameInput.classList.remove('error');
1247
+ hsblInput.classList.remove('error');
1248
+ errorInfoInput.textContent = '';
1249
+ hsblInput.value = '';
1250
+ }
1251
+ });
1252
+
1253
+ // Continue button
1254
+ continueButton.addEventListener('click', async function () {
1255
+ if (chatDetails) {
1256
+ if (!validateInfoInputs()) {
1257
+ return;
1258
+ }
1259
+
1260
+ if (selectedChat) {
1261
+ selectedChat.hsbl = hsblInput.value.trim();
1262
+ selectedChat.userName = userNameInput.value.trim();
1263
+ }
1264
+
1265
+ errorInfoInput.textContent = '';
1266
+
1267
+ // Join group
1268
+ connection.invoke("JoinGroupAsync", selectedChat.hsbl).then(() => {
1269
+ console.log(`Đã join nhóm: ${selectedChat.hsbl}`);
1270
+ }).catch(err => console.error(err.toString()));
1271
+
1272
+ var messages = await loadMessageHistory(selectedChat.hsbl, 1);
1273
+ await appendMessageHistory(messages);
1274
+
1275
+ chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1276
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1277
+
1278
+ chatDetailsDisplay = !chatDetailsDisplay;
1279
+ chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1280
+
1281
+ setTimeout(scrollToBottom, 0);
1282
+ }
1283
+ });
1284
+
1285
+ // Details back button
1286
+ detailsBackButton.addEventListener('click', function () {
1287
+ if (chatDetails) {
1288
+ // Leave group
1289
+ connection.invoke("RemoveFromGroupAsync", selectedChat.hsbl).then(() => {
1290
+ console.log(`Đã rời nhóm: ${selectedChat.hsbl}`);
1291
+ }).catch(err => console.error(err.toString()));
1292
+
1293
+ // Reset state before go back
1294
+ currentPage = 1;
1295
+ hasMoreMessages = true;
1296
+ isLoading = false;
1297
+ messageContainer.innerHTML = '';
1298
+
1299
+ chatBoxInputInfoDisplay = !chatBoxInputInfoDisplay;
1300
+ chatBoxInputInfo.style.display = chatBoxInputInfoDisplay ? 'none' : 'flex';
1301
+
1302
+ chatDetailsDisplay = !chatDetailsDisplay;
1303
+ chatDetails.style.display = chatDetailsDisplay ? 'none' : 'flex';
1304
+ }
1305
+ });
1306
+
1307
+ // Send message button
1308
+ btnSendMessage.addEventListener('click', async function () {
1309
+ const message = shadowRoot.getElementById('message-input').value;
1310
+ console.log('Gửi tin nhắn:', message);
1311
+ if (message) {
1312
+ try {
1313
+ await connection.invoke("SendMessageToGroupAsync", selectedChat.hsbl, message, userNameInput.value.trim());
1314
+ shadowRoot.getElementById("message-input").value = "";
1315
+
1316
+ const textarea = shadowRoot.getElementById('message-input');
1317
+ textarea.style.height = 'auto';
1318
+ } catch (err) {
1319
+ console.error("Lỗi gửi tin nhắn:", err);
1320
+ }
1321
+ }
1322
+ });
1323
+
1324
+ // // ================== EMOJI PICKER ==================
1325
+ // // Tạo picker động (chỉ tạo một lần)
1326
+ // const picker = document.createElement('emoji-picker');
1327
+
1328
+ // // Thêm picker vào DOM (dưới message-sending để popup đúng vị trí)
1329
+ // shadowRoot.querySelector('.message-sending').appendChild(picker);
1330
+
1331
+ // // Xử lý chọn emoji → chèn vào textarea
1332
+ // picker.addEventListener('emoji-click', (event) => {
1333
+ // const emoji = event.detail.unicode;
1334
+
1335
+ // const start = messageInput.selectionStart;
1336
+ // const end = messageInput.selectionEnd;
1337
+ // const text = messageInput.value;
1338
+
1339
+ // messageInput.value = text.substring(0, start) + emoji + text.substring(end);
1340
+
1341
+ // const newPos = start + emoji.length;
1342
+ // messageInput.setSelectionRange(newPos, newPos);
1343
+ // messageInput.focus();
1344
+
1345
+ // picker.classList.remove('show');
1346
+
1347
+ // messageInput.dispatchEvent(new Event('input'));
1348
+ // });
1349
+
1350
+ // // Toggle hiển thị picker khi click nút emoji
1351
+ // emojiButton.addEventListener('click', (e) => {
1352
+ // e.stopPropagation();
1353
+ // picker.classList.toggle('show');
1354
+ // });
1355
+
1356
+ // // Đóng picker khi click bên ngoài
1357
+ // document.addEventListener('click', (e) => {
1358
+ // if (!picker.contains(e.target) && e.target !== emojiButton) {
1359
+ // picker.classList.remove('show');
1360
+ // }
1361
+ // });
1362
+
1363
+ // ================== SIGNALR CONNECTION ==================
1364
+ const connection = new signalR.HubConnectionBuilder()
1365
+ .withUrl(`${BASE_URL}chatHub?token=${token}&ePartnerCode=${ePartnerCode}`, {
1366
+ withCredentials: false,
1367
+ })
1368
+ .configureLogging(signalR.LogLevel.Information)
1369
+ .withAutomaticReconnect()
1370
+ .build();
1371
+
1372
+ // Nhận tin nhắn từ server
1373
+ connection.on("ReceiveGroupMessage", (data) => {
1374
+ console.log('Tin nhắn nhận được:', data);
1375
+ const isMe = userId === data.ma_doi_tac_nsd;
1376
+ const msg = document.createElement("div");
1377
+ msg.classList.add(isMe ? 'me' : 'partner');
1378
+ msg.innerHTML = `
1379
+ <div class="info">
1380
+ ${isMe ?
1381
+ `<div class="message-title">
1382
+ <h5>${data.ten}</h5>
1383
+ <p class="message-time">${data.ngay_gui}</p>
1384
+ </div>
1385
+ <img class="logo-item" src="${data.link_logo}" alt="Logo" />`
1386
+ :
1387
+ `<img class="logo-item" src="${data.link_logo}" alt="Logo" />
1388
+ <div class="message-title">
1389
+ <h5>${data.ten}</h5>
1390
+ <p class="message-time">${data.ngay_gui}</p>
1391
+ </div>`
1392
+ }
1393
+ </div>
1394
+ <div class="message">
1395
+ <p>${data.noi_dung_tin_nhan}</p>
1396
+ </div>
1397
+ `;
1398
+ shadowRoot.getElementById("message-container").appendChild(msg);
1399
+ scrollToBottom();
1400
+ });
1401
+
1402
+ // Start connection
1403
+ connection
1404
+ .start()
1405
+ .then(() => {
1406
+ console.log("Kết nối SignalR thành công!");
1407
+ })
1408
+ .catch((err) => console.error("Lỗi kết nối SignalR:", err));
1409
+ // ================== END OF FILE ==================
1410
+ }
1411
+
1412
+ async initSignalR() {
1413
+ const signalR = window.signalR || await this.loadSignalR();
1414
+
1415
+ this.connection = new signalR.HubConnectionBuilder()
1416
+ .withUrl(`${BASE_URL}chatHub`, { withCredentials: false })
1417
+ .withAutomaticReconnect()
1418
+ .configureLogging(signalR.LogLevel.Information)
1419
+ .build();
1420
+
1421
+ // Thêm các on listener nếu cần từ script.js, nhưng script.js dùng connection riêng
1422
+
1423
+ try {
1424
+ await this.connection.start();
1425
+ console.log("SignalR Connected.");
1426
+ } catch (err) {
1427
+ console.error("SignalR Connection Error: ", err);
1428
+ }
1429
+ }
1430
+
1431
+ loadSignalR() {
1432
+ return new Promise((resolve, reject) => {
1433
+ if (typeof signalR !== "undefined") return resolve();
1434
+
1435
+ const script = document.createElement('script');
1436
+ script.src = "https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js";
1437
+ script.onload = () => {
1438
+ console.log("SignalR đã tải xong.");
1439
+ resolve();
1440
+ };
1441
+ script.onerror = reject;
1442
+ document.head.appendChild(script);
1443
+ });
1444
+ }
1445
+
1446
+ setupEmojiPickerAfterLoad() {
1447
+ const shadow = this.shadowRoot;
1448
+ const messageSending = shadow.querySelector('.message-sending');
1449
+ const emojiButton = shadow.querySelector('#emoji-button');
1450
+ const messageInput = shadow.querySelector('#message-input');
1451
+
1452
+ if (!messageSending || !emojiButton || !messageInput) return;
1453
+
1454
+ // Tạo emoji-picker element
1455
+ let picker = messageSending.querySelector('emoji-picker');
1456
+ if (!picker) {
1457
+ picker = document.createElement('emoji-picker');
1458
+ messageSending.appendChild(picker);
1459
+ }
1460
+
1461
+ // Xử lý chọn emoji
1462
+ picker.addEventListener('emoji-click', (event) => {
1463
+ const emoji = event.detail.unicode;
1464
+ const start = messageInput.selectionStart;
1465
+ const end = messageInput.selectionEnd;
1466
+ messageInput.value = messageInput.value.substring(0, start) + emoji + messageInput.value.substring(end);
1467
+ const newPos = start + emoji.length;
1468
+ messageInput.setSelectionRange(newPos, newPos);
1469
+ messageInput.focus();
1470
+ picker.style.display = 'none';
1471
+ messageInput.dispatchEvent(new Event('input')); // resize textarea
1472
+ });
1473
+
1474
+ // Toggle picker
1475
+ emojiButton.addEventListener('click', (e) => {
1476
+ e.stopPropagation();
1477
+ picker.style.display = picker.style.display === 'block' ? 'none' : 'block';
1478
+ });
1479
+
1480
+ // Đóng khi click ngoài
1481
+ document.addEventListener('click', (e) => {
1482
+ if (!picker.contains(e.target) && e.target !== emojiButton) {
1483
+ picker.style.display = 'none';
1484
+ }
1485
+ });
1486
+ }
1487
+ }
1488
+
1489
+ customElements.define('chat-widget', ChatWidget);