@chat21/chat21-ionic 3.4.31 → 3.4.32-rc10

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 (88) hide show
  1. package/CHANGELOG.md +167 -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 +71 -1
  21. package/src/app/components/navbar/navbar.component.ts +100 -42
  22. package/src/app/components/project-item/project-item.component.ts +79 -52
  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 +52 -11
  26. package/src/app/components/sidebar-user-details/sidebar-user-details.component.scss +304 -17
  27. package/src/app/components/sidebar-user-details/sidebar-user-details.component.ts +217 -27
  28. package/src/app/modals/create-ticket/create-ticket.page.ts +4 -2
  29. package/src/app/pages/conversation-detail/conversation-detail.page.html +7 -3
  30. package/src/app/pages/conversation-detail/conversation-detail.page.ts +89 -5
  31. package/src/app/pages/conversations-list/conversations-list.module.ts +3 -5
  32. package/src/app/pages/conversations-list/conversations-list.page.html +2 -0
  33. package/src/app/pages/conversations-list/conversations-list.page.ts +120 -26
  34. package/src/app/pages/unassigned-conversations/unassigned-conversations.module.ts +16 -4
  35. package/src/app/pages/unassigned-conversations/unassigned-conversations.page.html +43 -17
  36. package/src/app/pages/unassigned-conversations/unassigned-conversations.page.scss +25 -1
  37. package/src/app/pages/unassigned-conversations/unassigned-conversations.page.ts +279 -13
  38. package/src/app/pipe/filter.pipe.spec.ts +8 -0
  39. package/src/app/pipe/filter.pipe.ts +15 -0
  40. package/src/app/pipe/find.pipe.spec.ts +8 -0
  41. package/src/app/pipe/find.pipe.ts +15 -0
  42. package/src/app/services/global-settings/global-settings.service.ts +11 -3
  43. package/src/app/services/nav-proxy.service.ts +0 -1
  44. package/src/app/services/project_users/project-users.service.spec.ts +16 -0
  45. package/src/app/services/project_users/project-users.service.ts +63 -0
  46. package/src/app/services/projects/project.service.ts +2 -1
  47. package/src/app/services/tiledesk/tiledesk.service.ts +24 -0
  48. package/src/app/services/triggerEvents/triggerEvents.ts +40 -0
  49. package/src/app/services/websocket/websocket-js.ts +59 -534
  50. package/src/app/services/websocket/websocket-js_old.ts +578 -0
  51. package/src/app/services/websocket/websocket.service.ts +67 -14
  52. package/src/app/services/websocket/websocket.worker.ts +242 -0
  53. package/src/app/shared/shared.module.ts +26 -10
  54. package/src/app/utils/globals.ts +2 -0
  55. package/src/app/utils/permissions.constants.ts +138 -0
  56. package/src/app/utils/project-utils.ts +2 -2
  57. package/src/app/utils/utils.ts +18 -1
  58. package/src/assets/i18n/ar.json +11 -1
  59. package/src/assets/i18n/az.json +11 -1
  60. package/src/assets/i18n/de.json +11 -1
  61. package/src/assets/i18n/en.json +11 -1
  62. package/src/assets/i18n/es.json +11 -1
  63. package/src/assets/i18n/fr.json +11 -1
  64. package/src/assets/i18n/it.json +13 -3
  65. package/src/assets/i18n/kk.json +11 -1
  66. package/src/assets/i18n/pt.json +11 -1
  67. package/src/assets/i18n/ru.json +11 -1
  68. package/src/assets/i18n/sr.json +11 -1
  69. package/src/assets/i18n/sv.json +11 -1
  70. package/src/assets/i18n/tr.json +11 -1
  71. package/src/assets/i18n/uk.json +11 -1
  72. package/src/assets/i18n/uz.json +12 -1
  73. package/src/assets/js/agentDesktop-sdk.js +55 -0
  74. package/src/assets/js/chat21client.js +36 -0
  75. package/src/assets/js/mqtt-keepalive-worker.js +53 -0
  76. package/src/assets/test.html +5 -2
  77. package/src/chat-config-template.json +1 -0
  78. package/src/chat-config.json +1 -0
  79. package/src/chat21-core/models/projectUsers.ts +19 -0
  80. package/src/chat21-core/models/project_user.ts +2 -1
  81. package/src/chat21-core/models/projects.ts +1 -0
  82. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +1 -1
  83. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +1 -1
  84. package/src/chat21-core/providers/tiledesk/tiledesk-auth.service.ts +3 -0
  85. package/src/chat21-core/utils/constants.ts +6 -0
  86. package/src/chat21-core/utils/convertRequestToConversation.ts +2 -2
  87. package/src/chat21-core/utils/utils.ts +53 -3
  88. package/src/variables.scss +3 -0
