import { Component, Input, OnInit } from '@angular/core'; import { AbstractControl, Validators } from '@angular/forms'; import { PolicyService } from '@core/services/policy.service'; import { SpinnerService } from '@core/services/spinner.service'; import { AdHocReportingAPI } from '@core/typings/api/ad-hoc-reporting.typing'; import { AdHocReportingUI } from '@core/typings/ui/ad-hoc-reporting.typing'; import { AudienceService } from '@features/audience/audience.service'; import { AudienceDetail, AudienceMember } from '@features/audience/audience.typing'; import { AllAudiencesResolver } from '@features/audience/resolvers/all-audiences.resolver'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { AdHocReportingService } from '@features/reporting/services/ad-hoc-reporting.service'; import { BasicSFTPCredentialsResolver } from '@features/sftp/resolvers/basic-sftp-credentials.resolver'; import { SFTPService } from '@features/sftp/sftp.service'; import { DateService, DependentDateGroup, Frequency, SiblingValueValidator, TableDataDownloadFormat, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup, TypeToken } from '@yourcause/common'; import { AnalyticsService, EventType } from '@yourcause/common/analytics'; import { I18nService } from '@yourcause/common/i18n'; import { YCModalComponent } from '@yourcause/common/modals'; interface SendReportGroup { sftpId: number; audienceId: number; showMaskedData: boolean; exportFileTypeId: TableDataDownloadFormat; expirationHours: number; frequency?: Frequency; hours?: number; week?: number; month?: number; dayOfMonth?: number; dayOfWeek?: number; } @Component({ selector: 'gc-send-report-modal', templateUrl: './send-report-modal.component.html', styleUrls: ['./send-report-modal.component.scss'] }) export class SendReportModalComponent extends YCModalComponent< AdHocReportingAPI.SendReportPayload|AdHocReportingAPI.ScheduleReportPayload > implements OnInit { @Input() report: AdHocReportingAPI.UserSavedReport; @Input() isSchedule: boolean; page: 'connection'|'setup' = 'connection'; reportDetail: AdHocReportingAPI.UserSavedReportDetail; sftpOptions: TypeaheadSelectOption[] = []; audienceOptions: TypeaheadSelectOption[] = []; clientName = this.clientSettingsService.clientBranding.name; formGroup: TypeSafeFormGroup; connectionFormGroup: TypeSafeFormGroup<{ accessType: string; }>; selectedUsers: AudienceMember[]; timezoneText = this.clientSettingsService.clientSettings.defaultTimezone || 'UTC'; isAM = true; addingExternalUser = false; selectedAudience: AudienceDetail; stickyUsers: string[] = []; // emails of those who can't be removed based on selected audience topLevelFilters = this.audienceService.getTopLevelFiltersForUserAudienceTable(); primaryButtonText: string; secondaryButtonText: string; canSeeMaskedData = this.policyService.grantApplication.canSeeMaskedApplicants(); sendText = this.i18n.translate( 'GLOBAL:textSend', {}, 'Send' ); saveText = this.i18n.translate( 'common:btnSave', {}, 'Save' ); backText = this.i18n.translate( 'common:btnBack', {}, 'Back' ); nextText = this.i18n.translate( 'common:btnNext', {}, 'Next' ); dataFeedText = this.i18n.translate( 'common:lblDataFeed', {}, 'Data feed' ); fileInEmailText = this.i18n.translate( 'common:textDownloadableFileInEmail', {}, 'Downloadable file in email' ); accessOptionVals = { dataFeed: 'dataFeed', email: 'fileInEmail' }; accessOptions: TypeaheadSelectOption[] = [{ label: this.dataFeedText, value: this.accessOptionVals.dataFeed }, { label: this.fileInEmailText, value: this.accessOptionVals.email }]; fileTypeOptions: TypeaheadSelectOption[] = [{ label: this.i18n.translate( 'common:textCSV', {}, 'CSV' ), value: TableDataDownloadFormat.CSV }, { label: this.i18n.translate( 'common:textExcel', {}, 'Excel' ), value: TableDataDownloadFormat.XLSX }]; userSelectLabel: string; selectedAudienceText = this.i18n.translate( 'CONFIG:textAddAdditionalAudienceMembers', {}, 'Add additional audience members' ); whoWillGetErrorReportsText = this.i18n.translate( 'common:textWhoWillReceiveErrorReports', {}, 'Who will receive error reports if a file fails to send' ); $dateGroup = new TypeToken>(); constructor ( private adHocReportingService: AdHocReportingService, private basicSFTPCredentialsResolver: BasicSFTPCredentialsResolver, private allAudiencesResolver: AllAudiencesResolver, private sftpService: SFTPService, private audienceService: AudienceService, private clientSettingsService: ClientSettingsService, private i18n: I18nService, private formBuilder: TypeSafeFormBuilder, private spinnerService: SpinnerService, private policyService: PolicyService, private typeSafeFormBuilder: TypeSafeFormBuilder, private dateService: DateService, private analyticsService: AnalyticsService ) { super(); } get basicCredentialsList () { return this.sftpService.basicCredentialsList; } async ngOnInit () { this.spinnerService.startSpinner(); this.page = this.isSchedule ? 'connection' : 'setup'; this.setText(); this.reportDetail = this.adHocReportingService.reportDetails[ this.report.id ]; if (this.reportDetail.schedule) { this.isAM = this.reportDetail.schedule.isAM; } this.selectedUsers = this.isSchedule && this.reportDetail.schedule ? this.reportDetail.schedule.users || [] : []; await Promise.all([ this.allAudiencesResolver.resolve(), this.basicSFTPCredentialsResolver.resolve(), this.sftpService.setUserCredentialsAndList() ]); this.setSelectOptions(); await this.setFormGroup(); this.spinnerService.stopSpinner(); } setText () { if (this.isSchedule) { if (this.page === 'connection') { this.primaryButtonText = this.nextText; this.secondaryButtonText = null; } else { this.userSelectLabel = (this.connectionFormGroup.value.accessType === this.accessOptionVals.email || !this.isSchedule) ? this.selectedAudienceText : this.whoWillGetErrorReportsText; this.primaryButtonText = this.saveText; this.secondaryButtonText = this.backText; } } else { this.primaryButtonText = this.sendText; } } handlePrimaryClick () { if (this.page === 'connection') { this.page = 'setup'; this.setText(); this.analyticsService.emitEvent({ eventName: 'Go to send report setup', eventType: EventType.Click, extras: null }); } else { this.onSubmit(); this.analyticsService.emitEvent({ eventName: 'Send report submit', eventType: EventType.Click, extras: null }); } } handleSecondaryClick () { this.page = 'connection'; this.setText(); } setSelectOptions () { const basicCredentials = this.sftpService.basicCredentialsList; const userCredentialsList = this.sftpService.userCredentialsList; const audiences = this.audienceService.allAudiences; this.audienceOptions = audiences.length > 0 ? audiences.map((audience) => { return { label: audience.name, value: audience.id }; }) : undefined; this.sftpOptions = this.isSchedule ? userCredentialsList : basicCredentials.map((option) => { return { label: option.siteName, value: option.id }; }).concat({ label: this.i18n.translate( 'reporting:textClientNameSecureStorage', { clientName: 'GrantsConnect' }, '__clientName__ secure storage' ), value: 0 }); } async setFormGroup () { const schedule = this.reportDetail.schedule || {} as AdHocReportingUI.AdHocReportSchedule; if (this.isSchedule && schedule.audienceId) { await this.setSelectedAudience(schedule.audienceId); } this.connectionFormGroup = this.typeSafeFormBuilder.group({ accessType: (this.isSchedule && schedule.sftpId) ? this.accessOptionVals.dataFeed : this.accessOptionVals.email }, { validator: [ this.dataFeedAccessTypeValidator() ] }); const sftpIdVal = this.isSchedule ? (this.connectionFormGroup.value.accessType === this.accessOptionVals.email ? 0 : schedule.sftpId) : 0; this.formGroup = this.formBuilder.group({ sftpId: [sftpIdVal, Validators.required], audienceId: this.isSchedule ? schedule.audienceId : null, showMaskedData: this.isSchedule ? schedule.showMaskedData : false, exportFileTypeId: this.isSchedule && this.reportDetail.schedule ? this.reportDetail.schedule.exportFileTypeId : TableDataDownloadFormat.CSV, expirationHours: [ this.isSchedule && !!schedule.expirationHours ? schedule.expirationHours : 24, [ Validators.required, Validators.min(.1) ] ], ...(this.isSchedule ? { frequency: [this.connectionFormGroup.value.accessType === this.accessOptionVals.dataFeed ? Frequency.Daily : schedule.frequency, Validators.required], hours: schedule.hours, week: schedule.week, month: schedule.month, dayOfMonth: schedule.dayOfMonth, dayOfWeek: schedule.dayOfWeek } : {}) }, this.isSchedule ? { validator: [ SiblingValueValidator('week', 'frequency', Frequency.Weekly), SiblingValueValidator('dayOfWeek', 'frequency', Frequency.Weekly), SiblingValueValidator('month', 'frequency', Frequency.Monthly), SiblingValueValidator('dayOfMonth', 'frequency', Frequency.Monthly), this.accessTypeSFTPSelectionValidator() ] } : undefined); } dataFeedAccessTypeValidator () { return (group: AbstractControl) => { let returnVal = null; if (group.value.accessType === this.accessOptionVals.dataFeed) { if (!this.sftpService.userCredentialsList.length) { returnVal = { accessType: { atLeastOneSFTPConnectionRequired: { i18nKey: 'common:textAtLeastOneSFTPConnectionRequired', defaultValue: 'At least one SFTP connection is required to send a data feed' } } }; } } return returnVal; }; } accessTypeSFTPSelectionValidator () { return (group: AbstractControl) => { let returnVal = null; if (this.connectionFormGroup.value.accessType === this.accessOptionVals.dataFeed) { if (!group.value.sftpId) { returnVal = { sftpId: { mustSelectSFTP: { i18nKey: 'common:textThisFieldIsRequiredForDataFeeds', defaultValue: 'This field is required for data feeds' } } }; } } return returnVal; }; } async setSelectedAudience (id: number) { const audience = this.audienceService.allAudiences.find((item) => { return item.id === id; }); if (audience) { this.spinnerService.startSpinner(); await this.audienceService.setAudienceDetail(audience); this.selectedAudience = this.audienceService.audienceDetailMap[id]; this.spinnerService.stopSpinner(); } else { this.selectedAudience = null; } this.setStickyUsers(); } async onAudienceChange () { const audienceId = this.formGroup.value.audienceId; if (this.selectedAudience && (this.selectedAudience.id !== audienceId)) { this.removePreviousAudienceMembersFromSelected(); } await this.setSelectedAudience(audienceId); if (this.selectedAudience) { const alreadySelected = this.selectedUsers.map((user) => { return user.external ? user.email : user.id; }); const adds = this.selectedAudience.members.filter((user) => { return user.external ? !alreadySelected.includes(user.email) : !alreadySelected.includes(user.id); }); this.selectedUsers = [ ...adds, ...this.selectedUsers ]; } } setStickyUsers () { if (this.selectedAudience) { this.stickyUsers = this.selectedAudience.members.map((user) => { return user.email; }); } else { this.stickyUsers = []; } } removePreviousAudienceMembersFromSelected () { const potentialRemovals = this.selectedAudience.members.map((user) => { return user.external ? user.email : user.id; }); this.selectedUsers = this.selectedUsers.filter((user) => { return user.external ? !potentialRemovals.includes(user.email) : !potentialRemovals.includes(user.id); }); } onSubmit () { const formValue = this.formGroup.value; const oneOffUsers = this.selectedUsers.filter((user) => { return !this.stickyUsers.includes(user.email); }); const sftpId = (formValue.sftpId === 0 || (this.isSchedule && this.connectionFormGroup.value.accessType === this.accessOptionVals.email)) ? null : formValue.sftpId; const expirationHours = !sftpId ? formValue.expirationHours : null; const exportFileTypeId = formValue.exportFileTypeId; if (this.isSchedule) { const frequencyType: Frequency = formValue.frequency; const hour = this.dateService.convertTimeInputsTo24Hour( formValue.hours, this.isAM ); let day: number; let frequencyValue: number; switch (frequencyType) { case Frequency.Daily: day = null; break; case Frequency.Weekly: day = formValue.dayOfWeek; frequencyValue = formValue.week; break; case Frequency.Monthly: day = formValue.dayOfMonth; frequencyValue = formValue.month; break; } this.closeModal.emit({ reportId: this.report.id, sftpId, audienceId: formValue.audienceId, oneOffUsers, failureDeliveryUsers: oneOffUsers, frequencyType, day, hour, frequencyValue, expirationHours, showMaskedData: formValue.showMaskedData, exportFileTypeId }); } else { this.closeModal.emit({ reportId: this.report.id, sftpId, clientEmailTemplateId: 0, audienceId: formValue.audienceId, oneOffUsers, expirationHours, showMaskedData: formValue.showMaskedData, exportFileTypeId }); } } }