@chat21/chat21-ionic 3.4.27 → 3.4.28-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 (69) hide show
  1. package/CHANGELOG.md +110 -4
  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 +71 -10
  6. package/src/app/components/canned-response/canned-response.component.html +26 -23
  7. package/src/app/components/canned-response/canned-response.component.scss +0 -2
  8. package/src/app/components/canned-response/canned-response.component.ts +3 -1
  9. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.html +24 -1
  10. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.scss +30 -0
  11. package/src/app/components/conversation-detail/message-text-area/message-text-area.component.ts +29 -7
  12. package/src/app/components/conversation-info/info-content/info-content.component.ts +2 -2
  13. package/src/app/components/conversation-info/info-group/info-group.component.ts +23 -21
  14. package/src/app/components/conversations-list/header-conversations-list/header-conversations-list.component.html +1 -1
  15. package/src/app/components/conversations-list/header-conversations-list/header-conversations-list.component.ts +5 -1
  16. package/src/app/components/navbar/navbar.component.html +3 -3
  17. package/src/app/components/navbar/navbar.component.ts +29 -38
  18. package/src/app/components/project-item/project-item.component.ts +70 -52
  19. package/src/app/components/sidebar/sidebar.component.html +65 -45
  20. package/src/app/components/sidebar/sidebar.component.ts +110 -117
  21. package/src/app/components/sidebar-user-details/sidebar-user-details.component.html +2 -2
  22. package/src/app/components/sidebar-user-details/sidebar-user-details.component.ts +10 -7
  23. package/src/app/modals/create-ticket/create-ticket.page.ts +4 -2
  24. package/src/app/pages/conversation-detail/conversation-detail.page.html +7 -3
  25. package/src/app/pages/conversation-detail/conversation-detail.page.ts +89 -5
  26. package/src/app/pages/conversations-list/conversations-list.page.html +2 -0
  27. package/src/app/pages/conversations-list/conversations-list.page.ts +40 -2
  28. package/src/app/services/global-settings/global-settings.service.ts +11 -3
  29. package/src/app/services/nav-proxy.service.ts +0 -1
  30. package/src/app/services/project_users/project-users.service.spec.ts +16 -0
  31. package/src/app/services/project_users/project-users.service.ts +63 -0
  32. package/src/app/services/tiledesk/tiledesk.service.ts +0 -16
  33. package/src/app/services/triggerEvents/triggerEvents.ts +28 -0
  34. package/src/app/services/websocket/websocket-js.ts +59 -534
  35. package/src/app/services/websocket/websocket-js_old.ts +578 -0
  36. package/src/app/services/websocket/websocket.service.ts +9 -10
  37. package/src/app/services/websocket/websocket.worker.ts +242 -0
  38. package/src/app/shared/shared.module.ts +11 -2
  39. package/src/app/utils/globals.ts +2 -0
  40. package/src/app/utils/permissions.constants.ts +138 -0
  41. package/src/app/utils/project-utils.ts +2 -2
  42. package/src/app/utils/utils.ts +18 -1
  43. package/src/assets/i18n/ar.json +11 -1
  44. package/src/assets/i18n/az.json +11 -1
  45. package/src/assets/i18n/de.json +11 -1
  46. package/src/assets/i18n/en.json +11 -1
  47. package/src/assets/i18n/es.json +11 -1
  48. package/src/assets/i18n/fr.json +11 -1
  49. package/src/assets/i18n/it.json +13 -3
  50. package/src/assets/i18n/kk.json +11 -1
  51. package/src/assets/i18n/pt.json +11 -1
  52. package/src/assets/i18n/ru.json +11 -1
  53. package/src/assets/i18n/sr.json +11 -1
  54. package/src/assets/i18n/sv.json +11 -1
  55. package/src/assets/i18n/tr.json +11 -1
  56. package/src/assets/i18n/uk.json +11 -1
  57. package/src/assets/i18n/uz.json +12 -1
  58. package/src/assets/js/agentDesktop-sdk.js +55 -0
  59. package/src/assets/js/chat21client.js +36 -0
  60. package/src/assets/js/mqtt-keepalive-worker.js +53 -0
  61. package/src/assets/test.html +5 -2
  62. package/src/chat-config-template.json +1 -0
  63. package/src/chat-config.json +1 -0
  64. package/src/chat21-core/models/projectUsers.ts +19 -0
  65. package/src/chat21-core/models/project_user.ts +25 -0
  66. package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +1 -1
  67. package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +1 -1
  68. package/src/chat21-core/providers/tiledesk/tiledesk-auth.service.ts +3 -0
  69. package/src/chat21-core/utils/utils.ts +16 -2
