tas-uell-sdk 0.0.6 → 0.1.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 +72 -3
- package/esm2020/lib/components/tas-btn/tas-btn.component.mjs +27 -35
- package/esm2020/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.mjs +109 -0
- package/esm2020/lib/components/tas-videocall/tas-videocall.component.mjs +114 -8
- package/esm2020/lib/components/tas-waiting-room/tas-waiting-room.component.mjs +104 -46
- package/esm2020/lib/config/tas.config.mjs +1 -1
- package/esm2020/lib/interfaces/tas.interfaces.mjs +7 -1
- package/esm2020/lib/services/geolocation.service.mjs +56 -0
- package/esm2020/lib/services/tas.service.mjs +38 -1
- package/esm2020/lib/tas-uell-sdk.module.mjs +11 -5
- package/esm2020/public-api.mjs +3 -1
- package/fesm2015/tas-uell-sdk.mjs +458 -90
- package/fesm2015/tas-uell-sdk.mjs.map +1 -1
- package/fesm2020/tas-uell-sdk.mjs +446 -89
- package/fesm2020/tas-uell-sdk.mjs.map +1 -1
- package/lib/components/tas-btn/tas-btn.component.d.ts +6 -4
- package/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.d.ts +33 -0
- package/lib/components/tas-videocall/tas-videocall.component.d.ts +27 -2
- package/lib/components/tas-waiting-room/tas-waiting-room.component.d.ts +29 -8
- package/lib/config/tas.config.d.ts +3 -0
- package/lib/interfaces/tas.interfaces.d.ts +26 -2
- package/lib/services/geolocation.service.d.ts +24 -0
- package/lib/services/tas.service.d.ts +13 -1
- package/lib/tas-uell-sdk.module.d.ts +5 -3
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Input, ViewChild, NgModule } from '@angular/core';
|
|
2
|
+
import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Input, ViewChild, EventEmitter, Output, NgModule } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject, Subscription } from 'rxjs';
|
|
4
4
|
import { map, catchError } from 'rxjs/operators';
|
|
5
5
|
import * as OT from '@opentok/client';
|
|
6
|
+
import { __awaiter } from 'tslib';
|
|
6
7
|
import interact from 'interactjs';
|
|
7
8
|
import * as i1 from '@ng-bootstrap/ng-bootstrap';
|
|
8
|
-
import
|
|
9
|
+
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
|
10
|
+
import * as i4 from '@angular/common';
|
|
9
11
|
import { CommonModule } from '@angular/common';
|
|
10
12
|
import { FormsModule } from '@angular/forms';
|
|
11
13
|
|
|
@@ -86,6 +88,12 @@ var ViewMode;
|
|
|
86
88
|
ViewMode["FULLSCREEN"] = "FULLSCREEN";
|
|
87
89
|
ViewMode["PIP"] = "PIP";
|
|
88
90
|
})(ViewMode || (ViewMode = {}));
|
|
91
|
+
// Appointment types
|
|
92
|
+
var AppointmentStatus;
|
|
93
|
+
(function (AppointmentStatus) {
|
|
94
|
+
AppointmentStatus["CONFIRMED"] = "CONFIRMED";
|
|
95
|
+
AppointmentStatus["CANCELLED"] = "CANCELLED";
|
|
96
|
+
})(AppointmentStatus || (AppointmentStatus = {}));
|
|
89
97
|
|
|
90
98
|
class TasService {
|
|
91
99
|
constructor(httpClient, config) {
|
|
@@ -120,6 +128,15 @@ class TasService {
|
|
|
120
128
|
this.ownerHasLeft$ = this.ownerHasLeftSubject.asObservable();
|
|
121
129
|
this.joinableSubject = new BehaviorSubject(false);
|
|
122
130
|
this.joinable$ = this.joinableSubject.asObservable();
|
|
131
|
+
// Observable for when backend requests geolocation activation
|
|
132
|
+
this.activateGeoSubject = new BehaviorSubject(false);
|
|
133
|
+
this.activateGeo$ = this.activateGeoSubject.asObservable();
|
|
134
|
+
// Observable for when geo request is active (owner waiting for user response)
|
|
135
|
+
this.geoRequestActiveSubject = new BehaviorSubject(false);
|
|
136
|
+
this.geoRequestActive$ = this.geoRequestActiveSubject.asObservable();
|
|
137
|
+
// Observable for when all geo has been granted
|
|
138
|
+
this.allGeoGrantedSubject = new BehaviorSubject(false);
|
|
139
|
+
this.allGeoGranted$ = this.allGeoGrantedSubject.asObservable();
|
|
123
140
|
this.statusPollingInterval = null;
|
|
124
141
|
this.DEFAULT_POLL_INTERVAL_MS = 30000; // Default 30s
|
|
125
142
|
this.wasOwnerPresent = false;
|
|
@@ -412,6 +429,19 @@ class TasService {
|
|
|
412
429
|
throw error;
|
|
413
430
|
}));
|
|
414
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Get user appointments within a date range.
|
|
434
|
+
* @param params Date range for querying appointments
|
|
435
|
+
* @returns Observable of appointment array
|
|
436
|
+
*/
|
|
437
|
+
getAppointments(params) {
|
|
438
|
+
return this.httpClient
|
|
439
|
+
.get(`v2/proxy/appointment/agendas/user/appointments?fromDate=${params.fromDate}&toDate=${params.toDate}`, { headers: {} })
|
|
440
|
+
.pipe(catchError((error) => {
|
|
441
|
+
console.error('TAS Service: getAppointments failed', error);
|
|
442
|
+
throw error;
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
415
445
|
/**
|
|
416
446
|
* Start automatic status polling for the current session.
|
|
417
447
|
* Status is polled every STATUS_POLL_INTERVAL_MS milliseconds.
|
|
@@ -448,6 +478,21 @@ class TasService {
|
|
|
448
478
|
}
|
|
449
479
|
// Update joinable status
|
|
450
480
|
this.joinableSubject.next(content.joinable);
|
|
481
|
+
// Update activateGeo status
|
|
482
|
+
if (content.activateGeo !== undefined) {
|
|
483
|
+
console.log('[TAS DEBUG] activateGeo received:', content.activateGeo);
|
|
484
|
+
this.activateGeoSubject.next(content.activateGeo);
|
|
485
|
+
}
|
|
486
|
+
// Update geoRequestActive status (owner waiting for user geo response)
|
|
487
|
+
if (content.geoRequestActive !== undefined) {
|
|
488
|
+
console.log('[TAS DEBUG] geoRequestActive received:', content.geoRequestActive);
|
|
489
|
+
this.geoRequestActiveSubject.next(content.geoRequestActive);
|
|
490
|
+
}
|
|
491
|
+
// Update allGeoGranted status (all users responded with geo)
|
|
492
|
+
if (content.allGeoGranted !== undefined) {
|
|
493
|
+
console.log('[TAS DEBUG] allGeoGranted received:', content.allGeoGranted);
|
|
494
|
+
this.allGeoGrantedSubject.next(content.allGeoGranted);
|
|
495
|
+
}
|
|
451
496
|
// Check if owner has joined
|
|
452
497
|
const ownerJoined = this.checkIfOwnerJoined(content.users);
|
|
453
498
|
this.ownerHasJoinedSubject.next(ownerJoined);
|
|
@@ -657,6 +702,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
657
702
|
}] }];
|
|
658
703
|
} });
|
|
659
704
|
|
|
705
|
+
class GeolocationService {
|
|
706
|
+
constructor() {
|
|
707
|
+
this.cachedPosition = null;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Request current geolocation position using Web Geolocation API.
|
|
711
|
+
* Works in both browser and Capacitor environments.
|
|
712
|
+
* @returns Promise with {latitude, longitude} or null if denied/unavailable
|
|
713
|
+
*/
|
|
714
|
+
getCurrentPosition() {
|
|
715
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
716
|
+
if (!navigator.geolocation) {
|
|
717
|
+
console.warn('[GeolocationService] Geolocation not supported');
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
return new Promise((resolve) => {
|
|
721
|
+
navigator.geolocation.getCurrentPosition((position) => {
|
|
722
|
+
const geoPosition = {
|
|
723
|
+
latitude: position.coords.latitude,
|
|
724
|
+
longitude: position.coords.longitude,
|
|
725
|
+
};
|
|
726
|
+
this.cachedPosition = geoPosition;
|
|
727
|
+
resolve(geoPosition);
|
|
728
|
+
}, (error) => {
|
|
729
|
+
console.warn('[GeolocationService] Geolocation error:', error.message);
|
|
730
|
+
resolve(null);
|
|
731
|
+
}, {
|
|
732
|
+
enableHighAccuracy: false,
|
|
733
|
+
timeout: 30000,
|
|
734
|
+
maximumAge: 60000,
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Get the cached position from the last successful request.
|
|
741
|
+
*/
|
|
742
|
+
getCachedPosition() {
|
|
743
|
+
return this.cachedPosition;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Clear the cached position.
|
|
747
|
+
*/
|
|
748
|
+
clearCache() {
|
|
749
|
+
this.cachedPosition = null;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
GeolocationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
753
|
+
GeolocationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, providedIn: 'root' });
|
|
754
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, decorators: [{
|
|
755
|
+
type: Injectable,
|
|
756
|
+
args: [{
|
|
757
|
+
providedIn: 'root',
|
|
758
|
+
}]
|
|
759
|
+
}] });
|
|
760
|
+
|
|
660
761
|
class TasAvatarComponent {
|
|
661
762
|
constructor() {
|
|
662
763
|
this.name = '';
|
|
@@ -731,9 +832,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
731
832
|
}] } });
|
|
732
833
|
|
|
733
834
|
class TasVideocallComponent {
|
|
734
|
-
constructor(activeModal, tasService) {
|
|
835
|
+
constructor(activeModal, tasService, geolocationService) {
|
|
735
836
|
this.activeModal = activeModal;
|
|
736
837
|
this.tasService = tasService;
|
|
838
|
+
this.geolocationService = geolocationService;
|
|
737
839
|
this.participantName = '';
|
|
738
840
|
this.tenant = '';
|
|
739
841
|
this.businessRole = TasBusinessRole.USER;
|
|
@@ -745,6 +847,12 @@ class TasVideocallComponent {
|
|
|
745
847
|
this.ownerHasJoined = false;
|
|
746
848
|
this.hasVideoStream = false;
|
|
747
849
|
this.dismissedUsers = [];
|
|
850
|
+
this.showLocationPanel = true; // Show by default for owners, will be updated based on user's location status
|
|
851
|
+
this.userHasLocation = false; // Tracks if the user has shared their location
|
|
852
|
+
this.geoLocationStatus = 'unknown';
|
|
853
|
+
// Geo panel states for owner
|
|
854
|
+
this.geoRequestActive = false; // Owner sent request, waiting for user
|
|
855
|
+
this.allGeoGranted = false; // All users responded with geo
|
|
748
856
|
this.subscriptions = new Subscription();
|
|
749
857
|
}
|
|
750
858
|
ngOnInit() {
|
|
@@ -822,6 +930,30 @@ class TasVideocallComponent {
|
|
|
822
930
|
this.dismissedUsers.push(userId);
|
|
823
931
|
this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
|
|
824
932
|
}
|
|
933
|
+
/**
|
|
934
|
+
* Close the location panel
|
|
935
|
+
*/
|
|
936
|
+
closeLocationPanel() {
|
|
937
|
+
this.showLocationPanel = false;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Request the user to share their location
|
|
941
|
+
*/
|
|
942
|
+
requestUserLocation() {
|
|
943
|
+
if (!this.videoCallId) {
|
|
944
|
+
console.error('Cannot request location: videoCallId not set');
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const body = {
|
|
948
|
+
videoCallId: this.videoCallId,
|
|
949
|
+
action: UserCallAction.REQUEST_GEOLOCALIZATION,
|
|
950
|
+
};
|
|
951
|
+
// TODO: Send location request action to backend when endpoint is ready
|
|
952
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
953
|
+
next: () => console.log('Location request sent'),
|
|
954
|
+
error: (err) => console.error('Error requesting location:', err),
|
|
955
|
+
});
|
|
956
|
+
}
|
|
825
957
|
// Private Methods
|
|
826
958
|
setupSubscriptions() {
|
|
827
959
|
// Call state subscription
|
|
@@ -860,6 +992,80 @@ class TasVideocallComponent {
|
|
|
860
992
|
this.activeModal.close('owner_left');
|
|
861
993
|
}
|
|
862
994
|
}));
|
|
995
|
+
// ActivateGeo subscription - only for non-owners (users)
|
|
996
|
+
this.subscriptions.add(this.tasService.activateGeo$.subscribe((activateGeo) => {
|
|
997
|
+
if (activateGeo && !this.canAdmitUsers) {
|
|
998
|
+
console.log('[TAS DEBUG] activateGeo=true, checking geo status for user...');
|
|
999
|
+
this.handleActivateGeo();
|
|
1000
|
+
}
|
|
1001
|
+
}));
|
|
1002
|
+
// GeoRequestActive subscription - for owners
|
|
1003
|
+
this.subscriptions.add(this.tasService.geoRequestActive$.subscribe((active) => {
|
|
1004
|
+
console.log('[TAS DEBUG] geoRequestActive changed:', active);
|
|
1005
|
+
this.geoRequestActive = active;
|
|
1006
|
+
}));
|
|
1007
|
+
// AllGeoGranted subscription - for owners
|
|
1008
|
+
this.subscriptions.add(this.tasService.allGeoGranted$.subscribe((granted) => {
|
|
1009
|
+
console.log('[TAS DEBUG] allGeoGranted changed:', granted);
|
|
1010
|
+
this.allGeoGranted = granted;
|
|
1011
|
+
}));
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Handle activateGeo request from backend (for non-owner users).
|
|
1015
|
+
* If geo is already active, report it. If not, prompt user.
|
|
1016
|
+
*/
|
|
1017
|
+
handleActivateGeo() {
|
|
1018
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1019
|
+
console.log('[TAS DEBUG] handleActivateGeo - current status:', this.geoLocationStatus);
|
|
1020
|
+
// Check if we already have a cached position
|
|
1021
|
+
const cachedPosition = this.geolocationService.getCachedPosition();
|
|
1022
|
+
if (cachedPosition) {
|
|
1023
|
+
console.log('[TAS DEBUG] Geolocation already active, reporting to backend:', cachedPosition);
|
|
1024
|
+
this.geoLocationStatus = 'active';
|
|
1025
|
+
this.reportGeoStatus(cachedPosition.latitude, cachedPosition.longitude);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
// Try to get position
|
|
1029
|
+
console.log('[TAS DEBUG] Requesting geolocation from user...');
|
|
1030
|
+
const position = yield this.geolocationService.getCurrentPosition();
|
|
1031
|
+
if (position) {
|
|
1032
|
+
console.log('[TAS DEBUG] Geolocation obtained:', position);
|
|
1033
|
+
this.geoLocationStatus = 'active';
|
|
1034
|
+
this.reportGeoStatus(position.latitude, position.longitude);
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
console.log('[TAS DEBUG] Geolocation denied or unavailable');
|
|
1038
|
+
this.geoLocationStatus = 'denied';
|
|
1039
|
+
// Report that geo is not available (with no coordinates)
|
|
1040
|
+
this.reportGeoStatus();
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Report geolocation status to backend.
|
|
1046
|
+
*/
|
|
1047
|
+
reportGeoStatus(latitude, longitude) {
|
|
1048
|
+
if (!this.videoCallId) {
|
|
1049
|
+
console.error('[TAS DEBUG] Cannot report geo status: videoCallId not set');
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
// TODO: If the permission for geo location is not granted, we should not report it to the backend
|
|
1053
|
+
if (!this.geoLocationStatus) {
|
|
1054
|
+
console.error('[TAS DEBUG] Cannot report geo status: geoLocationStatus not set');
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const body = {
|
|
1058
|
+
userId: this.userId,
|
|
1059
|
+
videoCallId: this.videoCallId,
|
|
1060
|
+
action: UserCallAction.ACTIVATE_GEOLOCATION,
|
|
1061
|
+
latitude,
|
|
1062
|
+
longitude,
|
|
1063
|
+
};
|
|
1064
|
+
console.log('[TAS DEBUG] Reporting geo status to backend:', body);
|
|
1065
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
1066
|
+
next: () => console.log('[TAS DEBUG] Geo status reported successfully'),
|
|
1067
|
+
error: (err) => console.error('[TAS DEBUG] Error reporting geo status:', err),
|
|
1068
|
+
});
|
|
863
1069
|
}
|
|
864
1070
|
initializeCall() {
|
|
865
1071
|
if (this.isReturningFromPip) {
|
|
@@ -949,12 +1155,12 @@ class TasVideocallComponent {
|
|
|
949
1155
|
});
|
|
950
1156
|
}
|
|
951
1157
|
}
|
|
952
|
-
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
|
|
953
|
-
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", 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-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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1158
|
+
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 });
|
|
1159
|
+
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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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"] }] });
|
|
954
1160
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
|
|
955
1161
|
type: Component,
|
|
956
|
-
args: [{ selector: 'tas-videocall', template: "<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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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}\n"] }]
|
|
957
|
-
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { sessionId: [{
|
|
1162
|
+
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-video-camera\"></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 <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></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:var(--Primary-Uell, #1da4b1);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 .control-btn.muted{background:#f39c12}.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{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.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);box-shadow:0 4px 12px #0003}.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"] }]
|
|
1163
|
+
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }]; }, propDecorators: { sessionId: [{
|
|
958
1164
|
type: Input
|
|
959
1165
|
}], token: [{
|
|
960
1166
|
type: Input
|
|
@@ -962,6 +1168,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
962
1168
|
type: Input
|
|
963
1169
|
}], videoCallId: [{
|
|
964
1170
|
type: Input
|
|
1171
|
+
}], userId: [{
|
|
1172
|
+
type: Input
|
|
965
1173
|
}], participantName: [{
|
|
966
1174
|
type: Input
|
|
967
1175
|
}], tenant: [{
|
|
@@ -981,15 +1189,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
981
1189
|
var WaitingRoomState;
|
|
982
1190
|
(function (WaitingRoomState) {
|
|
983
1191
|
WaitingRoomState["CHECKING_STATUS"] = "CHECKING_STATUS";
|
|
984
|
-
WaitingRoomState["
|
|
1192
|
+
WaitingRoomState["WAITING_FOR_JOINABLE"] = "WAITING_FOR_JOINABLE";
|
|
985
1193
|
WaitingRoomState["READY"] = "READY";
|
|
986
1194
|
WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
|
|
1195
|
+
WaitingRoomState["JOINING"] = "JOINING";
|
|
987
1196
|
WaitingRoomState["ERROR"] = "ERROR";
|
|
988
1197
|
})(WaitingRoomState || (WaitingRoomState = {}));
|
|
989
1198
|
class TasWaitingRoomComponent {
|
|
990
|
-
constructor(activeModal, tasService, modalService, cdr) {
|
|
1199
|
+
constructor(activeModal, tasService, geolocationService, modalService, cdr) {
|
|
991
1200
|
this.activeModal = activeModal;
|
|
992
1201
|
this.tasService = tasService;
|
|
1202
|
+
this.geolocationService = geolocationService;
|
|
993
1203
|
this.modalService = modalService;
|
|
994
1204
|
this.cdr = cdr;
|
|
995
1205
|
// Status endpoint params
|
|
@@ -999,6 +1209,7 @@ class TasWaitingRoomComponent {
|
|
|
999
1209
|
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
1000
1210
|
this.WaitingRoomState = WaitingRoomState; // Expose enum to template
|
|
1001
1211
|
this.errorMessage = '';
|
|
1212
|
+
this.isJoinable = false;
|
|
1002
1213
|
// Session data from status response
|
|
1003
1214
|
this.resolvedSessionId = '';
|
|
1004
1215
|
this.resolvedAppointmentId = null;
|
|
@@ -1007,6 +1218,8 @@ class TasWaitingRoomComponent {
|
|
|
1007
1218
|
// Subscriptions
|
|
1008
1219
|
this.subscriptions = new Subscription();
|
|
1009
1220
|
this.videoCallModalRef = null;
|
|
1221
|
+
// Geolocation
|
|
1222
|
+
this.geoPosition = null;
|
|
1010
1223
|
}
|
|
1011
1224
|
/** Whether current user is an owner */
|
|
1012
1225
|
get isOwner() {
|
|
@@ -1015,8 +1228,74 @@ class TasWaitingRoomComponent {
|
|
|
1015
1228
|
}
|
|
1016
1229
|
ngOnInit() {
|
|
1017
1230
|
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnInit');
|
|
1231
|
+
this.requestMediaPermissions();
|
|
1232
|
+
this.requestGeolocation();
|
|
1018
1233
|
this.checkStatus();
|
|
1019
1234
|
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Request camera and microphone permissions.
|
|
1237
|
+
*/
|
|
1238
|
+
requestMediaPermissions() {
|
|
1239
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1240
|
+
console.log('[TAS DEBUG] Requesting media permissions...');
|
|
1241
|
+
try {
|
|
1242
|
+
const stream = yield navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
|
1243
|
+
// Stop tracks immediately - we just needed the permission
|
|
1244
|
+
stream.getTracks().forEach(track => track.stop());
|
|
1245
|
+
console.log('[TAS DEBUG] Media permissions granted');
|
|
1246
|
+
}
|
|
1247
|
+
catch (error) {
|
|
1248
|
+
console.warn('[TAS DEBUG] Media permissions denied or unavailable:', error);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Request geolocation immediately on init.
|
|
1254
|
+
* Only for regular users (not owners/backoffice).
|
|
1255
|
+
* If user allows, store position and send to backend.
|
|
1256
|
+
*/
|
|
1257
|
+
requestGeolocation() {
|
|
1258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1259
|
+
// Only request geolocation for regular users, not owners/backoffice
|
|
1260
|
+
if (this.isOwner || this.isBackoffice) {
|
|
1261
|
+
console.log('[TAS DEBUG] Skipping geolocation for owner/backoffice');
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
console.log('[TAS DEBUG] Requesting geolocation...');
|
|
1265
|
+
const position = yield this.geolocationService.getCurrentPosition();
|
|
1266
|
+
if (position) {
|
|
1267
|
+
console.log('[TAS DEBUG] Geolocation obtained:', position);
|
|
1268
|
+
this.geoPosition = position;
|
|
1269
|
+
// Send to backend when videoCallId is available
|
|
1270
|
+
this.sendGeolocationToBackend();
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
console.log('[TAS DEBUG] Geolocation denied or unavailable');
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Send geolocation to backend via modify user endpoint.
|
|
1279
|
+
* NOTE: Endpoint call is prepared but may not be active yet.
|
|
1280
|
+
*/
|
|
1281
|
+
sendGeolocationToBackend() {
|
|
1282
|
+
var _a;
|
|
1283
|
+
if (!this.geoPosition || !this.videoCallId) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
console.log('[TAS DEBUG] Sending geolocation to backend...');
|
|
1287
|
+
const body = {
|
|
1288
|
+
userId: (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id,
|
|
1289
|
+
videoCallId: this.videoCallId,
|
|
1290
|
+
action: UserCallAction.ACTIVATE_GEOLOCATION,
|
|
1291
|
+
latitude: this.geoPosition.latitude,
|
|
1292
|
+
longitude: this.geoPosition.longitude,
|
|
1293
|
+
};
|
|
1294
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
1295
|
+
next: () => console.log('[TAS DEBUG] Geolocation sent successfully'),
|
|
1296
|
+
error: (err) => console.error('[TAS DEBUG] Failed to send geolocation:', err),
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1020
1299
|
ngOnDestroy() {
|
|
1021
1300
|
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnDestroy');
|
|
1022
1301
|
this.subscriptions.unsubscribe();
|
|
@@ -1050,6 +1329,8 @@ class TasWaitingRoomComponent {
|
|
|
1050
1329
|
this.resolvedAppointmentId = content.appointmentId;
|
|
1051
1330
|
this.videoCallId = content.videoCallId;
|
|
1052
1331
|
console.log('[TAS DEBUG] Status response:', content);
|
|
1332
|
+
// Try to send geolocation now that we have videoCallId
|
|
1333
|
+
this.sendGeolocationToBackend();
|
|
1053
1334
|
// Start polling for status updates
|
|
1054
1335
|
this.tasService.startStatusPolling(statusParams);
|
|
1055
1336
|
// Subscribe to joinable status
|
|
@@ -1071,36 +1352,33 @@ class TasWaitingRoomComponent {
|
|
|
1071
1352
|
console.log('[TAS DEBUG] handleJoinableChange called', {
|
|
1072
1353
|
joinable,
|
|
1073
1354
|
currentState: this.state,
|
|
1074
|
-
isOwner: this.isOwner,
|
|
1075
|
-
isBackoffice: this.isBackoffice,
|
|
1076
1355
|
resolvedSessionId: this.resolvedSessionId,
|
|
1077
1356
|
});
|
|
1078
|
-
|
|
1357
|
+
this.isJoinable = joinable;
|
|
1358
|
+
// Don't update state if already getting token, joining, or in error
|
|
1079
1359
|
if (this.state === WaitingRoomState.GETTING_TOKEN ||
|
|
1080
|
-
this.state === WaitingRoomState.
|
|
1360
|
+
this.state === WaitingRoomState.JOINING ||
|
|
1081
1361
|
this.state === WaitingRoomState.ERROR) {
|
|
1082
1362
|
console.log('[TAS DEBUG] Skipping state update - already in:', this.state);
|
|
1083
1363
|
return;
|
|
1084
1364
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
this.getTokenForOwner();
|
|
1090
|
-
}
|
|
1365
|
+
// Both users and owners: show join button based on joinable
|
|
1366
|
+
if (joinable) {
|
|
1367
|
+
console.log('[TAS DEBUG] Joinable is true, showing join button');
|
|
1368
|
+
this.state = WaitingRoomState.READY;
|
|
1091
1369
|
}
|
|
1092
1370
|
else {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
console.log('[TAS DEBUG] Non-owner: joinable is true, getting token');
|
|
1096
|
-
this.getTokenForOwner(); // Also get token when joinable
|
|
1097
|
-
}
|
|
1098
|
-
else {
|
|
1099
|
-
console.log('[TAS DEBUG] Non-owner: waiting for owner');
|
|
1100
|
-
this.state = WaitingRoomState.WAITING_FOR_OWNER;
|
|
1101
|
-
this.cdr.detectChanges();
|
|
1102
|
-
}
|
|
1371
|
+
console.log('[TAS DEBUG] Waiting for joinable...');
|
|
1372
|
+
this.state = WaitingRoomState.WAITING_FOR_JOINABLE;
|
|
1103
1373
|
}
|
|
1374
|
+
this.cdr.detectChanges();
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Called when user clicks the join button.
|
|
1378
|
+
* Calls /start to get token then auto-joins.
|
|
1379
|
+
*/
|
|
1380
|
+
joinSession() {
|
|
1381
|
+
this.startSessionAndJoin();
|
|
1104
1382
|
}
|
|
1105
1383
|
/**
|
|
1106
1384
|
* Check if user has owner/backoffice role
|
|
@@ -1111,9 +1389,9 @@ class TasWaitingRoomComponent {
|
|
|
1111
1389
|
this.businessRole === TasBusinessRole.MANAGER);
|
|
1112
1390
|
}
|
|
1113
1391
|
/**
|
|
1114
|
-
*
|
|
1392
|
+
* Start session and join - called when user clicks join button
|
|
1115
1393
|
*/
|
|
1116
|
-
|
|
1394
|
+
startSessionAndJoin() {
|
|
1117
1395
|
if (!this.resolvedSessionId) {
|
|
1118
1396
|
this.state = WaitingRoomState.ERROR;
|
|
1119
1397
|
this.errorMessage = 'Session ID not available';
|
|
@@ -1143,10 +1421,14 @@ class TasWaitingRoomComponent {
|
|
|
1143
1421
|
this.cdr.detectChanges();
|
|
1144
1422
|
return;
|
|
1145
1423
|
}
|
|
1146
|
-
console.log('[TAS DEBUG] Token obtained successfully');
|
|
1424
|
+
console.log('[TAS DEBUG] Token obtained successfully, joining session...');
|
|
1147
1425
|
this.token = tokenResponse.content.token;
|
|
1148
|
-
this.state = WaitingRoomState.
|
|
1426
|
+
this.state = WaitingRoomState.JOINING;
|
|
1149
1427
|
this.cdr.detectChanges();
|
|
1428
|
+
// Auto-join immediately
|
|
1429
|
+
this.tasService.stopStatusPolling();
|
|
1430
|
+
this.activeModal.close('joining');
|
|
1431
|
+
this.openVideoCallModal();
|
|
1150
1432
|
},
|
|
1151
1433
|
error: (err) => {
|
|
1152
1434
|
var _a;
|
|
@@ -1159,19 +1441,6 @@ class TasWaitingRoomComponent {
|
|
|
1159
1441
|
},
|
|
1160
1442
|
}));
|
|
1161
1443
|
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Join the session - token already obtained
|
|
1164
|
-
*/
|
|
1165
|
-
joinSession() {
|
|
1166
|
-
if (!this.resolvedSessionId || !this.token) {
|
|
1167
|
-
this.errorMessage = 'Session not ready';
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
// Close waiting room and open video call
|
|
1171
|
-
this.tasService.stopStatusPolling();
|
|
1172
|
-
this.activeModal.close('joining');
|
|
1173
|
-
this.openVideoCallModal();
|
|
1174
|
-
}
|
|
1175
1444
|
/**
|
|
1176
1445
|
* Closes the waiting room
|
|
1177
1446
|
*/
|
|
@@ -1189,6 +1458,7 @@ class TasWaitingRoomComponent {
|
|
|
1189
1458
|
this.checkStatus();
|
|
1190
1459
|
}
|
|
1191
1460
|
openVideoCallModal() {
|
|
1461
|
+
var _a;
|
|
1192
1462
|
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
1193
1463
|
size: 'xl',
|
|
1194
1464
|
windowClass: 'tas-video-modal',
|
|
@@ -1199,6 +1469,7 @@ class TasWaitingRoomComponent {
|
|
|
1199
1469
|
this.videoCallModalRef.componentInstance.token = this.token;
|
|
1200
1470
|
this.videoCallModalRef.componentInstance.appointmentId = this.resolvedAppointmentId;
|
|
1201
1471
|
this.videoCallModalRef.componentInstance.videoCallId = this.videoCallId;
|
|
1472
|
+
this.videoCallModalRef.componentInstance.userId = (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id;
|
|
1202
1473
|
this.videoCallModalRef.componentInstance.tenant = this.tenant;
|
|
1203
1474
|
this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
|
|
1204
1475
|
this.videoCallModalRef.componentInstance.isReturningFromPip = false;
|
|
@@ -1209,12 +1480,12 @@ class TasWaitingRoomComponent {
|
|
|
1209
1480
|
});
|
|
1210
1481
|
}
|
|
1211
1482
|
}
|
|
1212
|
-
TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: i1.NgbModal }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1213
|
-
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 <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!--
|
|
1483
|
+
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 });
|
|
1484
|
+
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 <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_JOINABLE State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_JOINABLE\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Esperando que la sesi\u00F3n est\u00E9 disponible...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </div>\n\n <!-- READY State (Join button enabled) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- JOINING State (Auto-joining) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.JOINING\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Ingresando a la llamada...</p>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-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\">\n {{ errorMessage }}\n </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 </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.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%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.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.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.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.success{color:#1da4b1}.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)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.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.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1214
1485
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
|
|
1215
1486
|
type: Component,
|
|
1216
|
-
args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!--
|
|
1217
|
-
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
|
|
1487
|
+
args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_JOINABLE State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_JOINABLE\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Esperando que la sesi\u00F3n est\u00E9 disponible...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </div>\n\n <!-- READY State (Join button enabled) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- JOINING State (Auto-joining) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.JOINING\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Ingresando a la llamada...</p>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-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\">\n {{ errorMessage }}\n </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 </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.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%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.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.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.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.success{color:#1da4b1}.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)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.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.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"] }]
|
|
1488
|
+
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
|
|
1218
1489
|
type: Input
|
|
1219
1490
|
}], entityId: [{
|
|
1220
1491
|
type: Input
|
|
@@ -1233,12 +1504,15 @@ class TasButtonComponent {
|
|
|
1233
1504
|
// Status endpoint params
|
|
1234
1505
|
this.roomType = TasRoomType.TAS;
|
|
1235
1506
|
this.businessRole = TasBusinessRole.USER;
|
|
1507
|
+
// Style customization
|
|
1508
|
+
this.variant = 'default';
|
|
1509
|
+
this.buttonLabel = 'Iniciar TAS';
|
|
1236
1510
|
this.isLoading = false;
|
|
1237
|
-
this.buttonText = 'Iniciar TAS';
|
|
1238
1511
|
// Status check state
|
|
1239
1512
|
this.isCheckingStatus = false;
|
|
1240
1513
|
this.isStatusError = false;
|
|
1241
1514
|
this.statusErrorMessage = '';
|
|
1515
|
+
this.isJoinable = false; // Tracks joinable field from status response
|
|
1242
1516
|
this.subscriptions = new Subscription();
|
|
1243
1517
|
this.currentModalRef = null;
|
|
1244
1518
|
this.videoCallModalRef = null;
|
|
@@ -1251,18 +1525,22 @@ class TasButtonComponent {
|
|
|
1251
1525
|
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
1252
1526
|
this.businessRole === TasBusinessRole.MANAGER);
|
|
1253
1527
|
}
|
|
1254
|
-
/** Whether the button should be visible */
|
|
1255
|
-
get isVisible() {
|
|
1256
|
-
// Backoffice: always show (disabled if error)
|
|
1257
|
-
// Other roles: hide if status error
|
|
1258
|
-
if (this.isBackoffice) {
|
|
1259
|
-
return true;
|
|
1260
|
-
}
|
|
1261
|
-
return !this.isStatusError;
|
|
1262
|
-
}
|
|
1263
1528
|
/** Whether the button should be disabled */
|
|
1264
1529
|
get isDisabled() {
|
|
1265
|
-
return this.isLoading || this.
|
|
1530
|
+
return this.isLoading || this.isStatusError || !this.isJoinable;
|
|
1531
|
+
}
|
|
1532
|
+
/** Reason why the button is disabled (for tooltip) */
|
|
1533
|
+
get disabledReason() {
|
|
1534
|
+
if (this.isLoading) {
|
|
1535
|
+
return '';
|
|
1536
|
+
}
|
|
1537
|
+
if (this.isStatusError) {
|
|
1538
|
+
return this.statusErrorMessage || 'Error al verificar el estado';
|
|
1539
|
+
}
|
|
1540
|
+
if (!this.isJoinable) {
|
|
1541
|
+
return 'Todavía no es el horario de la llamada';
|
|
1542
|
+
}
|
|
1543
|
+
return '';
|
|
1266
1544
|
}
|
|
1267
1545
|
ngOnInit() {
|
|
1268
1546
|
// Subscribe to viewMode to handle PiP return
|
|
@@ -1305,17 +1583,10 @@ class TasButtonComponent {
|
|
|
1305
1583
|
checkStatus() {
|
|
1306
1584
|
// Skip if required inputs are not available
|
|
1307
1585
|
if (!this.tenant || !this.entityId) {
|
|
1308
|
-
console.log('[TAS DEBUG] checkStatus skipped - missing required inputs');
|
|
1309
1586
|
return;
|
|
1310
1587
|
}
|
|
1311
1588
|
this.isCheckingStatus = true;
|
|
1312
1589
|
this.statusErrorMessage = '';
|
|
1313
|
-
console.log('[TAS DEBUG] checkStatus called with:', {
|
|
1314
|
-
roomType: this.roomType,
|
|
1315
|
-
entityId: this.entityId,
|
|
1316
|
-
tenant: this.tenant,
|
|
1317
|
-
businessRole: this.businessRole,
|
|
1318
|
-
});
|
|
1319
1590
|
this.subscriptions.add(this.tasService.getProxyVideoStatus({
|
|
1320
1591
|
roomType: this.roomType,
|
|
1321
1592
|
entityId: this.entityId,
|
|
@@ -1323,7 +1594,7 @@ class TasButtonComponent {
|
|
|
1323
1594
|
businessRole: this.businessRole,
|
|
1324
1595
|
}).subscribe({
|
|
1325
1596
|
next: (response) => {
|
|
1326
|
-
var _a;
|
|
1597
|
+
var _a, _b, _c;
|
|
1327
1598
|
// Check if response is actually an error (some HTTP adapters return errors in next)
|
|
1328
1599
|
// Also check for undefined/null or missing content
|
|
1329
1600
|
const isErrorResponse = !response ||
|
|
@@ -1333,21 +1604,20 @@ class TasButtonComponent {
|
|
|
1333
1604
|
(response === null || response === void 0 ? void 0 : response.error) ||
|
|
1334
1605
|
(response === null || response === void 0 ? void 0 : response.name) === 'HttpErrorResponse';
|
|
1335
1606
|
if (isErrorResponse) {
|
|
1336
|
-
console.error('[TAS DEBUG] Status check returned error in response:', response);
|
|
1337
1607
|
this.isCheckingStatus = false;
|
|
1338
1608
|
this.isStatusError = true;
|
|
1339
1609
|
this.statusErrorMessage = ((_a = response === null || response === void 0 ? void 0 : response.error) === null || _a === void 0 ? void 0 : _a.message) || (response === null || response === void 0 ? void 0 : response.message) || 'Error checking status';
|
|
1340
1610
|
}
|
|
1341
1611
|
else {
|
|
1342
|
-
console.log('[TAS DEBUG] Status check successful:', response);
|
|
1343
1612
|
this.isCheckingStatus = false;
|
|
1344
1613
|
this.isStatusError = false;
|
|
1345
1614
|
this.statusErrorMessage = '';
|
|
1615
|
+
// Update joinable state from response
|
|
1616
|
+
this.isJoinable = (_c = (_b = response.content) === null || _b === void 0 ? void 0 : _b.joinable) !== null && _c !== void 0 ? _c : false;
|
|
1346
1617
|
}
|
|
1347
1618
|
},
|
|
1348
1619
|
error: (err) => {
|
|
1349
1620
|
var _a;
|
|
1350
|
-
console.error('[TAS DEBUG] Status check failed:', err);
|
|
1351
1621
|
this.isCheckingStatus = false;
|
|
1352
1622
|
this.isStatusError = true;
|
|
1353
1623
|
this.statusErrorMessage = ((_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 checking status';
|
|
@@ -1356,23 +1626,12 @@ class TasButtonComponent {
|
|
|
1356
1626
|
}
|
|
1357
1627
|
onClick() {
|
|
1358
1628
|
var _a;
|
|
1359
|
-
console.log('[TAS DEBUG] onClick called');
|
|
1360
|
-
console.log('[TAS DEBUG] Inputs:', {
|
|
1361
|
-
tenant: this.tenant,
|
|
1362
|
-
entityId: this.entityId,
|
|
1363
|
-
roomType: this.roomType,
|
|
1364
|
-
businessRole: this.businessRole,
|
|
1365
|
-
currentUser: this.currentUser,
|
|
1366
|
-
});
|
|
1367
1629
|
if (!this.tenant || !((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
1368
|
-
console.error('[TAS DEBUG] Tenant or current user not available');
|
|
1369
1630
|
return;
|
|
1370
1631
|
}
|
|
1371
1632
|
if (!this.entityId) {
|
|
1372
|
-
console.error('[TAS DEBUG] entityId is required');
|
|
1373
1633
|
return;
|
|
1374
1634
|
}
|
|
1375
|
-
console.log('[TAS DEBUG] Validation passed, opening waiting room modal');
|
|
1376
1635
|
this.openWaitingRoomModal();
|
|
1377
1636
|
}
|
|
1378
1637
|
openWaitingRoomModal() {
|
|
@@ -1414,10 +1673,10 @@ class TasButtonComponent {
|
|
|
1414
1673
|
}
|
|
1415
1674
|
}
|
|
1416
1675
|
TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, deps: [{ token: i1.NgbModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1417
|
-
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" }, ngImport: i0, template: "<
|
|
1676
|
+
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 [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;padding:10px 24px;font-weight:500;margin-right:0}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"], directives: [{ type: i1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "triggers", "container", "disableTooltip", "tooltipClass", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1418
1677
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
|
|
1419
1678
|
type: Component,
|
|
1420
|
-
args: [{ selector: 'tas-btn', template: "<
|
|
1679
|
+
args: [{ selector: 'tas-btn', template: "<span\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;padding:10px 24px;font-weight:500;margin-right:0}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"] }]
|
|
1421
1680
|
}], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: { roomType: [{
|
|
1422
1681
|
type: Input
|
|
1423
1682
|
}], entityId: [{
|
|
@@ -1428,6 +1687,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
1428
1687
|
type: Input
|
|
1429
1688
|
}], currentUser: [{
|
|
1430
1689
|
type: Input
|
|
1690
|
+
}], variant: [{
|
|
1691
|
+
type: Input
|
|
1692
|
+
}], buttonLabel: [{
|
|
1693
|
+
type: Input
|
|
1431
1694
|
}] } });
|
|
1432
1695
|
|
|
1433
1696
|
class TasFloatingCallComponent {
|
|
@@ -1576,6 +1839,107 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
1576
1839
|
args: [{ selector: 'tas-floating-call', template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n <!-- Video content area - shows main video only -->\n <div class=\"floating-content\">\n <!-- Main video container (subscriber if available, otherwise publisher) -->\n <div id=\"pip-main-video\" class=\"pip-main-video\"></div>\n\n <!-- Bottom controls -->\n <div class=\"floating-controls\">\n <button\n class=\"action-btn expand-btn\"\n (click)=\"onExpand()\"\n title=\"Expand to fullscreen\"\n >\n <i class=\"fa fa-expand\"></i>\n </button>\n <button\n class=\"action-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Unmute microphone' : 'Mute microphone'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button class=\"action-btn hangup-btn\" (click)=\"onHangUp()\" title=\"Hang up call\">\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-floating-container{position:fixed;bottom:20px;right:20px;width:280px;height:180px;background:#000;border-radius:12px;box-shadow:0 8px 32px #00000080;z-index:9999;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none;transition:opacity .3s ease,visibility .3s ease,box-shadow .2s ease;opacity:0;visibility:hidden;pointer-events:none}.tas-floating-container.visible{opacity:1;visibility:visible;pointer-events:auto}.tas-floating-container:hover{box-shadow:0 8px 32px #00000080,0 0 0 2px #ffffff4d}.floating-content{position:relative;width:100%;height:100%;overflow:hidden}.pip-main-video{position:absolute;top:0;left:0;width:100%;height:100%;background:#000}.pip-main-video ::ng-deep video{width:100%;height:100%;object-fit:cover}.pip-main-video ::ng-deep .OT_subscriber,.pip-main-video ::ng-deep .OT_publisher{width:100%!important;height:100%!important}.pip-main-video ::ng-deep .OT_edge-bar-item,.pip-main-video ::ng-deep .OT_mute,.pip-main-video ::ng-deep .OT_audio-level-meter,.pip-main-video ::ng-deep .OT_bar,.pip-main-video ::ng-deep .OT_name{display:none!important}.floating-controls{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:12px;padding:6px 14px;background:rgba(0,0,0,.7);border-radius:24px;backdrop-filter:blur(8px);opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease}.tas-floating-container:hover .floating-controls{opacity:1;visibility:visible}.action-btn{width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s ease}.action-btn.expand-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.expand-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn.muted{background:#f39c12;color:#fff}.action-btn.mute-btn.muted:hover{background:#e67e22}.action-btn.hangup-btn{background:#dc3545;color:#fff}.action-btn.hangup-btn:hover{background:#c82333;transform:scale(1.1)}\n"] }]
|
|
1577
1840
|
}], ctorParameters: function () { return [{ type: TasService }, { type: i1.NgbModal }]; } });
|
|
1578
1841
|
|
|
1842
|
+
class TasIncomingAppointmentComponent {
|
|
1843
|
+
constructor(tasService) {
|
|
1844
|
+
this.tasService = tasService;
|
|
1845
|
+
// Passthrough inputs for tas-btn
|
|
1846
|
+
this.roomType = TasRoomType.TAS;
|
|
1847
|
+
this.businessRole = TasBusinessRole.USER;
|
|
1848
|
+
this.enterCall = new EventEmitter();
|
|
1849
|
+
this.appointment = null;
|
|
1850
|
+
this.isLoading = true;
|
|
1851
|
+
this.hasError = false;
|
|
1852
|
+
this.subscriptions = new Subscription();
|
|
1853
|
+
}
|
|
1854
|
+
ngOnInit() {
|
|
1855
|
+
this.loadAppointments();
|
|
1856
|
+
}
|
|
1857
|
+
ngOnDestroy() {
|
|
1858
|
+
this.subscriptions.unsubscribe();
|
|
1859
|
+
}
|
|
1860
|
+
loadAppointments() {
|
|
1861
|
+
const today = new Date();
|
|
1862
|
+
const in7Days = new Date(today);
|
|
1863
|
+
in7Days.setDate(today.getDate() + 7);
|
|
1864
|
+
this.subscriptions.add(this.tasService
|
|
1865
|
+
.getAppointments({ fromDate: this.formatDate(today), toDate: this.formatDate(in7Days) })
|
|
1866
|
+
.subscribe({
|
|
1867
|
+
next: (response) => {
|
|
1868
|
+
// Handle both array response and wrapped response (e.g., { content: [...] })
|
|
1869
|
+
const appointments = Array.isArray(response)
|
|
1870
|
+
? response
|
|
1871
|
+
: (response === null || response === void 0 ? void 0 : response.content) || [];
|
|
1872
|
+
// Filter for confirmed appointments and get the first one
|
|
1873
|
+
const confirmed = appointments.filter((a) => a.status === AppointmentStatus.CONFIRMED);
|
|
1874
|
+
this.appointment = confirmed.length > 0 ? confirmed[0] : null;
|
|
1875
|
+
this.isLoading = false;
|
|
1876
|
+
},
|
|
1877
|
+
error: () => {
|
|
1878
|
+
this.hasError = true;
|
|
1879
|
+
this.isLoading = false;
|
|
1880
|
+
},
|
|
1881
|
+
}));
|
|
1882
|
+
}
|
|
1883
|
+
onEnterCall() {
|
|
1884
|
+
if (this.appointment) {
|
|
1885
|
+
this.enterCall.emit(this.appointment);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Format date to Spanish format: "Lunes 8 de diciembre"
|
|
1890
|
+
*/
|
|
1891
|
+
get formattedDate() {
|
|
1892
|
+
if (!this.appointment)
|
|
1893
|
+
return '';
|
|
1894
|
+
const [year, month, day] = this.appointment.date.split('-').map(Number);
|
|
1895
|
+
const date = new Date(year, month - 1, day);
|
|
1896
|
+
const dayNames = [
|
|
1897
|
+
'Domingo', 'Lunes', 'Martes', 'Miércoles',
|
|
1898
|
+
'Jueves', 'Viernes', 'Sábado'
|
|
1899
|
+
];
|
|
1900
|
+
const monthNames = [
|
|
1901
|
+
'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio',
|
|
1902
|
+
'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'
|
|
1903
|
+
];
|
|
1904
|
+
const dayName = dayNames[date.getDay()];
|
|
1905
|
+
const dayNum = date.getDate();
|
|
1906
|
+
const monthName = monthNames[date.getMonth()];
|
|
1907
|
+
return `${dayName} ${dayNum} de ${monthName}`;
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Format time range: "9:00 - 9:30"
|
|
1911
|
+
*/
|
|
1912
|
+
get formattedTimeRange() {
|
|
1913
|
+
if (!this.appointment)
|
|
1914
|
+
return '';
|
|
1915
|
+
return `${this.appointment.startTime} - ${this.appointment.endTime}`;
|
|
1916
|
+
}
|
|
1917
|
+
formatDate(date) {
|
|
1918
|
+
const year = date.getFullYear();
|
|
1919
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
1920
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
1921
|
+
return `${year}-${month}-${day}`;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
TasIncomingAppointmentComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1925
|
+
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" }, 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 && !appointment\">\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 <!-- Appointment state -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointment\">\n \n <div class=\"appointment-card\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"appointment.title\" [size]=\"48\"></tas-avatar>\n \n <span class=\"doctor-name\">{{ appointment.title }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formattedDate }}</span>\n <span class=\"time\">{{ formattedTimeRange }}</span>\n </div>\n <tas-btn\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"roomType\"\n [entityId]=\"entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\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}.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"] }] });
|
|
1926
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, decorators: [{
|
|
1927
|
+
type: Component,
|
|
1928
|
+
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 && !appointment\">\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 <!-- Appointment state -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointment\">\n \n <div class=\"appointment-card\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"appointment.title\" [size]=\"48\"></tas-avatar>\n \n <span class=\"doctor-name\">{{ appointment.title }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formattedDate }}</span>\n <span class=\"time\">{{ formattedTimeRange }}</span>\n </div>\n <tas-btn\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"roomType\"\n [entityId]=\"entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\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}.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"] }]
|
|
1929
|
+
}], ctorParameters: function () { return [{ type: TasService }]; }, propDecorators: { roomType: [{
|
|
1930
|
+
type: Input
|
|
1931
|
+
}], entityId: [{
|
|
1932
|
+
type: Input
|
|
1933
|
+
}], tenant: [{
|
|
1934
|
+
type: Input
|
|
1935
|
+
}], businessRole: [{
|
|
1936
|
+
type: Input
|
|
1937
|
+
}], currentUser: [{
|
|
1938
|
+
type: Input
|
|
1939
|
+
}], enterCall: [{
|
|
1940
|
+
type: Output
|
|
1941
|
+
}] } });
|
|
1942
|
+
|
|
1579
1943
|
class TasUellSdkModule {
|
|
1580
1944
|
/**
|
|
1581
1945
|
* Use forRoot() to configure the TAS SDK module with required dependencies.
|
|
@@ -1622,12 +1986,14 @@ TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", versio
|
|
|
1622
1986
|
TasVideocallComponent,
|
|
1623
1987
|
TasFloatingCallComponent,
|
|
1624
1988
|
TasWaitingRoomComponent,
|
|
1625
|
-
TasAvatarComponent
|
|
1989
|
+
TasAvatarComponent,
|
|
1990
|
+
TasIncomingAppointmentComponent], imports: [CommonModule, FormsModule, NgbTooltipModule], exports: [TasButtonComponent,
|
|
1626
1991
|
TasVideocallComponent,
|
|
1627
1992
|
TasFloatingCallComponent,
|
|
1628
1993
|
TasWaitingRoomComponent,
|
|
1629
|
-
TasAvatarComponent
|
|
1630
|
-
|
|
1994
|
+
TasAvatarComponent,
|
|
1995
|
+
TasIncomingAppointmentComponent] });
|
|
1996
|
+
TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule, NgbTooltipModule]] });
|
|
1631
1997
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, decorators: [{
|
|
1632
1998
|
type: NgModule,
|
|
1633
1999
|
args: [{
|
|
@@ -1637,14 +2003,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
1637
2003
|
TasFloatingCallComponent,
|
|
1638
2004
|
TasWaitingRoomComponent,
|
|
1639
2005
|
TasAvatarComponent,
|
|
2006
|
+
TasIncomingAppointmentComponent,
|
|
1640
2007
|
],
|
|
1641
|
-
imports: [CommonModule, FormsModule],
|
|
2008
|
+
imports: [CommonModule, FormsModule, NgbTooltipModule],
|
|
1642
2009
|
exports: [
|
|
1643
2010
|
TasButtonComponent,
|
|
1644
2011
|
TasVideocallComponent,
|
|
1645
2012
|
TasFloatingCallComponent,
|
|
1646
2013
|
TasWaitingRoomComponent,
|
|
1647
2014
|
TasAvatarComponent,
|
|
2015
|
+
TasIncomingAppointmentComponent,
|
|
1648
2016
|
],
|
|
1649
2017
|
}]
|
|
1650
2018
|
}] });
|
|
@@ -1657,5 +2025,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
|
|
|
1657
2025
|
* Generated bundle index. Do not edit.
|
|
1658
2026
|
*/
|
|
1659
2027
|
|
|
1660
|
-
export { CallState, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
|
|
2028
|
+
export { AppointmentStatus, CallState, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
|
|
1661
2029
|
//# sourceMappingURL=tas-uell-sdk.mjs.map
|