diff --git a/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts b/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts
new file mode 100644
index 00000000..03c01a41
--- /dev/null
+++ b/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts
@@ -0,0 +1,18 @@
+import {NgModule} from '@angular/core';
+import {RouterModule} from '@angular/router';
+import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard';
+import {ManageStakeholdersComponent} from "./manageStakeholders.component";
+
+@NgModule({
+ imports: [
+ RouterModule.forChild([
+ {
+ path: '',
+ component: ManageStakeholdersComponent,
+ canDeactivate: [PreviousRouteRecorder]
+ }
+ ])
+ ]
+})
+export class ManageStakeholdersRoutingModule {
+}
diff --git a/monitor-admin/manageStakeholders/manageStakeholders.component.html b/monitor-admin/manageStakeholders/manageStakeholders.component.html
new file mode 100644
index 00000000..5b981cde
--- /dev/null
+++ b/monitor-admin/manageStakeholders/manageStakeholders.component.html
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ No profiles to manage yet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{text}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This stakeholder will permanently be deleted. Are you sure you want to proceed?
+
+
diff --git a/monitor-admin/manageStakeholders/manageStakeholders.component.less b/monitor-admin/manageStakeholders/manageStakeholders.component.less
new file mode 100644
index 00000000..ee701579
--- /dev/null
+++ b/monitor-admin/manageStakeholders/manageStakeholders.component.less
@@ -0,0 +1,23 @@
+@import (reference) "~src/assets/openaire-theme/less/color.less";
+
+.setType(@color) {
+ border-bottom: 4px solid fade(@color, 30%);
+
+ & .type {
+ color: @color;
+ }
+}
+
+.uk-card {
+ &.funder {
+ .setType(@funder-color);
+ }
+
+ &.ri {
+ .setType(@ri-color);
+ }
+
+ &.organization {
+ .setType(@organization-color);
+ }
+}
diff --git a/monitor-admin/manageStakeholders/manageStakeholders.component.ts b/monitor-admin/manageStakeholders/manageStakeholders.component.ts
new file mode 100644
index 00000000..5e637b3e
--- /dev/null
+++ b/monitor-admin/manageStakeholders/manageStakeholders.component.ts
@@ -0,0 +1,311 @@
+import {Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
+import {StakeholderService} from "../../monitor/services/stakeholder.service";
+import {EnvProperties} from "../../utils/properties/env-properties";
+import {Stakeholder, StakeholderEntities, Visibility} from "../../monitor/entities/stakeholder";
+import {Subscriber, zip} from "rxjs";
+import {StakeholderUtils} from "../utils/indicator-utils";
+import {UntypedFormBuilder, UntypedFormGroup} from "@angular/forms";
+import {AlertModal} from "../../utils/modal/alert";
+import {Option} from "../../sharedComponents/input/input.component";
+import {Title} from "@angular/platform-browser";
+import {UserManagementService} from "../../services/user-management.service";
+import {Session} from "../../login/utils/helper.class";
+import {EditStakeholderComponent} from "../general/edit-stakeholder/edit-stakeholder.component";
+import {properties} from "src/environments/environment";
+import {ActivatedRoute} from "@angular/router";
+import {CacheIndicatorsService} from "../utils/cache-indicators/cache-indicators.service";
+import {NotificationHandler} from "../../utils/notification-handler";
+
+type Tab = 'all' | 'templates'| 'profiles';
+
+declare var UIkit;
+
+@Component({
+ selector: 'home',
+ templateUrl: "./manageStakeholders.component.html",
+ styleUrls: ["./manageStakeholders.component.less"]
+})
+export class ManageStakeholdersComponent implements OnInit, OnDestroy {
+
+ public properties: EnvProperties;
+ public loading: boolean = true;
+ public deleteLoading: boolean = false;
+ public stakeholderUtils: StakeholderUtils = new StakeholderUtils();
+ public defaultStakeholders: Stakeholder[];
+ public stakeholders: Stakeholder[];
+ public alias: string[];
+ public stakeholder: Stakeholder;
+ public index: number;
+ public user = null;
+ public tab: Tab = 'all';
+ /**
+ * Filtered Stakeholders
+ */
+ public displayDefaultStakeholders: Stakeholder[];
+ public displayStakeholders: Stakeholder[];
+ /**
+ * Top filters
+ */
+ public filters: UntypedFormGroup;
+ public all: Option = {
+ value: 'all',
+ label: 'All'
+ };
+
+ public callback: Function;
+
+ /**
+ * Grid or List View
+ */
+ private subscriptions: any[] = [];
+ @ViewChild('editStakeholderModal', { static: true }) editStakeholderModal: AlertModal;
+ @ViewChild('deleteStakeholderModal', { static: true }) deleteStakeholderModal: AlertModal;
+ @ViewChild('editStakeholderComponent', { static: true }) editStakeholderComponent: EditStakeholderComponent;
+
+ constructor(private stakeholderService: StakeholderService,
+ private cacheIndicatorsService: CacheIndicatorsService,
+ private userManagementService: UserManagementService,
+ private route: ActivatedRoute,
+ private title: Title,
+ private fb: UntypedFormBuilder) {
+ }
+
+ ngOnInit(): void {
+ this.buildFilters();
+ this.properties = properties;
+ this.title.setTitle('Manage profiles');
+ this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => {
+ this.user = user;
+ }));
+ let data = zip(
+ this.stakeholderService.getDefaultStakeholders(this.properties.monitorServiceAPIURL),
+ this.stakeholderService.getMyStakeholders(this.properties.monitorServiceAPIURL),
+ this.stakeholderService.getAlias(this.properties.monitorServiceAPIURL)
+ );
+ this.subscriptions.push(data.subscribe(res => {
+ this.defaultStakeholders = res[0];
+ this.stakeholders = res[1];
+ this.displayDefaultStakeholders = res[0];
+ this.displayStakeholders = res[1];
+ this.alias = res[2];
+ this.loading = false;
+ }, error => {
+ this.loading = false;
+ }));
+ }
+
+ ngOnDestroy(): void {
+ this.subscriptions.forEach(value => {
+ if (value instanceof Subscriber) {
+ value.unsubscribe();
+ } else if (value instanceof Function) {
+ value();
+ }
+ });
+ }
+
+ hide(element: any) {
+ UIkit.dropdown(element).hide();
+ }
+
+
+ private buildFilters() {
+ this.filters = this.fb.group({
+ status: this.fb.control('all'),
+ keyword: this.fb.control('')
+ });
+ this.subscriptions.push(this.filters.get('status').valueChanges.subscribe(value => {
+ this.onStatusChange(value);
+ }));
+ this.subscriptions.push(this.filters.get('keyword').valueChanges.subscribe(value => {
+ this.onKeywordChange(value);
+ }));
+ }
+
+ onStatusChange(value) {
+ this.displayDefaultStakeholders = this.filterStatus(this.defaultStakeholders, value);
+ this.displayStakeholders = this.filterStatus(this.stakeholders, value);
+ }
+
+ onKeywordChange(value) {
+ this.displayDefaultStakeholders = this.filterByKeyword(this.defaultStakeholders, value);
+ this.displayStakeholders = this.filterByKeyword(this.stakeholders, value);
+ }
+
+ private filterStatus(stakeholders: Stakeholder[], value): Stakeholder[] {
+ if (value === 'all') {
+ return stakeholders;
+ } else {
+ return stakeholders.filter(stakeholder => stakeholder.visibility == value);
+ }
+ }
+
+ private filterByKeyword(stakeholders: Stakeholder[], value): Stakeholder[] {
+ if (!value) {
+ return stakeholders;
+ } else {
+ return stakeholders.filter(stakeholder =>
+ stakeholder.name && stakeholder.name.toLowerCase().includes(value.toLowerCase()) ||
+ stakeholder.type && stakeholder.type.toLowerCase().includes(value.toLowerCase()) ||
+ stakeholder.index_id && stakeholder.index_id.toLowerCase().includes(value.toLowerCase()) ||
+ stakeholder.index_shortName && stakeholder.index_shortName.toLowerCase().includes(value.toLowerCase()) ||
+ stakeholder.index_name && stakeholder.index_name.toLowerCase().includes(value.toLowerCase())
+ );
+ }
+ }
+
+ public editStakeholder(stakeholder: Stakeholder = null, isDefault: boolean = false) {
+ if (isDefault) {
+ this.index = (stakeholder) ? this.defaultStakeholders.findIndex(value => value._id === stakeholder._id) : -1;
+ } else {
+ this.index = (stakeholder) ? this.stakeholders.findIndex(value => value._id === stakeholder._id) : -1;
+ }
+ if (!stakeholder) {
+ this.stakeholder = new Stakeholder(null, null, null,
+ null, null, null, null, null);
+ } else {
+ this.stakeholder = stakeholder;
+ }
+ this.editStakeholderComponent.init(this.stakeholder, this.alias, this.defaultStakeholders, isDefault, this.index === -1);
+ if (this.index !== -1) {
+ this.callback = (stakeholder: Stakeholder) => {
+ let index: number;
+ if (stakeholder.defaultId == null) {
+ index = this.alias.findIndex(value => value == this.defaultStakeholders[this.index].alias);
+ this.defaultStakeholders[this.index] = stakeholder;
+ } else {
+ index = this.alias.findIndex(value => value == this.stakeholders[this.index].alias);
+ this.stakeholders[this.index] = stakeholder;
+ }
+ if(index != -1) {
+ this.alias[index] = stakeholder.alias;
+ }
+ this.editStakeholderModal.cancel();
+ };
+ this.editStakeholderModal.alertTitle = 'Edit ' + this.stakeholder.name;
+ this.editStakeholderModal.okButtonText = 'Save Changes';
+ } else {
+ this.callback = (stakeholder: Stakeholder) => {
+ if (stakeholder.defaultId === null) {
+ this.defaultStakeholders.push(stakeholder);
+ } else {
+ this.stakeholders.push(stakeholder);
+ }
+ this.alias.push(stakeholder.alias);
+ this.editStakeholderModal.cancel();
+ };
+ this.editStakeholderModal.alertTitle = 'Create a new ' + (isDefault?'Default ':'') + 'Profile';
+ this.editStakeholderModal.okButtonText = 'Create';
+ }
+ this.editStakeholderModal.cancelButtonText = 'Cancel';
+ this.editStakeholderModal.okButtonLeft = false;
+ this.editStakeholderModal.alertMessage = false;
+ this.editStakeholderModal.stayOpen = true;
+ this.editStakeholderModal.open();
+ }
+
+ public createReport(stakeholder: Stakeholder) {
+ this.cacheIndicatorsService.createReport(stakeholder.alias).subscribe(report => {
+ NotificationHandler.rise('A caching process for ' + stakeholder.name + ' has been started.' )
+ }, error => {
+ console.log(error);
+ NotificationHandler.rise(error.message(), 'danger');
+ });
+ }
+
+ public deleteStakeholderOpen(stakeholder: Stakeholder) {
+ this.stakeholder = stakeholder;
+ this.deleteStakeholderModal.alertTitle = 'Delete ' + this.stakeholder.index_name;
+ this.deleteStakeholderModal.cancelButtonText = 'No';
+ this.deleteStakeholderModal.okButtonText = 'Yes';
+ this.deleteStakeholderModal.alertMessage = false;
+ this.deleteStakeholderModal.stayOpen = true;
+ this.deleteStakeholderModal.open();
+ }
+
+ public deleteStakeholder() {
+ this.deleteLoading = true;
+ if (!this.stakeholder.defaultId) {
+ this.index = (this.stakeholder) ? this.defaultStakeholders.findIndex(value => value._id === this.stakeholder._id) : -1;
+ } else {
+ this.index = (this.stakeholder) ? this.stakeholders.findIndex(value => value._id === this.stakeholder._id) : -1;
+ }
+ this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, [this.stakeholder._id]).subscribe(() => {
+ UIkit.notification(this.stakeholder.name+ ' has been successfully deleted', {
+ status: 'success',
+ timeout: 6000,
+ pos: 'bottom-right'
+ });
+ if (!this.stakeholder.defaultId) {
+ this.defaultStakeholders.splice(this.index, 1);
+ } else {
+ this.stakeholders.splice(this.index, 1);
+ }
+ this.alias = this.alias.filter(item => item !== this.stakeholder.alias);
+ this.deleteLoading = false;
+ this.deleteStakeholderModal.cancel();
+ }, error => {
+ UIkit.notification('An error has occurred. Please try again later', {
+ status: 'danger',
+ timeout: 6000,
+ pos: 'bottom-right'
+ });
+ this.deleteLoading = false;
+ this.deleteStakeholderModal.cancel();
+ }));
+ }
+
+ changeStakeholderStatus(stakeholder: Stakeholder, visibility: Visibility) {
+ let path = [
+ stakeholder._id
+ ];
+ this.subscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility).subscribe(returnedElement => {
+ stakeholder.visibility = returnedElement.visibility;
+ UIkit.notification(stakeholder.name+ '\'s status has been successfully changed to ' + stakeholder.visibility.toLowerCase(), {
+ status: 'success',
+ timeout: 6000,
+ pos: 'bottom-right'
+ });
+ }, error => {
+ UIkit.notification('An error has occurred. Please try again later', {
+ status: 'danger',
+ timeout: 6000,
+ pos: 'bottom-right'
+ });
+ }));
+ }
+
+ public isManager(): boolean {
+ return this.isCurator() || (Session.isKindOfMonitorManager(this.user));
+ }
+
+ public isProfileManager(stakeholder: Stakeholder): boolean {
+ return this.isCurator() || (Session.isManager(stakeholder.type, stakeholder.alias, this.user));
+ }
+
+ public isCurator(): boolean {
+ return this.isAdmin() || Session.isMonitorCurator(this.user);
+ }
+
+ public isAdmin(): boolean {
+ return Session.isPortalAdministrator(this.user);
+ }
+
+ get typesAsString() {
+ return this.stakeholderUtils.types.slice(0, this.stakeholderUtils.types.length - 1).map(type => type.label).join(', ') +
+ ' or ' + this.stakeholderUtils.types[this.stakeholderUtils.types.length - 1].label
+ }
+
+ private isTab(tab: Tab): boolean {
+ switch (tab) {
+ case "all":
+ return true;
+ case "profiles":
+ return true;
+ case "templates":
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/monitor-admin/manageStakeholders/manageStakeholders.module.ts b/monitor-admin/manageStakeholders/manageStakeholders.module.ts
new file mode 100644
index 00000000..41847068
--- /dev/null
+++ b/monitor-admin/manageStakeholders/manageStakeholders.module.ts
@@ -0,0 +1,50 @@
+import {NgModule} from "@angular/core";
+import {ManageStakeholdersComponent} from "./manageStakeholders.component";
+import {ManageStakeholdersRoutingModule} from "./manageStakeholders-routing.module";
+import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard";
+import {CommonModule} from "@angular/common";
+import {RouterModule} from "@angular/router";
+import {InputModule} from "../../sharedComponents/input/input.module";
+import {LoadingModule} from "../../utils/loading/loading.module";
+import {AlertModalModule} from "../../utils/modal/alertModal.module";
+import {ReactiveFormsModule} from "@angular/forms";
+import {IconsModule} from "../../utils/icons/icons.module";
+import {IconsService} from "../../utils/icons/icons.service";
+import {earth, incognito, restricted} from "../../utils/icons/icons";
+import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module";
+import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module";
+import {SearchInputModule} from "../../sharedComponents/search-input/search-input.module";
+import {
+ SidebarMobileToggleModule
+} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module";
+import {SliderTabsModule} from "../../sharedComponents/tabs/slider-tabs.module";
+import {EditStakeholderModule} from "../general/edit-stakeholder/edit-stakeholder.module";
+
+@NgModule({
+ declarations: [ManageStakeholdersComponent],
+ imports: [
+ ManageStakeholdersRoutingModule,
+ CommonModule,
+ RouterModule,
+ InputModule,
+ LoadingModule,
+ AlertModalModule,
+ ReactiveFormsModule,
+ EditStakeholderModule,
+ IconsModule,
+ PageContentModule,
+ LogoUrlPipeModule,
+ SearchInputModule,
+ SidebarMobileToggleModule,
+ SliderTabsModule
+ ],
+ providers: [
+ PreviousRouteRecorder,
+ ],
+ exports: [ManageStakeholdersComponent]
+})
+export class ManageStakeholdersModule {
+ constructor(private iconsService: IconsService) {
+ this.iconsService.registerIcons([earth, incognito, restricted]);
+ }
+}
diff --git a/monitor/entities/stakeholder.ts b/monitor/entities/stakeholder.ts
index 13ed58cf..357f42ab 100644
--- a/monitor/entities/stakeholder.ts
+++ b/monitor/entities/stakeholder.ts
@@ -314,6 +314,6 @@ export enum StakeholderEntities {
export let stakeholderTypes: Option[] = [
{value: 'funder', label: StakeholderEntities.FUNDER},
{value: 'ri', label: StakeholderEntities.RI},
- {value: 'project', label: StakeholderEntities.PROJECT},
- {value: 'organization', label: StakeholderEntities.ORGANIZATION}
+ {value: 'organization', label: StakeholderEntities.ORGANIZATION},
+ {value: 'project', label: StakeholderEntities.PROJECT}
];