@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
@@ -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,8 +83,12 @@ 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';
87
86
  import { UploadService } from 'src/chat21-core/providers/abstract/upload.service';
87
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service';
88
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
89
+ import { getOSCode, hasRole } from 'src/app/utils/utils';
90
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
91
+ import { TriggerEvents } from 'src/app/services/triggerEvents/triggerEvents';
88
92
 
89
93
  @Component({
90
94
  selector: 'app-conversation-detail',
@@ -109,6 +113,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
109
113
  private subscriptions: Array<any>
110
114
  public tenant: string;
111
115
  public loggedUser: UserModel
116
+ public projectUser: ProjectUser;
112
117
  public conversationWith: string
113
118
  public conversationWithFullname: string
114
119
  public messages: Array<MessageModel> = []
@@ -138,6 +143,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
138
143
  public tagsCannedFilter: Array<any> = [];
139
144
  public SHOW_CANNED_RESPONSES: boolean = false
140
145
  public canShowCanned: boolean = true
146
+ public rolesCanned: { [key: string]: boolean }
141
147
 
142
148
  public SHOW_COPILOT_SUGGESTIONS: boolean = false;
143
149
 
@@ -172,6 +178,10 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
172
178
  copilotQuestion: string = '';
173
179
  /**COPILOT : end */
174
180
 
181
+ /** TICKET: start */
182
+ isTicketEnabled: boolean = false;
183
+ /** TICKET: end */
184
+
175
185
  isMine = isMine
176
186
  isInfo = isInfo
177
187
  isFirstMessage = isFirstMessage
@@ -242,18 +252,20 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
242
252
  public toastController: ToastController,
243
253
  public tiledeskService: TiledeskService,
244
254
  public projectService: ProjectService,
255
+ public projectUsersService: ProjectUsersService,
245
256
  private networkService: NetworkService,
246
257
  private events: EventsService,
247
258
  private webSocketService: WebsocketService,
248
259
  public projectPlanUtils: ProjectPlanUtils,
260
+ public triggerEvents: TriggerEvents,
249
261
  private g: Globals,
250
262
  ) {
251
263
  // Change list on date change
252
264
  this.route.paramMap.subscribe((params) => {
253
265
  this.logger.log('[CONVS-DETAIL] - constructor -> params: ', params)
254
266
  this.conversationWith = params.get('IDConv')
255
- this.conversationWithFullname = params.get('FullNameConv')
256
- this.conv_type = params.get('Convtype')
267
+ this.conversationWithFullname = decodeURIComponent(params.get('FullNameConv'))
268
+ this.conv_type = decodeURIComponent(params.get('Convtype'))
257
269
 
258
270
  this.events.publish('supportconvid:haschanged', this.conversationWith)
259
271
  })
@@ -420,6 +432,8 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
420
432
  ionViewDidEnter() {
421
433
  this.logger.log('[CONVS-DETAIL] > ionViewDidEnter')
422
434
  // this.info_content_child_enabled = true;
435
+ // Scroll to bottom to show the last message without animation
436
+ this.scrollToLastMessage()
423
437
  }
424
438
 
425
439
  // Unsubscibe when new page transition end
@@ -479,6 +493,7 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
479
493
  this.messages = [] // list messages of conversation
480
494
  this.isFileSelected = false // indicates if a file has been selected (image to upload)
481
495
  this.isEmailEnabled = (this.appConfigProvider.getConfig().emailSection === 'true' || this.appConfigProvider.getConfig().emailSection === true) ? true : false;
496
+ this.isTicketEnabled = (this.appConfigProvider.getConfig().ticketSection === 'true' || this.appConfigProvider.getConfig().ticketSection === true) ? true : false;
482
497
  this.isWhatsappTemplatesEnabled = (this.appConfigProvider.getConfig().whatsappTemplatesSection === 'true' || this.appConfigProvider.getConfig().whatsappTemplatesSection === true) ? true : false;
483
498
  this.fileUploadAccept = this.appConfigProvider.getConfig().fileUploadAccept
484
499
 
@@ -538,7 +553,6 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
538
553
  this.logger.log('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT * COMPLETE *',)
539
554
  })
540
555
  }else {
541
- this.canShowCanned = false;
542
556
  this.offlineMsgEmail = false;
543
557
  }