@@ -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
 
@@ -81,11 +81,11 @@
81
81
  [searchable]="false">
82
82
  <ng-template ng-label-tmp let-item="item">
83
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>
84
+ <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label | translate}} </span>
85
85
  </ng-template>
86
86
  <ng-template ng-option-tmp let-item="item" let-index="index">
87
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>
88
+ <span id="sidebaravatar_{{item.name}}" style="text-transform: capitalize; margin-left:8px"> {{item.label | translate}} </span>
89
89
  </ng-template>
90
90
  </ng-select>
91
91
  </section>
@@ -25,6 +25,7 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
25
25
  // HAS_CLICKED_OPEN_USER_DETAIL: boolean = false;
26
26
  // @Output() onCloseUserDetailsSidebar = new EventEmitter();
27
27
 
28
+ @Input() logOut: boolean;
28
29
 
29
30
  public browserLang: string;
30
31
  private logger: LoggerService = LoggerInstance.getInstance()
@@ -242,9 +243,9 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
242
243
  .set('SubscriptionPaymentProblem', text['SubscriptionPaymentProblem'])
243
244
  .set('ThePlanHasExpired', text['ThePlanHasExpired'])
244
245
 
245
- this.teammateStatus.forEach(element => {
246
- element.label = this.translationsMap.get(element.label)
247
- });
246
+ // this.teammateStatus.forEach(element => {
247
+ // element.label = this.translationsMap.get(element.label)
248
+ // });
248
249
 
249
250
  });
250
251
  }
