diff --git a/login/utils/helper.class.ts b/login/utils/helper.class.ts index 63e15b2f..7ade96d7 100644 --- a/login/utils/helper.class.ts +++ b/login/utils/helper.class.ts @@ -1,3 +1,5 @@ +import {stakeholderTypes} from "../../monitor/entities/stakeholder"; + export class User { email: string; firstname: string; @@ -98,23 +100,11 @@ export class Session { } public static isMonitorCurator(user: User): boolean { - return this.isCommunityCurator(user) || this.isProjectCurator(user) || this.isFunderCurator(user) || this.isOrganizationCurator(user); + return stakeholderTypes.filter(stakeholderType => this.isTypeCurator(stakeholderType.value, user)).length > 0; } public static isCommunityCurator(user: User): boolean { - return this.isTypeCurator("Community", user); - } - - public static isFunderCurator(user: User): boolean { - return this.isTypeCurator("Funder", user); - } - - public static isProjectCurator(user: User): boolean { - return this.isTypeCurator("Project", user); - } - - public static isOrganizationCurator(user: User): boolean { - return this.isTypeCurator("Institution", user); + return this.isTypeCurator("community", user); } private static isTypeCurator(type: string, user: User): boolean { @@ -122,16 +112,7 @@ export class Session { } public static isCurator(type: string, user: User): boolean { - if (type == 'funder') { - return user && this.isFunderCurator(user); - } else if (type == 'ri' || type == 'community') { - return user && this.isCommunityCurator(user); - } else if (type == 'organization' || type == 'institution') { - return user && this.isOrganizationCurator(user); - } else if (type == 'project') { - return user && this.isProjectCurator(user); - } - return false; + return stakeholderTypes.find(stakeholderType => stakeholderType.value == type) && this.isTypeCurator(type, user); } public static isPortalAdministrator(user: User): boolean { diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.less b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.less new file mode 100644 index 00000000..e5f71cdc --- /dev/null +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.less @@ -0,0 +1,10 @@ +.uk-border-circle { + width: 100px; + height: 100px; + position: relative; + + & > img { + max-width: 64px; + max-height: 64px; + } +} diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts new file mode 100644 index 00000000..f84f197d --- /dev/null +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts @@ -0,0 +1,443 @@ +import {Component, Input, OnDestroy, ViewChild} from "@angular/core"; +import {Stakeholder} from "../../../monitor/entities/stakeholder"; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; +import {StakeholderUtils} from "../../utils/indicator-utils"; +import {Option} from "../../../sharedComponents/input/input.component"; +import {Subscription} from "rxjs"; +import {EnvProperties} from "../../../utils/properties/env-properties"; +import {properties} from "src/environments/environment"; +import {StakeholderService} from "../../../monitor/services/stakeholder.service"; +import {UtilitiesService} from "../../../services/utilities.service"; +import {Role, Session, User} from "../../../login/utils/helper.class"; +import {UserManagementService} from "../../../services/user-management.service"; +import {StringUtils} from "../../../utils/string-utils.class"; +import {NotifyFormComponent} from "../../../notifications/notify-form/notify-form.component"; +import {NotificationUtils} from "../../../notifications/notification-utils"; +import {Notification} from "../../../notifications/notifications"; +import {NotificationHandler} from "../../../utils/notification-handler"; +import {StatsProfilesService} from "../../utils/services/stats-profiles.service"; + +@Component({ + selector: 'edit-stakeholder', + template: ` +
+ + `, + styleUrls: ['edit-stakeholder.component.less'] +}) +export class EditStakeholderComponent implements OnDestroy { + @Input() + public disableAlias: boolean = false; + public stakeholderFb: UntypedFormGroup; + public secure: boolean = false; + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public defaultStakeholdersOptions: Option[]; + public defaultStakeholders: Stakeholder[]; + public alias: string[]; + public stakeholder: Stakeholder; + public isDefault: boolean; + public isNew: boolean; + public loading: boolean = false; + public types: Option[]; + public statsProfiles: string[]; + public properties: EnvProperties = properties; + private subscriptions: any[] = []; + /** + * Photo upload + * */ + public file: File; + public photo: string | ArrayBuffer; + public uploadError: string; + public deleteCurrentPhoto: boolean = false; + private maxsize: number = 200 * 1024; + user: User; + @ViewChild('notify', {static: true}) notify: NotifyFormComponent; + private notification: Notification; + + constructor(private fb: UntypedFormBuilder, + private stakeholderService: StakeholderService, + private statsProfileService: StatsProfilesService, + private utilsService: UtilitiesService, private userManagementService: UserManagementService,) { + } + + ngOnDestroy() { + this.reset(); + } + + public init(stakeholder: Stakeholder, alias: string[], defaultStakeholders: Stakeholder[], isDefault: boolean, isNew: boolean) { + this.reset(); + this.deleteCurrentPhoto = false; + this.stakeholder = stakeholder; + this.alias = alias; + this.defaultStakeholders = defaultStakeholders; + this.isDefault = isDefault; + this.isNew = isNew; + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + if (this.isCurator) { + this.subscriptions.push(this.statsProfileService.getStatsProfiles().subscribe(statsProfiles => { + this.statsProfiles = statsProfiles; + }, error => { + this.statsProfiles = []; + })); + } else { + this.statsProfiles = []; + } + this.types = this.stakeholderUtils.getTypesByUserRoles(this.user, this.stakeholder.alias); + this.stakeholderFb = this.fb.group({ + _id: this.fb.control(this.stakeholder._id), + defaultId: this.fb.control(this.stakeholder.defaultId), + name: this.fb.control(this.stakeholder.name, Validators.required), + description: this.fb.control(this.stakeholder.description), + index_name: this.fb.control(this.stakeholder.index_name, Validators.required), + index_id: this.fb.control(this.stakeholder.index_id, Validators.required), + index_shortName: this.fb.control(this.stakeholder.index_shortName, Validators.required), + statsProfile: this.fb.control(this.stakeholder.statsProfile, Validators.required), + locale: this.fb.control(this.stakeholder.locale, Validators.required), + projectUpdateDate: this.fb.control(this.stakeholder.projectUpdateDate), + creationDate: this.fb.control(this.stakeholder.creationDate), + alias: this.fb.control(this.stakeholder.alias, + [ + Validators.required, + this.stakeholderUtils.aliasValidatorString( + this.alias.filter(alias => alias !== this.stakeholder.alias) + )] + ), + isDefault: this.fb.control((this.isDefault)), + visibility: this.fb.control(this.stakeholder.visibility, Validators.required), + type: this.fb.control(this.stakeholder.type, Validators.required), + topics: this.fb.control(this.stakeholder.topics), + isUpload: this.fb.control(this.stakeholder.isUpload), + logoUrl: this.fb.control(this.stakeholder.logoUrl), + }); + if (this.stakeholder.isUpload) { + this.stakeholderFb.get('logoUrl').clearValidators(); + this.stakeholderFb.get('logoUrl').updateValueAndValidity(); + } else { + this.stakeholderFb.get('logoUrl').setValidators([StringUtils.urlValidator()]); + this.stakeholderFb.get('logoUrl').updateValueAndValidity(); + } + this.subscriptions.push(this.stakeholderFb.get('isUpload').valueChanges.subscribe(value => { + if (value == true) { + this.stakeholderFb.get('logoUrl').clearValidators(); + this.stakeholderFb.updateValueAndValidity(); + } else { + this.stakeholderFb.get('logoUrl').setValidators([StringUtils.urlValidator()]); + this.stakeholderFb.updateValueAndValidity(); + } + })); + this.secure = (!this.stakeholderFb.get('logoUrl').value || this.stakeholderFb.get('logoUrl').value.includes('https://')); + this.subscriptions.push(this.stakeholderFb.get('logoUrl').valueChanges.subscribe(value => { + this.secure = (!value || value.includes('https://')); + })); + this.initPhoto(); + this.subscriptions.push(this.stakeholderFb.get('type').valueChanges.subscribe(value => { + this.onTypeChange(value, defaultStakeholders); + })); + this.stakeholderFb.setControl('defaultId', this.fb.control(stakeholder.defaultId, (this.isDefault && !this.isNew)?[]:Validators.required)); + if (!this.isNew) { + this.notification = NotificationUtils.editStakeholder(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.notify.reset(this.notification.message); + if (this.isAdmin) { + if (this.disableAlias) { + setTimeout(() => { + this.stakeholderFb.get('alias').disable(); + }, 0); + } + } else { + if (!this.isCurator) { + setTimeout(() => { + this.stakeholderFb.get('statsProfile').disable(); + }, 0); + } + setTimeout(() => { + this.stakeholderFb.get('alias').disable(); + this.stakeholderFb.get('index_id').disable(); + this.stakeholderFb.get('index_name').disable(); + this.stakeholderFb.get('index_shortName').disable(); + }, 0); + } + setTimeout(() => { + this.stakeholderFb.get('type').disable(); + }, 0); + } else { + this.notification = NotificationUtils.createStakeholder(this.user.firstname + ' ' + this.user.lastname); + this.notify.reset(this.notification.message); + setTimeout(() => { + this.stakeholderFb.get('type').enable(); + }, 0); + } + })); + } + + public get isAdmin() { + return Session.isPortalAdministrator(this.user); + } + + public get isCurator() { + return this.stakeholder && (this.isAdmin || Session.isCurator(this.stakeholder.type, this.user)); + } + + public get disabled(): boolean { + return (this.stakeholderFb && this.stakeholderFb.invalid) || + (this.stakeholderFb && this.stakeholderFb.pristine && !this.isNew && !this.file) || + (this.uploadError && this.uploadError.length > 0); + } + + public get dirty(): boolean { + return this.stakeholderFb && this.stakeholderFb.dirty; + } + + public get canChooseTemplate(): boolean { + return this.isNew && this.stakeholderFb.get('type').valid && !!this.defaultStakeholdersOptions; + } + + reset() { + this.uploadError = null; + this.stakeholderFb = null; + this.subscriptions.forEach(subscription => { + if (subscription instanceof Subscription) { + subscription.unsubscribe(); + } + }); + } + + onTypeChange(value, defaultStakeholders: Stakeholder[]) { + this.stakeholderFb.setControl('defaultId', this.fb.control(this.stakeholder.defaultId, (this.isDefault && !this.isNew)?[]:Validators.required)); + this.defaultStakeholdersOptions = [{ + label: 'New blank profile', + value: '-1' + }]; + defaultStakeholders.filter(stakeholder => stakeholder.type === value).forEach(stakeholder => { + this.defaultStakeholdersOptions.push({ + label: 'Use ' + stakeholder.name + ' profile', + value: stakeholder._id + }) + }); + } + + public save(callback: Function, errorCallback: Function = null) { + this.loading = true; + if (this.file) { + this.subscriptions.push(this.utilsService.uploadPhoto(this.properties.utilsService + "/upload/" + encodeURIComponent(this.stakeholderFb.getRawValue().type) + "/" + encodeURIComponent(this.stakeholderFb.getRawValue().alias), this.file).subscribe(res => { + this.deletePhoto(); + this.stakeholderFb.get('logoUrl').setValue(res.filename); + this.removePhoto(); + this.saveStakeholder(callback, errorCallback); + }, error => { + this.uploadError = "An error has been occurred during upload your image. Try again later"; + this.saveStakeholder(callback, errorCallback); + })); + } else if (this.deleteCurrentPhoto) { + this.deletePhoto(); + this.saveStakeholder(callback, errorCallback); + } else { + this.saveStakeholder(callback, errorCallback); + } + } + + public saveStakeholder(callback: Function, errorCallback: Function = null) { + if (this.isNew) { + let defaultStakeholder = this.defaultStakeholders.find(value => value._id === this.stakeholderFb.getRawValue().defaultId); + this.stakeholderFb.setValue(this.stakeholderUtils.createFunderFromDefaultProfile(this.stakeholderFb.getRawValue(), + (defaultStakeholder ? defaultStakeholder.topics : []), this.stakeholderFb.getRawValue().isDefault)); + this.removePhoto(); + if(this.stakeholderFb.getRawValue().isDefault) { + this.stakeholderFb.get('defaultId').setValue(null); + } + this.subscriptions.push(this.stakeholderService.buildStakeholder(this.properties.monitorServiceAPIURL, + this.stakeholderFb.getRawValue()).subscribe(stakeholder => { + this.notification.entity = stakeholder._id; + this.notification.stakeholder = stakeholder.alias; + this.notification.stakeholderType = stakeholder.type; + this.notification.groups = [Role.curator(stakeholder.type)]; + this.notify.sendNotification(this.notification); + NotificationHandler.rise(stakeholder.name + ' has been successfully created'); + callback(stakeholder); + this.loading = false; + }, error => { + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + if (errorCallback) { + errorCallback(error) + } + this.loading = false; + })); + } else { + this.subscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, this.stakeholderFb.getRawValue()).subscribe(stakeholder => { + this.notification.entity = stakeholder._id; + this.notification.stakeholder = stakeholder.alias; + this.notification.stakeholderType = stakeholder.type; + this.notification.groups = [Role.curator(stakeholder.type), Role.manager(stakeholder.type, stakeholder.alias)]; + this.notify.sendNotification(this.notification); + NotificationHandler.rise(stakeholder.name + ' has been successfully saved'); + callback(stakeholder); + this.loading = false; + }, error => { + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + if (errorCallback) { + errorCallback(error) + } + this.loading = false; + })); + } + } + + fileChangeEvent(event) { + if (event.target.files && event.target.files[0]) { + this.file = event.target.files[0]; + if (this.file.type !== 'image/png' && this.file.type !== 'image/jpeg') { + this.uploadError = 'You must choose a file with type: image/png or image/jpeg!'; + this.stakeholderFb.get('isUpload').setValue(false); + this.stakeholderFb.get('isUpload').markAsDirty(); + this.removePhoto(); + } else if (this.file.size > this.maxsize) { + this.uploadError = 'File exceeds size\'s limit! Maximum resolution is 256x256 pixels.'; + this.stakeholderFb.get('isUpload').setValue(false); + this.stakeholderFb.get('isUpload').markAsDirty(); + this.removePhoto(); + } else { + this.uploadError = null; + const reader = new FileReader(); + reader.readAsDataURL(this.file); + reader.onload = () => { + this.photo = reader.result; + this.stakeholderFb.get('isUpload').setValue(true); + this.stakeholderFb.get('isUpload').markAsDirty(); + }; + } + } + } + + initPhoto() { + if (this.stakeholderFb.getRawValue().isUpload) { + this.photo = this.properties.utilsService + "/download/" + this.stakeholderFb.get('logoUrl').value; + } + } + + removePhoto() { + if (this.file) { + if (typeof document != 'undefined') { + (Note:
+- Check out the indicator pages (for funders, - research institutions and - research initiatives) + Check out the indicator pages (for {{entities.FUNDERS}}, + {{entities.ORGANIZATIONS}} and + {{entities.RIS}}) for the specific indicators for each type of dashboard, and the methodology and terminology page on how we produce the metrics.
@@ -67,6 +68,7 @@ import {Subscriber} from "rxjs"; export class IndicatorThemesComponent implements OnInit, OnDestroy { private subscriptions: any[] = []; public properties = properties; + public entities = StakeholderEntities; public breadcrumbs: Breadcrumb[] = [{name: 'home', route: '/'}, {name: 'Resources - Themes'}]; constructor(private router: Router, diff --git a/services/servicesUtils/customOptions.class.ts b/services/servicesUtils/customOptions.class.ts index 4a9572e4..c935ac31 100644 --- a/services/servicesUtils/customOptions.class.ts +++ b/services/servicesUtils/customOptions.class.ts @@ -1,4 +1,3 @@ -import {COOKIE} from '../../login/utils/helper.class'; import {HttpHeaders} from "@angular/common/http"; export type MediaType = 'application/json' | 'text/plain'