@chat21/chat21-ionic 3.4.31 → 3.4.32-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +127 -2
  2. package/angular.json +1 -0
  3. package/package.json +1 -1
  4. package/src/app/app.component.html +3 -1
  5. package/src/app/app.component.ts +72 -11
  6. package/src/app/chatlib/list-conversations-component/ion-list-conversations/ion-list-conversations.component.html +14 -2
  7. package/src/app/chatlib/list-conversations-component/ion-list-conversations/ion-list-conversations.component.scss +39 -2
  8. package/src/app/chatlib/list-conversations-component/list-conversations.module.ts +14 -0
  9. package/src/app/components/canned-response/canned-response.component.html +26 -23
  10. package/src/app/components/canned-response/canned-response.component.scss +0 -2
  11. package/src/app/components/canned-response/canned-response.component.ts +3 -1
  12. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.html +24 -1
  13. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.scss +30 -0
  14. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.ts +29 -7
  15. package/src/app/components/conversation-info/info-content/info-content.component.ts +2 -2
  16. package/src/app/components/conversation-info/info-group/info-group.component.ts +23 -21
  17. package/src/app/components/conversations-list/header-conversations-list/header-conversations-list.component.html +1 -1
  18. package/src/app/components/conversations-list/header-conversations-list/header-conversations-list.component.ts +5 -1
  19. package/src/app/components/navbar/navbar.component.html +35 -9
  20. package/src/app/components/navbar/navbar.component.scss +64 -0
  21. package/src/app/components/navbar/navbar.component.ts +100 -42
  22. package/src/app/components/project-item/project-item.component.ts +79 -51
  23. package/src/app/components/sidebar/sidebar.component.html +65 -45
  24. package/src/app/components/sidebar/sidebar.component.ts +110 -117
  25. package/src/app/components/sidebar-user-details/sidebar-user-details.component.html +3 -3
  26. package/src/app/components/sidebar-user-details/sidebar-user-details.component.ts +15 -22
  27. package/src/app/modals/create-ticket/create-ticket.page.ts +4 -2
  28. package/src/app/pages/conversation-detail/conversation-detail.page.html +7 -3
  29. package/src/app/pages/conversation-detail/conversation-detail.page.ts +89 -5
  30. package/src/app/pages/conversations-list/conversations-list.module.ts +3 -5
  31. package/src/app/pages/conversations-list/conversations-list.page.html +2 -0
  32. package/src/app/pages/conversations-list/conversations-list.page.ts +51 -11
  33. package/src/app/pages/unassigned-conversations/unassigned-conversations.module.ts +16 -4
  34. package/src/app/pages/unassigned-conversations/unassigned-conversations.page.html +41 -17
  35. package/src/app/pages/unassigned-conversations/unassigned-conversations.page.scss +10 -1
  36. package/src/app/pages/unassigned-conversations/unassigned-conversations.page.ts +114 -9
  37. package/src/app/services/global-settings/global-settings.service.ts +11 -3
  38. package/src/app/services/nav-proxy.service.ts +0 -1
  39. package/src/app/services/project_users/project-users.service.spec.ts +16 -0
  40. package/src/app/services/project_users/project-users.service.ts +63 -0
  41. package/src/app/services/projects/project.service.ts +2 -1
  42. package/src/app/services/tiledesk/tiledesk.service.ts +21 -16
  43. package/src/app/services/triggerEvents/triggerEvents.ts +28 -0
  44. package/src/app/services/websocket/websocket-js.ts +59 -534
  45. package/src/app/services/websocket/websocket-js_old.ts +578 -0
  46. package/src/app/services/websocket/websocket.service.ts +59 -10
  47. package/src/app/services/websocket/websocket.worker.ts +242 -0
  48. package/src/app/shared/shared.module.ts +11 -2
  49. package/src/app/utils/globals.ts +2 -0
  50. package/src/app/utils/permissions.constants.ts +138 -0
  51. package/src/app/utils/project-utils.ts +2 -2
  52. package/src/app/utils/utils.ts +18 -1
  53. package/src/assets/i18n/ar.json +11 -1
  54. package/src/assets/i18n/az.json +11 -1
  55. package/src/assets/i18n/de.json +11 -1
  56. package/src/assets/i18n/en.json +11 -1
  57. package/src/assets/i18n/es.json +11 -1
  58. package/src/assets/i18n/fr.json +11 -1
  59. package/src/assets/i18n/it.json +13 -3
  60. package/src/assets/i18n/kk.json +11 -1
  61. package/src/assets/i18n/pt.json +11 -1
  62. package/src/assets/i18n/ru.json +11 -1
  63. package/src/assets/i18n/sr.json +11 -1
  64. package/src/assets/i18n/sv.json +11 -1
  65. package/src/assets/i18n/tr.json +11 -1
  66. package/src/assets/i18n/uk.json +11 -1
  67. package/src/assets/i18n/uz.json +12 -1
  68. package/src/assets/js/agentDesktop-sdk.js +55 -0
  69. package/src/assets/js/chat21client.js +36 -0
  70. package/src/assets/js/mqtt-keepalive-worker.js +53 -0
  71. package/src/assets/test.html +5 -2
  72. package/src/chat-config-template.json +1 -0
  73. package/src/chat-config.json +1 -0
  74. package/src/chat21-core/models/projectUsers.ts +19 -0
  75. package/src/chat21-core/models/project_user.ts +2 -1
  76. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +1 -1
  77. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +1 -1
  78. package/src/chat21-core/providers/tiledesk/tiledesk-auth.service.ts +3 -0
  79. package/src/chat21-core/utils/constants.ts +5 -0
  80. package/src/chat21-core/utils/convertRequestToConversation.ts +1 -1
  81. package/src/chat21-core/utils/utils.ts +53 -3
  82. package/src/variables.scss +3 -0
