diff --git a/dashboard/sharedComponents/page-content/page-content.component.ts b/dashboard/sharedComponents/page-content/page-content.component.ts index 10b17a94..c46e7114 100644 --- a/dashboard/sharedComponents/page-content/page-content.component.ts +++ b/dashboard/sharedComponents/page-content/page-content.component.ts @@ -148,6 +148,7 @@ export class PageContentComponent implements OnInit, AfterViewInit, OnDestroy { calcStickyFooterOffset(element) { this.footer_height = element.offsetHeight; + this.cdr.detectChanges(); return window.innerHeight - this.footer_height; } } diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts index 12ceb468..34d7416b 100644 --- a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts @@ -14,6 +14,7 @@ import {Notification} from "../../../notifications/notifications"; import {NotificationHandler} from "../../../utils/notification-handler"; import {StatsProfilesService} from "../../utils/services/stats-profiles.service"; import {StakeholderBaseComponent} from "../../utils/stakeholder-base.component"; +import {StakeholderCategory} from "../../utils/indicator-utils"; @Component({ selector: 'edit-stakeholder', @@ -43,9 +44,8 @@ import {StakeholderBaseComponent} from "../../utils/stakeholder-base.component";
-
placeholder="Stats Profile">
@@ -150,13 +150,13 @@ import {StakeholderBaseComponent} from "../../utils/stakeholder-base.component"; export class EditStakeholderComponent extends StakeholderBaseComponent { @Input() public disableAlias: boolean = false; + public stakeholderCategory: StakeholderCategory; public stakeholderFb: UntypedFormGroup; public secure: boolean = false; public defaultStakeholdersOptions: Option[]; public defaultStakeholders: Stakeholder[]; public alias: string[]; public stakeholder: Stakeholder; - public isDefault: boolean; public isNew: boolean; public isFull: boolean; public loading: boolean = false; @@ -186,7 +186,7 @@ export class EditStakeholderComponent extends StakeholderBaseComponent { super.ngOnDestroy(); } - public init(stakeholder: Stakeholder, alias: string[], defaultStakeholders: Stakeholder[], isDefault: boolean, isNew: boolean, isFull: boolean = false) { + public init(stakeholder: Stakeholder, alias: string[], defaultStakeholders: Stakeholder[], stakeholderCategory: StakeholderCategory, isNew: boolean, isFull: boolean = false) { this.reset(); this.deleteCurrentPhoto = false; this.stakeholder = stakeholder; @@ -195,7 +195,7 @@ export class EditStakeholderComponent extends StakeholderBaseComponent { } this.alias = alias; this.defaultStakeholders = defaultStakeholders; - this.isDefault = isDefault; + this.stakeholderCategory = stakeholderCategory; this.isNew = isNew; this.isFull = isFull; this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { @@ -229,15 +229,13 @@ export class EditStakeholderComponent extends StakeholderBaseComponent { 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), funderType: this.fb.control(this.stakeholder.funderType), topics: this.fb.control(this.stakeholder.topics), isUpload: this.fb.control(this.stakeholder.isUpload), copy: this.fb.control(this.stakeholder.copy), - logoUrl: this.fb.control(this.stakeholder.logoUrl), - umbrella: this.fb.control(!!this.stakeholder.umbrella) + logoUrl: this.fb.control(this.stakeholder.logoUrl) }); if (this.stakeholder.isUpload) { this.stakeholderFb.get('logoUrl').clearValidators(); @@ -265,7 +263,6 @@ export class EditStakeholderComponent extends StakeholderBaseComponent { })); this.stakeholderFb.setControl('defaultId', this.fb.control(this.stakeholder.defaultId, (this.isDefault && !this.isNew) ? [] : Validators.required)); if (!this.isNew) { - this.stakeholderFb.get('umbrella').disable(); this.notification = NotificationUtils.editStakeholder(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); this.notify.reset(this.notification.message); if (this.isAdmin) { @@ -300,6 +297,10 @@ export class EditStakeholderComponent extends StakeholderBaseComponent { })); } + public get isDefault() { + return this.stakeholderCategory?.value === 'templates'; + } + public get isAdmin() { return Session.isPortalAdministrator(this.user); } @@ -324,7 +325,7 @@ export class EditStakeholderComponent extends StakeholderBaseComponent { public get canChangeCopy(): boolean { return this.isCurator && - !this.stakeholderFb.get('isDefault').getRawValue() && + !this.isDefault && this.stakeholderFb.get('defaultId').getRawValue() && this.stakeholderFb.get('defaultId').getRawValue() !== '-1'; } @@ -376,14 +377,16 @@ export class EditStakeholderComponent extends StakeholderBaseComponent { public saveStakeholder(callback: Function, errorCallback: Function = null) { if (this.isNew) { let copyId = null; - if (this.stakeholderFb.getRawValue().isDefault) { + if (this.isDefault) { copyId = this.stakeholderFb.getRawValue().defaultId !== '-1'?this.stakeholderFb.getRawValue().defaultId:null; this.stakeholderFb.get('defaultId').setValue(null); - this.stakeholderFb.removeControl('isDefault'); } this.removePhoto(); this.subscriptions.push(this.stakeholderService.buildStakeholder(this.properties.monitorServiceAPIURL, - this.stakeholderFb.getRawValue(), copyId, true).subscribe(stakeholder => { + this.stakeholderFb.getRawValue(), copyId, + this.stakeholderCategory.value !== 'dependent', + this.stakeholderCategory.value === 'umbrella') + .subscribe(stakeholder => { this.notification.entity = stakeholder._id; this.notification.stakeholder = stakeholder.alias; this.notification.stakeholderType = stakeholder.type; diff --git a/monitor-admin/general/general.component.ts b/monitor-admin/general/general.component.ts index 0c832fcf..c252f139 100644 --- a/monitor-admin/general/general.component.ts +++ b/monitor-admin/general/general.component.ts @@ -42,7 +42,7 @@ export class GeneralComponent extends BaseComponent implements OnInit { } public reset() { - this.editStakeholderComponent.init(this.stakeholder, this.alias, [], this.stakeholder.defaultId == null, false, true) + this.editStakeholderComponent.init(this.stakeholder, this.alias, [], null, false, true) } public save() { diff --git a/monitor-admin/manageStakeholders/manage-all.component.ts b/monitor-admin/manageStakeholders/manage-all.component.ts new file mode 100644 index 00000000..56c48cdf --- /dev/null +++ b/monitor-admin/manageStakeholders/manage-all.component.ts @@ -0,0 +1,131 @@ +import {Component, OnInit, QueryList, ViewChildren} from "@angular/core"; +import {ManageStakeholders} from "../../monitor/entities/stakeholder"; +import {Session} from "../../login/utils/helper.class"; +import {StakeholderBaseComponent} from "../utils/stakeholder-base.component"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {UserManagementService} from "../../services/user-management.service"; +import {ActivatedRoute} from "@angular/router"; +import {Title} from "@angular/platform-browser"; +import {Option} from "../../sharedComponents/input/input.component"; +import {zip} from "rxjs"; +import {StringUtils} from "../../utils/string-utils.class"; +import {StakeholderCategory} from "../utils/indicator-utils"; +import {ManageStakeholdersComponent} from "./manageStakeholders.component"; + +@Component({ + selector: `manage-all`, + template: ` +
+
+ +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+

+ No profiles to manage yet +

+
+ + + + +
+
+
+
+ ` +}) +export class ManageAllComponent extends StakeholderBaseComponent implements OnInit { + public loading: boolean = true; + public stakeholderCategories: StakeholderCategory[] = this.stakeholderUtils.stakeholderCategories; + public active: 'all' | 'templates' | 'standalone' | 'umbrella' | 'dependent' = 'all'; + public aliases: string[]; + public manageStakeholders: ManageStakeholders; + public user = null; + /** + * Filters + */ + public keyword: string; + public type: string = 'all'; + public types: Option[] = [{value: 'all', label: 'All'}].concat(this.stakeholderUtils.types); + @ViewChildren(ManageStakeholdersComponent) manageStakeholdersList: QueryList; + + public readonly StringUtils = StringUtils; + + constructor(private stakeholderService: StakeholderService, + private userManagementService: UserManagementService, + protected _route: ActivatedRoute, + protected _title: Title) { + super(); + } + + ngOnInit(): void { + this.title = 'Manage Profiles'; + this.setMetadata(); + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + if(!this.isManager()) { + this.stakeholderCategories = []; + } else if(!this.isCurator()) { + this.stakeholderCategories = this.stakeholderCategories.filter(category => category.value !== 'templates'); + } + })); + let data = zip( + this.stakeholderService.getMyStakeholders(this.properties.monitorServiceAPIURL), + this.stakeholderService.getAlias(this.properties.monitorServiceAPIURL) + ); + this.subscriptions.push(data.subscribe(res => { + this.manageStakeholders = res[0]; + this.aliases = res[1]; + this.loading = false; + }, error => { + this.loading = false; + })); + } + + public isManager(): boolean { + return this.isCurator() || (Session.isKindOfMonitorManager(this.user)); + } + + public isCurator(): boolean { + return this.isAdmin() || Session.isMonitorCurator(this.user); + } + + public isAdmin(): boolean { + return Session.isPortalAdministrator(this.user); + } + + get categories(): StakeholderCategory[] { + return this.stakeholderCategories.filter(category => category.value !== 'all' && + (this.active === 'all' || category.value === this.active)); + } +} diff --git a/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts b/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts deleted file mode 100644 index 03c01a41..00000000 --- a/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -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 index 797a1b3f..554c1dae 100644 --- a/monitor-admin/manageStakeholders/manageStakeholders.component.html +++ b/monitor-admin/manageStakeholders/manageStakeholders.component.html @@ -1,156 +1,81 @@ -
-
- -
- - - - - +
+
+
+

{{stakeholderCategory.plural}}

+ + +
+ +
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-

Profile Templates

- - -
-
- - - -
- +
+
+
+
+ + + + +
+
-
-
-

- No profiles to manage yet -

-
-
-
-

Profiles

- - -
- +
+
+
+
+ {{message}} +
+
+ + +
- - - - - -
-
- {{text}} -
-
- - - -
-
-
-
(); + /** + * Edit/Create/Delete + * */ + public message: string; + public deleteLoading: boolean = false; public stakeholder: Stakeholder; public index: number; + @Input() public user = null; - public tab: Tab = 'all'; - public currentPage: number = 1; - public currentTemplatesPage: number = 1; - public pageSize: number = 15; - public typeOptions: Option[]; - /** - * Filtered Stakeholders - */ - public displayDefaultStakeholders: Stakeholder[] = []; - public displayStakeholders: Stakeholder[] = []; - /** - * Top filters - */ - public filters: UntypedFormGroup; public callback: Function; - /** * Grid or List View */ @@ -57,133 +45,44 @@ export class ManageStakeholdersComponent extends StakeholderBaseComponent implem constructor(private stakeholderService: StakeholderService, private cacheIndicatorsService: CacheIndicatorsService, - private userManagementService: UserManagementService, - protected _route: ActivatedRoute, - protected _title: Title, - private fb: UntypedFormBuilder) { + protected cdr: ChangeDetectorRef) { super(); } ngOnInit(): void { - this.typeOptions = [{value: 'all', label: 'All'}].concat(this.stakeholderUtils.types); - this.buildFilters(); - this.title = 'Manage Profiles'; - this.setMetadata(); - this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { - this.user = user; - })); - let data = zip( - this.stakeholderService.getMyStakeholders(this.properties.monitorServiceAPIURL), - this.stakeholderService.getAlias(this.properties.monitorServiceAPIURL) - ); - this.subscriptions.push(data.subscribe(res => { - this.defaultStakeholders = res[0].templates; - this.stakeholders = res[0].standalone; - this.displayDefaultStakeholders = res[0].templates; - this.displayStakeholders = res[0].standalone; - this.alias = res[1]; - this.loading = false; - }, error => { - this.loading = false; - })); + super.ngOnInit(); + if(this.stakeholderCategory.value === 'templates') { + this.message = 'Create a new ' + this.stakeholderCategory.name + ' profile.'; + } else { + this.message = 'Create a new ' + this.stakeholderCategory.name + + ' profile by selecting a type and generate indicators based on a default or a blank profile.'; + } } hide(element: any) { UIkit.dropdown(element).hide(); } - - private buildFilters() { - this.filters = this.fb.group({ - status: this.fb.control('all'), - type: this.fb.control('all'), - keyword: this.fb.control('') - }); - this.subscriptions.push(this.filters.get('status').valueChanges.subscribe(value => { - this.filtering(); - })); - this.subscriptions.push(this.filters.get('type').valueChanges.subscribe(value => { - this.filtering(); - })); - this.subscriptions.push(this.filters.get('keyword').valueChanges.subscribe(value => { - this.filtering(); - })); + + changed() { + this.aliasesChange.emit(this.aliases); + super.changed(); } - private filterByStatus(stakeholders: Stakeholder[], value): Stakeholder[] { - if (value === 'all') { - return stakeholders; - } else { - return stakeholders.filter(stakeholder => stakeholder.visibility == value); - } - } - - private filterByType(stakeholders: Stakeholder[], value) { - if(value == 'all') { - return stakeholders; - } else { - return stakeholders.filter(item => item.type == 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()) - ); - } - } - - filtering() { - let keyword = this.filters.get('keyword').value; - let type = this.filters.get('type').value; - let status = this.filters.get('status').value; - let displayStakeholders = this.stakeholders; - let displayDefaultStakeholders = this.defaultStakeholders; - - displayStakeholders = this.filterByKeyword(displayStakeholders, keyword); - displayStakeholders = this.filterByType(displayStakeholders, type); - displayStakeholders = this.filterByStatus(displayStakeholders, status); - displayDefaultStakeholders = this.filterByKeyword(displayDefaultStakeholders, keyword); - displayDefaultStakeholders = this.filterByType(displayDefaultStakeholders, type); - displayDefaultStakeholders = this.filterByStatus(displayDefaultStakeholders, status); - - this.displayStakeholders = displayStakeholders; - this.displayDefaultStakeholders = displayDefaultStakeholders; - this.currentPage = 1; - this.currentTemplatesPage = 1; - } - - 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; - } + public editStakeholder(stakeholder: Stakeholder = null) { + 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); + this.editStakeholderComponent.init(this.stakeholder, this.aliases, this.defaultStakeholders, this.stakeholderCategory, 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; - } + let index: number = this.aliases.findIndex(value => value == this.stakeholders[this.index].alias); + this.stakeholders[this.index] = stakeholder; if(index != -1) { - this.alias[index] = stakeholder.alias; + this.aliases[index] = stakeholder.alias; } this.editStakeholderModal.cancel(); }; @@ -191,16 +90,12 @@ export class ManageStakeholdersComponent extends StakeholderBaseComponent implem 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.stakeholders.push(stakeholder); + this.aliases.push(stakeholder.alias) + this.changed(); this.editStakeholderModal.cancel(); - this.filtering(); }; - this.editStakeholderModal.alertTitle = 'Create a new ' + (isDefault?'Default ':'') + 'Profile'; + this.editStakeholderModal.alertTitle = 'Create a new ' + this.stakeholderCategory.name + ' profile'; this.editStakeholderModal.okButtonText = 'Create'; } this.editStakeholderModal.cancelButtonText = 'Cancel'; @@ -231,26 +126,18 @@ export class ManageStakeholdersComponent extends StakeholderBaseComponent implem 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.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.stakeholders.splice(this.index, 1); + this.aliases = this.aliases.filter(item => item !== this.stakeholder.alias); + this.changed(); this.deleteLoading = false; this.deleteStakeholderModal.cancel(); - this.filtering(); }, error => { UIkit.notification('An error has occurred. Please try again later', { status: 'danger', @@ -281,33 +168,16 @@ export class ManageStakeholdersComponent extends StakeholderBaseComponent implem }); })); } - - public isManager(): boolean { - return this.isCurator() || (Session.isKindOfMonitorManager(this.user)); - } - - public isProfileManager(stakeholder: Stakeholder): boolean { + + public isManager(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 - } - - public updateCurrentPage($event) { - this.currentPage = $event.value; - } - - public updateCurrentTemplatesPage($event) { - this.currentTemplatesPage = $event.value; - } } diff --git a/monitor-admin/manageStakeholders/manageStakeholders.module.ts b/monitor-admin/manageStakeholders/manageStakeholders.module.ts index 2d2bd224..77087917 100644 --- a/monitor-admin/manageStakeholders/manageStakeholders.module.ts +++ b/monitor-admin/manageStakeholders/manageStakeholders.module.ts @@ -1,6 +1,4 @@ import {NgModule} from "@angular/core"; -import {ManageStakeholdersComponent} from "./manageStakeholders.component"; -import {ManageStakeholdersRoutingModule} from "./manageStakeholders-routing.module"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; import {InputModule} from "../../sharedComponents/input/input.module"; @@ -19,11 +17,20 @@ import { import {SliderTabsModule} from "../../sharedComponents/tabs/slider-tabs.module"; import {EditStakeholderModule} from "../general/edit-stakeholder/edit-stakeholder.module"; import {PagingModule} from "../../utils/paging.module"; +import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard"; +import {ManageAllComponent} from "./manage-all.component"; +import {ManageStakeholdersComponent} from "./manageStakeholders.component"; @NgModule({ - declarations: [ManageStakeholdersComponent], + declarations: [ManageAllComponent, ManageStakeholdersComponent], imports: [ - ManageStakeholdersRoutingModule, + RouterModule.forChild([ + { + path: '', + component: ManageAllComponent, + canDeactivate: [PreviousRouteRecorder] + } + ]), CommonModule, RouterModule, InputModule, @@ -40,7 +47,7 @@ import {PagingModule} from "../../utils/paging.module"; PagingModule ], providers: [], - exports: [ManageStakeholdersComponent] + exports: [ManageAllComponent] }) export class ManageStakeholdersModule { constructor(private iconsService: IconsService) { diff --git a/monitor-admin/shared/filtered-stakeholders-base.component.ts b/monitor-admin/shared/filtered-stakeholders-base.component.ts new file mode 100644 index 00000000..35809631 --- /dev/null +++ b/monitor-admin/shared/filtered-stakeholders-base.component.ts @@ -0,0 +1,96 @@ +import { + ChangeDetectorRef, + Directive, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges +} from "@angular/core"; +import {StakeholderBaseComponent} from "../utils/stakeholder-base.component"; +import {StakeholderCategory} from "../utils/indicator-utils"; +import {Stakeholder, StakeholderType} from "../../monitor/entities/stakeholder"; + +@Directive() +export class FilteredStakeholdersBaseComponent extends StakeholderBaseComponent implements OnInit, OnChanges { + /** + * Filtering + * */ + @Input() + public keyword: string; + @Input() + public type: StakeholderType | 'all' = 'all'; + /** Stakeholders */ + @Input() + public stakeholderCategory: StakeholderCategory; + @Input() + public stakeholders: Stakeholder[]; + @Output() + public stakeholdersChange: EventEmitter = new EventEmitter(); + /** + * Filtered Stakeholders + */ + public filteredStakeholders: Stakeholder[] = []; + public currentPage: number = 1; + public pageSize: number = 16; + + protected cdr: ChangeDetectorRef + + constructor() { + super(); + } + + ngOnInit(): void { + this.filterByKeyword(); + this.filterByType(); + } + + ngOnChanges(changes: SimpleChanges) { + if(changes.type) { + this.filterByType(); + } + if(changes.keyword) { + this.filterByKeyword(); + } + if(changes.stakeholders) { + this.filterByKeyword(); + this.filterByType(); + this.cdr.detectChanges(); + } + } + + changed() { + this.stakeholdersChange.emit(this.stakeholders); + this.filterByType(); + this.filterByKeyword(); + } + + public filterByType() { + if(this.type == 'all') { + this.filteredStakeholders = this.stakeholders; + } else { + this.filteredStakeholders = this.stakeholders.filter(item => item.type == this.type); + } + this.currentPage = 1; + } + + public filterByKeyword(): void{ + if (!this.keyword) { + this.filteredStakeholders = this.stakeholders; + } else { + this.filteredStakeholders = this.stakeholders.filter(stakeholder => + stakeholder.name?.toLowerCase().includes(this.keyword.toLowerCase()) || + stakeholder.alias?.toLowerCase().includes(this.keyword.toLowerCase()) || + stakeholder.index_id?.toLowerCase().includes(this.keyword.toLowerCase()) || + stakeholder.index_shortName?.toLowerCase().includes(this.keyword.toLowerCase()) || + stakeholder.index_name?.toLowerCase().includes(this.keyword.toLowerCase()) + ); + } + this.currentPage = 1; + } + + public updateCurrentPage($event) { + this.currentPage = $event.value; + } +} diff --git a/monitor-admin/topic/topic.component.html b/monitor-admin/topic/topic.component.html index 9e7e1bed..28285dd6 100644 --- a/monitor-admin/topic/topic.component.html +++ b/monitor-admin/topic/topic.component.html @@ -227,8 +227,7 @@
- +
  • diff --git a/monitor-admin/umbrella/filtered-stakeholders.component.ts b/monitor-admin/umbrella/filtered-stakeholders.component.ts new file mode 100644 index 00000000..0a7cda79 --- /dev/null +++ b/monitor-admin/umbrella/filtered-stakeholders.component.ts @@ -0,0 +1,92 @@ +import {ChangeDetectorRef, Component, EventEmitter, Input, Output} from "@angular/core"; +import {FilteredStakeholdersBaseComponent} from "../shared/filtered-stakeholders-base.component"; + +@Component({ + selector: 'filtered-stakeholders', + template: ` +
    + +
    +
    +
    +
    +
    + + + +
    +
    +
    +

    + {{ stakeholder.name }} +

    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    No {{ entities.stakeholders }} have been found.
    +
    +
    +
    + ` +}) +export class FilteredStakeholdersComponent extends FilteredStakeholdersBaseComponent { + @Output() + public added: EventEmitter = new EventEmitter(); + @Output() + public removed: EventEmitter = new EventEmitter(); + @Input() + public add: boolean = false; + @Input() + public remove: boolean = false; + + constructor(protected cdr: ChangeDetectorRef) { + super(); + } +} diff --git a/monitor-admin/umbrella/umbrella.component.ts b/monitor-admin/umbrella/umbrella.component.ts new file mode 100644 index 00000000..7a799544 --- /dev/null +++ b/monitor-admin/umbrella/umbrella.component.ts @@ -0,0 +1,329 @@ +import {ChangeDetectorRef, Component, OnInit, ViewChild} from "@angular/core"; +import {StakeholderBaseComponent} from "../utils/stakeholder-base.component"; +import {ActivatedRoute, Router} from "@angular/router"; +import {Title} from "@angular/platform-browser"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {ManageStakeholders, Stakeholder, StakeholderType} from "../../monitor/entities/stakeholder"; +import {Session, User} from "../../login/utils/helper.class"; +import {UserManagementService} from "../../services/user-management.service"; +import {FormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; +import {AlertModal} from "../../utils/modal/alert"; +import {NotificationHandler} from "../../utils/notification-handler"; +import {Option} from "../../sharedComponents/input/input.component"; +import {IDeactivateComponent} from "../../utils/can-exit.guard"; +import {FullScreenModalComponent} from "../../utils/modal/full-screen-modal/full-screen-modal.component"; +import {map} from "rxjs/operators"; +import {StakeholderCategory} from "../utils/indicator-utils"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; + +declare var UIkit; + +@Component({ + selector: 'umbrella', + template: ` +
    + +
    + +
    +
    +
    +
    No types yet. Add a type to start.
    +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    + +
    + You are about to remove {{ entities[umbrella.types[index]] }} from the + umbrella of {{ stakeholder.name }}. + All the profiles added to this type will be removed too. + Are you sure you want to proceed? +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + ` +}) +export class UmbrellaComponent extends StakeholderBaseComponent implements OnInit, IDeactivateComponent { + public loading: boolean = false; + public stakeholder: Stakeholder; + public user: User; + public types: Option[] = []; + public manageStakeholders: ManageStakeholders; + public activeIndex: number = 0; + public index: number; + public updateFb: UntypedFormGroup; + public keyword: string; + @ViewChild('addTypeModal', {static: true}) addTypeModal: AlertModal; + @ViewChild('deleteTypeModal', {static: true}) deleteTypeModal: AlertModal; + @ViewChild('manageStakeholdersModal', {static: true}) manageStakeholdersModal: FullScreenModalComponent; + + constructor(protected _route: ActivatedRoute, + protected _router: Router, + protected _title: Title, + private fb: FormBuilder, + private userManagementService: UserManagementService, + private stakeholderService: StakeholderService, + private cdr: ChangeDetectorRef) { + super(); + } + + ngOnInit(): void { + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + })); + this.subscriptions.push(this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => { + if (stakeholder) { + this.stakeholder = stakeholder; + if (!this.stakeholder.umbrella) { + this.navigateToError(); + } else { + this.umbrella = stakeholder.umbrella; + } + this.title = stakeholder.name + " | Manage Umbrella"; + this.setMetadata(); + } + })); + } + + canExit(): boolean { + this.stakeholderService.setStakeholder(this.stakeholder); + return true; + } + + resetUpdateForm(action: "ADD" | "REMOVE" = "ADD", type: StakeholderType = null, child: string = null): void { + this.updateFb = this.fb.group({ + type: this.fb.control(type, Validators.required), + action: this.fb.control(action, Validators.required), + }); + if (child) { + this.updateFb.addControl("child", this.fb.control(child, Validators.required)); + } + } + + addTypeOpen(): void { + this.resetUpdateForm(); + this.addTypeModal.cancelButtonText = 'Cancel'; + this.addTypeModal.okButtonLeft = false; + this.addTypeModal.alertMessage = false; + this.addTypeModal.alertTitle = 'Add a type'; + this.addTypeModal.stayOpen = true; + this.addTypeModal.open(); + } + + deleteTypeOpen(index: number): void { + this.index = index; + this.resetUpdateForm("REMOVE", this.umbrella.types[this.index]); + this.deleteTypeModal.cancelButtonText = 'No'; + this.deleteTypeModal.okButtonText = 'Yes'; + this.deleteTypeModal.alertTitle = 'Remove ' + this.umbrella.types[this.index]; + this.deleteTypeModal.stayOpen = true; + this.deleteTypeModal.open(); + } + + addType() { + this.loading = true; + this.updateUmbrella(this.addTypeModal); + } + + deleteType() { + this.loading = true; + this.updateUmbrella(this.deleteTypeModal); + } + + addStakeholder(id: string) { + this.resetUpdateForm('ADD', this.activeType, id); + this.updateUmbrella(); + } + + removeStakeholder(id: string) { + this.resetUpdateForm('REMOVE', this.activeType, id); + this.updateUmbrella(); + } + + public manageStakeholdersOpen() { + this.manageStakeholdersModal.title = 'Manage ' + this.entities.stakeholders; + this.manageStakeholdersModal.okButtonText = "Done"; + this.manageStakeholdersModal.okButton = true; + this.setManageStakeholders(); + this.manageStakeholdersModal.open(); + } + + setManageStakeholders() { + this.loading = true; + this.subscriptions.push(this.stakeholderService.getMyStakeholders(this.properties.monitorServiceAPIURL, this.activeType).pipe(map(manageStakeholders => { + delete manageStakeholders.templates; + delete manageStakeholders.umbrella; + return manageStakeholders; + })).subscribe(manageStakeholders => { + this.filterManagedStakeholders(manageStakeholders); + this.manageStakeholders.standalone.sort(this.sort); + this.manageStakeholders.dependent.sort(this.sort); + this.loading = false; + })); + } + + filterManagedStakeholders(manageStakeholders: ManageStakeholders) { + manageStakeholders.standalone.forEach(stakeholder => { + stakeholder['added'] = this.children.findIndex(child => child._id === stakeholder._id) !== -1; + }); + manageStakeholders.dependent.forEach(stakeholder => { + stakeholder['added'] = this.children.findIndex(child => child._id === stakeholder._id) !== -1; + }); + this.manageStakeholders = manageStakeholders; + } + + sort(a: any, b: any): number { + if (a['added'] && !b['added']) { + return -1; + } else if (!a['added'] && b['added']) { + return 1; + } else { + return 0 + } + } + + updateUmbrella(modal: AlertModal = null) { + this.subscriptions.push(this.stakeholderService.updateUmbrella(this.properties.monitorServiceAPIURL, this.stakeholder._id, this.updateFb.getRawValue()) + .subscribe(umbrella => { + this.loading = false; + if(modal) { + modal.cancel(); + } + this.umbrella = umbrella; + if(!this.activeType) { + this.activeIndex = 0; + } + this.filterManagedStakeholders(this.manageStakeholders); + this.cdr.detectChanges(); + }, error => { + this.loading = false; + if(modal) { + modal.cancel(); + } + NotificationHandler.rise(error.error.message, 'danger'); + })); + } + + hide(element: any) { + UIkit.dropdown(element).hide(); + } + + chooseType(index: number) { + this.activeIndex = index; + } + + get umbrella() { + return this.stakeholder.umbrella; + } + + get activeType(): StakeholderType { + return this.umbrella.types[this.activeIndex]; + } + + set umbrella(umbrella) { + this.stakeholder.umbrella = HelperFunctions.copy(umbrella); + this.types = this.stakeholderUtils.types.filter(type => !umbrella.types.includes(type.value)); + } + + get isCurator(): boolean { + return Session.isPortalAdministrator(this.user) || Session.isCurator(this.stakeholder.type, this.user); + } + + get children(): Stakeholder[] { + if (this.activeType) { + return this.umbrella.children[this.activeType].map(stakeholder => { + stakeholder['added'] = true; + return stakeholder; + }); + } + return null; + } + + get categories(): StakeholderCategory[] { + return this.stakeholderUtils.stakeholderCategories.filter(category => category.value === 'standalone' || category.value === 'dependent'); + } +} diff --git a/monitor-admin/umbrella/umbrella.module.ts b/monitor-admin/umbrella/umbrella.module.ts new file mode 100644 index 00000000..a07ecae1 --- /dev/null +++ b/monitor-admin/umbrella/umbrella.module.ts @@ -0,0 +1,26 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {RouterModule} from "@angular/router"; +import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard"; +import {UmbrellaComponent} from "./umbrella.component"; +import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module"; +import {IconsModule} from "../../utils/icons/icons.module"; +import {AlertModalModule} from "../../utils/modal/alertModal.module"; +import {LoadingModule} from "../../utils/loading/loading.module"; +import {ReactiveFormsModule} from "@angular/forms"; +import {InputModule} from "../../sharedComponents/input/input.module"; +import {CanExitGuard} from "../../utils/can-exit.guard"; +import {FullScreenModalModule} from "../../utils/modal/full-screen-modal/full-screen-modal.module"; +import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module"; +import {PagingModule} from "../../utils/paging.module"; +import {FilteredStakeholdersComponent} from "./filtered-stakeholders.component"; +import {SearchInputModule} from "../../sharedComponents/search-input/search-input.module"; + +@NgModule({ + declarations: [UmbrellaComponent, FilteredStakeholdersComponent], + imports: [CommonModule, RouterModule.forChild([ + {path: '', component: UmbrellaComponent, canDeactivate: [PreviousRouteRecorder, CanExitGuard]} + ]), PageContentModule, IconsModule, AlertModalModule, LoadingModule, ReactiveFormsModule, InputModule, FullScreenModalModule, LogoUrlPipeModule, PagingModule, SearchInputModule], + exports: [UmbrellaComponent] +}) +export class UmbrellaModule {} diff --git a/monitor-admin/utils/indicator-utils.ts b/monitor-admin/utils/indicator-utils.ts index d21f7df2..6e516ca1 100644 --- a/monitor-admin/utils/indicator-utils.ts +++ b/monitor-admin/utils/indicator-utils.ts @@ -43,9 +43,25 @@ export interface OAIndicator { denominator: IndicatorPath; } +export interface StakeholderCategory { + name: string, + plural: string, + value: 'all' | 'templates' | 'standalone' | 'umbrella' | 'dependent' + tooltip?: string +} + export class StakeholderConfiguration { public static ENTITIES: Entities = new Entities(); + public static STAKEHOLDER_CATEGORIES: StakeholderCategory[] = [ + {name: 'All', plural: 'All', value: 'all'}, + {name: 'Template', plural: 'Templates', value: 'templates'}, + {name: 'Standalone', plural: 'Standalone', value: 'standalone'}, + {name: 'Umbrella', plural: 'Umbrella', value: 'umbrella'}, + {name: 'Integrated ', plural: 'Integrated', value: 'dependent', + tooltip: 'A profile that doesn\'t have his own ' + StakeholderConfiguration.ENTITIES.stakeholder + + ', but can be integrated into another ' + StakeholderConfiguration.ENTITIES.stakeholder + '.'} + ]; public static TYPES: Option[] = [ {value: 'funder', label: StakeholderConfiguration.ENTITIES.funder}, {value: 'ri', label: StakeholderConfiguration.ENTITIES.ri}, @@ -68,6 +84,7 @@ export class StakeholderConfiguration { public static NUMBER_MULTI_INDICATOR_PATHS = false; public static CHART_MULTI_INDICATOR_PATHS = true; public static openAccess: Map = new Map(); + } export class StakeholderUtils { @@ -75,8 +92,12 @@ export class StakeholderUtils { return StakeholderConfiguration.ENTITIES; } + get stakeholderCategories(): StakeholderCategory[] { + return StakeholderConfiguration.STAKEHOLDER_CATEGORIES; + } + get types() { - return StakeholderConfiguration.TYPES + return StakeholderConfiguration.TYPES; } get funderTypes() { @@ -560,9 +581,9 @@ export class IndicatorUtils { } else if (filterType == "start_year" || filterType == "end_year") { //if has date filter already // if (filterType == "start_year" && parseInt(filterValue) > parseInt(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0])) { - queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; + queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; // } else if (filterType == "end_year" && parseInt(filterValue) < parseInt(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0])) { - queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; + queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; // } filterApplied = true; } @@ -887,6 +908,8 @@ export class IndicatorUtils { return ChartHelper.prefix + "index_id" + ChartHelper.suffix; } else if (currentValue == stakeholder.index_shortName) { return ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + } else { + return null; } } diff --git a/monitor/entities/stakeholder.ts b/monitor/entities/stakeholder.ts index be5c0727..0cf78950 100644 --- a/monitor/entities/stakeholder.ts +++ b/monitor/entities/stakeholder.ts @@ -16,7 +16,7 @@ export type Format = 'NUMBER' | 'PERCENTAGE'; export type Visibility = 'PUBLIC' | 'PRIVATE' | 'RESTRICTED'; export type Overlay = 'embed' | 'description' | false; -export class ManageStakeholder { +export class ManageStakeholders { templates: Stakeholder[]; standalone: Stakeholder[]; dependent: Stakeholder[]; @@ -93,8 +93,8 @@ export class StakeholderInfo extends Stakeholder { } export class Umbrella { - types: string[]; - children: any[]; + types: StakeholderType[]; + children: any; } export class Topic { diff --git a/monitor/services/hasDashboard.guard.ts b/monitor/services/hasDashboard.guard.ts new file mode 100644 index 00000000..666d8255 --- /dev/null +++ b/monitor/services/hasDashboard.guard.ts @@ -0,0 +1,41 @@ +import {Injectable} from "@angular/core"; +import {ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree} from "@angular/router"; +import {Observable} from "rxjs"; +import {map, take, tap} from "rxjs/operators"; +import {StakeholderService} from "./stakeholder.service"; +import {properties} from "../../../../environments/environment"; +import {LinksResolver} from "../../../search/links-resolver"; + +@Injectable({ + providedIn: 'root' +}) +export class HasDashboardGuard { + + constructor(protected router: Router, + protected stakeholderService: StakeholderService) { + } + + check(path: string, alias: string, type: string, child: string): Observable | boolean { + let get = this.stakeholderService.getStakeholder(alias); + if(child) { + get = this.stakeholderService.getChildStakeholder(alias, type, child); + } + return get.pipe(take(1), map(stakeholder => { + return stakeholder.standalone || (!!stakeholder && !!child); + }),tap(authorized => { + if(!authorized){ + this.router.navigate([LinksResolver.default.errorLink], {queryParams: {'page': path}}); + } + })); + } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return this.check(state.url, route.params.stakeholder, route.params.type, route.params.child); + } + + canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return this.check(state.url, childRoute.data?.stakeholder?childRoute.data.stakeholder:childRoute.params.stakeholder, + childRoute.params.type, childRoute.params.child); + } + +} diff --git a/monitor/services/stakeholder.service.ts b/monitor/services/stakeholder.service.ts index 9c398c6c..fc582976 100644 --- a/monitor/services/stakeholder.service.ts +++ b/monitor/services/stakeholder.service.ts @@ -2,12 +2,11 @@ import {Injectable} from "@angular/core"; import {HttpClient} from "@angular/common/http"; import {BehaviorSubject, from, Observable, Subscriber} from "rxjs"; import { - Indicator, - ManageStakeholder, + Indicator, ManageStakeholders, Section, Stakeholder, - StakeholderInfo, - SubCategory, + StakeholderInfo, StakeholderType, + SubCategory, Umbrella, Visibility } from "../entities/stakeholder"; import {HelperFunctions} from "../../utils/HelperFunctions.class"; @@ -26,6 +25,12 @@ export interface MoveIndicator { to: SectionInfo; } +export interface UpdateUmbrella { + type: StakeholderType; + action: "ADD" | "REMOVE", + child: string +} + @Injectable({ providedIn: "root" }) @@ -55,11 +60,7 @@ export class StakeholderService { this.sub = this.http.get(properties.monitorServiceAPIURL + '/stakeholder/' + encodeURIComponent(alias), CustomOptions.registryOptions()).pipe(map(stakeholder => { return HelperFunctions.copy(Stakeholder.checkIsUpload(stakeholder)); })).subscribe(stakeholder => { - if (stakeholder.standalone) { - this.stakeholderSubject.next(stakeholder); - } else { - this.stakeholderSubject.next(null); - } + this.stakeholderSubject.next(stakeholder); resolve(); }, error => { this.stakeholderSubject.next(null); @@ -133,8 +134,8 @@ export class StakeholderService { })); } - getMyStakeholders(url: string, type: string = null): Observable { - return this.http.get(url + '/my-stakeholder' + ((type) ? ('?type=' + type) : ''), CustomOptions.registryOptions()).pipe(map(manageStakeholder => { + getMyStakeholders(url: string, type: string = null): Observable { + return this.http.get(url + '/my-stakeholder' + ((type) ? ('?type=' + type) : ''), CustomOptions.registryOptions()).pipe(map(manageStakeholder => { return HelperFunctions.copy({ templates: Stakeholder.checkIsUpload(manageStakeholder.templates), standalone: Stakeholder.checkIsUpload(manageStakeholder.standalone), @@ -144,14 +145,15 @@ export class StakeholderService { })); } - buildStakeholder(url: string, stakeholder: Stakeholder, copyId: string, umbrella: boolean = false): Observable { + buildStakeholder(url: string, stakeholder: Stakeholder, copyId: string, standalone: boolean = true, umbrella: boolean = false): Observable { if (stakeholder.alias && stakeholder.alias.startsWith('/')) { stakeholder.alias = stakeholder.alias.slice(1); } let buildStakeholder = { stakeholder: stakeholder, copyId: copyId, - umbrella: umbrella + umbrella: umbrella, + standalone: standalone } return this.http.post(url + '/build-stakeholder', buildStakeholder, CustomOptions.registryOptions()).pipe(map(stakeholder => { return HelperFunctions.copy(Stakeholder.checkIsUpload(stakeholder)); @@ -218,6 +220,12 @@ export class StakeholderService { })); } + updateUmbrella(url: string, id: string, update: UpdateUmbrella): Observable { + return this.http.post(url + '/' + id + '/umbrella', update, CustomOptions.registryOptions()).pipe(map(umbrella => { + return HelperFunctions.copy(umbrella); + })); + } + getStakeholderAsObservable(): Observable { return this.stakeholderSubject.asObservable(); }