@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.
- package/CHANGELOG.md +167 -2
- package/angular.json +1 -0
- package/package.json +1 -1
- package/src/app/app.component.html +3 -1
- package/src/app/app.component.ts +72 -11
- package/src/app/chatlib/list-conversations-component/ion-list-conversations/ion-list-conversations.component.html +14 -2
- package/src/app/chatlib/list-conversations-component/ion-list-conversations/ion-list-conversations.component.scss +39 -2
- package/src/app/chatlib/list-conversations-component/list-conversations.module.ts +14 -0
- package/src/app/components/canned-response/canned-response.component.html +26 -23
- package/src/app/components/canned-response/canned-response.component.scss +0 -2
- package/src/app/components/canned-response/canned-response.component.ts +3 -1
- package/src/app/components/conversation-detail/message-text-area/message-text-area.component.html +24 -1
- package/src/app/components/conversation-detail/message-text-area/message-text-area.component.scss +30 -0
- package/src/app/components/conversation-detail/message-text-area/message-text-area.component.ts +29 -7
- package/src/app/components/conversation-info/info-content/info-content.component.ts +2 -2
- package/src/app/components/conversation-info/info-group/info-group.component.ts +23 -21
- package/src/app/components/conversations-list/header-conversations-list/header-conversations-list.component.html +1 -1
- package/src/app/components/conversations-list/header-conversations-list/header-conversations-list.component.ts +5 -1
- package/src/app/components/navbar/navbar.component.html +35 -9
- package/src/app/components/navbar/navbar.component.scss +71 -1
- package/src/app/components/navbar/navbar.component.ts +100 -42
- package/src/app/components/project-item/project-item.component.ts +79 -52
- package/src/app/components/sidebar/sidebar.component.html +65 -45
- package/src/app/components/sidebar/sidebar.component.ts +110 -117
- package/src/app/components/sidebar-user-details/sidebar-user-details.component.html +52 -11
- package/src/app/components/sidebar-user-details/sidebar-user-details.component.scss +304 -17
- package/src/app/components/sidebar-user-details/sidebar-user-details.component.ts +217 -27
- package/src/app/modals/create-ticket/create-ticket.page.ts +4 -2
- package/src/app/pages/conversation-detail/conversation-detail.page.html +7 -3
- package/src/app/pages/conversation-detail/conversation-detail.page.ts +89 -5
- package/src/app/pages/conversations-list/conversations-list.module.ts +3 -5
- package/src/app/pages/conversations-list/conversations-list.page.html +2 -0
- package/src/app/pages/conversations-list/conversations-list.page.ts +120 -26
- package/src/app/pages/unassigned-conversations/unassigned-conversations.module.ts +16 -4
- package/src/app/pages/unassigned-conversations/unassigned-conversations.page.html +43 -17
- package/src/app/pages/unassigned-conversations/unassigned-conversations.page.scss +25 -1
- package/src/app/pages/unassigned-conversations/unassigned-conversations.page.ts +279 -13
- package/src/app/pipe/filter.pipe.spec.ts +8 -0
- package/src/app/pipe/filter.pipe.ts +15 -0
- package/src/app/pipe/find.pipe.spec.ts +8 -0
- package/src/app/pipe/find.pipe.ts +15 -0
- package/src/app/services/global-settings/global-settings.service.ts +11 -3
- package/src/app/services/nav-proxy.service.ts +0 -1
- package/src/app/services/project_users/project-users.service.spec.ts +16 -0
- package/src/app/services/project_users/project-users.service.ts +63 -0
- package/src/app/services/projects/project.service.ts +2 -1
- package/src/app/services/tiledesk/tiledesk.service.ts +24 -0
- package/src/app/services/triggerEvents/triggerEvents.ts +40 -0
- package/src/app/services/websocket/websocket-js.ts +59 -534
- package/src/app/services/websocket/websocket-js_old.ts +578 -0
- package/src/app/services/websocket/websocket.service.ts +67 -14
- package/src/app/services/websocket/websocket.worker.ts +242 -0
- package/src/app/shared/shared.module.ts +26 -10
- package/src/app/utils/globals.ts +2 -0
- package/src/app/utils/permissions.constants.ts +138 -0
- package/src/app/utils/project-utils.ts +2 -2
- package/src/app/utils/utils.ts +18 -1
- package/src/assets/i18n/ar.json +11 -1
- package/src/assets/i18n/az.json +11 -1
- package/src/assets/i18n/de.json +11 -1
- package/src/assets/i18n/en.json +11 -1
- package/src/assets/i18n/es.json +11 -1
- package/src/assets/i18n/fr.json +11 -1
- package/src/assets/i18n/it.json +13 -3
- package/src/assets/i18n/kk.json +11 -1
- package/src/assets/i18n/pt.json +11 -1
- package/src/assets/i18n/ru.json +11 -1
- package/src/assets/i18n/sr.json +11 -1
- package/src/assets/i18n/sv.json +11 -1
- package/src/assets/i18n/tr.json +11 -1
- package/src/assets/i18n/uk.json +11 -1
- package/src/assets/i18n/uz.json +12 -1
- package/src/assets/js/agentDesktop-sdk.js +55 -0
- package/src/assets/js/chat21client.js +36 -0
- package/src/assets/js/mqtt-keepalive-worker.js +53 -0
- package/src/assets/test.html +5 -2
- package/src/chat-config-template.json +1 -0
- package/src/chat-config.json +1 -0
- package/src/chat21-core/models/projectUsers.ts +19 -0
- package/src/chat21-core/models/project_user.ts +2 -1
- package/src/chat21-core/models/projects.ts +1 -0
- package/src/chat21-core/providers/firebase/firebase-conversation-handler.ts +1 -1
- package/src/chat21-core/providers/mqtt/mqtt-conversation-handler.ts +1 -1
- package/src/chat21-core/providers/tiledesk/tiledesk-auth.service.ts +3 -0
- package/src/chat21-core/utils/constants.ts +6 -0
- package/src/chat21-core/utils/convertRequestToConversation.ts +2 -2
- package/src/chat21-core/utils/utils.ts +53 -3
- 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.
|
|
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.
|
|
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
|
-
[
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
if(
|
|
833
|
-
|
|
834
|
-
|
|
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-
|
|
2
|
-
<ion-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
</ion-
|
|
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
|
-
|
|
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>
|