@@ -183,6 +183,36 @@
183
183
  }
184
184
  }
185
185
 
186
+ #ticket{
187
+ text-align: center;
188
+ font-size: 12px;
189
+
190
+ .buttons-container{
191
+ display: flex;
192
+ justify-content: center;
193
+ gap: 10px;
194
+
195
+ ion-button{
196
+ font-size: 12px;
197
+ --padding-top: 4px;
198
+ --padding-bottom: 4px;
199
+ --padding-start: 6px;
200
+ --padding-end: 6px;
201
+ --ripple-color: transparent;
202
+ text-transform: unset;
203
+ height: auto;
204
+
205
+ ion-icon{
206
+ margin-right: 4px;
207
+ }
208
+
209
+ &[name="add"]{
210
+ --background: var(--basic-blue);
211
+ }
212
+ }
213
+ }
214
+ }
215
+
186
216
  #fileInput {
187
217
  position: absolute;
188
218
  opacity: 0;
@@ -26,6 +26,7 @@ import { CopilotService } from 'src/app/services/copilot/copilot.service';
26
26
  import { BRAND_BASE_INFO } from 'src/app/utils/utils-resources';
27
27
  import { ProjectService } from 'src/app/services/projects/project.service';
28
28
  import { Project } from 'src/chat21-core/models/projects';
29
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
29
30
 
30
31
 
31
32
  @Component({
@@ -49,6 +50,7 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
49
50
  @ViewChild('fileInput', { static: false }) fileInput: any;
50
51
 
51
52
  @Input() loggedUser: UserModel;
53
+ @Input() projectUser: ProjectUser;
52
54
  @Input() conversationWith: string;
53
55
  @Input() channelType: string;
54
56
  @Input() channel: string;
@@ -61,16 +63,20 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
61
63
  @Input() emailSection: boolean;
62
64
  @Input() offlineMsgEmail: boolean;
63
65
  @Input() whatsappTemplatesSection: boolean;
66
+ @Input() ticketSection: boolean
64
67
  @Input() isOpenInfoConversation: boolean;
68
+ @Input() cannedSection: boolean;
65
69
  @Input() stylesMap: Map<string, string>;
66
70
  @Input() translationMap: Map<string, string>;
67
71
  @Input() dropEvent: any;
68
72
  @Input() disableTextarea: boolean;
73
+ @Input() roles: Array<string>;
69
74
  @Output() eventChangeTextArea = new EventEmitter<{msg: string, offsetHeight: number}>();
70
75
  @Output() eventSendMessage = new EventEmitter<{msg: string, type: string, metadata?: Object, attributes?: Object}>();
71
76
  @Output() onClickOpenCannedResponses = new EventEmitter<boolean>();
72
77
  @Output() onPresentModalScrollToBottom = new EventEmitter<boolean>();
73
78
  @Output() onOpenFooterSection = new EventEmitter<string>();
79
+ @Output() onOpenTicket = new EventEmitter<boolean>();
74
80
 
75
81
  public project: Project;
76
82
  public conversationEnabled = false;
@@ -293,6 +299,17 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
293
299
  this.prensentTemplateModal();
294
300
  }
295
301
 
302
+ onClickTicket(option: "open" | "close"){
303
+ this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] - onClickTicket', option);
304
+ switch(option){
305
+ case "open":
306
+ this.onOpenTicket.emit();
307
+ case "close":
308
+ this.section = 'chat'
309
+ }
310
+
311
+ }
312
+
296
313
 
