@chat21/chat21-ionic 3.4.32-rc1 → 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.
@@ -1,4 +1,4 @@
1
- import { Component, Input, OnInit, SecurityContext } from '@angular/core';
1
+ import { Component, Input, OnChanges, OnInit, SecurityContext, SimpleChanges } from '@angular/core';
2
2
  import { AlertController, ModalController } from '@ionic/angular';
3
3
  import { Router } from '@angular/router';
4
4
  import { NavProxyService } from 'src/app/services/nav-proxy.service';
@@ -11,14 +11,18 @@ import { EventsService } from 'src/app/services/events-service';
11
11
  import { ConversationModel } from 'src/chat21-core/models/conversation';
12
12
  import { TiledeskAuthService } from 'src/chat21-core/providers/tiledesk/tiledesk-auth.service';
13
13
  import { TiledeskService } from 'src/app/services/tiledesk/tiledesk.service';
14
-
14
+ import { getProjectIdSelectedConversation, isGroup } from 'src/chat21-core/utils/utils';
15
+ import { ImageRepoService } from 'src/chat21-core/providers/abstract/image-repo.service';
16
+ import { Project } from 'src/chat21-core/models/projects';
17
+ import { ProjectService } from 'src/app/services/projects/project.service';
18
+ import { PROJECTS_STORAGE_KEY } from 'src/chat21-core/utils/constants';
15
19
 
16
20
  @Component({
17
21
  selector: 'app-unassigned-conversations',
18
22
  templateUrl: './unassigned-conversations.page.html',
19
23
  styleUrls: ['./unassigned-conversations.page.scss'],
20
24
  })
