import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { SpinnerService } from '@core/services/spinner.service'; import { ApplicationAttachmentService } from '@features/application-view/application-attachments/application-attachments.service'; import { CommunicationFileUpload } from '@features/application-view/application-attachments/application-attachments.typing'; import { CommunicationsService } from '@features/communications/communications.service'; import { EmailService } from '@features/system-emails/email.service'; import { EmailNotificationType, EmailStatus } from '@features/system-emails/email.typing'; import { UserService } from '@features/users/user.service'; import { ArrayHelpersService, Panel, PanelSection } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ConfirmationModalComponent, ModalFactory } from '@yourcause/common/modals'; import { isEmpty, isEqual, xorWith } from 'lodash'; import { AddEditCommunicationComponent } from '../add-edit-communication-modal/add-edit-communication-modal.component'; import { Communication, CommunicationTypes, CommunicationVisibility } from '../communications.typing'; @Component({ selector: 'yc-communications-view', templateUrl: 'communications-view.component.html', styleUrls: ['./communications-view.component.scss'] }) export class CommunicationsViewComponent implements OnInit { private _allComms: Communication[][]; @Input() set allComms (value: Communication[][]) { this._allComms = value || []; if (this.initialized) { this.setPanels(); } } get allComms (): Communication[][] { return this._allComms; } @Input() isForNonprofit: boolean; @Input() currentCom: Communication[]; @Input() crudAvailable = true; @Input() isNomination = false; @Input() applicationId: number; @Input() isModalView = false; @Output() onRemove = new EventEmitter(); @Output() onUpdate = new EventEmitter(); @Output() onAdd = new EventEmitter(); @Output() onSendInvite = new EventEmitter(); @Output() onModalOpenOrClose = new EventEmitter(); @ViewChild('subjectEl') subjectEl: ElementRef; initialized = false; panelSection: PanelSection[] = [{ panels: [], name: '' }]; currentPanel: Panel; noPanelsMessage = ''; noCommsForNonprofit = this.i18n.translate( 'nonprofit:textNoCommsForNpo', {}, 'There are no communications for this nonprofit' ); noCommsForApplication = this.i18n.translate( 'nonprofit:textNoCommsForApp', {}, 'There are no communications for this application' ); showNoPanelsMessage = false; CommTypes = CommunicationTypes; CommunicationTypes: CommunicationTypes; constructor ( private modalFactory: ModalFactory, private communicationsService: CommunicationsService, private i18n: I18nService, private userService: UserService, private emailService: EmailService, private spinnerService: SpinnerService, private applicationAttachmentService: ApplicationAttachmentService, private arrayHelper: ArrayHelpersService, private activatedRoute: ActivatedRoute, private router: Router ) { } get commRecordId () { return this.activatedRoute.snapshot.params.commRecordId; } get emails () { return this.emailService.emails; } get inviteEmailActive () { return this.emailService.isEmailActive( EmailNotificationType.NomineeApplicantInvitation ); } ngOnInit () { this.initialized = true; this.noPanelsMessage = this.isForNonprofit ? this.noCommsForNonprofit : this.noCommsForApplication; this.setPanels(); } async setPanels () { this.panelSection = [{ name: this.i18n.translate('GLOBAL:textCommunications'), noPanelsMessage: this.noPanelsMessage, panels: this.allComms.map((comms) => { let descriptionTooltip = null; const comm = comms[0]; const numberOfComms = comms.length > 1 ? ` (${comms.length})` : ''; const date = comm.createdBy || comm.updatedBy; if (date) { descriptionTooltip = date.impersonatedBy ? date.impersonatedBy : date.impersonatedBy; } return (>{ name: comm.subject + numberOfComms, context: comms, description: this.communicationsService.getDescription(comm), descriptionTooltip, icon: ( comm.emailStatusType === EmailStatus.OnHold && this.inviteEmailActive ) ? 'paper-plane' : '', iconClass: ( comm.emailStatusType === EmailStatus.OnHold && this.inviteEmailActive ) ? 'link' : '', iconActions: this.getActions(comms) as any }); }) }]; const commsFromRoute = this.getActiveCommFromRoute(this.allComms[0]); await this.setActivePanel(commsFromRoute); } getActiveCommFromRoute (mostRecentComms: Communication[]) { let comms = mostRecentComms; if (this.commRecordId) { comms = this.allComms.find((commArray: Communication[]) => { return commArray.some((commRecord: Communication) => { return +commRecord.communicationId === +this.commRecordId; }); }); } return comms; } async downloadFile ( url: string, fileName: string ) { this.spinnerService.startSpinner(); await this.applicationAttachmentService.downloadUrlAs(url, fileName); this.spinnerService.stopSpinner(); } async setActivePanel (comms: Communication[]) { if (comms) { try { comms = await Promise.all(comms.map(async (comm) => { this.showNoPanelsMessage = false; if (comm && !comm.content) { comm = await this.communicationsService.getCommunicationDetail( comm, this.isForNonprofit ); } return comm; })); } catch (e) { console.error(e); } } this.currentCom = this.arrayHelper.sort(comms || [], 'communicationId', true); this.currentPanel = this.panelSection[0].panels .find((panel) => { // compares two arrays of objects and returns array of differences return isEmpty(xorWith(panel.context, comms, isEqual)); }); if (this.subjectEl) { (this.subjectEl.nativeElement).scrollIntoView({ behavior: 'smooth' }); } if (this.currentCom.length && !this.isModalView) { if (+this.commRecordId !== +this.currentCom[0].communicationId) { this.router.navigate([`../${+this.currentCom[0].communicationId}`], { relativeTo: this.activatedRoute }); } } } onEmptyPanelClick () { this.showNoPanelsMessage = true; } getActions (communications: Communication[]): { label: string; action: (comm: Communication) => any; icon: string; iconClass: string; }[] { const communication = communications[0]; switch (communication.type) { case CommunicationTypes.SYSTEM_EMAIL: { return ( communication.emailStatusType === EmailStatus.OnHold && this.inviteEmailActive ) ? [{ label: this.i18n.translate( 'GLOBAL:textSendEmail', {}, 'Send email' ), action: () => { this.sendYouHaveBeenNominatedEmail(communication); }, icon: 'paper-plane', iconClass: 'link' }] : []; } case CommunicationTypes.WORKFLOW_COMMENT: { return []; } case CommunicationTypes.EXTERNAL_EMAIL: { return [{ label: this.i18n.translate( 'common:textUpdateViewPermissions', {}, 'Update view permissions' ), action: () => { this.onEditClick(communication, true); }, icon: 'eye', iconClass: 'link' }, { label: this.i18n.translate( 'nonprofit:btnRemoveCommunication', {}, 'Remove communication' ), action: () => { this.onRemoveClick(communication); }, icon: 'times', iconClass: 'danger' }]; } default: { const createdByMe = communication.createdBy.email === this.userService.userEmail; const editAvailable = this.crudAvailable && communication.isManual && ((communication.visibility !== CommunicationVisibility.ONLY_ME) || createdByMe); const removeAvailable = this.crudAvailable && communication.isManual && createdByMe; return [editAvailable ? { label: this.i18n.translate( 'nonprofit:btnEditCommunication', {}, 'Edit communication' ), action: () => { this.onEditClick(communication, false); }, icon: 'pencil', iconClass: 'link' } : undefined, removeAvailable ? { label: this.i18n.translate( 'nonprofit:btnRemoveCommunication', {}, 'Remove communication' ), action: () => { this.onRemoveClick(communication); }, icon: 'times', iconClass: 'danger' } : undefined].filter((item) => !!item); } } } async openFile ( file: CommunicationFileUpload ) { this.spinnerService.startSpinner(); await this.applicationAttachmentService.downloadCommunicationFile({ file, joinCommunicationId: this.currentCom[0]?.joinCommunicationId, isForNonprofit: this.isForNonprofit, applicationId: this.applicationId }); this.spinnerService.stopSpinner(); } async sendYouHaveBeenNominatedEmail (comm: Communication) { this.onModalOpenOrClose.emit(true); const response = await this.modalFactory.open( ConfirmationModalComponent, { confirmText: this.i18n.translate( 'GLOBAL:textAreSureYouWantToSendThisEmail', {}, 'Are you sure you want to send this email?' ), modalHeader: `${this.i18n.translate( 'GLOBAL:textSendEmail', {}, 'Send Email' )} - ${comm.subject}`, confirmButtonText: this.i18n.translate( 'GLOBAL:textSend', {}, 'Send' ) } ); this.onModalOpenOrClose.emit(false); if (response) { this.onSendInvite.emit(comm); } } async onRemoveClick (comm: Communication) { const response = await this.modalFactory.open( ConfirmationModalComponent, { confirmText: this.i18n.translate( 'nonprofits:textAreSureYouWantToRemoveThisCommunication', {}, 'Are you sure you want to remove this communication? This action cannot be undone.' ), modalHeader: this.i18n.translate( 'nonprofits:textRemoveCommunication', {}, 'Remove Communication' ), confirmButtonText: this.i18n.translate( 'common:textRemove', {}, 'Remove' ) } ); if (response) { this.onRemove.emit(comm); } } async onEditClick (comm: Communication, updateViewPermissionsOnly: boolean) { comm = await this.communicationsService.getCommunicationDetail(comm, this.isForNonprofit); const response = await this.modalFactory.open( AddEditCommunicationComponent, { isForNonprofit: this.isForNonprofit, communication: comm, isNomination: this.isNomination, applicationId: this.applicationId, updateViewPermissionsOnly } ); if (response) { this.onUpdate.emit(response); } } async onAddClick () { const response = await this.modalFactory.open( AddEditCommunicationComponent, { isForNonprofit: this.isForNonprofit, isNomination: this.isNomination, applicationId: this.applicationId } ); if (response) { this.onAdd.emit(response); } } }