297
314
  /**
298
315
  *
@@ -575,8 +592,10 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
575
592
  if (!text.includes("/")) {
576
593
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 1 message: ', message);
577
594
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 1 message: ", message);
578
- this.messageString = '';
595
+
579
596
  this.sendMessage(text);
597
+ // this.messageString = '';
598
+
580
599
  this.countClicks = 0
581
600
  } else if (text.includes("/") && pos === 0 && this.countClicks > 1 && this.tagsCannedFilter.length > 0) {
582
601
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - tagsCannedFilter.length 2: ', this.tagsCannedFilter.length);
@@ -588,9 +607,10 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
588
607
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
589
608
  this.logger.log("[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
590
609
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 2 message: ', message);
591
- this.messageString = '';
592
-
610
+
593
611
  this.sendMessage(text);
612
+ // this.messageString = '';
613
+
594
614
  this.countClicks = 0
595
615
  } else if (text.includes("/") && pos > 0 && this.countClicks > 1 && this.tagsCannedFilter.length > 0 && text.substr(-1) !== '/') {
596
616
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - tagsCannedFilter.length 3: ', this.tagsCannedFilter.length);
@@ -602,17 +622,19 @@ export class MessageTextAreaComponent implements OnInit, AfterViewInit, OnChange
602
622
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
603
623
  this.logger.log("[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown in msg-texarea SEND MESSAGE 2 this.countClicks: ", this.countClicks);
604
624
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 2 message: ', message);
605
- this.messageString = '';
606
-
625
+
607
626
  this.sendMessage(text);
627
+ // this.messageString = '';
628
+
608
629
  this.countClicks = 0
609
630
  } else if (text.includes("/") && this.tagsCannedFilter.length === 0) {
610
631
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - tagsCannedFilter.length 3: ', this.tagsCannedFilter.length);
611
632
  this.logger.log('[CONVS-DETAIL][MSG-TEXT-AREA] onKeydown - SEND MESSAGE 3 message: ', message);
612
633
  this.logger.log("[CONVS-DETAIL] replaceTagInMessage onKeydown in msg-texarea SEND MESSAGE 3 message: ", message);
613
- this.messageString = '';
614
-
634
+
615
635
  this.sendMessage(text);
636
+ // this.messageString = '';
637
+
616
638
  this.countClicks = 0
617
639
 
618
640
  }
@@ -70,8 +70,8 @@ export class InfoContentComponent implements OnInit {
70
70
  this.route.paramMap.subscribe(params => {
71
71
  this.logger.log('[INFO-CONTENT-COMP] initialize params: ', params);
72
72
  this.conversationWith = params.get('IDConv');
73
- this.conversationWithFullname = params.get('FullNameConv');
74
- this.conv_type = params.get('Convtype');
73
+ this.conversationWithFullname = decodeURIComponent(params.get('FullNameConv'));
74
+ this.conv_type = decodeURIComponent(params.get('Convtype'));
75
75
 
76
76
  const conversationWith_segments = this.conversationWith.split('-');
77
77
 
@@ -151,27 +151,29 @@ export class InfoGroupComponent implements OnInit, AfterViewInit, OnChanges {
151
151
  });
152
152
 
153
153
  this.contactsService.loadContactDetail(key).pipe(takeUntil(this.unsubscribe$)).subscribe(user => {
154
- this.logger.log('InfoGroupComponent group detail loadContactDetail RES', user);
155
- // this.logger.log('InfoGroupComponent group detail this.presenceService.BSIsOnline.value()', this.presenceService.BSIsOnline.getValue);
156
-
157
- user.imageurl = this.imageRepoService.getImagePhotoUrl(key)
158
- // this.member_array.push({ userid: user.uid, avatar: user.avatar, color: user.color, email: user.email, fullname: user.fullname, imageurl: user.imageurl, userOnline: isOnline })
159
- var index = this.member_array.findIndex(m => m.userid === user.uid);
160
- this.logger.log('InfoGroupComponent member_array first of push index', index);
161
- this.logger.log('InfoGroupComponent member_array first of push', this.member_array);
162
- if (index === -1) {
163
- this.member_array.push(
164
- {
165
- userid: user.uid,
166
- avatar: user.avatar,
167
- color: user.color,
168
- email: user.email,
169
- fullname: user.fullname,
170
- imageurl: user.imageurl,
171
- userOnline: members_isonline_array[user.uid]['isSignin']
172
- })
173
- } else {
174
- this.logger.log('InfoGroupComponent member already exist in member_array');
154
+ if(user){
155
+ this.logger.log('InfoGroupComponent group detail loadContactDetail RES', user);
156
+ // this.logger.log('InfoGroupComponent group detail this.presenceService.BSIsOnline.value()', this.presenceService.BSIsOnline.getValue);
157
+
158
+ user.imageurl = this.imageRepoService.getImagePhotoUrl(key)
159
+ // this.member_array.push({ userid: user.uid, avatar: user.avatar, color: user.color, email: user.email, fullname: user.fullname, imageurl: user.imageurl, userOnline: isOnline })
160
+ var index = this.member_array.findIndex(m => m.userid === user.uid);
161
+ this.logger.log('InfoGroupComponent member_array first of push index', index);
162
+ this.logger.log('InfoGroupComponent member_array first of push', this.member_array);
163
+ if (index === -1) {
164
+ this.member_array.push(
165
+ {
166
+ userid: user.uid,
167
+ avatar: user.avatar,
168
+ color: user.color,
169
+ email: user.email,
170
+ fullname: user.fullname,
171
+ imageurl: user.imageurl,
172
+ userOnline: members_isonline_array[user.uid]['isSignin']
173
+ })
174
+ } else {
175
+ this.logger.log('InfoGroupComponent member already exist in member_array');
176
+ }
175
177
  }
176
178
  }, (error) => {
177
179
  this.logger.error('InfoGroupComponent group detail loadContactDetail - ERROR ', error);
@@ -34,7 +34,7 @@
34
34
  <!-- <ion-icon name="file-tray-full-outline"></ion-icon> -->
35
35
  </ion-button>
36
36
 
37
- <ion-button *ngIf="writeto_btn" ion-button fill="clear" (click)="onOpenContactsDirectory($event)"
37
+ <ion-button *ngIf="writeto_btn && isVisibleCNT && roles?.[PERMISSIONS.LEADS_READ]" ion-button fill="clear" (click)="onOpenContactsDirectory($event)"
38
38
  tooltip="{{translationMap?.get('ViewContactsList')}}" placement="bottom">
39
39
  <ion-icon slot="icon-only" name="create-outline"></ion-icon>
40
40
  <!-- <ion-icon slot="icon-only" name="people-outline"></ion-icon> -->
@@ -1,3 +1,4 @@
1
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
1
2
  import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'
2
3
  import { ModalController } from '@ionic/angular'
3
4
  import { EventsService } from 'src/app/services/events-service'
@@ -17,6 +18,8 @@ export class HeaderConversationsList implements OnInit {
17
18
  @Input() sound_btn: string;
18
19
  @Input() isMobile: boolean;
19
20
  @Input() isVisibleTKT: boolean = true;
21
+ @Input() isVisibleCNT: boolean = true;;
22
+ @Input() roles: Array<string>;
20
23
  @Output() onSoundChange = new EventEmitter<string>()
21
24
  @Output() openContactsDirectory = new EventEmitter()
22
25
  @Output() openProfileInfo = new EventEmitter()
@@ -24,6 +27,7 @@ export class HeaderConversationsList implements OnInit {
24
27
  createTicketModal = null
25
28
  public translationMap: Map<string, string>;
26
29
 
30
+ PERMISSIONS = PERMISSIONS;
27
31
  constructor(
28
32
  public events: EventsService,
29
33
  public modalController: ModalController,
@@ -62,7 +66,7 @@ export class HeaderConversationsList implements OnInit {
62
66
  // }
63
67
 
64
68
  ngOnInit() {
65
- // console.log('DDP HEADER SUPPORT MODE ', this.supportMode)
69
+ console.log('DDP HEADER SUPPORT MODE ', this.roles)
66
70
  }
67
71
 
68
72
  // START @Output() //
@@ -5,7 +5,7 @@
5
5
  </div>
6
6
  <div class="navbar-right">
7
7
  <!-- test site -->
8
- <ng-container *ngIf="project">
8
+ <ng-container *ngIf="project && roles?.[PERMISSIONS.SIMULATE_CONV]">
9
9
  <button class="btn simulate-visitor-btn" (click)="testWidgetPage()">
10
10
  <i class="material-icons">play_arrow</i>
11
11
  <!-- {{translationsMap?.get('NAVBAR.SIMULATE_VISITOR')}} -->
@@ -23,9 +23,9 @@
23
23
  </ng-container>
24
24
 
25
25
  <!-- ------ PROJECTS DROPDOWN ------ -->
26
- <ng-container *ngIf="project">
26
+ <ng-container *ngIf="project && roles?.[PERMISSIONS.CHANGE_PROJECT]">
27
27
  <li>
28
- <button class="btn dropdown-toggle project-dropdown" (click)="openDropdownProjects = !openDropdownProjects">
28
+ <button class="btn dropdown-toggle project-dropdown" (click)="toggleProjectsDropdown()">
29
29
  <span class="project-dropdown" style="text-transform: none"> {{ project?.id_project?.name }} </span>
30
30
  <i class="material-icons" style="margin-right: 3px;">arrow_drop_down</i>
31
31
  </button>
@@ -49,7 +49,7 @@
49
49
  </li>
50
50
 
51
51
  <!-- ADD PROJECT -->
52
- <li id="navbar_create_prjct" *ngIf="MT === true" (click)="onClickDropdownOption('addProject')" class="add-project">
52
+ <li id="navbar_create_prjct" *ngIf="isVisibleMT" (click)="onClickDropdownOption('addProject')" class="add-project">
53
53
  <a>
54
54
  <i class="material-icons">add_circle_outline </i>
55
55
  {{translationsMap?.get('NAVBAR.ADD_PROJECT')}}
@@ -71,9 +71,22 @@
71
71
  <!-- *ngFor="let prjct of projects?.slice().reverse() | slice:0:5; let i=index" -->
72
72
  <li *ngFor="let prjct of projects?.slice() | slice:0:5; let i=index" style="cursor: pointer">
73
73
  <a (click)="goToHome(prjct?.id_project?._id, prjct?.id_project?.name)"
74
- [ngClass]="{'li-selected' : prjct?.id_project?._id === project?.id_project?.id }">
75
-
76
- <span> {{ prjct?.id_project?.name }} </span>
74
+ [ngClass]="{'li-selected' : prjct?.id_project?._id === project?.id_project?.id }"
75
+ class="project-item-row">
76
+ <span class="project-item-name">{{ prjct?.id_project?.name }}</span>
77
+ <span class="project-item-status project-item-status-wrapper"
78
+ [attr.title]="translationsMap?.get(prjct?.teammateStatus?.label) || prjct?.teammateStatus?.name"
79
+ (click)="toggleStatusDropdown($event, prjct)">
80
+ <img style="width: 15px; height: 15px; position: relative; top: 1px; cursor: pointer;" height="15" width="15" [src]="prjct?.teammateStatus?.avatar" />
81
+ <div class="status-dropdown status-dropdown-fixed" *ngIf="openStatusDropdownProjectId === prjct?.id_project?._id" (click)="$event.stopPropagation()"
82
+ [style.top.px]="statusDropdownPosition.top" [style.right.px]="statusDropdownPosition.right">
83
+ <div class="status-dropdown-option" *ngFor="let status of TEAMMATE_STATUS"
84
+ (click)="$event.stopPropagation(); onChangeProjectStatus(prjct, status.id)">
85
+ <img style="width: 15px; height: 15px; margin-right: 6px;" [src]="status.avatar" />
86
+ <span>{{ translationsMap?.get(status.label) || status.name }}</span>
87
+ </div>
88
+ </div>
89
+ </span>
77
90
  </a>
78
91
  </li>
79
92
 
@@ -85,8 +98,21 @@
85
98
 
86
99
  <!-- *ngFor="let prjct of projects?.slice().reverse() | slice:5:10; let i=index" -->
87
100
  <li *ngFor="let prjct of projects?.slice() | slice:5:10; let i=index" style="cursor: pointer">
88
- <a (click)="goToHome(prjct?.id_project?._id, prjct?.id_project?.name)">
89
- {{ prjct?.id_project?.name }}
101
+ <a (click)="goToHome(prjct?.id_project?._id, prjct?.id_project?.name)" class="project-item-row">
102
+ <span class="project-item-name">{{ prjct?.id_project?.name }}</span>
103
+ <span class="project-item-status project-item-status-wrapper"
104
+ [attr.title]="translationsMap?.get(prjct?.teammateStatus?.label) || prjct?.teammateStatus?.name"
105
+ (click)="toggleStatusDropdown($event, prjct)">
106
+ <img style="width: 15px; height: 15px; position: relative; top: 1px; cursor: pointer;" height="15" width="15" [src]="prjct?.teammateStatus?.avatar" />
107
+ <div class="status-dropdown status-dropdown-fixed" *ngIf="openStatusDropdownProjectId === prjct?.id_project?._id" (click)="$event.stopPropagation()"
108
+ [style.top.px]="statusDropdownPosition.top" [style.right.px]="statusDropdownPosition.right">
109
+ <div class="status-dropdown-option" *ngFor="let status of TEAMMATE_STATUS"
110
+ (click)="$event.stopPropagation(); onChangeProjectStatus(prjct, status.id)">
111
+ <img style="width: 15px; height: 15px; margin-right: 6px;" [src]="status.avatar" />
112
+ <span>{{ translationsMap?.get(status.label) || status.name }}</span>
113
+ </div>
114
+ </div>
115
+ </span>
90
116
  </a>
91
117
  </li>
92
118
  </ng-container>
@@ -204,6 +204,70 @@ li{
204
204
  color: var(--dropdown-menu-hover-color);
205
205
  box-shadow: 0 12px 20px -10px rgba(var(--dropdown-menu-hover-color), 0.28), 0 4px 20px 0 rgba(0, 0, 0, 0.12), 0 7px 8px -5px rgba(var(--dropdown-menu-hover-color), 0.2);
206
206
  }
207
+
208
+ &.project-item-row {
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: space-between;
212
+ gap: 8px;
213
+
214
+ .project-item-name {
215
+ flex: 1;
216
+ min-width: 0;
217
+ text-align: left;
218
+ overflow: hidden;
219
+ text-overflow: ellipsis;
220
+ }
221
+
222
+ .project-item-status {
223
+ flex: 0 0 10%;
224
+ display: flex;
225
+ justify-content: flex-end;
226
+ align-items: center;
227
+
228
+ &.project-item-status-wrapper {
229
+ position: relative;
230
+ }
231
+
232
+ .status-dropdown {
233
+ position: absolute;
234
+ right: 100%;
235
+ top: 50%;
236
+ transform: translateY(-50%);
237
+ margin-right: 4px;
238
+ min-width: 140px;
239
+ padding: 4px 0;
240
+ background-color: var(--dropdown-menu-background);
241
+ border-radius: 3px;
242
+ box-shadow: 0 2px 5px 0 rgb(0, 0, 0, 0.26);
243
+ z-index: 1001;
244
+ list-style: none;
245
+
246
+ &.status-dropdown-fixed {
247
+ position: fixed;
248
+ right: auto;
249
+ left: auto;
250
+ margin-right: 0;
251
+ transform: translateY(-50%);
252
+ }
253
+
254
+ .status-dropdown-option {
255
+ display: flex;
256
+ align-items: center;
257
+ padding: 8px 16px;
258
+ font-size: 13px;
259
+ color: var(--dropdown-menu-color);
260
+ cursor: pointer;
261
+ white-space: nowrap;
262
+
263
+ &:hover {
264
+ background-color: var(--dropdown-menu-hover-background);
265
+ color: var(--dropdown-menu-hover-color);
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
207
271
  }
208
272
 
209
273
 
@@ -7,6 +7,13 @@ import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service
7
7
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
8
8
  import { Project } from 'src/chat21-core/models/projects';
9
9
  import { CustomTranslateService } from 'src/chat21-core/providers/custom-translate.service';
10
+ import { getUserStatusFromProjectUser } from 'src/chat21-core/utils/utils';
11
+ import { TEAMMATE_STATUS } from 'src/chat21-core/utils/constants';
12
+ import { WebsocketService } from 'src/app/services/websocket/websocket.service';
13
+ import { ProjectUser } from 'src/chat21-core/models/project_user';
14
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service';
15
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
16
+ import { getOSCode, hasRole } from 'src/app/utils/utils';
10
17
 
11
18
  @Component({
12
19
  selector: 'app-navbar',
@@ -21,24 +28,31 @@ export class NavbarComponent implements OnInit {
21
28
  private logger: LoggerService = LoggerInstance.getInstance();
22
29
  private tiledeskToken: string;
23
30
 
24
- public projects: Project[] = [];
31
+ public projects: ProjectUser[] = [];
25
32
  public project: any = [];
26
33
  private USER_ROLE: string;
27
34
 
28
35
  public translationsMap: Map<string, string> = new Map();
29
36
 
30
37
  public openDropdownProjects: boolean = false
38
+ public openStatusDropdownProjectId: string | null = null
39
+ public TEAMMATE_STATUS = TEAMMATE_STATUS
31
40
  private public_Key: string;
32
41
  public isVisible: boolean;
33
- public MT: boolean;
42
+ public isVisibleMT: boolean;
34
43
 
44
+ public projectUser: ProjectUser;
45
+ public roles: { [key: string]: boolean }
46
+ PERMISSIONS = PERMISSIONS;
35
47
  constructor(
36
48
  private projectService: ProjectService,
49
+ public projectUsersService: ProjectUsersService,
37
50
  private tiledeskAuthService: TiledeskAuthService,
38
51
  private appConfigProvider: AppConfigProvider,
39
52
  private translateService: CustomTranslateService,
40
53
  private events: EventsService,
41
54
  private cdref: ChangeDetectorRef,
55
+ private wsService: WebsocketService,
42
56
  ) { }
43
57
 
44
58
  ngOnInit() {
@@ -60,7 +74,10 @@ export class NavbarComponent implements OnInit {
60
74
  "NAVBAR.ADD_PROJECT",
61
75
  "NAVBAR.RECENT_PROJECTS",
62
76
  "NAVBAR.OTHER_PROJECTS",
63
- "LABEL_CHAT"
77
+ "LABEL_CHAT",
78
+ "LABEL_AVAILABLE",
79
+ "LABEL_NOT_AVAILABLE",
80
+ "LABEL_INACTIVE"
64
81
  ]
65
82
 
66
83
  this.translationsMap = this.translateService.translateLanguage(keys)
@@ -78,14 +95,17 @@ export class NavbarComponent implements OnInit {
78
95
 
79
96
  getProjects() {
80
97
  this.logger.log('[NAVBAR] calling getProjects ... ');
81
- this.projectService.getProjects().subscribe((projects: any) => {
98
+ this.projectService.getProjects().subscribe((projects: ProjectUser[]) => {
82
99
  this.logger.log('[NAVBAR] getProjects PROJECTS ', projects);
83
100
  if (projects) {
84
101
  // this.projects = projects;
85
- this.projects = projects.filter((project: any) => {
102
+ this.projects = projects.filter((project: ProjectUser) => {
86
103
  // this.logger.log('[NAVBAR] getProjects PROJECTS status ', project.id_project.status);
87
104
  return project.id_project.status === 100;
88
105
  });
106
+ this.projects.forEach((project: ProjectUser) => {
107
+ project.teammateStatus = getUserStatusFromProjectUser(project as any);
108
+ });
89
109
  this.logger.log('[NAVBAR] getProjects this.projects ', this.projects);
90
110
  }
91
111
  }, (error) => {
@@ -96,11 +116,14 @@ export class NavbarComponent implements OnInit {
96
116
  }
97
117
 
98
118
  getStoredProjectAndUserRole() {
99
- this.events.subscribe('storage:last_project',project =>{
119
+ this.events.subscribe('storage:last_project',async (project) =>{
100
120
  this.logger.log('[NAVBAR] stored_project ', project)
101
121
  if (project && project !== 'undefined') {
102
122
  this.project = project;
103
123
  this.USER_ROLE = project.role;
124
+ this.projectUser = await this.projectUsersService.getProjectUserByProjectId(project.id_project.id)
125
+ this.roles = this.checkRoles()
126
+ this.logger.log('[SIDEBAR] roles ', this.roles)
104
127
  }
105
128
  })
106
129
  }
@@ -108,44 +131,24 @@ export class NavbarComponent implements OnInit {
108
131
  getOSCODE() {
109
132
  this.public_Key = this.appConfigProvider.getConfig().t2y12PruGU9wUtEGzBJfolMIgK;
110
133
  this.logger.log('[NAVBAR] AppConfigService getAppConfig public_Key', this.public_Key)
111
- this.logger.log('[NAVBAR] public_Key', this.public_Key)
112
-
113
- let keys = this.public_Key.split("-");
114
- // this.logger.log('PUBLIC-KEY (Navbar) - public_Key keys', keys)
115
-
116
- keys.forEach(key => {
117
- // this.logger.log('NavbarComponent public_Key key', key)
118
- if (key.includes("PAY")) {
119
- // this.logger.log('PUBLIC-KEY (Navbar) - key', key);
120
- let pay = key.split(":");
121
- // this.logger.log('PUBLIC-KEY (Navbar) - pay key&value', pay);
122
- if (pay[1] === "F") {
123
- this.isVisible = false;
124
- // this.logger.log('PUBLIC-KEY (Navbar) - pay isVisible', this.isVisible);
125
- } else {
126
- this.isVisible = true;
127
- // this.logger.log('PUBLIC-KEY (Navbar) - pay isVisible', this.isVisible);
128
- }
129
- }
130
-
131
- if (key.includes("MTT")) {
132
- // this.logger.log('PUBLIC-KEY (Navbar) - key', key);
133
- let mt = key.split(":");
134
- // this.logger.log('PUBLIC-KEY (Navbar) - mt key&value', mt);
135
- if (mt[1] === "F") {
136
- this.MT = false;
137
- // this.logger.log('PUBLIC-KEY (Navbar) - mt is', this.MT);
138
- } else {
139
- this.MT = true;
140
- // this.logger.log('PUBLIC-KEY (Navbar) - mt is', this.MT);
141
- }
142
- }
143
- });
134
+
135
+ this.isVisibleMT = getOSCode("MTT", this.public_Key);
136
+
137
+ }
138
+
139
+ checkRoles(): { [key: string]: boolean } {
140
+ const permissionKeys = [
141
+ 'CHANGE_PROJECT',
142
+ 'SIMULATE_CONV',
143
+ ] as const;
144
144
 
145
- if (!this.public_Key.includes("MTT")) {
146
- this.MT = false;
147
- // this.logger.log('PUBLIC-KEY (Navbar) - mt is', this.MT);
145
+ const roles: { [key: string]: boolean } = {};
146
+ for (const key of permissionKeys) {
147
+ const permission = PERMISSIONS[key];
148
+ roles[permission] = hasRole(this.projectUser, permission);
148
149
  }
150
+
151
+ return roles;
149
152
 
150
153
  }
151
154
 
@@ -177,6 +180,61 @@ export class NavbarComponent implements OnInit {
177
180
  window.open(url, '_blank');
178
181
  }
179
182
 
183
+ toggleProjectsDropdown() {
184
+ this.openDropdownProjects = !this.openDropdownProjects
185
+ if (!this.openDropdownProjects) {
186
+ this.openStatusDropdownProjectId = null
187
+ }
188
+ }
189
+
190
+ statusDropdownPosition = { top: 0, right: 0 }
191
+
192
+ toggleStatusDropdown(event: Event, prjct: any) {
193
+ event.stopPropagation()
194
+ event.preventDefault()
195
+ const projectId = prjct?.id_project?._id
196
+ const isOpening = this.openStatusDropdownProjectId !== projectId
197
+ if (isOpening) {
198
+ const el = event.currentTarget as HTMLElement
199
+ const rect = el.getBoundingClientRect()
200
+ this.statusDropdownPosition = {
201
+ top: rect.top + rect.height / 2,
202
+ right: window.innerWidth - rect.left + 4
203
+ }
204
+ }
205
+ this.openStatusDropdownProjectId = this.openStatusDropdownProjectId === projectId ? null : projectId
206
+ }
207
+
208
+ onChangeProjectStatus(projectUser: ProjectUser, selectedStatusID: any) {
209
+ // TODO: implementare aggiornamento status progetto
210
+ this.logger.log('[NAVBAR] onChangeProjectStatus placeholder', projectUser, selectedStatusID)
211
+ this.openStatusDropdownProjectId = null
212
+
213
+ let IS_AVAILABLE = null
214
+ let profilestatus = ''
215
+ if (selectedStatusID === 1) {
216
+ IS_AVAILABLE = true
217
+ } else if (selectedStatusID === 2) {
218
+ IS_AVAILABLE = false
219
+ } else if (selectedStatusID === 3) {
220
+ IS_AVAILABLE = false
221
+ profilestatus = 'inactive'
222
+ }
223
+
224
+ this.wsService.updateCurrentUserAvailability(this.tiledeskToken, projectUser.id_project._id, IS_AVAILABLE, profilestatus).subscribe((projectUserUpdated: any) => {
225
+
226
+ this.logger.log('[NAVBAR] - PROJECT-USER UPDATED ', projectUser)
227
+ this.projects.find(p => p.id_project._id === projectUser.id_project._id).teammateStatus = getUserStatusFromProjectUser(projectUserUpdated as any);
228
+
229
+ }, (error) => {
230
+ this.logger.error('[NAVBAR] - PROJECT-USER UPDATED - ERROR ', error);
231
+
232
+ }, () => {
233
+ this.logger.log('[NAVBAR] - PROJECT-USER UPDATED * COMPLETE *');
234
+
235
+ });
236
+ }
237
+
180
238
  goToHome(id_project: string, project_name: string,) {
181
239
  // this.logger.log('!NAVBAR goToHome prjct ', prjct)
182
240
  this.logger.log('[NAVBAR] goToHome id_project ', id_project, 'project_name', project_name)