@@ -261,7 +262,7 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
261
262
  listenToCurrentStoredProject() {
262
263
  this.events.subscribe('storage:last_project', projectObjct => {
263
264
  if (projectObjct && projectObjct !== 'undefined') {
264
- // this.logger.log('[SIDEBAR-USER-DETAILS] - GET STORED PROJECT ', projectObjct)
265
+ this.logger.log('[SIDEBAR-USER-DETAILS] - GET STORED PROJECT ', projectObjct)
265
266
 
266
267
  //TODO: recuperare info da root e non da id_project
267
268
  this.project = {
@@ -284,6 +285,8 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
284
285
  } else if (this.project.profile.type === 'payment' && this.project.profile.name === 'enterprise') {
285
286
  this.getEnterprisePlanTranslation();
286
287
  }
288
+
289
+ this.wsService.subscriptionToWsCurrentProjectUserAvailability(this.project._id, projectObjct._id);
287
290
  }
288
291
  })
289
292
 
@@ -329,14 +332,14 @@ export class SidebarUserDetailsComponent implements OnInit, OnChanges {
329
332
  // this.logger.log('teammateStatus ', this.teammateStatus)
330
333
  this.selectedStatus = this.teammateStatus[2].id;
331
334
  this.logger.debug('[SIDEBAR-USER-DETAILS] - PROFILE_STATUS selected option', this.teammateStatus[2].name);
332
- this.teammateStatus = this.teammateStatus.slice(0)
335
+ // this.teammateStatus = this.teammateStatus.slice(0)
333
336
  } else if (projectUser['user_available'] === false && (projectUser['profileStatus'] === '' || !projectUser['profileStatus'])) {
334
337
  this.selectedStatus = this.teammateStatus[1].id;
335
338
  this.logger.debug('[SIDEBAR-USER-DETAILS] - PROFILE_STATUS selected option', this.teammateStatus[1].name);
336
- this.teammateStatus = this.teammateStatus.slice(0)
339
+ // this.teammateStatus = this.teammateStatus.slice(0)
337
340
  } else if (projectUser['user_available'] === true && (projectUser['profileStatus'] === '' || !projectUser['profileStatus'])) {
338
341
  this.selectedStatus = this.teammateStatus[0].id
339
- this.teammateStatus = this.teammateStatus.slice(0)
342
+ // this.teammateStatus = this.teammateStatus.slice(0)
340
343
  this.logger.debug('[SIDEBAR-USER-DETAILS] - PROFILE_STATUS selected option', this.teammateStatus[0].name);
341
344
  }
342
345
  this.IS_BUSY = projectUser['isBusy']
@@ -9,6 +9,7 @@ import * as uuid from 'uuid';
9
9
  import { EventsService } from 'src/app/services/events-service'
10
10
  import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
11
11
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
12
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service'
12
13
 
13
14
  @Component({
14
15
  selector: 'app-create-ticket',
@@ -64,6 +65,7 @@ export class CreateTicketPage implements OnInit {
64
65
  logger: LoggerService = LoggerInstance.getInstance();
65
66
  constructor(
66
67
  public modalController: ModalController,
68
+ public projectUsersService: ProjectUsersService,
67
69
  public tiledeskService: TiledeskService,
68
70
  public appConfigProvider: AppConfigProvider,
69
71
  public events: EventsService
@@ -104,7 +106,7 @@ export class CreateTicketPage implements OnInit {
104
106
  // Create the array of the project-users and contacts displayed in the combo box "Requester"
105
107
  // -------------------------------------------------------------------------------------------
106
108
  getProjectUsersAndContacts(projctid: string) {
107
- const projectUsers = this.tiledeskService.getProjectUsersByProjectId(projctid)
109
+ const projectUsers = this.projectUsersService.getProjectUsersByProjectId(projctid)
108
110
  const leads = this.tiledeskService.getAllLeadsActiveWithLimit(projctid,10000)
109
111
 
110
112
  zip(projectUsers, leads).subscribe(
@@ -243,7 +245,7 @@ export class CreateTicketPage implements OnInit {
243
245
  // -------------------------------------------------------------------------------------------------------------------
244
246
  getProjectUserBotsAndDepts(projctid: string) {
245
247
  // this.loadingAssignee = true;
246
- const projectUsers = this.tiledeskService.getProjectUsersByProjectId( projctid)
248
+ const projectUsers = this.projectUsersService.getProjectUsersByProjectId( projctid)
247
249
  const bots = this.tiledeskService.getAllBotByProjectId(projctid)
248
250
  const depts = this.tiledeskService.getDeptsByProjectId(projctid)
249
251
 
@@ -173,7 +173,7 @@
173
173
  <!-- ----------------------------------------------------------- -->
174
174
  <app-canned-response *ngIf="SHOW_CANNED_RESPONSES"
175
175
  id="canned"
176
- [canShowCanned]="canShowCanned"
176
+ [roles]="rolesCanned"
177
177
  [conversationWith]="conversationWith"
178
178
  [conversationWithFullname]="conversationWithFullname"
179
179
  [currentString]="messageStr"
@@ -201,7 +201,8 @@
201
201
  <!-- [tagsCannedFilter]="tagsCannedFilter" -->
202
202
  <!-- openInfoConversation {{openInfoConversation}} - isMobile {{isMobile}} -->
203
203
  <app-message-text-area *ngIf="(openInfoConversation === false && isMobile === true) || (openInfoConversation === true && isMobile === false) || (openInfoConversation === false && isMobile === false)"
204
- [loggedUser]="loggedUser"
204
+ [loggedUser]="loggedUser"
205
+ [projectUser]="projectUser"
205
206
  [conversationWith]="conversationWith"
206
207
  [channelType]="channelType"
207
208
  [channel]="conversation?.attributes?.request_channel"
@@ -213,8 +214,10 @@
213
214
  [fileUploadAccept]="fileUploadAccept"
214
215
  [emailSection]="isEmailEnabled"
215
216
  [offlineMsgEmail]="offlineMsgEmail"
217
+ [cannedSection]="canShowCanned"
216
218
  [whatsappTemplatesSection]="isWhatsappTemplatesEnabled"
217
219
  [isOpenInfoConversation]="openInfoConversation"
220
+ [ticketSection]="isTicketEnabled"
218
221
  [stylesMap]="styleMap"
219
222
  [translationMap]="translationsMap"
220
223
  [dropEvent]="dropEvent"
@@ -223,7 +226,8 @@
223
226
  (onClickOpenCannedResponses)="onClickOpenCannedResponses($event)"
224
227
  (eventSendMessage)="returnSendMessage($event)"
225
228
  (onPresentModalScrollToBottom)="onPresentModalScrollToBottom($event)"
226
- (onOpenFooterSection)="onOpenFooterSection($event)">
229
+ (onOpenFooterSection)="onOpenFooterSection($event)"
230
+ (onOpenTicket)="onOpenTicket($event)">
227
231
  </app-message-text-area>
228
232
  <!-- [events]="eventsReplaceTexareaText.asObservable()" -->
229
233
  </ion-row>
@@ -83,7 +83,11 @@ import { WebsocketService } from 'src/app/services/websocket/websocket.service';
83
83
  import { Project } from 'src/chat21-core/models/projects';
84
84
  import { Globals } from 'src/app/utils/globals';
85
85
  import { ProjectService } from 'src/app/services/projects/project.service';
86
- import { getOSCode } from 'src/app/utils/utils';
86
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service';
87
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
88
+ import { getOSCode, hasRole } from 'src/app/utils/utils';
89
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
90
+ import { TriggerEvents } from 'src/app/services/triggerEvents/triggerEvents';
87
91
 
88
92
  @Component({
89
93
  selector: 'app-conversation-detail',
@@ -108,6 +112,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
108
112
  private subscriptions: Array<any>
109
113
  public tenant: string;
110
114
  public loggedUser: UserModel
115
+ public projectUser: ProjectUser;
111
116
  public conversationWith: string
112
117
  public conversationWithFullname: string
113
118
  public messages: Array<MessageModel> = []
@@ -137,6 +142,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
137
142
  public tagsCannedFilter: Array<any> = [];
138
143
  public SHOW_CANNED_RESPONSES: boolean = false
139
144
  public canShowCanned: boolean = true
145
+ public rolesCanned: { [key: string]: boolean }
140
146
 
141
147
  public SHOW_COPILOT_SUGGESTIONS: boolean = false;
142
148
 
@@ -171,6 +177,10 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
171
177
  copilotQuestion: string = '';
172
178
  /**COPILOT : end */
173
179
 
180
+ /** TICKET: start */
181
+ isTicketEnabled: boolean = false;
182
+ /** TICKET: end */
183
+
174
184
  isMine = isMine
175
185
  isInfo = isInfo
176
186
  isFirstMessage = isFirstMessage
@@ -240,18 +250,20 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
240
250
  public toastController: ToastController,
241
251
  public tiledeskService: TiledeskService,
242
252
  public projectService: ProjectService,
253
+ public projectUsersService: ProjectUsersService,
243
254
  private networkService: NetworkService,
244
255
  private events: EventsService,
245
256
  private webSocketService: WebsocketService,
246
257
  public projectPlanUtils: ProjectPlanUtils,
258
+ public triggerEvents: TriggerEvents,
247
259
  private g: Globals,
248
260
  ) {
249
261
  // Change list on date change
250
262
  this.route.paramMap.subscribe((params) => {
251
263
  this.logger.log('[CONVS-DETAIL] - constructor -> params: ', params)
252
264
  this.conversationWith = params.get('IDConv')
253
- this.conversationWithFullname = params.get('FullNameConv')
254
- this.conv_type = params.get('Convtype')
265
+ this.conversationWithFullname = decodeURIComponent(params.get('FullNameConv'))
266
+ this.conv_type = decodeURIComponent(params.get('Convtype'))
255
267
 
256
268
  this.events.publish('supportconvid:haschanged', this.conversationWith)
257
269
  })
@@ -418,6 +430,8 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
418
430
  ionViewDidEnter() {
419
431
  this.logger.log('[CONVS-DETAIL] > ionViewDidEnter')
420
432
  // this.info_content_child_enabled = true;
433
+ // Scroll to bottom to show the last message without animation
434
+ this.scrollToLastMessage()
421
435
  }
422
436
 
423
437
  // Unsubscibe when new page transition end
@@ -477,6 +491,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
477
491
  this.messages = [] // list messages of conversation
478
492
  this.isFileSelected = false // indicates if a file has been selected (image to upload)
479
493
  this.isEmailEnabled = (this.appConfigProvider.getConfig().emailSection === 'true' || this.appConfigProvider.getConfig().emailSection === true) ? true : false;
494
+ this.isTicketEnabled = (this.appConfigProvider.getConfig().ticketSection === 'true' || this.appConfigProvider.getConfig().ticketSection === true) ? true : false;
480
495
  this.isWhatsappTemplatesEnabled = (this.appConfigProvider.getConfig().whatsappTemplatesSection === 'true' || this.appConfigProvider.getConfig().whatsappTemplatesSection === true) ? true : false;
481
496
 
482
497
  this.cannedResponsesService.initialize(appconfig.apiUrl)
@@ -534,7 +549,6 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
534
549
  this.logger.log('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT * COMPLETE *',)
535
550
  })
536
551
  }else {
537
- this.canShowCanned = false;
538
552
  this.offlineMsgEmail = false;
539
553
  }
540
554
 
@@ -545,10 +559,13 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
545
559
  this.logger.log('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT RES', project)
546
560
  if (project) {
547
561
  const projectId = project.id_project
548
- this.canShowCanned = this.projectPlanUtils.checkPlanIsExpired(project)
562
+ this.projectUser = await this.projectUsersService.getProjectUserByProjectId(project._id)
549
563
  this.offlineMsgEmail = this.checkOfflineMsgEmailIsEnabled(project)
550
564
  this.isCopilotEnabled = this.projectPlanUtils.checkProjectProfileFeature(project, 'copilot');
551
565
  this.fileUploadAccept = this.checkAcceptedUploadFile(project)
566
+ this.rolesCanned = this.checkCannedResponsesRoles()
567
+ this.canShowCanned = this.checkCannedResponses(project)
568
+ this.logger.log('[CONVS-DETAIL] this.rolesCanned ', this.canShowCanned)
552
569
  }
553
570
  }, (error) => {
554
571
  this.logger.error('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT - ERROR ', error)
@@ -586,6 +603,40 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
586
603
  return this.appConfigProvider.getConfig().fileUploadAccept
587
604
  }
588
605
 
606
+ checkCannedResponses(project: Project): boolean {
607
+ let expires = this.projectPlanUtils.checkPlanIsExpired(project)
608
+ this.logger.log('[CONVS-DETAIL] checkCannedResponses expires ', expires)
609
+ if(expires){
610
+ return false
611
+ }
612
+
613
+ let hasRoleToShowCanned = this.rolesCanned[PERMISSIONS.CANNED_RESPONSES_READ]
614
+ this.logger.log('[CONVS-DETAIL] checkCannedResponses hasRoleToShowCanned ', hasRoleToShowCanned)
615
+ if(!hasRoleToShowCanned){
616
+ return false
617
+ }
618
+
619
+ return true
620
+ }
621
+
622
+ checkCannedResponsesRoles(): { [key: string]: boolean } {
623
+ const permissionKeys = [
624
+ 'CANNED_RESPONSES_CREATE',
625
+ 'CANNED_RESPONSES_READ',
626
+ 'CANNED_RESPONSES_UPDATE',
627
+ 'CANNED_RESPONSES_DELETE',
628
+ ] as const;
629
+
630
+ const roles: { [key: string]: boolean } = {};
631
+ for (const key of permissionKeys) {
632
+ const permission = PERMISSIONS[key];
633
+ roles[permission] = hasRole(this.projectUser, permission);
634
+ }
635
+
636
+ return roles;
637
+
638
+ }
639
+
589
640
  // getProjectIdSelectedConversation(conversationWith: string): string{
590
641
  // const conversationWith_segments = conversationWith.split('-')
591
642
  // // Removes the last element of the array if is = to the separator
@@ -669,6 +720,11 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
669
720
  "WHATSAPP.ERROR_WHATSAPP_NOT_INSTALLED",
670
721
  "WHATSAPP.ERROR_WHATSAPP_GENERIC_ERROR",
671
722
 
723
+ "TICKET.OPEN_TICKET",
724
+ "TICKET.DESCRIPTION",
725
+ "TICKET.CONFIRM",
726
+ "TICKET.CLOSE",
727
+
672
728
  "COPILOT.ASK_AI",
673
729
  "COPILOT.NO_SUGGESTIONS_PRESENT",
674
730
 
@@ -1884,6 +1940,11 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
1884
1940
  }
1885
1941
 
1886
1942
 
1943
+ onOpenTicket(event) {
1944
+ this.logger.debug('[CONVS-DETAIL] openTicketOnExternalService - conversationWith ', this.conversationWith)
1945
+ const detailOBJ = { event: 'onOpenTicketExternally', request_id: this.conversationWith, conversation: this.conversation }
1946
+ this.triggerEvents.triggerOnOpenTicketExternally(detailOBJ)
1947
+ }
1887
1948
  // -------------- START SCROLL/RESIZE -------------- //
1888
1949
  /** */
1889
1950
  resizeTextArea() {
@@ -1927,6 +1988,29 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
1927
1988
  }
1928
1989
  }
1929
1990
 
1991
+ /**
1992
+ * Scroll to last message without animation using requestAnimationFrame
1993
+ * This is a best practice alternative to setTimeout
1994
+ */
1995
+ private scrollToLastMessage() {
1996
+ this.showIonContent = true
1997
+ if (this.ionContentChatArea) {
1998
+ // Use requestAnimationFrame for better performance
1999
+ requestAnimationFrame(() => {
2000
+ requestAnimationFrame(() => {
2001
+ // Double RAF ensures DOM is fully rendered
2002
+ this.ionContentChatArea.scrollToBottom(0).then(() => {
2003
+ this.logger.log('[CONVS-DETAIL] scroll posizionato all\'ultimo messaggio')
2004
+ }).catch((error) => {
2005
+ this.logger.error('[CONVS-DETAIL] errore durante lo scroll:', error)
2006
+ })
2007
+ })
2008
+ })
2009
+ } else {
2010
+ this.logger.warn('[CONVS-DETAIL] ionContentChatArea non disponibile')
2011
+ }
2012
+ }
2013
+
1930
2014
  /**
1931
2015
  * detectBottom
1932
2016
  */
@@ -7,6 +7,8 @@
7
7
  [sound_btn]="sound_btn"
8
8
  [isMobile]="isMobile"
9
9
  [isVisibleTKT]="isVisibleTKT"
10
+ [isVisibleCNT]="isVisibleCNT"
11
+ [roles]="rolesHeader"
10
12
  (onSoundChange)="onSoundChange($event)"
11
13
  (openContactsDirectory)=openContactsDirectory($event)
12
14
  (openProfileInfo)=openProfileInfo($event)>