544
558
 
@@ -549,10 +563,13 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
549
563
  this.logger.log('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT RES', project)
550
564
  if (project) {
551
565
  const projectId = project.id_project
552
- this.canShowCanned = this.projectPlanUtils.checkPlanIsExpired(project)
566
+ this.projectUser = await this.projectUsersService.getProjectUserByProjectId(project._id)
553
567
  this.offlineMsgEmail = this.checkOfflineMsgEmailIsEnabled(project)
554
568
  this.isCopilotEnabled = this.projectPlanUtils.checkProjectProfileFeature(project, 'copilot');
555
569
  this.fileUploadAccept = this.checkAcceptedUploadFile(project)
570
+ this.rolesCanned = this.checkCannedResponsesRoles()
571
+ this.canShowCanned = this.checkCannedResponses(project)
572
+ this.logger.log('[CONVS-DETAIL] this.rolesCanned ', this.canShowCanned)
556
573
  }
557
574
  }, (error) => {
558
575
  this.logger.error('[CONVS-DETAIL] - GET PROJECTID BY CONV RECIPIENT - ERROR ', error)
@@ -590,6 +607,40 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
590
607
  return this.appConfigProvider.getConfig().fileUploadAccept
591
608
  }
592
609
 
610
+ checkCannedResponses(project: Project): boolean {
611
+ let expires = this.projectPlanUtils.checkPlanIsExpired(project)
612
+ this.logger.log('[CONVS-DETAIL] checkCannedResponses expires ', expires)
613
+ if(expires){
614
+ return false
615
+ }
616
+
617
+ let hasRoleToShowCanned = this.rolesCanned[PERMISSIONS.CANNED_RESPONSES_READ]
618
+ this.logger.log('[CONVS-DETAIL] checkCannedResponses hasRoleToShowCanned ', hasRoleToShowCanned)
619
+ if(!hasRoleToShowCanned){
620
+ return false
621
+ }
622
+
623
+ return true
624
+ }
625
+
626
+ checkCannedResponsesRoles(): { [key: string]: boolean } {
627
+ const permissionKeys = [
628
+ 'CANNED_RESPONSES_CREATE',
629
+ 'CANNED_RESPONSES_READ',
630
+ 'CANNED_RESPONSES_UPDATE',
631
+ 'CANNED_RESPONSES_DELETE',
632
+ ] as const;
633
+
634
+ const roles: { [key: string]: boolean } = {};
635
+ for (const key of permissionKeys) {
636
+ const permission = PERMISSIONS[key];
637
+ roles[permission] = hasRole(this.projectUser, permission);
638
+ }
639
+
640
+ return roles;
641
+
642
+ }
643
+
593
644
  // getProjectIdSelectedConversation(conversationWith: string): string{
594
645
  // const conversationWith_segments = conversationWith.split('-')
595
646
  // // Removes the last element of the array if is = to the separator
@@ -673,6 +724,11 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
673
724
  "WHATSAPP.ERROR_WHATSAPP_NOT_INSTALLED",
674
725
  "WHATSAPP.ERROR_WHATSAPP_GENERIC_ERROR",
675
726
 
727
+ "TICKET.OPEN_TICKET",
728
+ "TICKET.DESCRIPTION",
729
+ "TICKET.CONFIRM",
730
+ "TICKET.CLOSE",
731
+
676
732
  "COPILOT.ASK_AI",
677
733
  "COPILOT.NO_SUGGESTIONS_PRESENT",
678
734
 
@@ -1888,6 +1944,11 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
1888
1944
  }
1889
1945
 
1890
1946
 
1947
+ onOpenTicket(event) {
1948
+ this.logger.debug('[CONVS-DETAIL] openTicketOnExternalService - conversationWith ', this.conversationWith)
1949
+ const detailOBJ = { event: 'onOpenTicketExternally', request_id: this.conversationWith, conversation: this.conversation }
1950
+ this.triggerEvents.triggerOnOpenTicketExternally(detailOBJ)
1951
+ }
1891
1952
  // -------------- START SCROLL/RESIZE -------------- //
1892
1953
  /** */
1893
1954
  resizeTextArea() {
@@ -1931,6 +1992,29 @@ export class ConversationDetailPage implements OnInit, OnDestroy, AfterViewInit
1931
1992
  }