21
- export class UnassignedConversationsPage implements OnInit {
25
+ export class UnassignedConversationsPage implements OnInit, OnChanges {
22
26
 
23
27
  @Input() iframe_URL: any;
24
28
  @Input() callerBtn: string;
@@ -35,6 +39,7 @@ export class UnassignedConversationsPage implements OnInit {
35
39
  // @Input() unassigned_convs_url: any;
36
40
 
37
41
  iframe_url_sanitized: any;
42
+ private loggedUserUid: string;
38
43
  private logger: LoggerService = LoggerInstance.getInstance();
39
44
  // has_loaded: boolean;
40
45
  ion_content: any;
@@ -52,8 +57,15 @@ export class UnassignedConversationsPage implements OnInit {
52
57
  private translateService: CustomTranslateService,
53
58
  private events: EventsService,
54
59
  private tiledeskAuthService: TiledeskAuthService,
55
- private tiledeskService: TiledeskService
56
- ) { }
60
+ private tiledeskService: TiledeskService,
61
+ private projectService: ProjectService,
62
+ public imageRepoService: ImageRepoService,
63
+ public appStorageService: AppStorageService,
64
+ ) {
65
+ if (this.tiledeskAuthService.getCurrentUser()) {
66
+ this.loggedUserUid = this.tiledeskAuthService.getCurrentUser().uid;
67
+ }
68
+ }
57
69
 
58
70
  ngOnInit() {
59
71
  const keys = [
@@ -63,6 +75,7 @@ export class UnassignedConversationsPage implements OnInit {
63
75
  'LABEL_MSG_PUSH_START_CHAT'
64
76
  ];
65
77
  this.translationMap = this.translateService.translateLanguage(keys);
78
+
66
79
  this.unassignedConversationsList = this.unassignedConversations ?? [];
67
80
  if (!this.stylesMap) {
68
81
  this.stylesMap = new Map([['themeColor', '#165CEE']]);
@@ -71,16 +84,100 @@ export class UnassignedConversationsPage implements OnInit {
71
84
  this.translationMapConversation = this.translateService.translateLanguage(['CLOSED', 'Resolve']);
72
85
  }
73
86
  this.logger.log('[UNASSIGNED-CONVS-PAGE] unassignedConversationsList', this.unassignedConversationsList);
87
+ this.processConversationsForDisplay();
88
+ this.loadAndStoreProjects();
74
89
  // this.buildIFRAME();
75
90
  this.listenToPostMsg();
76
91
  this.hideHotjarFeedbackBtn();
77
92
  this.events.subscribe('style', (data)=>this.loadStyle(data))
78
93
  }
79
94
 
95
+ ngOnChanges(changes: SimpleChanges) {
96
+ if (changes['unassignedConversations'] && changes['unassignedConversations'].currentValue) {
97
+ this.unassignedConversationsList = this.unassignedConversations ?? [];
98
+ this.processConversationsForDisplay();
99
+ }
100
+ }
101
+
80
102
  ngOnDestroy(){
81
103
  this.logger.log('[UNASSIGNED-CONVS-PAGE] - onDestroy called', this.iframe_URL);
82
104
  }
83
105
 
106
+ /** Chiama onImageLoaded e onConversationLoaded per ogni conversazione in lista */
107
+ private processConversationsForDisplay() {
108
+ if (!this.unassignedConversationsList?.length) return;
109
+ this.unassignedConversationsList.forEach((conv) => {
110
+ this.onImageLoaded(conv);
111
+ this.onConversationLoaded(conv);
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Recupera tutti i progetti con getProjects e li salva in AppStorage.
117
+ * Se la chiave esiste già nello storage, salta la chiamata remota e usa i dati in cache.
118
+ * Al termine richiama processConversationsForDisplay per aggiornare i project_name.
119
+ */
120
+ private loadAndStoreProjects() {
121
+ const stored = this.appStorageService.getItem(PROJECTS_STORAGE_KEY);
122
+ if (stored) {
123
+ this.processConversationsForDisplay();
124
+ return;
125
+ }
126
+ const token = this.tiledeskAuthService.getTiledeskToken();
127
+ if (!token) return;
128
+ this.projectService.getProjects().subscribe(
129
+ (projects: Project[]) => {
130
+ if (!projects?.length) return;
131
+ let projectsMap: Record<string, Project> = {};
132
+ const stored = this.appStorageService.getItem(PROJECTS_STORAGE_KEY);
133
+ if (stored) {
134
+ try {
135
+ projectsMap = JSON.parse(stored) || {};
136
+ } catch (e) {
137
+ this.logger.warn('[UNASSIGNED-CONVS-PAGE] loadAndStoreProjects - failed to parse stored projects', e);
138
+ }
139
+ }
140
+ let hasChanges = false;
141
+ projects.forEach((project) => {
142
+ const projectId = project.id_project?._id || project.id_project?.id || project._id;
143
+ if (!projectId) return;
144
+ if (!projectsMap[projectId]) {
145
+ projectsMap[projectId] = project.id_project || project;
146
+ hasChanges = true;
147
+ }
148
+ });
149
+ if (hasChanges) {
150
+ this.appStorageService.setItem(PROJECTS_STORAGE_KEY, JSON.stringify(projectsMap));
151
+ this.logger.log('[UNASSIGNED-CONVS-PAGE] loadAndStoreProjects - saved', Object.keys(projectsMap).length, 'projects');
152
+ }
153
+ this.processConversationsForDisplay();
154
+ },
155
+ (err) => {
156
+ this.logger.error('[UNASSIGNED-CONVS-PAGE] loadAndStoreProjects - error', err);
157
+ this.processConversationsForDisplay();
158
+ }
159
+ );
160
+ }
161
+
162
+ /** Recupera il progetto dalla chiave di storage (all_projects); se non trovato restituisce null */
163
+ private getProjectFromStorage(conversation: ConversationModel): Project | null {
164
+ let projectId: string | undefined;
165
+ if (conversation.attributes?.['projectId']) {
166
+ projectId = conversation.attributes['projectId'];
167
+ } else if (conversation.attributes) {
168
+ projectId = getProjectIdSelectedConversation(conversation.uid);
169
+ }
170
+ if (!projectId) return null;
171
+ const stored = this.appStorageService.getItem(PROJECTS_STORAGE_KEY);
172
+ if (!stored) return null;
173
+ try {
174
+ const projectsMap: Record<string, Project> = JSON.parse(stored);
175
+ return projectsMap[projectId] || null;
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+
84
181
  hideHotjarFeedbackBtn() {
85
182
  const hotjarFeedbackBtn = <HTMLElement>document.querySelector("#_hj_feedback_container > div > button")
86
183
  if (hotjarFeedbackBtn) {
@@ -165,8 +262,7 @@ export class UnassignedConversationsPage implements OnInit {
165
262
  }, {
166
263
  text: 'Ok',
167
264
  handler: () => {
168
- let user = this.tiledeskAuthService.getCurrentUser();
169
- this.tiledeskService.addParticipant(request.uid, user.uid, request.attributes.projectId).subscribe((res: any) => {
265
+ this.tiledeskService.addParticipant(request.uid, this.loggedUserUid, request.attributes.projectId).subscribe((res: any) => {
170
266
  this.logger.log('[APP-COMP] addParticipant - RES ', res);
171
267
  this.onClose(request);
172
268
  }, (error) => {
@@ -280,12 +376,77 @@ export class UnassignedConversationsPage implements OnInit {
280
376
  this.presentAlertConfirmJoinRequest(conversation)
281
377
  }
282
378
 
283
- onImageLoaded(conversation: ConversationModel) {
284
- this.logger.log('[UNASSIGNED-CONVS-PAGE] onImageLoaded', conversation);
379
+ onImageLoaded(conversation: any) {
380
+ // this.logger.log('[CONVS-LIST-PAGE] onImageLoaded', conversation)
381
+ let conversation_with_fullname = conversation.sender_fullname
382
+ let conversation_with = conversation.sender
383
+ if (conversation.sender === this.loggedUserUid) {
384
+ conversation_with = conversation.recipient
385
+ conversation_with_fullname = conversation.recipient_fullname
386
+ } else if (isGroup(conversation)) {
387
+ // conversation_with_fullname = conv.sender_fullname;
388
+ // conv.last_message_text = conv.last_message_text;
389
+ conversation_with = conversation.recipient
390
+ conversation_with_fullname = conversation.recipient_fullname
391
+ }
392
+ if (!conversation_with.startsWith('support-group')) {
393
+ conversation.image = this.imageRepoService.getImagePhotoUrl(conversation_with)
394
+ }
285
395
  }
286
396
 
287
397
  onConversationLoaded(conversation: ConversationModel) {
288
- this.logger.log('[UNASSIGNED-CONVS-PAGE] onConversationLoaded', conversation);
398
+ this.logger.log('[CONVS-LIST-PAGE] onConversationLoaded ', conversation)
399
+ // this.logger.log('[CONVS-LIST-PAGE] onConversationLoaded is new? ', conversation.is_new)
400
+ // if (conversation.is_new === false) {
401
+ // this.ionContentConvList.scrollToTop(0);
402
+ // }
403
+
404
+ const keys = ['YOU', 'SENT_AN_IMAGE', 'SENT_AN_ATTACHMENT']
405
+ const translationMap = this.translateService.translateLanguage(keys)
406
+ // Fixes the bug: if a snippet of code is pasted and sent it is not displayed correctly in the convesations list
407
+
408
+ var regex = /<br\s*[\/]?>/gi
409
+ if (conversation ) { //&& conversation.last_message_text
410
+ conversation.last_message_text = conversation.last_message_text.replace(regex, '',)
411
+
412
+ //FIX-BUG: 'YOU: YOU: YOU: text' on last-message-text in conversation-list
413
+ if (conversation.sender === this.loggedUserUid && !conversation.last_message_text.includes(': ')) {
414
+ // this.logger.log('[CONVS-LIST-PAGE] onConversationLoaded', conversation)
415
+
416
+ if (conversation.type !== 'image' && conversation.type !== 'file') {
417
+ conversation.last_message_text = translationMap.get('YOU') + ': ' + conversation.last_message_text
418
+ } else if (conversation.type === 'image') {
419
+ // this.logger.log('[CONVS-LIST-PAGE] HAS SENT AN IMAGE');
420
+ // this.logger.log("[CONVS-LIST-PAGE] translationMap.get('YOU')")
421
+ const SENT_AN_IMAGE = (conversation['last_message_text'] = translationMap.get('SENT_AN_IMAGE'))
422
+
423
+ conversation.last_message_text = translationMap.get('YOU') + ': ' + SENT_AN_IMAGE
424
+ } else if (conversation.type === 'file') {
425
+ // this.logger.log('[CONVS-LIST-PAGE] HAS SENT FILE')
426
+ const SENT_AN_ATTACHMENT = (conversation['last_message_text'] = translationMap.get('SENT_AN_ATTACHMENT'))
427
+ conversation.last_message_text = translationMap.get('YOU') + ': ' + SENT_AN_ATTACHMENT
428
+ }
429
+ } else {
430
+ if (conversation.type === 'image') {
431
+ // this.logger.log('[CONVS-LIST-PAGE] HAS SENT AN IMAGE');
432
+ // this.logger.log("[CONVS-LIST-PAGE] translationMap.get('YOU')")
433
+ const SENT_AN_IMAGE = (conversation['last_message_text'] = translationMap.get('SENT_AN_IMAGE'))
434
+
435
+ conversation.last_message_text = SENT_AN_IMAGE
436
+ } else if (conversation.type === 'file') {
437
+ // this.logger.log('[CONVS-LIST-PAGE] HAS SENT FILE')
438
+ const SENT_AN_ATTACHMENT = (conversation['last_message_text'] = translationMap.get('SENT_AN_ATTACHMENT'))
439
+ conversation.last_message_text = SENT_AN_ATTACHMENT
440
+ }
441
+ }
442
+ }
443
+
444
+ const project = this.getProjectFromStorage(conversation);
445
+ if (project) {
446
+ if (!conversation.attributes) conversation.attributes = {};
447
+ conversation.attributes.projectId = project._id;
448
+ conversation.attributes.project_name = project.name;
449
+ }
289
450
  }
290
451
 
291
452
  async onClose(conversation?: ConversationModel) {
@@ -0,0 +1,8 @@
1
+ import { FilterPipe } from './filter.pipe';
2
+
3
+ describe('FilterPipe', () => {
4
+ it('create an instance', () => {
5
+ const pipe = new FilterPipe();
6
+ expect(pipe).toBeTruthy();
7
+ });
8
+ });
@@ -0,0 +1,15 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+
3
+ @Pipe({
4
+ name: 'filter'
5
+ })
6
+ export class FilterPipe implements PipeTransform {
7
+
8
+ transform(items: any[], filter: Object): any {
9
+ if (!items || !filter) {
10
+ return items;
11
+ }
12
+ return items.filter(item => item[filter['key']] === filter['value']);
13
+ }
14
+
15
+ }
@@ -0,0 +1,8 @@
1
+ import { FindPipe } from './find.pipe';
2
+
3
+ describe('FindPipe', () => {
4
+ it('create an instance', () => {
5
+ const pipe = new FindPipe();
6
+ expect(pipe).toBeTruthy();
7
+ });
8
+ });
@@ -0,0 +1,15 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+
3
+ @Pipe({
4
+ name: 'find'
5
+ })
6
+ export class FindPipe implements PipeTransform {
7
+
8
+ transform(items: any[], filter: Object): any {
9
+ if (!items || !filter) {
10
+ return items;
11
+ }
12
+ return items.find(item => item[filter['key']] === filter['value']);
13
+ }
14
+
15
+ }
@@ -7,6 +7,8 @@ import { map } from 'rxjs/operators';
7
7
  import { LoggerService } from 'src/chat21-core/providers/abstract/logger.service';
8
8
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
9
9
  import { AppStorageService } from 'src/chat21-core/providers/abstract/app-storage.service';
10
+ import { Project } from 'src/chat21-core/models/projects';
11
+ import { Observable } from 'rxjs';
10
12
 
11
13
 
12
14
  @Injectable({
@@ -93,6 +95,23 @@ export class TiledeskService {
93
95
  }))
94
96
  }
95
97
 
98
+
99
+ public getProjectUsersByProjectId(project_id: string) {
100
+ const url = this.SERVER_BASE_URL + project_id + '/project_users/';
101
+ this.logger.log('[TILEDESK-SERVICE] - GET PROJECT-USER URL', url);
102
+
103
+ const httpOptions = {
104
+ headers: new HttpHeaders({
105
+ 'Content-Type': 'application/json',
106
+ Authorization: this.tiledeskToken
107
+ })
108
+ };
109
+ return this.http.get(url, httpOptions).pipe(map((res: any) => {
110
+ this.logger.log('[TILEDESK-SERVICE] - GET PROJECT-USER RES ', res);
111
+ return res
112
+ }))
113
+ }
114
+
96
115
  public getAllLeadsActiveWithLimit(project_id: string, limit: number) {
97
116
  const url = this.SERVER_BASE_URL + project_id + '/leads?limit=' + limit + '&with_fullname=true';
98
117
  this.logger.log('[TILEDESK-SERVICE] - GET ALL ACTIVE LEADS (LIMIT 10000) - URL', url);
@@ -131,4 +131,16 @@ export class TriggerEvents {
131
131
  }
132
132
 
133
133
 
134
+ public triggerOnConversationChanged(conversation: ConversationModel) {
135
+ this.logger.debug(' ---------------- triggerOnConversationChanged ---------------- ', conversation);
136
+ try {
137
+ const windowContext = this.windowContext;
138
+ if (windowContext) {
139
+ windowContext.postMessage({ type: 'onConversationChanged', data: conversation }, '*');
140
+ }
141
+ } catch (e) {
142
+ this.logger.error('[TRIGGER-HANDLER] > Error triggerOnConversationChanged:' + e);
143
+ }
144
+ }
145
+
134
146
  }
@@ -94,16 +94,20 @@ export class WebsocketService {
94
94
  * Sottoscrive alle conversations di un singolo progetto.
95
95
  * Mantenuto per retrocompatibilità. Preferire subscriptionToWsConversationsForOnlineProjects
96
96
  * per sottoscrivere ai progetti online con status Available.
97
+ * @param project_id ID del progetto
98
+ * @param skipClear Se true, non svuota wsRequestsList (usato quando si sottoscrivono più progetti in sequenza)
97
99
  */
98
- subscriptionToWsConversations(project_id) {
100
+ subscriptionToWsConversations(project_id, skipClear = false) {
99
101
  // console.log("[WS-SERV] - CALLED SUBSC TO WS CONVS - PROJECT ID ", project_id);
100
102
  var self = this;
101
- this.wsRequestsList = [];
103
+ if (!skipClear) {
104
+ this.wsRequestsList = [];
105
+ }
102
106
 
103
- this.webSocketJs.ref('/' + project_id + '/requests', 'getCurrentProjectAndSubscribeTo_WsRequests',
107
+ this.webSocketJs.ref('/' + project_id + '/requests', 'getCurrentProjectAndSubscribeTo_WsRequests_' + project_id,
104
108
 
105
109
  function (data) {
106
- // console.log("[WS-SERV] - CONVS - CREATE DATA ", data);
110
+ // console.log("[WS-SERV] - CONVS - CREATE DATA for project ", project_id, data);
107
111
  if (data) {
108
112
  // ------------------------------------------------
109
113
  // @ Agents - pass in data agents get from snapshot
@@ -326,7 +330,7 @@ export class WebsocketService {
326
330
  this.subscribedConversationProjectIds
327
331
  );
328
332
  this.subscribedConversationProjectIds.forEach((projectId) =>
329
- this.subscriptionToWsConversations(projectId)
333
+ this.subscriptionToWsConversations(projectId, true)
330
334
  );
331
335
  return this.subscribedConversationProjectIds;
332
336
  }
@@ -25,6 +25,9 @@ import { SafeHtmlPipe } from '../directives/safe-html.pipe';
25
25
  import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
26
26
  import { createTranslateLoader } from 'src/chat21-core/utils/utils';
27
27
  import { HttpClient } from '@angular/common/http';
28
+ import { RouterModule } from '@angular/router';
29
+ import { FindPipe } from '../pipe/find.pipe';
30
+ import { FilterPipe } from '../pipe/filter.pipe';
28
31
 
29
32
  @NgModule({
30
33
  declarations: [
@@ -67,12 +70,13 @@ import { HttpClient } from '@angular/common/http';
67
70
  SidebarUserDetailsComponent,
68
71
 
69
72
  //DIRECTIVES
70
- AutofocusDirective,
71
- TooltipDirective,
72
- MarkedPipe,
73
- HtmlEntitiesEncodePipe,
74
- SafeHtmlPipe,
75
-
73
+ AutofocusDirective,
74
+ TooltipDirective,
75
+ MarkedPipe,
76
+ HtmlEntitiesEncodePipe,
77
+ SafeHtmlPipe,
78
+ FindPipe,
79
+ FilterPipe,
76
80
 
77
81
 
78
82
  AvatarProfileComponent,
@@ -123,6 +127,10 @@ import { HttpClient } from '@angular/common/http';
123
127
  MarkedPipe,
124
128
  HtmlEntitiesEncodePipe,
125
129
  SafeHtmlPipe,
130
+ FindPipe,
131
+ FilterPipe,
132
+
133
+ RouterModule,
126
134
 
127
135
  //COMMON COMPONENTS
128
136
  AvatarProfileComponent,
@@ -140,16 +148,15 @@ import { HttpClient } from '@angular/common/http';
140
148
  MomentModule,
141
149
  NgSelectModule,
142
150
  FormsModule,
143
-
144
151
  TranslateModule.forChild({
145
- loader: {
146
- provide: TranslateLoader,
147
- useFactory: (createTranslateLoader),
148
- deps: [HttpClient]
149
- }
150
- })
151
-
152
- ],
152
+ loader: {
153
+ provide: TranslateLoader,
154
+ useFactory: (createTranslateLoader),
155
+ deps: [HttpClient]
156
+ }
157
+ }),
158
+ RouterModule.forChild([])
159
+ ],
153
160
  schemas: [
154
161
  CUSTOM_ELEMENTS_SCHEMA,
155
162
  NO_ERRORS_SCHEMA
@@ -20,6 +20,7 @@ export interface Project {
20
20
  isActiveSubscription?: boolean;
21
21
  profile?: any;
22
22
  offlineMsgEmail?: boolean;
23
+ teammateStatus?: any;
23
24
  // subscription_end_date?: any;
24
25
  // subscription_id?: any;
25
26
  // subscription_creation_date?: any;
@@ -116,6 +116,7 @@ export const PLATFORM_DESKTOP = 'desktop';
116
116
 
117
117
  // STORAGE
118
118
  export const STORAGE_PREFIX = 'tiledesk_widget_';
119
+ export const PROJECTS_STORAGE_KEY = 'all_projects';
119
120
 
120
121
  // links
121
122
  export const FIREBASESTORAGE_BASE_URL_IMAGE = 'https://firebasestorage.googleapis.com/v0/b/' //+ 'chat-v2-dev.appspot.com/o/';
@@ -152,6 +153,6 @@ export const TEAMMATE_STATUS = [
152
153
  { id: 1, name: 'Available', avatar: 'assets/img/teammate-status/avaible.svg', label: "LABEL_AVAILABLE" },
153
154
  { id: 2, name: 'Unavailable', avatar: 'assets/img/teammate-status/unavaible.svg', label: "LABEL_NOT_AVAILABLE" },
154
155
  { id: 3, name: 'Inactive', avatar: 'assets/img/teammate-status/inactive.svg', label: "LABEL_INACTIVE" },
155
- ];
156
+ ];
156
157
 
157
158
 
@@ -30,7 +30,7 @@ export class ConvertRequestToConversation {
30
30
  '',
31
31
  request.lead && request.lead.fullname ? request.lead.fullname: null,
32
32
  request.status || '0',
33
- moment(request.createdAt).unix(),
33
+ moment(request.createdAt).valueOf(),
34
34
  getColorBck(request.lead.fullname),
35
35
  avatarPlaceholder(request.lead.fullname),
36
36
  false,