tas-uell-sdk 0.1.3 → 0.2.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.
- package/README.md +1 -0
- package/esm2020/lib/components/tas-btn/tas-btn.component.mjs +29 -16
- package/esm2020/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.mjs +1 -1
- package/esm2020/lib/components/tas-videocall/tas-videocall.component.mjs +126 -41
- package/esm2020/lib/components/tas-waiting-room/tas-waiting-room.component.mjs +135 -34
- package/esm2020/lib/icons/tas-icons.mjs +17 -0
- package/esm2020/lib/interfaces/tas.interfaces.mjs +8 -1
- package/esm2020/lib/services/tas.service.mjs +88 -26
- package/fesm2015/tas-uell-sdk.mjs +397 -112
- package/fesm2015/tas-uell-sdk.mjs.map +1 -1
- package/fesm2020/tas-uell-sdk.mjs +395 -111
- package/fesm2020/tas-uell-sdk.mjs.map +1 -1
- package/lib/components/tas-btn/tas-btn.component.d.ts +3 -1
- package/lib/components/tas-videocall/tas-videocall.component.d.ts +42 -5
- package/lib/components/tas-waiting-room/tas-waiting-room.component.d.ts +32 -13
- package/lib/icons/tas-icons.d.ts +8 -0
- package/lib/interfaces/tas.interfaces.d.ts +19 -3
- package/lib/services/tas.service.d.ts +13 -2
- package/package.json +1 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Input, ViewChild, EventEmitter, Output, NgModule } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject, Subscription } from 'rxjs';
|
|
4
|
-
import { map, catchError } from 'rxjs/operators';
|
|
3
|
+
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
|
4
|
+
import { map, catchError, finalize, shareReplay } from 'rxjs/operators';
|
|
5
5
|
import * as OT from '@opentok/client';
|
|
6
6
|
import { __awaiter } from 'tslib';
|
|
7
7
|
import interact from 'interactjs';
|
|
8
8
|
import * as i1 from '@ng-bootstrap/ng-bootstrap';
|
|
9
9
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
|
10
|
-
import * as i4 from '@angular/
|
|
10
|
+
import * as i4 from '@angular/platform-browser';
|
|
11
|
+
import * as i4$1 from '@angular/common';
|
|
11
12
|
import { CommonModule } from '@angular/common';
|
|
12
13
|
import { FormsModule } from '@angular/forms';
|
|
13
14
|
|
|
@@ -67,7 +68,14 @@ var UserCallAction;
|
|
|
67
68
|
UserCallAction["CHANGE_STATUS"] = "CHANGE_STATUS";
|
|
68
69
|
UserCallAction["REQUEST_GEOLOCALIZATION"] = "REQUEST_GEOLOCALIZATION";
|
|
69
70
|
UserCallAction["ACTIVATE_GEOLOCATION"] = "ACTIVATE_GEOLOCATION";
|
|
71
|
+
UserCallAction["DENY_GEOLOCATION"] = "DENY_GEOLOCATION";
|
|
70
72
|
})(UserCallAction || (UserCallAction = {}));
|
|
73
|
+
var GeoStatus;
|
|
74
|
+
(function (GeoStatus) {
|
|
75
|
+
GeoStatus["PENDING"] = "PENDING";
|
|
76
|
+
GeoStatus["GRANTED"] = "GRANTED";
|
|
77
|
+
GeoStatus["DENIED"] = "DENIED";
|
|
78
|
+
})(GeoStatus || (GeoStatus = {}));
|
|
71
79
|
var RoomUserStatus;
|
|
72
80
|
(function (RoomUserStatus) {
|
|
73
81
|
RoomUserStatus["ASSIGNED"] = "ASSIGNED";
|
|
@@ -199,6 +207,9 @@ class TasService {
|
|
|
199
207
|
// Observable for when all geo has been granted
|
|
200
208
|
this.allGeoGrantedSubject = new BehaviorSubject(false);
|
|
201
209
|
this.allGeoGranted$ = this.allGeoGrantedSubject.asObservable();
|
|
210
|
+
// Observable for individual user geo status (for owner panel)
|
|
211
|
+
this.userGeoInfoSubject = new BehaviorSubject([]);
|
|
212
|
+
this.userGeoInfo$ = this.userGeoInfoSubject.asObservable();
|
|
202
213
|
this.statusPollingInterval = null;
|
|
203
214
|
this.DEFAULT_POLL_INTERVAL_MS = 30000; // Default 30s
|
|
204
215
|
this.wasOwnerPresent = false;
|
|
@@ -206,6 +217,10 @@ class TasService {
|
|
|
206
217
|
this.currentAppointmentId = null;
|
|
207
218
|
this.currentVideoCallId = null;
|
|
208
219
|
this.currentTenant = null;
|
|
220
|
+
// Status cache (1 second TTL)
|
|
221
|
+
this.STATUS_CACHE_TTL_MS = 1000;
|
|
222
|
+
this.statusCache = null;
|
|
223
|
+
this.inflightStatusRequests = new Map();
|
|
209
224
|
}
|
|
210
225
|
// ... (Getters and other methods remain unchanged)
|
|
211
226
|
/**
|
|
@@ -224,7 +239,6 @@ class TasService {
|
|
|
224
239
|
if (params.tenant) {
|
|
225
240
|
this.currentTenant = params.tenant;
|
|
226
241
|
}
|
|
227
|
-
console.log(`[TAS DEBUG] Starting status polling with interval ${intervalMs}ms`);
|
|
228
242
|
// Initial status fetch
|
|
229
243
|
this.fetchAndProcessStatus(params);
|
|
230
244
|
// Set up periodic polling
|
|
@@ -328,7 +342,6 @@ class TasService {
|
|
|
328
342
|
}
|
|
329
343
|
// Session Management
|
|
330
344
|
disconnectSession(clearStorage = true) {
|
|
331
|
-
console.log('[TAS DEBUG] TasService.disconnectSession called. clearStorage:', clearStorage);
|
|
332
345
|
// Call finishSession before disconnecting if we have a sessionId
|
|
333
346
|
const sessionIdToFinish = this.currentSessionId;
|
|
334
347
|
// Clear storage FIRST to prevent any race conditions where state might be saved after disconnect
|
|
@@ -359,11 +372,9 @@ class TasService {
|
|
|
359
372
|
businessRole: this.currentBusinessRole,
|
|
360
373
|
}).subscribe({
|
|
361
374
|
next: (response) => {
|
|
362
|
-
console.log('[TAS DEBUG] Session finished successfully:', response);
|
|
363
375
|
this.isFinishingSession = false;
|
|
364
376
|
},
|
|
365
377
|
error: (error) => {
|
|
366
|
-
console.error('[TAS DEBUG] Error finishing session:', error);
|
|
367
378
|
this.isFinishingSession = false;
|
|
368
379
|
},
|
|
369
380
|
});
|
|
@@ -398,7 +409,6 @@ class TasService {
|
|
|
398
409
|
// If so, don't restore it on page reload
|
|
399
410
|
const wasDisconnected = sessionStorage.getItem(this.DISCONNECTED_FLAG_KEY);
|
|
400
411
|
if (wasDisconnected === 'true') {
|
|
401
|
-
console.log('[TAS DEBUG] Session was intentionally disconnected, skipping restore');
|
|
402
412
|
this.clearSessionState();
|
|
403
413
|
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
404
414
|
return;
|
|
@@ -406,14 +416,12 @@ class TasService {
|
|
|
406
416
|
// Don't restore if we're already disconnected or if there's no active session
|
|
407
417
|
// This prevents restoring sessions that were properly disconnected
|
|
408
418
|
if (this.callStateSubject.getValue() === CallState.DISCONNECTED) {
|
|
409
|
-
console.log('[TAS DEBUG] Call state is DISCONNECTED, skipping restore');
|
|
410
419
|
this.clearSessionState();
|
|
411
420
|
return;
|
|
412
421
|
}
|
|
413
422
|
try {
|
|
414
423
|
const state = JSON.parse(savedState);
|
|
415
424
|
if (state.sessionId && state.token) {
|
|
416
|
-
console.log('[TAS DEBUG] Restoring session from storage');
|
|
417
425
|
// Force PiP mode for restoration to ensure UI consistency
|
|
418
426
|
this.viewModeSubject.next(ViewMode.PIP);
|
|
419
427
|
if (state.businessRole) {
|
|
@@ -422,18 +430,15 @@ class TasService {
|
|
|
422
430
|
this.connectSession(state.sessionId, state.token, containerId, // Use the same container for both since we are in PiP
|
|
423
431
|
containerId, this.currentBusinessRole)
|
|
424
432
|
.then(() => {
|
|
425
|
-
console.log('[TAS DEBUG] Session restored successfully');
|
|
426
433
|
// Clear the disconnected flag if restoration succeeds
|
|
427
434
|
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
428
435
|
})
|
|
429
436
|
.catch((err) => {
|
|
430
|
-
console.error('[TAS DEBUG] Failed to restore session:', err);
|
|
431
437
|
this.clearSessionState(); // Clear bad state
|
|
432
438
|
});
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
441
|
catch (e) {
|
|
436
|
-
console.error('[TAS DEBUG] Error parsing saved session state', e);
|
|
437
442
|
this.clearSessionState();
|
|
438
443
|
}
|
|
439
444
|
}
|
|
@@ -452,19 +457,77 @@ class TasService {
|
|
|
452
457
|
throw error;
|
|
453
458
|
}));
|
|
454
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Generate cache key from request payload
|
|
462
|
+
*/
|
|
463
|
+
getStatusCacheKey(payload) {
|
|
464
|
+
return `${payload.roomType}-${payload.entityId}-${payload.tenant}-${payload.sessionId || ''}`;
|
|
465
|
+
}
|
|
455
466
|
/**
|
|
456
467
|
* PROXY circuit status: /v2/proxy/video/status
|
|
468
|
+
* Uses a 1-second cache to avoid redundant API calls from multiple components
|
|
469
|
+
* Implements request deduplication for simultaneous calls
|
|
457
470
|
*/
|
|
458
471
|
getProxyVideoStatus(payload) {
|
|
459
|
-
|
|
472
|
+
const cacheKey = this.getStatusCacheKey(payload);
|
|
473
|
+
const now = Date.now();
|
|
474
|
+
// 1. Check if we have a valid cached result (less than 1 second old)
|
|
475
|
+
if (this.statusCache &&
|
|
476
|
+
this.statusCache.key === cacheKey &&
|
|
477
|
+
(now - this.statusCache.timestamp) < this.STATUS_CACHE_TTL_MS) {
|
|
478
|
+
// Return cached error if present
|
|
479
|
+
if (this.statusCache.error) {
|
|
480
|
+
return new Observable(observer => {
|
|
481
|
+
observer.error(this.statusCache.error);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
// Return cached response
|
|
485
|
+
if (this.statusCache.response) {
|
|
486
|
+
return new Observable(observer => {
|
|
487
|
+
observer.next(this.statusCache.response);
|
|
488
|
+
observer.complete();
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// 2. Check if there is already an inflight request for this key
|
|
493
|
+
if (this.inflightStatusRequests.has(cacheKey)) {
|
|
494
|
+
return this.inflightStatusRequests.get(cacheKey);
|
|
495
|
+
}
|
|
496
|
+
// 3. Make the API call, share it, and cache the Observable
|
|
497
|
+
const request$ = this.httpClient
|
|
460
498
|
.post('v2/proxy/video/status', {
|
|
461
499
|
body: payload,
|
|
462
500
|
headers: {},
|
|
463
501
|
})
|
|
464
|
-
.pipe(map((response) =>
|
|
502
|
+
.pipe(map((response) => {
|
|
503
|
+
const typedResponse = response;
|
|
504
|
+
// Cache successful response
|
|
505
|
+
this.statusCache = {
|
|
506
|
+
key: cacheKey,
|
|
507
|
+
response: typedResponse,
|
|
508
|
+
error: null,
|
|
509
|
+
timestamp: Date.now(),
|
|
510
|
+
};
|
|
511
|
+
return typedResponse;
|
|
512
|
+
}), catchError((error) => {
|
|
465
513
|
console.error('TAS Service: getProxyVideoStatus failed', error);
|
|
514
|
+
// Cache error response
|
|
515
|
+
this.statusCache = {
|
|
516
|
+
key: cacheKey,
|
|
517
|
+
response: null,
|
|
518
|
+
error: error,
|
|
519
|
+
timestamp: Date.now(),
|
|
520
|
+
};
|
|
466
521
|
throw error;
|
|
467
|
-
})
|
|
522
|
+
}),
|
|
523
|
+
// Clean up inflight map when the observable completes or errors
|
|
524
|
+
finalize(() => {
|
|
525
|
+
this.inflightStatusRequests.delete(cacheKey);
|
|
526
|
+
}),
|
|
527
|
+
// Share the result with all subscribers (deduplication)
|
|
528
|
+
shareReplay(1));
|
|
529
|
+
this.inflightStatusRequests.set(cacheKey, request$);
|
|
530
|
+
return request$;
|
|
468
531
|
}
|
|
469
532
|
/**
|
|
470
533
|
* PROXY circuit user modification: /v2/proxy/video/user/modify
|
|
@@ -534,7 +597,6 @@ class TasService {
|
|
|
534
597
|
this.processStatusResponse(response);
|
|
535
598
|
},
|
|
536
599
|
error: (err) => {
|
|
537
|
-
console.error('[TAS DEBUG] Status polling error:', err);
|
|
538
600
|
},
|
|
539
601
|
});
|
|
540
602
|
}
|
|
@@ -556,17 +618,18 @@ class TasService {
|
|
|
556
618
|
this.joinableSubject.next(content.joinable);
|
|
557
619
|
// Update activateGeo status
|
|
558
620
|
if (content.activateGeo !== undefined) {
|
|
559
|
-
console.log('[TAS DEBUG] activateGeo received:', content.activateGeo);
|
|
560
621
|
this.activateGeoSubject.next(content.activateGeo);
|
|
561
622
|
}
|
|
562
623
|
// Update geoRequestActive status (owner waiting for user geo response)
|
|
563
|
-
if (content.geoRequestActive !== undefined) {
|
|
564
|
-
console.log('[TAS DEBUG] geoRequestActive received:', content.geoRequestActive);
|
|
624
|
+
if (content.geoRequestActive !== undefined && content.geoRequestActive !== null) {
|
|
565
625
|
this.geoRequestActiveSubject.next(content.geoRequestActive);
|
|
566
626
|
}
|
|
627
|
+
else if (content.geoRequestActive === null) {
|
|
628
|
+
// Reset to false when null
|
|
629
|
+
this.geoRequestActiveSubject.next(false);
|
|
630
|
+
}
|
|
567
631
|
// Update allGeoGranted status (all users responded with geo)
|
|
568
632
|
if (content.allGeoGranted !== undefined) {
|
|
569
|
-
console.log('[TAS DEBUG] allGeoGranted received:', content.allGeoGranted);
|
|
570
633
|
this.allGeoGrantedSubject.next(content.allGeoGranted);
|
|
571
634
|
}
|
|
572
635
|
// Check if owner has joined
|
|
@@ -574,7 +637,6 @@ class TasService {
|
|
|
574
637
|
this.ownerHasJoinedSubject.next(ownerJoined);
|
|
575
638
|
// Detect if owner left: was present, now not present
|
|
576
639
|
if (this.wasOwnerPresent && !ownerJoined) {
|
|
577
|
-
console.log('[TAS DEBUG] Owner has left the session');
|
|
578
640
|
this.ownerHasLeftSubject.next(true);
|
|
579
641
|
}
|
|
580
642
|
if (ownerJoined) {
|
|
@@ -589,6 +651,16 @@ class TasService {
|
|
|
589
651
|
status: RoomUserStatus.WAITING,
|
|
590
652
|
}));
|
|
591
653
|
this.waitingRoomUsersSubject.next(waitingUsers);
|
|
654
|
+
// Extract geo info for USER role participants (for owner panel)
|
|
655
|
+
const userGeoInfo = content.users
|
|
656
|
+
.filter((u) => u.rol === 'USER')
|
|
657
|
+
.map((u) => ({
|
|
658
|
+
userId: u.userId,
|
|
659
|
+
geoStatus: u.geoStatus,
|
|
660
|
+
latitude: u.latitude,
|
|
661
|
+
longitude: u.longitude,
|
|
662
|
+
}));
|
|
663
|
+
this.userGeoInfoSubject.next(userGeoInfo);
|
|
592
664
|
}
|
|
593
665
|
/**
|
|
594
666
|
* Admit a user from the waiting room by changing their status.
|
|
@@ -660,11 +732,9 @@ class TasService {
|
|
|
660
732
|
businessRole: this.currentBusinessRole,
|
|
661
733
|
}).subscribe({
|
|
662
734
|
next: (response) => {
|
|
663
|
-
console.log('[TAS DEBUG] Session finished on disconnect event:', response);
|
|
664
735
|
this.isFinishingSession = false;
|
|
665
736
|
},
|
|
666
737
|
error: (error) => {
|
|
667
|
-
console.error('[TAS DEBUG] Error finishing session on disconnect:', error);
|
|
668
738
|
this.isFinishingSession = false;
|
|
669
739
|
},
|
|
670
740
|
});
|
|
@@ -828,6 +898,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
828
898
|
}]
|
|
829
899
|
}] });
|
|
830
900
|
|
|
901
|
+
/**
|
|
902
|
+
* SVG icons used internally by TAS SDK components.
|
|
903
|
+
* Icons are stored as strings to avoid asset bundling complexity.
|
|
904
|
+
*/
|
|
905
|
+
const TAS_ICONS = {
|
|
906
|
+
home: `<svg width="120" height="100" viewBox="0 0 120 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
907
|
+
<rect x="10" width="100" height="100" rx="50" fill="#44D8E8" fill-opacity="0.2"/>
|
|
908
|
+
<path d="M45 70C43.625 70 42.4479 69.5104 41.4688 68.5313C40.4896 67.5521 40 66.375 40 65V46.5625L37.5 48.5C36.9583 48.9167 36.3438 49.0833 35.6562 49C34.9688 48.9167 34.4167 48.5833 34 48C33.5833 47.4583 33.4271 46.8542 33.5312 46.1875C33.6354 45.5208 33.9583 44.9792 34.5 44.5625L56.9375 27.3125C57.3958 26.9792 57.8854 26.7292 58.4062 26.5625C58.9271 26.3958 59.4583 26.3125 60 26.3125C60.5417 26.3125 61.0729 26.3958 61.5938 26.5625C62.1146 26.7292 62.6042 26.9792 63.0625 27.3125L85.5 44.5C86.0417 44.9167 86.3646 45.4583 86.4688 46.125C86.5729 46.7917 86.4167 47.4167 86 48C85.5833 48.5833 85.0417 48.9063 84.375 48.9688C83.7083 49.0313 83.0833 48.8542 82.5 48.4375L60 31.25L45 42.75V65H50.0625C50.7708 65 51.3542 65.2396 51.8125 65.7188C52.2708 66.1979 52.5 66.7917 52.5 67.5C52.5 68.2083 52.2604 68.8021 51.7812 69.2813C51.3021 69.7604 50.7083 70 50 70H45ZM67.3125 73.9375C66.9792 73.9375 66.6667 73.875 66.375 73.75C66.0833 73.625 65.8125 73.4375 65.5625 73.1875L58.5 66.125C58 65.625 57.75 65.0417 57.75 64.375C57.75 63.7083 58 63.125 58.5 62.625C59 62.125 59.5833 61.875 60.25 61.875C60.9167 61.875 61.5 62.125 62 62.625L67.3125 67.875L79.6875 55.5C80.1875 55 80.7812 54.7604 81.4688 54.7813C82.1562 54.8021 82.75 55.0625 83.25 55.5625C83.75 56.0625 84 56.6458 84 57.3125C84 57.9792 83.75 58.5625 83.25 59.0625L69.0625 73.1875C68.8125 73.4375 68.5417 73.625 68.25 73.75C67.9583 73.875 67.6458 73.9375 67.3125 73.9375Z" fill="white"/>
|
|
909
|
+
<path d="M110 5L106.575 3.425L105 0L103.425 3.425L100 5L103.425 6.575L105 10L106.575 6.575L110 5Z" fill="#44D8E8"/>
|
|
910
|
+
<path d="M10 51L6.575 49.425L5 46L3.425 49.425L0 51L3.425 52.575L5 56L6.575 52.575L10 51Z" fill="#44D8E8"/>
|
|
911
|
+
<path d="M95 43.5L93.2875 42.7125L92.5 41L91.7125 42.7125L90 43.5L91.7125 44.2875L92.5 46L93.2875 44.2875L95 43.5Z" fill="#44D8E8"/>
|
|
912
|
+
<path d="M42 4.5L40.2875 3.7125L39.5 2L38.7125 3.7125L37 4.5L38.7125 5.2875L39.5 7L40.2875 5.2875L42 4.5Z" fill="#44D8E8"/>
|
|
913
|
+
<path d="M88 83.5L86.2875 82.7125L85.5 81L84.7125 82.7125L83 83.5L84.7125 84.2875L85.5 86L86.2875 84.2875L88 83.5Z" fill="#44D8E8"/>
|
|
914
|
+
<path d="M120 97.5L118.287 96.7125L117.5 95L116.713 96.7125L115 97.5L116.713 98.2875L117.5 100L118.287 98.2875L120 97.5Z" fill="#44D8E8"/>
|
|
915
|
+
</svg>`,
|
|
916
|
+
};
|
|
917
|
+
|
|
831
918
|
class TasAvatarComponent {
|
|
832
919
|
constructor() {
|
|
833
920
|
this.name = '';
|
|
@@ -901,11 +988,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
901
988
|
type: Input
|
|
902
989
|
}] } });
|
|
903
990
|
|
|
991
|
+
/** User geolocation panel view states */
|
|
992
|
+
var UserGeoViewState;
|
|
993
|
+
(function (UserGeoViewState) {
|
|
994
|
+
UserGeoViewState["HIDDEN"] = "HIDDEN";
|
|
995
|
+
UserGeoViewState["INITIAL"] = "INITIAL";
|
|
996
|
+
UserGeoViewState["VERIFYING"] = "VERIFYING";
|
|
997
|
+
UserGeoViewState["VERIFIED"] = "VERIFIED";
|
|
998
|
+
UserGeoViewState["DENIED"] = "DENIED";
|
|
999
|
+
})(UserGeoViewState || (UserGeoViewState = {}));
|
|
904
1000
|
class TasVideocallComponent {
|
|
905
|
-
constructor(activeModal, tasService, geolocationService) {
|
|
1001
|
+
constructor(activeModal, tasService, geolocationService, sanitizer) {
|
|
906
1002
|
this.activeModal = activeModal;
|
|
907
1003
|
this.tasService = tasService;
|
|
908
1004
|
this.geolocationService = geolocationService;
|
|
1005
|
+
this.sanitizer = sanitizer;
|
|
909
1006
|
this.participantName = '';
|
|
910
1007
|
this.tenant = '';
|
|
911
1008
|
this.businessRole = TasBusinessRole.USER;
|
|
@@ -923,11 +1020,22 @@ class TasVideocallComponent {
|
|
|
923
1020
|
// Geo panel states for owner
|
|
924
1021
|
this.geoRequestActive = false; // Owner sent request, waiting for user
|
|
925
1022
|
this.allGeoGranted = false; // All users responded with geo
|
|
1023
|
+
this.userGeoInfo = []; // Individual user geo status
|
|
1024
|
+
// User geo panel state (for owners)
|
|
1025
|
+
this.userGeoViewState = UserGeoViewState.HIDDEN;
|
|
1026
|
+
this.UserGeoViewState = UserGeoViewState; // Expose enum to template
|
|
1027
|
+
this.devModeEnabled = false; // Enable dev controls for testing
|
|
1028
|
+
this.geoPanelDismissed = false; // Track if owner manually closed the panel
|
|
926
1029
|
this.subscriptions = new Subscription();
|
|
1030
|
+
this.homeIcon = this.sanitizer.bypassSecurityTrustHtml(TAS_ICONS.home);
|
|
927
1031
|
}
|
|
928
1032
|
ngOnInit() {
|
|
929
1033
|
this.setupSubscriptions();
|
|
930
1034
|
this.initializeCall();
|
|
1035
|
+
// For owners: show the geo panel to request user location (unless dismissed)
|
|
1036
|
+
if (this.canAdmitUsers && !this.geoPanelDismissed) {
|
|
1037
|
+
this.userGeoViewState = UserGeoViewState.INITIAL;
|
|
1038
|
+
}
|
|
931
1039
|
}
|
|
932
1040
|
ngAfterViewInit() {
|
|
933
1041
|
this.initInteract();
|
|
@@ -969,6 +1077,43 @@ class TasVideocallComponent {
|
|
|
969
1077
|
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
970
1078
|
this.businessRole === TasBusinessRole.MANAGER);
|
|
971
1079
|
}
|
|
1080
|
+
/** Users with pending geo status */
|
|
1081
|
+
get pendingUsers() {
|
|
1082
|
+
return this.userGeoInfo.filter(u => u.geoStatus === GeoStatus.PENDING);
|
|
1083
|
+
}
|
|
1084
|
+
/** Users who granted geo */
|
|
1085
|
+
get grantedUsers() {
|
|
1086
|
+
return this.userGeoInfo.filter(u => u.geoStatus === GeoStatus.GRANTED);
|
|
1087
|
+
}
|
|
1088
|
+
/** Users who denied geo */
|
|
1089
|
+
get deniedUsers() {
|
|
1090
|
+
return this.userGeoInfo.filter(u => u.geoStatus === GeoStatus.DENIED);
|
|
1091
|
+
}
|
|
1092
|
+
/** Show location panel only if owner and there are users who haven't granted */
|
|
1093
|
+
get shouldShowLocationPanel() {
|
|
1094
|
+
if (!this.canAdmitUsers || this.userGeoInfo.length === 0) {
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
// Hide if all users have granted
|
|
1098
|
+
const allGranted = this.userGeoInfo.every(u => u.geoStatus === GeoStatus.GRANTED);
|
|
1099
|
+
return !allGranted;
|
|
1100
|
+
}
|
|
1101
|
+
/** Check if any user has denied geo */
|
|
1102
|
+
get hasAnyDenied() {
|
|
1103
|
+
return this.deniedUsers.length > 0;
|
|
1104
|
+
}
|
|
1105
|
+
/** Show user geo panel for owners when not hidden */
|
|
1106
|
+
get shouldShowUserGeoPanel() {
|
|
1107
|
+
return this.canAdmitUsers && this.userGeoViewState !== UserGeoViewState.HIDDEN;
|
|
1108
|
+
}
|
|
1109
|
+
/** Set user geo view state (for dev controls or close button) */
|
|
1110
|
+
setUserGeoViewState(state) {
|
|
1111
|
+
this.userGeoViewState = state;
|
|
1112
|
+
// Track if owner manually dismissed the panel
|
|
1113
|
+
if (state === UserGeoViewState.HIDDEN) {
|
|
1114
|
+
this.geoPanelDismissed = true;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
972
1117
|
/**
|
|
973
1118
|
* Admit a user from the waiting room
|
|
974
1119
|
*/
|
|
@@ -1007,7 +1152,7 @@ class TasVideocallComponent {
|
|
|
1007
1152
|
this.showLocationPanel = false;
|
|
1008
1153
|
}
|
|
1009
1154
|
/**
|
|
1010
|
-
* Request the user to share their location
|
|
1155
|
+
* Request the user to share their location (called by owner)
|
|
1011
1156
|
*/
|
|
1012
1157
|
requestUserLocation() {
|
|
1013
1158
|
if (!this.videoCallId) {
|
|
@@ -1018,9 +1163,12 @@ class TasVideocallComponent {
|
|
|
1018
1163
|
videoCallId: this.videoCallId,
|
|
1019
1164
|
action: UserCallAction.REQUEST_GEOLOCALIZATION,
|
|
1020
1165
|
};
|
|
1021
|
-
// TODO: Send location request action to backend when endpoint is ready
|
|
1022
1166
|
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
1023
|
-
next: () =>
|
|
1167
|
+
next: () => {
|
|
1168
|
+
console.log('Location request sent');
|
|
1169
|
+
// Set panel to verifying state while waiting for user response
|
|
1170
|
+
this.userGeoViewState = UserGeoViewState.VERIFYING;
|
|
1171
|
+
},
|
|
1024
1172
|
error: (err) => console.error('Error requesting location:', err),
|
|
1025
1173
|
});
|
|
1026
1174
|
}
|
|
@@ -1057,7 +1205,6 @@ class TasVideocallComponent {
|
|
|
1057
1205
|
// Owner left subscription - disconnect non-owners
|
|
1058
1206
|
this.subscriptions.add(this.tasService.ownerHasLeft$.subscribe((hasLeft) => {
|
|
1059
1207
|
if (hasLeft && !this.canAdmitUsers) { // Non-owner user
|
|
1060
|
-
console.log('[TAS DEBUG] Owner left, disconnecting user');
|
|
1061
1208
|
this.hangUp();
|
|
1062
1209
|
this.activeModal.close('owner_left');
|
|
1063
1210
|
}
|
|
@@ -1065,63 +1212,78 @@ class TasVideocallComponent {
|
|
|
1065
1212
|
// ActivateGeo subscription - only for non-owners (users)
|
|
1066
1213
|
this.subscriptions.add(this.tasService.activateGeo$.subscribe((activateGeo) => {
|
|
1067
1214
|
if (activateGeo && !this.canAdmitUsers) {
|
|
1068
|
-
console.log('[TAS DEBUG] activateGeo=true, checking geo status for user...');
|
|
1069
1215
|
this.handleActivateGeo();
|
|
1070
1216
|
}
|
|
1071
1217
|
}));
|
|
1072
1218
|
// GeoRequestActive subscription - for owners
|
|
1073
1219
|
this.subscriptions.add(this.tasService.geoRequestActive$.subscribe((active) => {
|
|
1074
|
-
console.log('[TAS DEBUG] geoRequestActive changed:', active);
|
|
1075
1220
|
this.geoRequestActive = active;
|
|
1076
1221
|
}));
|
|
1077
|
-
// AllGeoGranted subscription - for owners
|
|
1222
|
+
// AllGeoGranted subscription - for owners to update panel state
|
|
1078
1223
|
this.subscriptions.add(this.tasService.allGeoGranted$.subscribe((granted) => {
|
|
1079
|
-
console.log('[TAS DEBUG] allGeoGranted changed:', granted);
|
|
1080
1224
|
this.allGeoGranted = granted;
|
|
1225
|
+
// For owners: update panel state based on geo status
|
|
1226
|
+
if (this.canAdmitUsers && granted) {
|
|
1227
|
+
this.userGeoViewState = UserGeoViewState.VERIFIED;
|
|
1228
|
+
}
|
|
1229
|
+
}));
|
|
1230
|
+
// UserGeoInfo subscription - for owner geo panel
|
|
1231
|
+
this.subscriptions.add(this.tasService.userGeoInfo$.subscribe((info) => {
|
|
1232
|
+
this.userGeoInfo = info;
|
|
1233
|
+
// Note: We don't auto-switch to DENIED here - that only happens
|
|
1234
|
+
// after owner requests and user denies (via geoRequestActive flow)
|
|
1081
1235
|
}));
|
|
1082
1236
|
}
|
|
1083
1237
|
/**
|
|
1084
1238
|
* Handle activateGeo request from backend (for non-owner users).
|
|
1085
|
-
*
|
|
1239
|
+
* Directly prompts browser for geolocation - no panel for users.
|
|
1086
1240
|
*/
|
|
1087
1241
|
handleActivateGeo() {
|
|
1088
1242
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1243
|
+
// Clear any cached position to force re-prompting
|
|
1244
|
+
this.geolocationService.clearCache();
|
|
1245
|
+
// Request geolocation from user (browser will prompt)
|
|
1246
|
+
const position = yield this.geolocationService.getCurrentPosition();
|
|
1247
|
+
if (position) {
|
|
1094
1248
|
this.geoLocationStatus = 'active';
|
|
1095
|
-
this.reportGeoStatus(
|
|
1096
|
-
|
|
1249
|
+
this.reportGeoStatus(position.latitude, position.longitude);
|
|
1250
|
+
}
|
|
1251
|
+
else {
|
|
1252
|
+
this.geoLocationStatus = 'denied';
|
|
1253
|
+
this.denyGeoLocation();
|
|
1097
1254
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
/** Start geolocation verification (called from template button) */
|
|
1258
|
+
startGeoVerification() {
|
|
1259
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1260
|
+
this.userGeoViewState = UserGeoViewState.VERIFYING;
|
|
1261
|
+
// Clear any cached position to force re-prompting
|
|
1262
|
+
this.geolocationService.clearCache();
|
|
1263
|
+
// Request geolocation from user (browser will prompt)
|
|
1100
1264
|
const position = yield this.geolocationService.getCurrentPosition();
|
|
1101
1265
|
if (position) {
|
|
1102
|
-
console.log('[TAS DEBUG] Geolocation obtained:', position);
|
|
1103
1266
|
this.geoLocationStatus = 'active';
|
|
1267
|
+
this.userGeoViewState = UserGeoViewState.VERIFIED;
|
|
1104
1268
|
this.reportGeoStatus(position.latitude, position.longitude);
|
|
1105
1269
|
}
|
|
1106
1270
|
else {
|
|
1107
|
-
console.log('[TAS DEBUG] Geolocation denied or unavailable');
|
|
1108
1271
|
this.geoLocationStatus = 'denied';
|
|
1109
|
-
|
|
1110
|
-
this.
|
|
1272
|
+
this.userGeoViewState = UserGeoViewState.DENIED;
|
|
1273
|
+
this.denyGeoLocation();
|
|
1111
1274
|
}
|
|
1112
1275
|
});
|
|
1113
1276
|
}
|
|
1114
1277
|
/**
|
|
1115
|
-
* Report geolocation
|
|
1278
|
+
* Report granted geolocation to backend.
|
|
1279
|
+
* IMPORTANT: Only call with valid coordinates.
|
|
1116
1280
|
*/
|
|
1117
1281
|
reportGeoStatus(latitude, longitude) {
|
|
1118
1282
|
if (!this.videoCallId) {
|
|
1119
|
-
console.error('[TAS DEBUG] Cannot report geo status: videoCallId not set');
|
|
1120
1283
|
return;
|
|
1121
1284
|
}
|
|
1122
|
-
//
|
|
1123
|
-
if (
|
|
1124
|
-
console.error('[TAS DEBUG] Cannot report geo status: geoLocationStatus not set');
|
|
1285
|
+
// Validate coordinates are present
|
|
1286
|
+
if (latitude === undefined || latitude === null || longitude === undefined || longitude === null) {
|
|
1125
1287
|
return;
|
|
1126
1288
|
}
|
|
1127
1289
|
const body = {
|
|
@@ -1131,11 +1293,21 @@ class TasVideocallComponent {
|
|
|
1131
1293
|
latitude,
|
|
1132
1294
|
longitude,
|
|
1133
1295
|
};
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1296
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({});
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Report denied geolocation to backend.
|
|
1300
|
+
*/
|
|
1301
|
+
denyGeoLocation() {
|
|
1302
|
+
if (!this.videoCallId) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const body = {
|
|
1306
|
+
userId: this.userId,
|
|
1307
|
+
videoCallId: this.videoCallId,
|
|
1308
|
+
action: UserCallAction.DENY_GEOLOCATION,
|
|
1309
|
+
};
|
|
1310
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({});
|
|
1139
1311
|
}
|
|
1140
1312
|
initializeCall() {
|
|
1141
1313
|
if (this.isReturningFromPip) {
|
|
@@ -1225,12 +1397,12 @@ class TasVideocallComponent {
|
|
|
1225
1397
|
});
|
|
1226
1398
|
}
|
|
1227
1399
|
}
|
|
1228
|
-
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1229
|
-
TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasVideocallComponent, selector: "tas-videocall", inputs: { sessionId: "sessionId", token: "token", appointmentId: "appointmentId", videoCallId: "videoCallId", userId: "userId", participantName: "participantName", tenant: "tenant", businessRole: "businessRole", isReturningFromPip: "isReturningFromPip" }, viewQueries: [{ propertyName: "publisherContainer", first: true, predicate: ["publisherContainer"], descendants: true }, { propertyName: "subscriberContainer", first: true, predicate: ["subscriberContainer"], descendants: true }], ngImport: i0, template: "<div class=\"tas-videocall-wrapper\">\n <div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn control-btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n aria-label=\"Minimizar videollamada\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n </div>\n\n <!-- Location panel (shown for owners when user hasn't allowed location) -->\n <div class=\"location-panel\" *ngIf=\"showLocationPanel && canAdmitUsers\">\n <div class=\"location-header\">\n <i class=\"fa fa-map-marker header-icon\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n <button class=\"close-btn\" (click)=\"closeLocationPanel()\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n <div class=\"location-description\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n <div class=\"location-content\">\n <!-- Initial state: Show verify button -->\n <ng-container *ngIf=\"!geoRequestActive && !allGeoGranted\">\n <button class=\"verify-location-btn\" (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Loading state: Spinner while waiting for user response -->\n <ng-container *ngIf=\"geoRequestActive && !allGeoGranted\">\n <div class=\"geo-loading-container\">\n <div class=\"geo-spinner\"></div>\n <p class=\"loading-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"loading-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n <button class=\"verify-location-btn disabled\" disabled>\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Success state: Location verified -->\n <ng-container *ngIf=\"allGeoGranted\">\n <div class=\"geo-success-container\">\n <div class=\"success-icon\">\n <i class=\"fa fa-check\"></i>\n </div>\n <p class=\"success-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n </div>\n <div class=\"location-footer\">\n <span class=\"footer-icon\"><i class=\"fa fa-clock-o\"></i></span>\n <span class=\"footer-icon location-icon\"><i class=\"fa fa-map-marker\"></i></span>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:transparent;cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{background:transparent}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover,.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.15)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px)}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1400
|
+
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }, { token: i4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
1401
|
+
TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasVideocallComponent, selector: "tas-videocall", inputs: { sessionId: "sessionId", token: "token", appointmentId: "appointmentId", videoCallId: "videoCallId", userId: "userId", participantName: "participantName", tenant: "tenant", businessRole: "businessRole", isReturningFromPip: "isReturningFromPip" }, viewQueries: [{ propertyName: "publisherContainer", first: true, predicate: ["publisherContainer"], descendants: true }, { propertyName: "subscriberContainer", first: true, predicate: ["subscriberContainer"], descendants: true }], ngImport: i0, template: "<div class=\"tas-videocall-wrapper\">\n <div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn control-btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n aria-label=\"Minimizar videollamada\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n </div>\n\n <!-- Owner geolocation panel (for owners to request user location) -->\n <div class=\"user-geo-panel\" *ngIf=\"shouldShowUserGeoPanel || devModeEnabled\">\n <div class=\"user-geo-header\">\n <div class=\"header-title-row\">\n <i class=\"fa fa-map-marker header-icon\" *ngIf=\"userGeoViewState !== UserGeoViewState.INITIAL\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n </div>\n <button class=\"close-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n\n <div class=\"user-geo-description\" *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n\n <div class=\"user-geo-content\">\n <!-- INITIAL state: just the button -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.INITIAL\">\n <!-- spacer -->\n </ng-container>\n\n <!-- VERIFYING state: spinner + loading message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFYING\">\n <div class=\"user-geo-verifying\">\n <div class=\"geo-spinner-large\"></div>\n <p class=\"verifying-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"verifying-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n </ng-container>\n\n <!-- VERIFIED state: home icon with sparkles -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFIED\">\n <div class=\"user-geo-verified\">\n <div class=\"verified-icon-container\">\n <span class=\"home-icon\" [innerHTML]=\"homeIcon\"></span>\n </div>\n <p class=\"verified-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n\n <!-- DENIED state: error icon and message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.DENIED\">\n <div class=\"user-geo-denied\">\n <div class=\"denied-icon-container\">\n <i class=\"fa fa-times-circle\"></i>\n </div>\n <p class=\"denied-title\">La ubicaci\u00F3n fue rechazada</p>\n <p class=\"denied-subtitle\">El colaborador no permiti\u00F3 el acceso a su ubicaci\u00F3n.</p>\n </div>\n </ng-container>\n </div>\n\n <!-- Button (hidden in verified and denied states) -->\n <button \n class=\"user-geo-btn\" \n *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\"\n [class.disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n [disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n\n <!-- Dev controls -->\n <div class=\"dev-controls\" *ngIf=\"devModeEnabled\">\n <span class=\"dev-label\">Dev:</span>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.INITIAL)\">Initial</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFYING)\">Verifying</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFIED)\">Verified</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.DENIED)\">Denied</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\">Hide</button>\n </div>\n </div>\n</div>\n\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:transparent;cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{background:transparent}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover,.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.15)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px)}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-user-list{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;max-height:200px;overflow-y:auto}.location-panel .location-user-list .user-geo-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:rgba(255,255,255,.05);border-radius:6px}.location-panel .location-user-list .user-geo-item .user-icon{color:#fff9;font-size:14px}.location-panel .location-user-list .user-geo-item .user-name{flex:1;font-size:14px;color:#fff}.location-panel .location-user-list .user-geo-item .geo-status{display:flex;align-items:center;gap:.35rem;font-size:12px;padding:2px 8px;border-radius:12px}.location-panel .location-user-list .user-geo-item .geo-status.pending{color:#ffc107;background:rgba(255,193,7,.15)}.location-panel .location-user-list .user-geo-item .geo-status.granted{color:#4caf50;background:rgba(76,175,80,.15)}.location-panel .location-user-list .user-geo-item .geo-status.denied{color:#f44336;background:rgba(244,67,54,.15)}.location-panel .location-user-list .user-geo-item .geo-status i{font-size:10px}.location-panel .geo-denied-warning{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(244,67,54,.15);border-radius:6px;margin-bottom:1rem;color:#f44336;font-size:13px}.location-panel .geo-denied-warning i{font-size:14px}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}.user-geo-panel{width:360px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.user-geo-panel .user-geo-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:2.5rem;padding-top:2rem}.user-geo-panel .user-geo-header .header-title-row{display:flex;align-items:center;gap:.5rem}.user-geo-panel .user-geo-header .header-title-row .header-icon{font-size:16px;color:#fff}.user-geo-panel .user-geo-header .header-title-row h3{font-size:18px;font-weight:600;margin:0;color:#fff}.user-geo-panel .user-geo-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.user-geo-panel .user-geo-header .close-btn:hover{opacity:1}.user-geo-panel .user-geo-description{font-size:14px;color:#fffc;line-height:1.6;margin-bottom:1rem}.user-geo-panel .user-geo-description p{margin:0 0 .75rem}.user-geo-panel .user-geo-content{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.user-geo-panel .user-geo-verifying{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verifying .geo-spinner-large{width:80px;height:80px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite;margin-bottom:1.5rem}.user-geo-panel .user-geo-verifying .verifying-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .25rem}.user-geo-panel .user-geo-verifying .verifying-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-verified{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verified .verified-icon-container{margin-bottom:1rem}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon{display:block}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon svg{width:120px;height:100px}.user-geo-panel .user-geo-verified .verified-title{color:#fff;font-size:16px;font-weight:600;margin:0}.user-geo-panel .user-geo-denied{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-denied .denied-icon-container{width:100px;height:100px;border-radius:50%;background:rgba(244,67,54,.2);display:flex;align-items:center;justify-content:center;margin-bottom:1.5rem}.user-geo-panel .user-geo-denied .denied-icon-container i{font-size:48px;color:#f44336}.user-geo-panel .user-geo-denied .denied-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .5rem}.user-geo-panel .user-geo-denied .denied-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-btn{width:100%;padding:14px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:24px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;margin-top:auto;margin-bottom:1rem}.user-geo-panel .user-geo-btn:hover:not(.disabled){background:#178e99}.user-geo-panel .user-geo-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.user-geo-panel .dev-controls{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(0,0,0,.3);border-radius:6px;margin-bottom:1rem;flex-wrap:wrap}.user-geo-panel .dev-controls .dev-label{font-size:12px;color:#fff9;font-weight:600}.user-geo-panel .dev-controls .dev-btn{padding:4px 8px;font-size:11px;background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.user-geo-panel .dev-controls .dev-btn:hover{background:rgba(255,255,255,.2)}.user-geo-panel .user-geo-footer{display:flex;justify-content:center;gap:.75rem;padding-top:.5rem}.user-geo-panel .user-geo-footer .footer-icon{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff9;font-size:16px}.user-geo-panel .user-geo-footer .footer-icon.active{background:var(--Primary-Uell, #1da4b1);color:#fff}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1230
1402
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
|
|
1231
1403
|
type: Component,
|
|
1232
|
-
args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-wrapper\">\n <div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn control-btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n aria-label=\"Minimizar videollamada\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n </div>\n\n <!-- Location panel (shown for owners when user hasn't allowed location) -->\n <div class=\"location-panel\" *ngIf=\"showLocationPanel && canAdmitUsers\">\n <div class=\"location-header\">\n <i class=\"fa fa-map-marker header-icon\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n <button class=\"close-btn\" (click)=\"closeLocationPanel()\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n <div class=\"location-description\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n <div class=\"location-content\">\n <!-- Initial state: Show verify button -->\n <ng-container *ngIf=\"!geoRequestActive && !allGeoGranted\">\n <button class=\"verify-location-btn\" (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Loading state: Spinner while waiting for user response -->\n <ng-container *ngIf=\"geoRequestActive && !allGeoGranted\">\n <div class=\"geo-loading-container\">\n <div class=\"geo-spinner\"></div>\n <p class=\"loading-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"loading-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n <button class=\"verify-location-btn disabled\" disabled>\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Success state: Location verified -->\n <ng-container *ngIf=\"allGeoGranted\">\n <div class=\"geo-success-container\">\n <div class=\"success-icon\">\n <i class=\"fa fa-check\"></i>\n </div>\n <p class=\"success-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n </div>\n <div class=\"location-footer\">\n <span class=\"footer-icon\"><i class=\"fa fa-clock-o\"></i></span>\n <span class=\"footer-icon location-icon\"><i class=\"fa fa-map-marker\"></i></span>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:transparent;cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{background:transparent}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover,.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.15)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px)}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}\n"] }]
|
|
1233
|
-
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }]; }, propDecorators: { sessionId: [{
|
|
1404
|
+
args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-wrapper\">\n <div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-refresh\"></i>\n </button>\n <button\n class=\"btn control-btn pip-btn\"\n (click)=\"minimize()\"\n title=\"Minimizar (Picture in Picture)\"\n aria-label=\"Minimizar videollamada\"\n >\n <i class=\"fa fa-compress\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n </div>\n\n <!-- Owner geolocation panel (for owners to request user location) -->\n <div class=\"user-geo-panel\" *ngIf=\"shouldShowUserGeoPanel || devModeEnabled\">\n <div class=\"user-geo-header\">\n <div class=\"header-title-row\">\n <i class=\"fa fa-map-marker header-icon\" *ngIf=\"userGeoViewState !== UserGeoViewState.INITIAL\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n </div>\n <button class=\"close-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n\n <div class=\"user-geo-description\" *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n\n <div class=\"user-geo-content\">\n <!-- INITIAL state: just the button -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.INITIAL\">\n <!-- spacer -->\n </ng-container>\n\n <!-- VERIFYING state: spinner + loading message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFYING\">\n <div class=\"user-geo-verifying\">\n <div class=\"geo-spinner-large\"></div>\n <p class=\"verifying-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"verifying-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n </ng-container>\n\n <!-- VERIFIED state: home icon with sparkles -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.VERIFIED\">\n <div class=\"user-geo-verified\">\n <div class=\"verified-icon-container\">\n <span class=\"home-icon\" [innerHTML]=\"homeIcon\"></span>\n </div>\n <p class=\"verified-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n\n <!-- DENIED state: error icon and message -->\n <ng-container *ngIf=\"userGeoViewState === UserGeoViewState.DENIED\">\n <div class=\"user-geo-denied\">\n <div class=\"denied-icon-container\">\n <i class=\"fa fa-times-circle\"></i>\n </div>\n <p class=\"denied-title\">La ubicaci\u00F3n fue rechazada</p>\n <p class=\"denied-subtitle\">El colaborador no permiti\u00F3 el acceso a su ubicaci\u00F3n.</p>\n </div>\n </ng-container>\n </div>\n\n <!-- Button (hidden in verified and denied states) -->\n <button \n class=\"user-geo-btn\" \n *ngIf=\"userGeoViewState !== UserGeoViewState.VERIFIED && userGeoViewState !== UserGeoViewState.DENIED\"\n [class.disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n [disabled]=\"userGeoViewState === UserGeoViewState.VERIFYING\"\n (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n\n <!-- Dev controls -->\n <div class=\"dev-controls\" *ngIf=\"devModeEnabled\">\n <span class=\"dev-label\">Dev:</span>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.INITIAL)\">Initial</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFYING)\">Verifying</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.VERIFIED)\">Verified</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.DENIED)\">Denied</button>\n <button class=\"dev-btn\" (click)=\"setUserGeoViewState(UserGeoViewState.HIDDEN)\">Hide</button>\n </div>\n </div>\n</div>\n\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:transparent;cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{background:transparent}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover,.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.15)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px)}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-user-list{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;max-height:200px;overflow-y:auto}.location-panel .location-user-list .user-geo-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:rgba(255,255,255,.05);border-radius:6px}.location-panel .location-user-list .user-geo-item .user-icon{color:#fff9;font-size:14px}.location-panel .location-user-list .user-geo-item .user-name{flex:1;font-size:14px;color:#fff}.location-panel .location-user-list .user-geo-item .geo-status{display:flex;align-items:center;gap:.35rem;font-size:12px;padding:2px 8px;border-radius:12px}.location-panel .location-user-list .user-geo-item .geo-status.pending{color:#ffc107;background:rgba(255,193,7,.15)}.location-panel .location-user-list .user-geo-item .geo-status.granted{color:#4caf50;background:rgba(76,175,80,.15)}.location-panel .location-user-list .user-geo-item .geo-status.denied{color:#f44336;background:rgba(244,67,54,.15)}.location-panel .location-user-list .user-geo-item .geo-status i{font-size:10px}.location-panel .geo-denied-warning{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(244,67,54,.15);border-radius:6px;margin-bottom:1rem;color:#f44336;font-size:13px}.location-panel .geo-denied-warning i{font-size:14px}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}.user-geo-panel{width:360px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.user-geo-panel .user-geo-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:2.5rem;padding-top:2rem}.user-geo-panel .user-geo-header .header-title-row{display:flex;align-items:center;gap:.5rem}.user-geo-panel .user-geo-header .header-title-row .header-icon{font-size:16px;color:#fff}.user-geo-panel .user-geo-header .header-title-row h3{font-size:18px;font-weight:600;margin:0;color:#fff}.user-geo-panel .user-geo-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.user-geo-panel .user-geo-header .close-btn:hover{opacity:1}.user-geo-panel .user-geo-description{font-size:14px;color:#fffc;line-height:1.6;margin-bottom:1rem}.user-geo-panel .user-geo-description p{margin:0 0 .75rem}.user-geo-panel .user-geo-content{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center}.user-geo-panel .user-geo-verifying{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verifying .geo-spinner-large{width:80px;height:80px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite;margin-bottom:1.5rem}.user-geo-panel .user-geo-verifying .verifying-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .25rem}.user-geo-panel .user-geo-verifying .verifying-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-verified{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-verified .verified-icon-container{margin-bottom:1rem}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon{display:block}.user-geo-panel .user-geo-verified .verified-icon-container .home-icon svg{width:120px;height:100px}.user-geo-panel .user-geo-verified .verified-title{color:#fff;font-size:16px;font-weight:600;margin:0}.user-geo-panel .user-geo-denied{display:flex;flex-direction:column;align-items:center;text-align:center}.user-geo-panel .user-geo-denied .denied-icon-container{width:100px;height:100px;border-radius:50%;background:rgba(244,67,54,.2);display:flex;align-items:center;justify-content:center;margin-bottom:1.5rem}.user-geo-panel .user-geo-denied .denied-icon-container i{font-size:48px;color:#f44336}.user-geo-panel .user-geo-denied .denied-title{color:#fff;font-size:16px;font-weight:600;margin:0 0 .5rem}.user-geo-panel .user-geo-denied .denied-subtitle{color:#fff9;font-size:14px;margin:0}.user-geo-panel .user-geo-btn{width:100%;padding:14px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:24px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s ease;margin-top:auto;margin-bottom:1rem}.user-geo-panel .user-geo-btn:hover:not(.disabled){background:#178e99}.user-geo-panel .user-geo-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.user-geo-panel .dev-controls{display:flex;align-items:center;gap:.5rem;padding:.75rem;background:rgba(0,0,0,.3);border-radius:6px;margin-bottom:1rem;flex-wrap:wrap}.user-geo-panel .dev-controls .dev-label{font-size:12px;color:#fff9;font-weight:600}.user-geo-panel .dev-controls .dev-btn{padding:4px 8px;font-size:11px;background:rgba(255,255,255,.1);color:#fff;border:1px solid rgba(255,255,255,.2);border-radius:4px;cursor:pointer;transition:all .2s ease}.user-geo-panel .dev-controls .dev-btn:hover{background:rgba(255,255,255,.2)}.user-geo-panel .user-geo-footer{display:flex;justify-content:center;gap:.75rem;padding-top:.5rem}.user-geo-panel .user-geo-footer .footer-icon{width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff9;font-size:16px}.user-geo-panel .user-geo-footer .footer-icon.active{background:var(--Primary-Uell, #1da4b1);color:#fff}\n"] }]
|
|
1405
|
+
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i4.DomSanitizer }]; }, propDecorators: { sessionId: [{
|
|
1234
1406
|
type: Input
|
|
1235
1407
|
}], token: [{
|
|
1236
1408
|
type: Input
|
|
@@ -1290,20 +1462,41 @@ class TasWaitingRoomComponent {
|
|
|
1290
1462
|
this.videoCallModalRef = null;
|
|
1291
1463
|
// Geolocation
|
|
1292
1464
|
this.geoPosition = null;
|
|
1465
|
+
this.geoWasDenied = false; // Tracks if user denied geo permission
|
|
1466
|
+
this.geoResultSent = false; // Tracks if geo result was already sent
|
|
1467
|
+
// Permission tracking for auto-join
|
|
1468
|
+
this.mediaPermissionsGranted = false;
|
|
1469
|
+
this.geoPermissionsResolved = false;
|
|
1470
|
+
// Auto-retry tracking
|
|
1471
|
+
this.retryCount = 0;
|
|
1472
|
+
this.MAX_RETRIES = 1;
|
|
1293
1473
|
}
|
|
1294
1474
|
/** Whether current user is an owner */
|
|
1295
1475
|
get isOwner() {
|
|
1296
1476
|
var _a;
|
|
1297
1477
|
return ((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.role) === TasUserRole.OWNER;
|
|
1298
1478
|
}
|
|
1479
|
+
/** Whether we're still requesting permissions (for template) */
|
|
1480
|
+
get isRequestingPermissions() {
|
|
1481
|
+
return !this.mediaPermissionsGranted || (!this.geoPermissionsResolved && !this.isOwner && !this.isBackoffice);
|
|
1482
|
+
}
|
|
1483
|
+
/** Whether we're waiting for admission after permissions granted (for template) */
|
|
1484
|
+
get isWaitingForAdmission() {
|
|
1485
|
+
return this.mediaPermissionsGranted &&
|
|
1486
|
+
(this.geoPermissionsResolved || this.isOwner || this.isBackoffice) &&
|
|
1487
|
+
!this.isJoinable;
|
|
1488
|
+
}
|
|
1299
1489
|
ngOnInit() {
|
|
1300
1490
|
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnInit');
|
|
1301
1491
|
this.requestMediaPermissions();
|
|
1492
|
+
// Request geolocation permission and cache it (but don't send to backend yet)
|
|
1493
|
+
// The cached position will be sent when owner requests it via activateGeo
|
|
1302
1494
|
this.requestGeolocation();
|
|
1303
1495
|
this.checkStatus();
|
|
1304
1496
|
}
|
|
1305
1497
|
/**
|
|
1306
1498
|
* Request camera and microphone permissions.
|
|
1499
|
+
* Triggers auto-join when permissions are resolved.
|
|
1307
1500
|
*/
|
|
1308
1501
|
requestMediaPermissions() {
|
|
1309
1502
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1313,49 +1506,81 @@ class TasWaitingRoomComponent {
|
|
|
1313
1506
|
// Stop tracks immediately - we just needed the permission
|
|
1314
1507
|
stream.getTracks().forEach(track => track.stop());
|
|
1315
1508
|
console.log('[TAS DEBUG] Media permissions granted');
|
|
1509
|
+
this.mediaPermissionsGranted = true;
|
|
1316
1510
|
}
|
|
1317
1511
|
catch (error) {
|
|
1318
1512
|
console.warn('[TAS DEBUG] Media permissions denied or unavailable:', error);
|
|
1513
|
+
// Still allow joining - some users may not have camera/mic
|
|
1514
|
+
this.mediaPermissionsGranted = true;
|
|
1319
1515
|
}
|
|
1516
|
+
this.tryAutoJoin();
|
|
1517
|
+
this.cdr.detectChanges();
|
|
1320
1518
|
});
|
|
1321
1519
|
}
|
|
1322
1520
|
/**
|
|
1323
|
-
* Request geolocation
|
|
1521
|
+
* Request geolocation permission and cache result.
|
|
1324
1522
|
* Only for regular users (not owners/backoffice).
|
|
1325
|
-
*
|
|
1523
|
+
* Actual send to backend happens after we have videoCallId.
|
|
1524
|
+
* Triggers auto-join when geolocation is resolved.
|
|
1326
1525
|
*/
|
|
1327
1526
|
requestGeolocation() {
|
|
1328
1527
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1329
1528
|
// Only request geolocation for regular users, not owners/backoffice
|
|
1330
1529
|
if (this.isOwner || this.isBackoffice) {
|
|
1331
1530
|
console.log('[TAS DEBUG] Skipping geolocation for owner/backoffice');
|
|
1531
|
+
this.geoPermissionsResolved = true;
|
|
1332
1532
|
return;
|
|
1333
1533
|
}
|
|
1334
|
-
console.log('[TAS DEBUG] Requesting geolocation...');
|
|
1534
|
+
console.log('[TAS DEBUG] Requesting geolocation permission...');
|
|
1335
1535
|
const position = yield this.geolocationService.getCurrentPosition();
|
|
1336
1536
|
if (position) {
|
|
1337
|
-
console.log('[TAS DEBUG] Geolocation
|
|
1537
|
+
console.log('[TAS DEBUG] Geolocation granted:', position);
|
|
1338
1538
|
this.geoPosition = position;
|
|
1339
|
-
|
|
1340
|
-
this.sendGeolocationToBackend();
|
|
1539
|
+
this.geoWasDenied = false;
|
|
1341
1540
|
}
|
|
1342
1541
|
else {
|
|
1343
1542
|
console.log('[TAS DEBUG] Geolocation denied or unavailable');
|
|
1543
|
+
this.geoPosition = null;
|
|
1544
|
+
this.geoWasDenied = true;
|
|
1344
1545
|
}
|
|
1546
|
+
this.geoPermissionsResolved = true;
|
|
1547
|
+
// If we already have videoCallId, send now. Otherwise, it will be sent after status response.
|
|
1548
|
+
this.sendGeoResultToBackend();
|
|
1549
|
+
this.tryAutoJoin();
|
|
1550
|
+
this.cdr.detectChanges();
|
|
1345
1551
|
});
|
|
1346
1552
|
}
|
|
1347
1553
|
/**
|
|
1348
|
-
* Send
|
|
1349
|
-
*
|
|
1554
|
+
* Send geo result to backend (granted or denied).
|
|
1555
|
+
* Only sends if videoCallId is available and not already sent.
|
|
1556
|
+
*/
|
|
1557
|
+
sendGeoResultToBackend() {
|
|
1558
|
+
if (!this.videoCallId) {
|
|
1559
|
+
console.log('[TAS DEBUG] Cannot send geo result: videoCallId not available yet');
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (this.geoResultSent) {
|
|
1563
|
+
console.log('[TAS DEBUG] Geo result already sent, skipping');
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (this.geoPosition) {
|
|
1567
|
+
this.sendGeolocationToBackend();
|
|
1568
|
+
this.geoResultSent = true;
|
|
1569
|
+
}
|
|
1570
|
+
else if (this.geoWasDenied) {
|
|
1571
|
+
this.sendGeoDenialToBackend();
|
|
1572
|
+
this.geoResultSent = true;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Send granted geolocation to backend via modify user endpoint.
|
|
1350
1577
|
*/
|
|
1351
1578
|
sendGeolocationToBackend() {
|
|
1352
|
-
var _a;
|
|
1353
1579
|
if (!this.geoPosition || !this.videoCallId) {
|
|
1354
1580
|
return;
|
|
1355
1581
|
}
|
|
1356
1582
|
console.log('[TAS DEBUG] Sending geolocation to backend...');
|
|
1357
1583
|
const body = {
|
|
1358
|
-
userId: (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id,
|
|
1359
1584
|
videoCallId: this.videoCallId,
|
|
1360
1585
|
action: UserCallAction.ACTIVATE_GEOLOCATION,
|
|
1361
1586
|
latitude: this.geoPosition.latitude,
|
|
@@ -1366,6 +1591,23 @@ class TasWaitingRoomComponent {
|
|
|
1366
1591
|
error: (err) => console.error('[TAS DEBUG] Failed to send geolocation:', err),
|
|
1367
1592
|
});
|
|
1368
1593
|
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Send geolocation denial to backend via modify user endpoint.
|
|
1596
|
+
*/
|
|
1597
|
+
sendGeoDenialToBackend() {
|
|
1598
|
+
if (!this.videoCallId) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
console.log('[TAS DEBUG] Sending geo denial to backend...');
|
|
1602
|
+
const body = {
|
|
1603
|
+
videoCallId: this.videoCallId,
|
|
1604
|
+
action: UserCallAction.DENY_GEOLOCATION,
|
|
1605
|
+
};
|
|
1606
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
1607
|
+
next: () => console.log('[TAS DEBUG] Geo denial sent successfully'),
|
|
1608
|
+
error: (err) => console.error('[TAS DEBUG] Failed to send geo denial:', err),
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1369
1611
|
ngOnDestroy() {
|
|
1370
1612
|
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnDestroy');
|
|
1371
1613
|
this.subscriptions.unsubscribe();
|
|
@@ -1399,8 +1641,8 @@ class TasWaitingRoomComponent {
|
|
|
1399
1641
|
this.resolvedAppointmentId = content.appointmentId;
|
|
1400
1642
|
this.videoCallId = content.videoCallId;
|
|
1401
1643
|
console.log('[TAS DEBUG] Status response:', content);
|
|
1402
|
-
//
|
|
1403
|
-
this.
|
|
1644
|
+
// Now that we have videoCallId, send geo result if not already sent
|
|
1645
|
+
this.sendGeoResultToBackend();
|
|
1404
1646
|
// Start polling for status updates
|
|
1405
1647
|
this.tasService.startStatusPolling(statusParams);
|
|
1406
1648
|
// Subscribe to joinable status
|
|
@@ -1416,7 +1658,8 @@ class TasWaitingRoomComponent {
|
|
|
1416
1658
|
}));
|
|
1417
1659
|
}
|
|
1418
1660
|
/**
|
|
1419
|
-
* Handle changes to joinable status
|
|
1661
|
+
* Handle changes to joinable status.
|
|
1662
|
+
* Triggers auto-join when joinable becomes true.
|
|
1420
1663
|
*/
|
|
1421
1664
|
handleJoinableChange(joinable) {
|
|
1422
1665
|
console.log('[TAS DEBUG] handleJoinableChange called', {
|
|
@@ -1432,10 +1675,11 @@ class TasWaitingRoomComponent {
|
|
|
1432
1675
|
console.log('[TAS DEBUG] Skipping state update - already in:', this.state);
|
|
1433
1676
|
return;
|
|
1434
1677
|
}
|
|
1435
|
-
//
|
|
1678
|
+
// Update state and attempt auto-join
|
|
1436
1679
|
if (joinable) {
|
|
1437
|
-
console.log('[TAS DEBUG] Joinable is true,
|
|
1680
|
+
console.log('[TAS DEBUG] Joinable is true, attempting auto-join');
|
|
1438
1681
|
this.state = WaitingRoomState.READY;
|
|
1682
|
+
this.tryAutoJoin();
|
|
1439
1683
|
}
|
|
1440
1684
|
else {
|
|
1441
1685
|
console.log('[TAS DEBUG] Waiting for joinable...');
|
|
@@ -1444,11 +1688,33 @@ class TasWaitingRoomComponent {
|
|
|
1444
1688
|
this.cdr.detectChanges();
|
|
1445
1689
|
}
|
|
1446
1690
|
/**
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1691
|
+
* Attempt to auto-join the session when all conditions are met.
|
|
1692
|
+
* - For owners: just need joinable + sessionId
|
|
1693
|
+
* - For users: need media permissions + geo resolved + joinable + sessionId
|
|
1449
1694
|
*/
|
|
1450
|
-
|
|
1451
|
-
|
|
1695
|
+
tryAutoJoin() {
|
|
1696
|
+
// Don't try if already joining or in error
|
|
1697
|
+
if (this.state === WaitingRoomState.GETTING_TOKEN ||
|
|
1698
|
+
this.state === WaitingRoomState.JOINING) {
|
|
1699
|
+
console.log('[TAS DEBUG] tryAutoJoin skipped - already in state:', this.state);
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
// For owners: just need joinable
|
|
1703
|
+
if (this.isOwner) {
|
|
1704
|
+
if (this.isJoinable && this.resolvedSessionId) {
|
|
1705
|
+
console.log('[TAS DEBUG] Owner auto-joining...');
|
|
1706
|
+
this.startSessionAndJoin();
|
|
1707
|
+
}
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
// For users: need media + geo resolved + joinable
|
|
1711
|
+
if (this.mediaPermissionsGranted &&
|
|
1712
|
+
this.geoPermissionsResolved &&
|
|
1713
|
+
this.isJoinable &&
|
|
1714
|
+
this.resolvedSessionId) {
|
|
1715
|
+
console.log('[TAS DEBUG] User auto-joining...');
|
|
1716
|
+
this.startSessionAndJoin();
|
|
1717
|
+
}
|
|
1452
1718
|
}
|
|
1453
1719
|
/**
|
|
1454
1720
|
* Check if user has owner/backoffice role
|
|
@@ -1503,28 +1769,34 @@ class TasWaitingRoomComponent {
|
|
|
1503
1769
|
error: (err) => {
|
|
1504
1770
|
var _a;
|
|
1505
1771
|
console.error('[TAS DEBUG] /start request failed:', err);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1772
|
+
// Auto-retry on first failure
|
|
1773
|
+
if (this.retryCount < this.MAX_RETRIES) {
|
|
1774
|
+
this.retryCount++;
|
|
1775
|
+
console.log('[TAS DEBUG] Auto-retrying... attempt:', this.retryCount);
|
|
1776
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
1777
|
+
this.cdr.detectChanges();
|
|
1778
|
+
setTimeout(() => this.startSessionAndJoin(), 2000);
|
|
1779
|
+
}
|
|
1780
|
+
else {
|
|
1781
|
+
// Show error after retry fails
|
|
1782
|
+
this.state = WaitingRoomState.ERROR;
|
|
1783
|
+
this.errorMessage = ((_a = err === null || err === void 0 ? void 0 : err.error) === null || _a === void 0 ? void 0 : _a.message) || (err === null || err === void 0 ? void 0 : err.message) || 'Error al iniciar la sesión. Por favor, intente nuevamente.';
|
|
1784
|
+
this.tasService.stopStatusPolling();
|
|
1785
|
+
console.log('[TAS DEBUG] State set to ERROR, errorMessage:', this.errorMessage);
|
|
1786
|
+
this.cdr.detectChanges();
|
|
1787
|
+
}
|
|
1511
1788
|
},
|
|
1512
1789
|
}));
|
|
1513
1790
|
}
|
|
1514
1791
|
/**
|
|
1515
|
-
*
|
|
1516
|
-
|
|
1517
|
-
cancel() {
|
|
1518
|
-
this.tasService.stopStatusPolling();
|
|
1519
|
-
this.activeModal.dismiss('cancel');
|
|
1520
|
-
}
|
|
1521
|
-
/**
|
|
1522
|
-
* Retry after an error
|
|
1792
|
+
* Retry after an error.
|
|
1793
|
+
* Resets retry count and restarts the flow.
|
|
1523
1794
|
*/
|
|
1524
1795
|
retry() {
|
|
1525
1796
|
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
1526
1797
|
this.errorMessage = '';
|
|
1527
1798
|
this.token = '';
|
|
1799
|
+
this.retryCount = 0; // Reset retry count for auto-retry
|
|
1528
1800
|
this.checkStatus();
|
|
1529
1801
|
}
|
|
1530
1802
|
openVideoCallModal() {
|
|
@@ -1551,10 +1823,10 @@ class TasWaitingRoomComponent {
|
|
|
1551
1823
|
}
|
|
1552
1824
|
}
|
|
1553
1825
|
TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }, { token: i1.NgbModal }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1554
|
-
TasWaitingRoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasWaitingRoomComponent, selector: "tas-waiting-room", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser" }, ngImport: i0, template: "<div class=\"tas-waiting-room\">\n
|
|
1826
|
+
TasWaitingRoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasWaitingRoomComponent, selector: "tas-waiting-room", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser" }, ngImport: i0, template: "<div class=\"tas-waiting-room\">\n <div class=\"waiting-room-content\">\n <div class=\"state-container\">\n <!-- Loading states (show spinner) -->\n <ng-container *ngIf=\"state !== WaitingRoomState.ERROR\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n \n <!-- Requesting permissions -->\n <ng-container *ngIf=\"isRequestingPermissions\">\n <p class=\"state-message\">Solicitando permisos...</p>\n <p class=\"state-submessage\">Por favor, acept\u00E1 los permisos de c\u00E1mara y micr\u00F3fono.</p>\n </ng-container>\n \n <!-- Waiting for admission (permissions granted, not joinable yet) -->\n <ng-container *ngIf=\"isWaitingForAdmission\">\n <p class=\"state-message\">Medicina laboral va a admitirte</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </ng-container>\n \n <!-- Getting token / Joining -->\n <ng-container *ngIf=\"state === WaitingRoomState.GETTING_TOKEN || state === WaitingRoomState.JOINING\">\n <p class=\"state-message\">Conectando...</p>\n </ng-container>\n \n <!-- Checking status (when not requesting permissions) -->\n <ng-container *ngIf=\"state === WaitingRoomState.CHECKING_STATUS && !isRequestingPermissions\">\n <p class=\"state-message\">Verificando estado...</p>\n </ng-container>\n </ng-container>\n\n <!-- Error state (show error icon and message) -->\n <ng-container *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n <p class=\"error-details\" *ngIf=\"errorMessage\">{{ errorMessage }}</p>\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n </ng-container>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:300px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@media (max-width: 576px){.tas-waiting-room{min-height:250px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.action-btn{padding:10px 24px;font-size:14px}}\n"], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1555
1827
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
|
|
1556
1828
|
type: Component,
|
|
1557
|
-
args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n
|
|
1829
|
+
args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <div class=\"waiting-room-content\">\n <div class=\"state-container\">\n <!-- Loading states (show spinner) -->\n <ng-container *ngIf=\"state !== WaitingRoomState.ERROR\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n \n <!-- Requesting permissions -->\n <ng-container *ngIf=\"isRequestingPermissions\">\n <p class=\"state-message\">Solicitando permisos...</p>\n <p class=\"state-submessage\">Por favor, acept\u00E1 los permisos de c\u00E1mara y micr\u00F3fono.</p>\n </ng-container>\n \n <!-- Waiting for admission (permissions granted, not joinable yet) -->\n <ng-container *ngIf=\"isWaitingForAdmission\">\n <p class=\"state-message\">Medicina laboral va a admitirte</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </ng-container>\n \n <!-- Getting token / Joining -->\n <ng-container *ngIf=\"state === WaitingRoomState.GETTING_TOKEN || state === WaitingRoomState.JOINING\">\n <p class=\"state-message\">Conectando...</p>\n </ng-container>\n \n <!-- Checking status (when not requesting permissions) -->\n <ng-container *ngIf=\"state === WaitingRoomState.CHECKING_STATUS && !isRequestingPermissions\">\n <p class=\"state-message\">Verificando estado...</p>\n </ng-container>\n </ng-container>\n\n <!-- Error state (show error icon and message) -->\n <ng-container *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n <p class=\"error-details\" *ngIf=\"errorMessage\">{{ errorMessage }}</p>\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n </ng-container>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:300px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@media (max-width: 576px){.tas-waiting-room{min-height:250px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.action-btn{padding:10px 24px;font-size:14px}}\n"] }]
|
|
1558
1830
|
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
|
|
1559
1831
|
type: Input
|
|
1560
1832
|
}], entityId: [{
|
|
@@ -1578,6 +1850,9 @@ class TasButtonComponent {
|
|
|
1578
1850
|
// Style customization
|
|
1579
1851
|
this.variant = 'default';
|
|
1580
1852
|
this.buttonLabel = 'Iniciar TAS';
|
|
1853
|
+
// Skip status check - when true, button shows immediately without API call
|
|
1854
|
+
// Useful when parent component already knows the appointment is valid
|
|
1855
|
+
this.skipStatusCheck = false;
|
|
1581
1856
|
this.isLoading = false;
|
|
1582
1857
|
// Status check state
|
|
1583
1858
|
this.isCheckingStatus = false;
|
|
@@ -1622,6 +1897,12 @@ class TasButtonComponent {
|
|
|
1622
1897
|
this.videoCallModalRef = null;
|
|
1623
1898
|
}
|
|
1624
1899
|
}));
|
|
1900
|
+
// If skipStatusCheck is true, show button immediately without polling
|
|
1901
|
+
if (this.skipStatusCheck) {
|
|
1902
|
+
this.shouldShowButton = true;
|
|
1903
|
+
this.isJoinable = true;
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1625
1906
|
// Start status checking
|
|
1626
1907
|
this.startStatusPolling();
|
|
1627
1908
|
}
|
|
@@ -1669,6 +1950,11 @@ class TasButtonComponent {
|
|
|
1669
1950
|
.subscribe({
|
|
1670
1951
|
next: (response) => {
|
|
1671
1952
|
var _a, _b;
|
|
1953
|
+
// Validate response structure
|
|
1954
|
+
if (!response || !response.content) {
|
|
1955
|
+
this.handleStatusError('Invalid response');
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1672
1958
|
this.isCheckingStatus = false;
|
|
1673
1959
|
this.isStatusError = false;
|
|
1674
1960
|
this.statusErrorMessage = '';
|
|
@@ -1677,23 +1963,20 @@ class TasButtonComponent {
|
|
|
1677
1963
|
this.isJoinable = (_b = (_a = response.content) === null || _a === void 0 ? void 0 : _a.joinable) !== null && _b !== void 0 ? _b : false;
|
|
1678
1964
|
},
|
|
1679
1965
|
error: (err) => {
|
|
1680
|
-
this.
|
|
1681
|
-
const errorMessage = this.tasUtilityService.extractErrorMessage(err, 'Error checking status');
|
|
1682
|
-
this.statusErrorMessage = errorMessage;
|
|
1683
|
-
// Use utility service to determine if button should be shown
|
|
1684
|
-
this.shouldShowButton = this.tasUtilityService.shouldShowButton(errorMessage);
|
|
1685
|
-
// Stop polling on error
|
|
1686
|
-
this.stopStatusPolling();
|
|
1687
|
-
// If button should be hidden, don't treat as error
|
|
1688
|
-
if (!this.shouldShowButton) {
|
|
1689
|
-
this.isStatusError = false;
|
|
1690
|
-
}
|
|
1691
|
-
else {
|
|
1692
|
-
this.isStatusError = true;
|
|
1693
|
-
}
|
|
1966
|
+
this.handleStatusError(err);
|
|
1694
1967
|
},
|
|
1695
1968
|
}));
|
|
1696
1969
|
}
|
|
1970
|
+
handleStatusError(err) {
|
|
1971
|
+
this.isCheckingStatus = false;
|
|
1972
|
+
const errorMessage = this.tasUtilityService.extractErrorMessage(err, 'Error checking status');
|
|
1973
|
+
this.statusErrorMessage = errorMessage;
|
|
1974
|
+
// On any status error, hide the button
|
|
1975
|
+
this.shouldShowButton = false;
|
|
1976
|
+
this.isStatusError = false; // We don't show error UI, just hide the button
|
|
1977
|
+
// Stop polling on error
|
|
1978
|
+
this.stopStatusPolling();
|
|
1979
|
+
}
|
|
1697
1980
|
onClick() {
|
|
1698
1981
|
var _a;
|
|
1699
1982
|
if (!this.tenant || !((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
@@ -1743,7 +2026,7 @@ class TasButtonComponent {
|
|
|
1743
2026
|
}
|
|
1744
2027
|
}
|
|
1745
2028
|
TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, deps: [{ token: i1.NgbModal }, { token: TasService }, { token: TasUtilityService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1746
|
-
TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasButtonComponent, selector: "tas-btn", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", variant: "variant", buttonLabel: "buttonLabel" }, ngImport: i0, template: "<span\n *ngIf=\"shouldShowButton\"\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;font-weight:500;margin-right:0;padding:6px 14px}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "triggers", "container", "disableTooltip", "tooltipClass", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }] });
|
|
2029
|
+
TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasButtonComponent, selector: "tas-btn", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", variant: "variant", buttonLabel: "buttonLabel", skipStatusCheck: "skipStatusCheck" }, ngImport: i0, template: "<span\n *ngIf=\"shouldShowButton\"\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;font-weight:500;margin-right:0;padding:6px 14px}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "triggers", "container", "disableTooltip", "tooltipClass", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }] });
|
|
1747
2030
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
|
|
1748
2031
|
type: Component,
|
|
1749
2032
|
args: [{ selector: 'tas-btn', template: "<span\n *ngIf=\"shouldShowButton\"\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;font-weight:500;margin-right:0;padding:6px 14px}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"] }]
|
|
@@ -1761,6 +2044,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
1761
2044
|
type: Input
|
|
1762
2045
|
}], buttonLabel: [{
|
|
1763
2046
|
type: Input
|
|
2047
|
+
}], skipStatusCheck: [{
|
|
2048
|
+
type: Input
|
|
1764
2049
|
}] } });
|
|
1765
2050
|
|
|
1766
2051
|
class TasFloatingCallComponent {
|
|
@@ -2001,7 +2286,7 @@ class TasIncomingAppointmentComponent {
|
|
|
2001
2286
|
}
|
|
2002
2287
|
}
|
|
2003
2288
|
TasIncomingAppointmentComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2004
|
-
TasIncomingAppointmentComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasIncomingAppointmentComponent, selector: "tas-incoming-appointment", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", fromDate: "fromDate", toDate: "toDate" }, outputs: { enterCall: "enterCall" }, ngImport: i0, template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && appointments.length === 0\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointments list -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointments.length > 0\">\n <div class=\"appointment-card\" *ngFor=\"let appt of appointments\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"getOtherParticipant(appt)\" [size]=\"48\"></tas-avatar>\n <span class=\"doctor-name\">{{ getOtherParticipant(appt) }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formatAppointmentDate(appt) }}</span>\n <span class=\"time\">{{ formatTimeRange(appt) }}</span>\n </div>\n <tas-btn\n *ngIf=\"shouldShowTasBtn(appt)\"\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appt.roomType\"\n [entityId]=\"appt.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch;gap:16px}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }, { type: TasButtonComponent, selector: "tas-btn", inputs: ["roomType", "entityId", "tenant", "businessRole", "currentUser", "variant", "buttonLabel"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
2289
|
+
TasIncomingAppointmentComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasIncomingAppointmentComponent, selector: "tas-incoming-appointment", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser", fromDate: "fromDate", toDate: "toDate" }, outputs: { enterCall: "enterCall" }, ngImport: i0, template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && appointments.length === 0\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointments list -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointments.length > 0\">\n <div class=\"appointment-card\" *ngFor=\"let appt of appointments\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"getOtherParticipant(appt)\" [size]=\"48\"></tas-avatar>\n <span class=\"doctor-name\">{{ getOtherParticipant(appt) }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formatAppointmentDate(appt) }}</span>\n <span class=\"time\">{{ formatTimeRange(appt) }}</span>\n </div>\n <tas-btn\n *ngIf=\"shouldShowTasBtn(appt)\"\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appt.roomType\"\n [entityId]=\"appt.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch;gap:16px}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }, { type: TasButtonComponent, selector: "tas-btn", inputs: ["roomType", "entityId", "tenant", "businessRole", "currentUser", "variant", "buttonLabel", "skipStatusCheck"] }], directives: [{ type: i4$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
2005
2290
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, decorators: [{
|
|
2006
2291
|
type: Component,
|
|
2007
2292
|
args: [{ selector: 'tas-incoming-appointment', template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && appointments.length === 0\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointments list -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointments.length > 0\">\n <div class=\"appointment-card\" *ngFor=\"let appt of appointments\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"getOtherParticipant(appt)\" [size]=\"48\"></tas-avatar>\n <span class=\"doctor-name\">{{ getOtherParticipant(appt) }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formatAppointmentDate(appt) }}</span>\n <span class=\"time\">{{ formatTimeRange(appt) }}</span>\n </div>\n <tas-btn\n *ngIf=\"shouldShowTasBtn(appt)\"\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"appt.roomType\"\n [entityId]=\"appt.entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch;gap:16px}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"] }]
|
|
@@ -2108,5 +2393,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
2108
2393
|
* Generated bundle index. Do not edit.
|
|
2109
2394
|
*/
|
|
2110
2395
|
|
|
2111
|
-
export { AppointmentStatus, CallState, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasUtilityService, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
|
|
2396
|
+
export { AppointmentStatus, CallState, GeoStatus, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasUtilityService, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserGeoViewState, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
|
|
2112
2397
|
//# sourceMappingURL=tas-uell-sdk.mjs.map
|