1932
1993
  }
1933
1994
 
1995
+ /**
1996
+ * Scroll to last message without animation using requestAnimationFrame
1997
+ * This is a best practice alternative to setTimeout
1998
+ */
1999
+ private scrollToLastMessage() {
2000
+ this.showIonContent = true
2001
+ if (this.ionContentChatArea) {
2002
+ // Use requestAnimationFrame for better performance
2003
+ requestAnimationFrame(() => {
2004
+ requestAnimationFrame(() => {
2005
+ // Double RAF ensures DOM is fully rendered
2006
+ this.ionContentChatArea.scrollToBottom(0).then(() => {
2007
+ this.logger.log('[CONVS-DETAIL] scroll posizionato all\'ultimo messaggio')
2008
+ }).catch((error) => {
2009
+ this.logger.error('[CONVS-DETAIL] errore durante lo scroll:', error)
2010
+ })
2011
+ })
2012
+ })
2013
+ } else {
2014
+ this.logger.warn('[CONVS-DETAIL] ionContentChatArea non disponibile')
2015
+ }
2016
+ }
2017
+
1934
2018
  /**
1935
2019
  * detectBottom
1936
2020
  */
@@ -19,8 +19,7 @@ import { ProfileInfoPageModule } from '../profile-info/profile-info.module';
19
19
  // import { ConversationDetailPageModule } from '../conversation-detail/conversation-detail.module';
20
20
  import { SharedModule } from 'src/app/shared/shared.module';
21
21
  import { ScrollbarThemeModule } from '../../utils/scrollbar-theme.directive';
22
- import { ListConversationsComponent } from 'src/app/chatlib/list-conversations-component/list-conversations/list-conversations.component';
23
- import { IonListConversationsComponent } from 'src/app/chatlib/list-conversations-component/ion-list-conversations/ion-list-conversations.component';
22
+ import { ListConversationsModule } from 'src/app/chatlib/list-conversations-component/list-conversations.module';
24
23
  import { HeaderConversationsList } from 'src/app/components/conversations-list/header-conversations-list/header-conversations-list.component';
25
24
  import { HeaderConversationsListArchived } from 'src/app/components/conversations-list/header-conversations-list-archived/header-conversations-list-archived.component';
26
25
  import { HeaderConversationsListUnassigned } from 'src/app/components/conversations-list/header-conversations-list-unassigned/header-conversations-list-unassigned.component';
@@ -46,14 +45,13 @@ import { MomentModule } from 'ngx-moment';
46
45
  }
47
46
  }),
48
47
  SharedModule,
49
- MomentModule
48
+ MomentModule,
49
+ ListConversationsModule
50
50
  ],
51
51
  // entryComponents: [DdpHeaderComponent],
52
52
  declarations: [
53
53
  ConversationListPage,
54
54
  //******** COMPONENTS - init ********//
55
- ListConversationsComponent,
56
- IonListConversationsComponent,
57
55
  HeaderConversationsList,
58
56
  HeaderConversationsListArchived,
59
57
  HeaderConversationsListUnassigned,
@@ -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)>
@@ -53,7 +53,13 @@ import { Globals } from 'src/app/utils/globals';
53
53
  import { TriggerEvents } from 'src/app/services/triggerEvents/triggerEvents';
54
54
  import { MessageModel } from 'src/chat21-core/models/message';
55
55
  import { Project } from 'src/chat21-core/models/projects';
56
- import { getOSCode } from 'src/app/utils/utils';
56
+ import { getOSCode, hasRole } from 'src/app/utils/utils';
57
+ import { PERMISSIONS } from 'src/app/utils/permissions.constants';
58
+ import { ProjectUser } from 'src/chat21-core/models/projectUsers';
59
+ import { ProjectUsersService } from 'src/app/services/project_users/project-users.service';
60
+ import { ProjectService } from 'src/app/services/projects/project.service';
61
+
62
+ import { PROJECTS_STORAGE_KEY } from 'src/chat21-core/utils/constants';
57
63
 
