import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { AbstractControl, Validators } from '@angular/forms'; import { UserFromApi } from '@core/typings/client-user.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { UserService } from '@features/users/user.service'; import { ArrayHelpersService, EmailExtensionValidator, SimpleStringMap, TopLevelFilter, TypeaheadSelectOption, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { AudienceMember, AudiencePermissionAttrs, ManageAudienceMember } from '../audience.typing'; interface AudienceFormGroup { userSelect: number; addAllUsersCheckbox: boolean; } interface ExternalUsersGroup { externalName: string; externalEmail: string; } @Component({ selector: 'gc-manage-audience-table', templateUrl: './manage-audience-table.component.html', styleUrls: ['./manage-audience-table.component.scss'] }) export class ManageAudienceTableComponent implements OnInit, OnChanges { @Input() selectedUsers: (AudienceMember|ManageAudienceMember)[] = []; @Input() tableKey: string; @Input() allowExternal = true; @Input() includePermissionSelector = false; @Input() userIdAttribute: 'id'|'userId' = 'userId'; @Input() userSelectLabel = this.i18n.translate( 'CONFIG:textAddAdditionalAudienceMembers', {}, 'Add additional audience members' ); @Input() permissionColumnHeaderKey = 'common:hdrReportPermission'; @Input() permissionProp: keyof AudiencePermissionAttrs = 'canManage'; @Input() permissionDefaultValue: AudiencePermissionAttrs[K] = false; @Input() permissionOptions: TypeaheadSelectOption[] = [{ label: this.i18n.translate( 'APPLY:textManage', {}, 'Manage' ), value: true }, { label: this.i18n.translate( 'GLOBAL:textViewOnly', {}, 'View only' ), value: false }]; @Input() topLevelFilters: TopLevelFilter[] = []; @Input() filterOutUserId: number; // owner of report to filter out @Input() stickyAudienceName: string; @Input() stickyUsers: string[] = []; // users that can't be removed @Input() userManageMap: SimpleStringMap; @Input() externalAddAllLogic = false; @Output() onSelectedUsersChange = new EventEmitter< (AudienceMember|ManageAudienceMember)[] >(); @Output() onAddingExternalUserChange = new EventEmitter(); @Output() onAddAll = new EventEmitter(); formGroup: TypeSafeFormGroup; externalUserFormGroup: TypeSafeFormGroup; userOptions: TypeaheadSelectOption[] = []; allUsersSelected = false; companyName = this.clientSettingsService.clientBranding.name; addingExternalUser = false; allUsers: UserFromApi[]; constructor ( private formBuilder: TypeSafeFormBuilder, private i18n: I18nService, private clientSettingsService: ClientSettingsService, private userService: UserService, private arrayHelper: ArrayHelpersService ) { } ngOnInit () { this.allUsers = this.userService.allUsers.filter((user) => { return !user.isDeactivated && (this.filterOutUserId ? user.id !== this.filterOutUserId : true); }); this.setUserOptions(); this.formGroup = this.formBuilder.group({ userSelect: null, addAllUsersCheckbox: false }); } ngOnChanges () { if (this.formGroup) { this.setUserOptions(); } } setUserOptions () { this.userOptions = this.arrayHelper.sort( this.allUsers.filter((user) => { const existingUsers = this.selectedUsers.map((u) => u.id); return !existingUsers.includes((user as any)[this.userIdAttribute]); }).map((user) => { return { label: user.fullName, value: (user as any)[this.userIdAttribute], option: `
${user.fullName}
${user.email}
` }; }), 'label' ); this.allUsersSelected = this.userOptions.length === 0; if (this.formGroup) { const allUsersControl = this.formGroup.get('addAllUsersCheckbox'); if (this.allUsersSelected && !allUsersControl.value) { allUsersControl.setValue(true); } else if (!this.allUsersSelected && allUsersControl.value) { allUsersControl.setValue(false); } } } selectedUsersChange () { this.onSelectedUsersChange.emit(this.selectedUsers); } onUserChange (userId: number) { const found = this.allUsers.find((user) => { return (user as any)[this.userIdAttribute] === userId; }); if (found) { this.formGroup.get('userSelect').setValue(null); const newUser: AudienceMember = { id: (found as any)[this.userIdAttribute], email: found.email, external: false, name: found.fullName }; newUser[this.permissionProp] = this.permissionDefaultValue; if (!this.addingExternalUser) { this.selectedUsers = [ newUser, ...this.selectedUsers ]; this.selectedUsersChange(); } else { // Place the selected user below the external user being added this.selectedUsers = [ ...this.selectedUsers.slice(0, 1), newUser, ...this.selectedUsers.slice(1) ]; this.selectedUsersChange(); } this.setUserOptions(); } } saveExternalUser () { const formValue = this.externalUserFormGroup.value; this.selectedUsers = [ ...this.selectedUsers.slice(0, 0), { name: formValue.externalName, email: formValue.externalEmail, external: true, id: null }, ...this.selectedUsers.slice(1) ]; this.selectedUsersChange(); this.addingExternalUser = false; this.onAddingExternalUserChange.emit(this.addingExternalUser); } addExternalUser () { this.externalUserFormGroup = this.formBuilder.group({ externalName: ['', Validators.required], externalEmail: ['', [ Validators.required, EmailExtensionValidator, this.duplicateEmailValidator() ]] }); this.addingExternalUser = true; this.onAddingExternalUserChange.emit(this.addingExternalUser); this.selectedUsers = [ { name: '', email: '', external: true, id: null }, ...this.selectedUsers ]; this.selectedUsersChange(); } removeUser (row: AudienceMember) { const index = this.selectedUsers.findIndex((user) => { // find by email because external users might not have ids return row.email === user.email; }); this.selectedUsers = [ ...this.selectedUsers.slice(0, index), ...this.selectedUsers.slice(index + 1) ]; this.selectedUsersChange(); this.setUserOptions(); if (this.addingExternalUser) { this.addingExternalUser = false; this.onAddingExternalUserChange.emit(this.addingExternalUser); } } addAllUsers (addAll = true) { if (!this.externalAddAllLogic) { if (addAll) { const externalUsers = this.selectedUsers.filter((user, i) => { if (this.addingExternalUser) { return (i !== 0) && user.external; } return user.external; }); const internalUsers = this.allUsers.map((user) => { const foundSelected = this.selectedUsers.find((_user) => { return _user.id === (user as any)[this.userIdAttribute]; }); return { name: user.fullName, email: user.email, external: false, id: (user as any)[this.userIdAttribute], [this.permissionProp]: foundSelected ? foundSelected[this.permissionProp] : this.permissionDefaultValue }; }); const allUsers = this.arrayHelper.sort([ ...externalUsers, ...internalUsers ], 'name'); if (this.addingExternalUser) { this.selectedUsers = [ ...this.selectedUsers.slice(0, 0), ...allUsers ]; } else { this.selectedUsers = [ ...allUsers ]; } this.selectedUsersChange(); this.allUsersSelected = true; } this.setUserOptions(); } else { this.onAddAll.emit(); } } duplicateEmailValidator () { return (control: AbstractControl) => { const existing = this.selectedUsers.map((user) => user.email); const invalid = existing.includes(control.value); if (invalid) { return { duplicateEmail: { i18nKey: 'common:textEmailAlreadyExists', defaultValue: 'Email already exists' } }; } return null; }; } }