@@ -15,7 +15,10 @@ import { tranlatedLanguage } from '../../../chat21-core/utils/constants';
15
15
  // utils
16
16
  import { avatarPlaceholder, getColorBck } from 'src/chat21-core/utils/utils-user';
17
17
  import { BRAND_BASE_INFO, LOGOS_ITEMS } from 'src/app/utils/utils-resources';
18
- import { getOSCode } from 'src/app/utils/utils';
18
+ import { getOSCode, hasRole } from 'src/app/utils/utils';
19
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
20
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
21
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service';
19
22
 
20
23
  @Component({
21
24
  selector: 'app-sidebar',
@@ -31,7 +34,7 @@ export class SidebarComponent implements OnInit {
31
34
  IS_AVAILABLE: boolean = false;
32
35
  IS_INACTIVE: boolean = true;
33
36
  IS_BUSY: boolean;
34
- isVisibleAPP: boolean;
37
+ // isVisibleAPP: boolean;
35
38
  isVisibleANA: boolean;
36
39
  isVisibleACT: boolean;
37
40
  isVisibleMON: boolean;
@@ -41,9 +44,10 @@ export class SidebarComponent implements OnInit {
41
44
  project_id: string;
42
45
  DASHBOARD_URL: string;
43
46
  // HAS_CLICKED_OPEN_USER_DETAIL: boolean = false
44
- public translationMap: Map<string, string>;
47
+ public translationsMap: Map<string, string>;
45
48
  public_Key: any;
46
49
  conversations_lbl: string;
50
+ whatsappbroadcast_lbl: string;
47
51
  contacts_lbl: string;
48
52
  apps_lbl: string;
49
53
  analytics_lbl: string;
@@ -53,19 +57,14 @@ export class SidebarComponent implements OnInit {
53
57
  countClickOnOpenUserDetailSidebar: number = 0
54
58
  USER_PHOTO_PROFILE_EXIST: boolean;
55
59
  currentUser: any;
56
- dashboard_home_url: string;
57
- dashboard_knb_url: string;
58
- dashboard_bots_url: string;
59
- dashboard_convs_url: string;
60
- dashboard_contacts_url: string;
61
- dashboard_app_url: string;
62
- dashboard_analytics_url: string;
63
- dashboard_activities_url: string;
64
- dashboard_history_url: string;
65
- dashboard_settings_url: string;
66
- tiledesk_url: string;
60
+ URLS: { [key: string]: string} = {};
61
+
62
+ public projectUser: ProjectUser;
63
+ public roles: { [key: string]: boolean }
64
+
67
65
  LOGOS_ITEMS = LOGOS_ITEMS;
68
66
  BRAND_BASE_INFO = BRAND_BASE_INFO;
67
+ PERMISSIONS = PERMISSIONS;
69
68
  constructor(
70
69
  public imageRepoService: ImageRepoService,
71
70
  public appStorageService: AppStorageService,
@@ -75,12 +74,13 @@ export class SidebarComponent implements OnInit {
75
74
  public wsService: WebsocketService,
76
75
  public appConfigProvider: AppConfigProvider,
77
76
  private translate: TranslateService,
77
+ public projectUsersService: ProjectUsersService,
78
78
  public events: EventsService,
79
79
 
80
80
  ) { }
81
81
 
82
82
  ngOnInit() {
83
- this.tiledesk_url = BRAND_BASE_INFO['COMPANY_SITE_URL'] as string
83
+ this.URLS.TILEDESK = BRAND_BASE_INFO['COMPANY_SITE_URL'] as string
84
84
 
85
85
  this.DASHBOARD_URL = this.appConfig.getConfig().dashboardUrl + '#/project/';
86
86
  this.getStoredProjectAndUserRole()
@@ -92,36 +92,57 @@ export class SidebarComponent implements OnInit {
92
92
 
93
93
 
94
94
  getStoredProjectAndUserRole() {
95
- this.events.subscribe('storage:last_project',project =>{
95
+ this.events.subscribe('storage:last_project',async (project) =>{
96
96
  this.logger.log('[SIDEBAR] stored_project ', project)
97
97
  if (project && project !== 'undefined') {
98
98
  this.project_id = project.id_project.id
99
99
  this.USER_ROLE = project.role;
100
100
  this.buildURLs(this.USER_ROLE)
101
+ this.projectUser = await this.projectUsersService.getProjectUserByProjectId(project.id_project.id)
102
+ this.roles = this.checkRoles()
103
+ this.logger.log('[SIDEBAR] roles ', this.roles)
101
104
  }
102
105
  })
103
106
  }
104
107
 
105
108
  buildURLs(USER_ROLE) {
106
- this.dashboard_home_url = this.DASHBOARD_URL + this.project_id + '/home'
107
- this.dashboard_knb_url = this.DASHBOARD_URL + this.project_id + '/knowledge-bases'
108
- this.dashboard_bots_url = this.DASHBOARD_URL + this.project_id + '/bots'
109
- this.dashboard_convs_url = this.DASHBOARD_URL + this.project_id + '/wsrequests'
110
- this.dashboard_contacts_url = this.DASHBOARD_URL + this.project_id + '/contacts'
111
- this.dashboard_app_url = this.DASHBOARD_URL + this.project_id + '/app-store'
112
- this.dashboard_analytics_url = this.DASHBOARD_URL + this.project_id + '/analytics'
113
- this.dashboard_activities_url = this.DASHBOARD_URL + this.project_id + '/activities'
114
- this.dashboard_history_url = this.DASHBOARD_URL + this.project_id + '/history'
115
- this.dashboard_settings_url = ''
116
- if (USER_ROLE !== 'agent') {
117
- this.dashboard_settings_url = this.DASHBOARD_URL + this.project_id + '/widget-set-up'
118
- } else if (USER_ROLE === 'agent') {
119
- this.dashboard_settings_url = this.DASHBOARD_URL + this.project_id + '/cannedresponses'
120
- }
121
- this.tiledesk_url = 'https://www.tiledesk.com'
109
+ const base = this.DASHBOARD_URL + this.project_id;
110
+
111
+ this.URLS = {
112
+ HOME: `${base}/home`,
113
+ KNOWLEDGEBASE: `${base}/knowledge-bases`,
114
+ BOTS: `${base}/bots`,
115
+ MONITOR: `${base}/wsrequests`,
116
+ WHATSAPP: `${base}/automations`,
117
+ CONTACTS: `${base}/contacts`,
118
+ APPSTORE: `${base}/app-store`,
119
+ ANALYTICS: `${base}/analytics`,
120
+ ACTIVITIES: `${base}/activities`,
121
+ HISTORY: `${base}/history`,
122
+ SETTINGS: USER_ROLE !== 'agent' ? `${base}/widget-set-up` : `${base}/cannedresponses`,
123
+ TILEDESK: 'https://www.tiledesk.com'
124
+ };
125
+
126
+ this.setQueryParamsForAll({ tiledesk_logOut: BRAND_BASE_INFO['LOGOUT_ENABLED'] });
122
127
 
123
128
  }
124
129
 
130
+ // Funzione helper per aggiungere query params a tutte le URL
131
+ setQueryParamsForAll(queryParams: any) {
132
+ if (!queryParams) return;
133
+
134
+ const queryString = new URLSearchParams(queryParams).toString();
135
+
136
+ // Cicla tutte le chiavi di URLS
137
+ Object.keys(this.URLS).forEach(key => {
138
+ if (this.URLS[key]) {
139
+ // Controlla se già ci sono query params
140
+ const separator = this.URLS[key].includes('?') ? '&' : '?';
141
+ this.URLS[key] = `${this.URLS[key]}${queryString ? separator + queryString : ''}`;
142
+ }
143
+ });
144
+ }
145
+
125
146
  subcribeToAuthStateChanged() {
126
147
  this.messagingAuthService.BSAuthStateChanged.subscribe((state) => {
127
148
  this.logger.log('[SIDEBAR] BSAuthStateChanged ', state)
@@ -240,6 +261,7 @@ export class SidebarComponent implements OnInit {
240
261
  this.logger.error('[SIDEBAR] - ngOnInit - currentUser not found in storage ')
241
262
  }
242
263
  this.translateLabels()
264
+ this.translations()
243
265
  }
244
266
 
245
267
 
@@ -256,6 +278,7 @@ export class SidebarComponent implements OnInit {
256
278
 
257
279
  this.translate.get(keys).subscribe((text: string) => {
258
280
  this.conversations_lbl = text['Conversations'];
281
+ this.whatsappbroadcast_lbl = text['WhatsAppBroadcasts']
259
282
  this.contacts_lbl = text['LABEL_CONTACTS']
260
283
  this.apps_lbl = text['Apps']
261
284
  this.analytics_lbl = text['Analytics']
@@ -271,13 +294,55 @@ export class SidebarComponent implements OnInit {
271
294
 
272
295
  this.isVisibleANA = getOSCode("ANA", this.public_Key);
273
296
  this.isVisibleACT = getOSCode("ACT", this.public_Key);
274
- this.isVisibleAPP = getOSCode("APP", this.public_Key);
275
297
  this.isVisibleMON = getOSCode("MON", this.public_Key);
276
298
  this.isVisibleCNT = getOSCode("CNT", this.public_Key);
277
299
  this.isVisibleKNB = getOSCode("KNB", this.public_Key);
278
-
300
+
279
301
  }
280
302
 
303
+
304
+ checkRoles(): { [key: string]: boolean } {
305
+ const permissionKeys = [
306
+ 'HOME_READ',
307
+ 'KB_READ',
308
+ 'FLOWS_READ',
309
+ 'INBOX_READ',
310
+ 'AUTOMATIONSLOG_READ',
311
+ 'LEADS_READ',
312
+ 'ANALYTICS_READ',
313
+ 'ACTIVITIES_READ',
314
+ 'HISTORY_READ',
315
+ 'PROJECTSETTINGS_GENERAL_READ',
316
+ 'PROJECTSETTINGS_DEVELOPER_READ',
317
+ 'PROJECTSETTINGS_SMARTASSIGNMENT_READ',
318
+ 'PROJECTSETTINGS_NOTIFICATION_READ',
319
+ 'PROJECTSETTINGS_SECURITY_READ',
320
+ 'PROJECTSETTINGS_BANNED_READ',
321
+ 'PROJECTSETTINGS_ADVANCED_READ'
322
+ ] as const;
323
+
324
+ const roles: { [key: string]: boolean } = {};
325
+ for (const key of permissionKeys) {
326
+ const permission = PERMISSIONS[key];
327
+ roles[permission] = hasRole(this.projectUser, permission);
328
+ }
329
+
330
+
331
+ let settingRoleKEys = [
332
+ 'PROJECTSETTINGS_GENERAL_READ',
333
+ 'PROJECTSETTINGS_DEVELOPER_READ',
334
+ 'PROJECTSETTINGS_SMARTASSIGNMENT_READ',
335
+ 'PROJECTSETTINGS_NOTIFICATION_READ',
336
+ 'PROJECTSETTINGS_SECURITY_READ',
337
+ 'PROJECTSETTINGS_BANNED_READ',
338
+ 'PROJECTSETTINGS_ADVANCED_READ'
339
+ ] as const;
340
+ roles[PERMISSIONS.SETTINGS_READ] = settingRoleKEys.some(settingKey => roles[PERMISSIONS[settingKey]]);
341
+
342
+ return roles;
343
+
344
+ }
345
+
281
346
  listenTocurrentProjectUserUserAvailability$() {
282
347
  this.wsService.currentProjectUserAvailability$.subscribe((data) => {
283
348
  this.logger.log('[SIDEBAR] - $UBSC TO WS USER AVAILABILITY & BUSY STATUS RES ', data);
@@ -327,92 +392,20 @@ export class SidebarComponent implements OnInit {
327
392
  }
328
393
  }
329
394
 
330
- goToHome() {
331
- let url = this.DASHBOARD_URL + this.project_id + '/home'
332
- this.dashboard_home_url = url;
333
- const myWindow = window.open(url, '_self');
334
- myWindow.focus();
335
- }
336
-
337
- goToBots() {
338
- let url = this.DASHBOARD_URL + this.project_id + '/bots/my-chatbots/all'
339
- const myWindow = window.open(url, '_self');
340
- myWindow.focus();
341
- }
342
-
343
- goToConversations() {
344
- let url = this.DASHBOARD_URL + this.project_id + '/wsrequests'
345
- const myWindow = window.open(url, '_self');
346
- myWindow.focus();
347
- }
348
-
349
- goToContacts() {
350
- let url = this.DASHBOARD_URL + this.project_id + '/contacts'
351
- const myWindow = window.open(url, '_self');
352
- myWindow.focus();
353
- }
354
-
355
- goToAppStore() {
356
- let url = this.DASHBOARD_URL + this.project_id + '/app-store'
357
- const myWindow = window.open(url, '_self');
358
- myWindow.focus();
359
- }
360
-
361
- goToAnalytics() {
362
- let url = this.DASHBOARD_URL + this.project_id + '/analytics'
363
- const myWindow = window.open(url, '_self');
364
- myWindow.focus();
365
- }
366
-
367
- goToActivities() {
368
- let url = this.DASHBOARD_URL + this.project_id + '/activities'
369
- const myWindow = window.open(url, '_self');
370
- myWindow.focus();
371
- }
372
-
373
- goToHistory() {
374
- let url = this.DASHBOARD_URL + this.project_id + '/history'
375
- const myWindow = window.open(url, '_self');
376
- myWindow.focus();
377
- }
378
-
379
- goToWidgetSetUpOrToCannedResponses() {
380
- if (this.USER_ROLE !== 'agent') {
381
- this.goToWidgetSetUp()
382
- } else if (this.USER_ROLE === 'agent') {
383
- this.goToSettings_CannedResponses()
384
- }
385
- }
386
-
387
- goToWidgetSetUp() {
388
- let url = this.DASHBOARD_URL + this.project_id + '/widget-set-up'
389
- const myWindow = window.open(url, '_self');
390
- myWindow.focus();
391
- }
392
-
393
- goToSettings_CannedResponses() {
394
- let url = this.DASHBOARD_URL + this.project_id + '/cannedresponses'
395
- const myWindow = window.open(url, '_self');
396
- myWindow.focus();
397
- }
398
-
399
-
400
-
401
395
  public translations() {
402
396
  const keys = [
403
- 'LABEL_AVAILABLE',
404
- 'LABEL_NOT_AVAILABLE',
405
- 'LABEL_BUSY',
406
- 'VIEW_ALL_CONVERSATIONS',
407
- 'CONVERSATIONS_IN_QUEUE',
408
- 'CONVERSATION_IN_QUEUE',
409
- 'NO_CONVERSATION_IN_QUEUE',
410
- 'PINNED_PROJECT',
411
- 'CHANGE_PINNED_PROJECT',
412
- "CHANGE_TO_YOUR_STATUS_TO_AVAILABLE",
413
- "CHANGE_TO_YOUR_STATUS_TO_UNAVAILABLE"
397
+ 'Monitor',
398
+ 'Flows',
399
+ 'Knowledgebases',
400
+ 'WhatsAppBroadcasts',
401
+ 'LABEL_CONTACTS',
402
+ 'Apps',
403
+ 'Analytics',
404
+ 'Activities',
405
+ 'History',
406
+ 'Settings'
414
407
  ];
415
- this.translationMap = this.translateService.translateLanguage(keys);
408
+ this.translationsMap = this.translateService.translateLanguage(keys);
416
409
  }
417
410
 
418
411
 
@@ -71,23 +71,52 @@
71
71
  </span>
72
72
  </mat-slide-toggle> -->
73
73
 
74
- <ng-select style="text-align: left;"
74
+ <ng-container *ngIf="!isVisibleMPA" id="availability_dropdown_container">
75
+ <ng-select style="text-align: left;"
75
76
  (change)="changeAvailabilityStateInUserDetailsSidebar(selectedStatus)"
76
77
  [(ngModel)]="selectedStatus"
77
78
  class="teammate-status-in-drawer sidebar"
78
- [items]="teammateStatus"
79
+ [items]="TEAMMATE_STATUS"
79
80
  bindLabel="name" bindValue="id"
80
81
  [clearable]="false"
81
82
  [searchable]="false">
82
- <ng-template ng-label-tmp let-item="item">
83
- <img style="width: 15px;height: 15px;position: relative; top: 1px;" height="15" width="15" [src]="item?.avatar" />
84
- <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label}} </span>
85
- </ng-template>
86
- <ng-template ng-option-tmp let-item="item" let-index="index">
87
- <img style="width: 15px;height: 15px;position: relative; top: 1px;" height="15" width="15" [src]="item?.avatar" />
88
- <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label}} </span>
89
- </ng-template>
90
- </ng-select>
83
+ <ng-template ng-label-tmp let-item="item">
84
+ <img style="width: 15px;height: 15px;position: relative; top: 1px;" height="15" width="15" [src]="item?.avatar" />
85
+ <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label | translate}} </span>
86
+ </ng-template>
87
+ <ng-template ng-option-tmp let-item="item" let-index="index">
88
+ <img style="width: 15px;height: 15px;position: relative; top: 1px;" height="15" width="15" [src]="item?.avatar" />
89
+ <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label | translate}} </span>
90
+ </ng-template>
91
+ </ng-select>
92
+ </ng-container>
93
+
94
+ <ng-container *ngIf="isVisibleMPA && projects?.length > 0" id="projects_dropdown_container">
95
+ <div class="projects-dropdown-wrapper">
96
+ <button class="btn projects-dropdown-toggle" (click)="toggleProjectsDropdown()">
97
+ <span class="projects-dropdown-toggle-label">
98
+ {{ project?.name || 'Progetti' }}
99
+ <img *ngIf="project?.teammateStatus" style="width: 15px; height: 15px; position: relative; top: 1px; cursor: pointer;" height="15" width="15" [src]="project?.teammateStatus?.avatar" />
100
+ </span>
101
+ <i class="material-icons" style="margin-left: 4px; font-size: 18px;">arrow_drop_down</i>
102
+ </button>
103
+ <div id="projects_dropdown" class="dropdown-menu" [ngClass]="{'open': openDropdownProjects}">
104
+ <li *ngFor="let prjct of projects?.slice() | slice:0:10; let i=index" style="cursor: pointer"
105
+ (mouseenter)="openStatusDropdownOnHover($event, prjct)"
106
+ (mouseleave)="closeStatusDropdownOnLeave()">
107
+ <a [ngClass]="{'li-selected' : prjct?.id_project?._id === openStatusDropdownProjectId }"
108
+ class="project-item-row">
109
+ <span class="project-item-name">{{ prjct?.id_project?.name }}</span>
110
+ <span class="project-item-status project-item-status-wrapper"
111
+ [attr.title]="prjct?.teammateStatus?.name">
112
+ <span class="project-item-status-name">{{prjct?.teammateStatus?.name}}</span>
113
+ <img [src]="prjct?.teammateStatus?.avatar" />
114
+ </span>
115
+ </a>
116
+ </li>
117
+ </div>
118
+ </div>
119
+ </ng-container>
91
120
  </section>
92
121
 
93
122
  <hr class="first-divider">
@@ -140,4 +169,16 @@
140
169
  <div class="chat-version"> ver {{version}}</div>
141
170
  </section>
142
171
 
172
+ </div>
173
+
174
+ <!-- Status dropdown fuori da #user-details per evitare clipping da overflow -->
175
+ <div class="status-dropdown status-dropdown-fixed status-dropdown-outside" *ngIf="openStatusDropdownProjectId && selectedProjectForStatus"
176
+ [style.top.px]="statusDropdownPosition.top" [style.left.px]="statusDropdownPosition.left"
177
+ (mouseenter)="cancelStatusDropdownClose()"
178
+ (mouseleave)="closeStatusDropdownOnLeave()">
179
+ <div class="status-dropdown-option" *ngFor="let status of TEAMMATE_STATUS"
180
+ (click)="$event.stopPropagation(); onChangeProjectStatus(selectedProjectForStatus, status.id)">
181
+ <img style="width: 15px; height: 15px; margin-right: 6px;" [src]="status.avatar" />
182
+ <span>{{ status.name }}</span>
183
+ </div>
143
184
  </div>