58
64
  @Component({
59
65
  selector: 'app-conversations-list',
@@ -82,6 +88,7 @@ export class ConversationListPage implements OnInit {
82
88
  public archived_btn: boolean
83
89
  public sound_btn: string
84
90
  public isVisibleTKT: boolean = true;
91
+ public isVisibleCNT: boolean = true;;
85
92
  public convertMessage = convertMessage
86
93
  private isShowMenuPage = false
87
94
  private logger: LoggerService = LoggerInstance.getInstance()
@@ -104,6 +111,9 @@ export class ConversationListPage implements OnInit {
104
111
  public isMobile: boolean = false;
105
112
  public isInitialized: boolean = false;
106
113
 
114
+ public projectUser: ProjectUser;
115
+ public rolesHeader: { [key: string]: boolean }
116
+
107
117
  // PROJECT AVAILABILITY INFO: start
108
118
  project: Project
109
119
  profile_name_translated: string;
@@ -130,10 +140,14 @@ export class ConversationListPage implements OnInit {
130
140
  private translateService: CustomTranslateService,
131
141
  public tiledeskService: TiledeskService,
132
142
  public tiledeskAuthService: TiledeskAuthService,
143
+ public projectUsersService: ProjectUsersService,
144
+ public projectService: ProjectService,
133
145
  public appConfigProvider: AppConfigProvider,
134
146
  public platform: Platform,
135
147
  public wsService: WebsocketService,
136
148
  public g: Globals,
149
+ public appStorageService: AppStorageService,
150
+ private triggerEvents: TriggerEvents,
137
151
  ) {
138
152
  this.checkPlatform();
139
153
  this.translations();
@@ -207,8 +221,45 @@ export class ConversationListPage implements OnInit {
207
221
  // @ Lifehooks
208
222
  // -----------------------------------------------
209
223
  ngOnInit() {
210
- this.getAppConfigToHideDiplayBtns()
224
+ this.getAppConfigToHideDiplayBtns();
211
225
  this.getOSCODE();
226
+ this.loadAndStoreProjects();
227
+ }
228
+
229
+ /**
230
+ * Recupera tutti i progetti con getProjects e li salva in AppStorage.
231
+ * Prima di salvare verifica che la chiave non esista già e che non contenga già ogni singolo progetto.
232
+ */
233
+ private loadAndStoreProjects() {
234
+ const token = this.tiledeskAuthService.getTiledeskToken();
235
+ if (!token) return;
236
+ this.projectService.getProjects().subscribe((projects: Project[]) => {
237
+ if (!projects?.length) return;
238
+ let projectsMap: Record<string, Project> = {};
239
+ const stored = this.appStorageService.getItem(PROJECTS_STORAGE_KEY);
240
+ if (stored) {
241
+ try {
242
+ projectsMap = JSON.parse(stored) || {};
243
+ } catch (e) {
244
+ this.logger.warn('[CONVS-LIST-PAGE] loadAndStoreProjects - failed to parse stored projects', e);
245
+ }
246
+ }
247
+ let hasChanges = false;
248
+ projects.forEach((project) => {
249
+ const projectId = project.id_project?._id || project.id_project?.id;
250
+ if (!projectId) return;
251
+ if (!projectsMap[projectId]) {
252
+ projectsMap[projectId] = project.id_project;
253
+ hasChanges = true;
254
+ }
255
+ });
256
+ if (hasChanges) {
257
+ this.appStorageService.setItem(PROJECTS_STORAGE_KEY, JSON.stringify(projectsMap));
258
+ this.logger.log('[CONVS-LIST-PAGE] loadAndStoreProjects - saved', Object.keys(projectsMap).length, 'projects');
259
+ }
260
+ },
261
+ (err) => this.logger.error('[CONVS-LIST-PAGE] loadAndStoreProjects - error', err)
262
+ );
212
263
  }
213
264
 
214
265
  ngOnChanges() {
@@ -294,17 +345,19 @@ export class ConversationListPage implements OnInit {
294
345
  // ---------------------------------------------------------
295
346
  // Opens the Unassigned Conversations iframe
296
347
  // ---------------------------------------------------------
297
- openUnassignedConversations(IFRAME_URL: string, event) {
348
+ openUnassignedConversations(IFRAME_URL: string, event: { event: string; data: ConversationModel[] }) {
349
+ const unassignedConversations = event?.data ?? [];
350
+ const params = {
351
+ iframe_URL: IFRAME_URL,
352
+ callerBtn: event.event,
353
+ unassignedConversations,
354
+ stylesMap: this.stylesMap,
355
+ translationMapConversation: this.translationMapConversation,
356
+ };
298
357
  if (checkPlatformIsMobile()) {
299
- presentModal(this.modalController, UnassignedConversationsPage, {
300
- iframe_URL: IFRAME_URL,
301
- callerBtn: event,
302
- })
358
+ presentModal(this.modalController, UnassignedConversationsPage, params);
303
359
  } else {
304
- this.navService.push(UnassignedConversationsPage, {
305
- iframe_URL: IFRAME_URL,
306
- callerBtn: event,
307
- })
360
+ this.navService.push(UnassignedConversationsPage, params);
308
361
  }
309
362
  }
310
363
 
@@ -371,6 +424,11 @@ export class ConversationListPage implements OnInit {
371
424
  // save conversationHandler in chatManager
372
425
  this.chatManager.setConversationsHandler(this.conversationsHandlerService)
373
426
  this.showPlaceholder = false
427
+
428
+ // Hide loading spinner if there are no conversations
429
+ if (this.conversations.length === 0) {
430
+ this.loadingIsActive = false
431
+ }
374
432
  }
375
433
 
376
434
  // private manageStoredConversations() {
@@ -471,7 +529,7 @@ export class ConversationListPage implements OnInit {
471
529
  }
472
530
 
473
531
  listenToCurrentStoredProject() {
474
- this.events.subscribe('storage:last_project', projectObjct => {
532
+ this.events.subscribe('storage:last_project', async(projectObjct) => {
475
533
  if (projectObjct && projectObjct !== 'undefined') {
476
534
  this.logger.log('[CONVS-LIST-PAGE] - GET STORED PROJECT ', projectObjct)
477
535
 
@@ -496,6 +554,10 @@ export class ConversationListPage implements OnInit {
496
554
  } else if (this.project.profile.type === 'payment' && this.project.profile.name === 'enterprise') {
497
555
  this.profile_name_translated = this.translationMapHeader.get('PaydPlanNameEnterprise');
498
556
  }
557
+
558
+ this.projectUser = await this.projectUsersService.getProjectUserByProjectId(this.project._id)
559
+ this.rolesHeader = this.checkCannedResponsesRoles();
560
+ this.logger.log('[CONVS-LIST-PAGE] - GET PROJECT USER ROLES ', this.rolesHeader)
499
561
  }
500
562
  })
501
563
  }
@@ -631,8 +693,24 @@ export class ConversationListPage implements OnInit {
631
693
  const public_Key = this.appConfigProvider.getConfig().t2y12PruGU9wUtEGzBJfolMIgK
632
694
  this.logger.log('[CONVS-LIST-PAGE] AppConfigService getAppConfig public_Key', public_Key)
633
695
  this.isVisibleTKT = getOSCode("TKT", public_Key);
696
+ this.isVisibleCNT = getOSCode("CNT", public_Key);
634
697
  }
635
698
 
699
+ checkCannedResponsesRoles(): { [key: string]: boolean } {
700
+ const permissionKeys = [
701
+ 'LEADS_READ',
702
+ ] as const;
703
+
704
+ const roles: { [key: string]: boolean } = {};
705
+ for (const key of permissionKeys) {
706
+ const permission = PERMISSIONS[key];
707
+ roles[permission] = hasRole(this.projectUser, permission);
708
+ }
709
+
710
+ return roles;
711
+
712
+ }
713
+
636
714
  onBackButtonFN(event) {
637
715
  this.conversationType = 'active'
638
716
 
@@ -760,6 +838,7 @@ export class ConversationListPage implements OnInit {
760
838
  this.logger.log('[CONVS-LIST-PAGE] onConversationSelected active conversation.uid ', conversation.uid)
761
839
  this.events.publish('convList:onConversationSelected', conversation)
762
840
  }
841
+ this.triggerEvents.triggerOnConversationChanged(conversation)
763
842
  }
764
843
 
765
844
  onImageLoaded(conversation: any) {
@@ -827,22 +906,32 @@ export class ConversationListPage implements OnInit {
827
906
  }
828
907
  }
829
908
 
830
- if(conversation.attributes && conversation.attributes['projectId']){
831
- let project = localStorage.getItem(conversation.attributes['projectId'])
832
- if(project){
833
- project = JSON.parse(project)
834
- conversation.attributes.project_name = project['name']
835
- }
836
- }else if(conversation.attributes){
837
- const projectId = getProjectIdSelectedConversation(conversation.uid)
838
- let project = localStorage.getItem(projectId)
839
- if(project){
840
- project = JSON.parse(project)
841
- conversation.attributes.projectId = project['_id']
842
- conversation.attributes.project_name = project['name']
843
- }
909
+ const project = this.getProjectFromStorage(conversation);
910
+ if (project) {
911
+ if (!conversation.attributes) conversation.attributes = {};
912
+ conversation.attributes.projectId = project._id;
913
+ conversation.attributes.project_name = project.name;
844
914
  }
915
+ }
845
916
 
917
+ /** Recupera il progetto dalla chiave di storage (all_projects) */
918
+ private getProjectFromStorage(conversation: ConversationModel): Project | null {
919
+ let projectId: string | undefined;
920
+ if (conversation.attributes?.['projectId']) {
921
+ projectId = conversation.attributes['projectId'];
922
+ } else if (conversation.attributes) {
923
+ projectId = getProjectIdSelectedConversation(conversation.uid);
924
+ }
925
+ if (!projectId) return null;
926
+ const stored = this.appStorageService.getItem(PROJECTS_STORAGE_KEY);
927
+ this.logger.log('[CONVS-LIST-PAGE] getProjectFromStorage - stored', stored);
928
+ if (!stored) return null;
929
+ try {
930
+ const projectsMap: Record<string, Project> = JSON.parse(stored);
931
+ return projectsMap[projectId] || null;
932
+ } catch {
933
+ return null;
934
+ }
846
935
  }
847
936
 
848
937
  // isMarkdownLink(last_message_text) {
@@ -883,6 +972,10 @@ export class ConversationListPage implements OnInit {
883
972
 
884
973
  this.logger.log('[CONVS-LIST-PAGE] navigateByUrl this.uidConvSelected ', this.uidConvSelected)
885
974
 
975
+ const queryParams = this.route.snapshot.queryParams;
976
+ const queryString = new URLSearchParams(queryParams).toString();
977
+
978
+
886
979
  this.setUidConvSelected(uidConvSelected, converationType)
887
980
  if (checkPlatformIsMobile()) {
888
981
  this.logger.log('[CONVS-LIST-PAGE] checkPlatformIsMobile(): ', checkPlatformIsMobile())
@@ -900,6 +993,7 @@ export class ConversationListPage implements OnInit {
900
993
  if (this.conversationSelected && this.conversationSelected.conversation_with_fullname) {
901
994
  pageUrl = 'conversation-detail/' + this.uidConvSelected + '/' + encodeURIComponent(this.conversationSelected.conversation_with_fullname) + '/' + converationType
902
995
  }
996
+ pageUrl += queryString ? `?${queryString}` : '';
903
997
  this.logger.log('[CONVS-LIST-PAGE] setUidConvSelected navigateByUrl--->: ', pageUrl)
904
998
  // replace(/\(/g, '%28').replace(/\)/g, '%29') -> used for the encoder of any round brackets
905
999
  this.router.navigateByUrl(pageUrl.replace(/\(/g, '%28').replace(/\)/g, '%29'), {replaceUrl: true})
@@ -1,22 +1,34 @@
1
1
  import { NgModule } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { FormsModule } from '@angular/forms';
4
+ import { HttpClient } from '@angular/common/http';
4
5
 
5
6
  import { IonicModule } from '@ionic/angular';
7
+ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
8
+ import { createTranslateLoader } from 'src/chat21-core/utils/utils';
6
9
 
7
10
  import { UnassignedConversationsPageRoutingModule } from './unassigned-conversations-routing.module';
8
11
 
9
12
  import { UnassignedConversationsPage } from './unassigned-conversations.page';
13
+ import { ListConversationsModule } from 'src/app/chatlib/list-conversations-component/list-conversations.module';
14
+ import { MomentModule } from 'ngx-moment';
10
15
 
11
16
  @NgModule({
12
17
  imports: [
13
18
  CommonModule,
14
19
  FormsModule,
15
20
  IonicModule,
16
- UnassignedConversationsPageRoutingModule
21
+ UnassignedConversationsPageRoutingModule,
22
+ MomentModule,
23
+ ListConversationsModule,
24
+ TranslateModule.forChild({
25
+ loader: {
26
+ provide: TranslateLoader,
27
+ useFactory: createTranslateLoader,
28
+ deps: [HttpClient],
29
+ },
30
+ }),
17
31
  ],
18
- declarations: [
19
- UnassignedConversationsPage,
20
- ]
32
+ declarations: [UnassignedConversationsPage]
21
33
  })
22
34
  export class UnassignedConversationsPageModule {}
@@ -1,27 +1,53 @@
1
- <ion-header>
2
- <ion-toolbar [class.mobile]="isMobile">
3
- <ion-buttons slot="start">
4
- <ion-button ion-button fill="clear" (click)="onClose()">
5
- <ion-icon slot="icon-only" name="close"></ion-icon>
6
- </ion-button>
7
- </ion-buttons>
8
- <ion-title *ngIf="callerBtn !== 'pinbtn'" style="font-size: 16px;">
9
- {{translationMap?.get('UnassignedConversations') }}
10
- </ion-title>
11
- <ion-title *ngIf="callerBtn === 'pinbtn'" style="font-size: 16px;">
12
- {{translationMap?.get('PIN_A_PROJECT') }}
13
- </ion-title>
14
- </ion-toolbar>
15
- </ion-header>
1
+ <ion-toolbar [class.mobile]="isMobile">
2
+ <ion-title *ngIf="callerBtn !== 'pinbtn'" style="font-size: 16px;">
3
+ {{translationMap?.get('UnassignedConversations') }}
4
+ </ion-title>
5
+ <ion-title *ngIf="callerBtn === 'pinbtn'" style="font-size: 16px;">
6
+ {{translationMap?.get('PIN_A_PROJECT') }}
7
+ </ion-title>
8
+
9
+ <ion-buttons slot="end">
10
+ <ion-button ion-button fill="clear" (click)="onClose()">
11
+ <ion-icon slot="icon-only" name="close"></ion-icon>
12
+ </ion-button>
13
+ </ion-buttons>
14
+
15
+ </ion-toolbar>
16
16
 
17
17
  <ion-content overflow-scroll="true" id="iframe-ion-content"
18
18
  [ngClass]="{'ion-content-black-background' : isProjectsForPanel === true}">
19
- <!-- <iframe id="i_frame" style="width:100%; height:99%" frameBorder="0" allowfullscreen [src]="unassigned_convs_url_sanitized"></iframe> -->
20
- <div class="loader-spinner-wpr">
19
+ <div *ngIf="callerBtn === 'pinbtn'" class="loader-spinner-wpr">
21
20
  <div id="loader" class="loader">
22
21
  <svg class="circular" viewBox="25 25 50 50">
23
22
  <circle class="path" cx="50" cy="50" r="20" fill="none" stroke-width="2" stroke-miterlimit="10" />
24
23
  </svg>
25
24
  </div>
26
25
  </div>
26
+
27
+ <div *ngIf="callerBtn !== 'pinbtn'" class="list-avatar-page">
28
+ <ion-list>
29
+ <ng-container *ngIf="unassignedConversationsList.length > 0; else noConvs">
30
+ <ion-list-conversations
31
+ [listConversations]="unassignedConversationsList"
32
+ [stylesMap]="stylesMap"
33
+ [translationMap]="translationMapConversation"
34
+ [uidConvSelected]="uidConvSelected"
35
+ (onConversationSelected)="onConversationSelected($event)"
36
+ (onCloseConversation)="onCloseConversation($event)"
37
+ (onJoinConversation)="onJoinConversation($event)"
38
+ (onImageLoaded)="onImageLoaded($event)"
39
+ (onConversationLoaded)="onConversationLoaded($event)">
40
+ </ion-list-conversations>
41
+ </ng-container>
42
+ <ng-template #noConvs>
43
+ <div class="no-convs-container">
44
+ <ion-item id="no-convs" class="ion-text-center" lines="none">
45
+ <ion-label class="ion-text-wrap" color="medium">
46
+ {{ 'LABEL_MSG_PUSH_START_CHAT' | translate }}
47
+ </ion-label>
48
+ </ion-item>
49
+ </div>
50
+ </ng-template>
51
+ </ion-list>
52
+ </div>
27
53
  </ion-content>