[umbrella]: Change manage stakeholders to support umbrella and dependent profiles. Add umbrella sidebar menu item in admin. Add hasDashboard guard to handle depdendent stakeholders. Add filtered-stakeholders-base component and use it in manage stakehodlerds and umbrella components.

This commit is contained in:
Konstantinos Triantafyllou 2024-06-11 17:32:16 +03:00
parent 472eeef326
commit 96e63ae8c0
17 changed files with 916 additions and 383 deletions

View File

@ -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;
}
}

View File

@ -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";
</div>
<ng-container *ngIf="isCurator">
<div class="uk-width-1-3@m">
<div *ngIf="statsProfiles" input [formInput]="stakeholderFb.get('statsProfile')"
[type]="'select'"
[options]="statsProfiles"
<div input [formInput]="stakeholderFb.get('statsProfile')"
[type]="'select'" [options]="statsProfiles" [disabled]="statsProfiles?.length === 0">
placeholder="Stats Profile"></div>
</div>
<div class="uk-width-1-3@m">
@ -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;

View File

@ -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() {

View File

@ -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: `
<div page-content>
<div header class="uk-margin-bottom">
<sidebar-mobile-toggle class="uk-margin-top uk-hidden@m uk-display-block"></sidebar-mobile-toggle>
<div *ngIf="stakeholderCategories.length > 2" class="uk-margin-remove-bottom uk-margin-medium-top">
<slider-tabs [type]="'dynamic'" (activeEmitter)="active = $event">
<slider-tab *ngFor="let category of stakeholderCategories" [tabTitle]="category.plural"
[tabId]="category.value" [active]="active === category.value"></slider-tab>
</slider-tabs>
</div>
</div>
<div actions>
<div class="uk-section-xsmall">
<div class="uk-flex uk-flex-center uk-flex-wrap uk-flex-middle uk-flex-between@m">
<div class="uk-width-medium">
<div input type="select" placeholder="Type" [disabled]="loading"
[options]="types" [(value)]="type">
</div>
</div>
<div search-input [(value)]="keyword" [expandable]="true"
[disabled]="loading" [placeholder]="'Search ' + entities.stakeholders" searchInputClass="outer"
class="uk-width-1-3@xl uk-width-2-5@l uk-width-1-2@m uk-width-1-1 uk-flex uk-flex-right"></div>
</div>
</div>
</div>
<div inner>
<div *ngIf="loading" class="uk-margin-medium-top uk-padding-large message">
<loading></loading>
</div>
<div *ngIf="!loading" uk-height-match="target: .titleContainer; row: false">
<div uk-height-match="target: .logoContainer; row: false">
<div *ngIf="!isManager()" class="message">
<h4 class="uk-text-center">
No profiles to manage yet
</h4>
</div>
<ng-container *ngIf="isManager()">
<manage-stakeholders *ngFor="let category of categories"
[(aliases)]="aliases" [stakeholderCategory]="category"
[user]="user" [keyword]="keyword" [type]="type"
[defaultStakeholders]="manageStakeholders.templates"
[(stakeholders)]="manageStakeholders[category.value]">
</manage-stakeholders>
</ng-container>
</div>
</div>
</div>
</div>
`
})
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<ManageStakeholdersComponent>;
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));
}
}

View File

@ -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 {
}

View File

@ -1,156 +1,81 @@
<div page-content>
<div header>
<sidebar-mobile-toggle class="uk-margin-top uk-hidden@m uk-display-block"></sidebar-mobile-toggle>
<div *ngIf="isCurator()" class="uk-margin-remove-bottom uk-margin-medium-top">
<slider-tabs [type]="'dynamic'" (activeEmitter)="tab = $event">
<slider-tab tabTitle="All" [tabId]="'all'" [active]="tab === 'all'"></slider-tab>
<slider-tab tabTitle="Profile templates" [tabId]="'templates'"
[active]="tab === 'templates'"></slider-tab>
<slider-tab tabTitle="Profiles" [tabId]="'profiles'" [active]="tab === 'profiles'"></slider-tab>
</slider-tabs>
<div class="uk-section">
<div class="uk-flex uk-flex-middle uk-flex-between uk-margin-bottom">
<div class="uk-flex uk-flex-middle">
<h4 class="uk-margin-remove">{{stakeholderCategory.plural}}</h4>
<a *ngIf="stakeholderCategory.tooltip" class="uk-margin-small-left uk-button uk-button-link" [attr.uk-tooltip]="stakeholderCategory.tooltip">
<icon name="info" [flex]="true" [ratio]="0.8"></icon>
</a>
</div>
<paging-no-load *ngIf="filteredStakeholders?.length > pageSize"
(pageChange)="updateCurrentPage($event)"
[currentPage]="currentPage" [size]="pageSize"
[totalResults]="filteredStakeholders.length">
</paging-no-load>
</div>
<div actions>
<div class="uk-section-xsmall">
<div class="uk-flex uk-flex-center uk-flex-wrap uk-flex-middle uk-flex-between@m">
<div class="uk-width-medium uk-margin-small-bottom">
<div input type="select" placeholder="Type" [disabled]="loading"
[options]="typeOptions" [formInput]="filters.get('type')">
</div>
</div>
<div search-input [searchControl]="filters.get('keyword')" [expandable]="true" [disabled]="loading"
placeholder="Search Profiles" searchInputClass="outer"
class="uk-width-1-3@xl uk-width-2-5@l uk-width-1-2@m uk-width-1-1 uk-flex uk-flex-right"></div>
</div>
</div>
</div>
<div inner>
<div *ngIf="loading" class="uk-margin-medium-top uk-padding-large">
<loading></loading>
</div>
<div *ngIf="!loading" uk-height-match="target: .titleContainer; row: false">
<div uk-height-match="target: .logoContainer; row: false">
<div *ngIf="tab != 'profiles' && isCurator()" class="uk-section">
<div class="uk-flex uk-flex-middle uk-flex-between uk-margin-bottom">
<h4 class="uk-margin-remove">Profile Templates</h4>
<paging-no-load *ngIf="displayDefaultStakeholders?.length > pageSize"
(pageChange)="updateCurrentTemplatesPage($event)"
[currentPage]="currentTemplatesPage" [size]="pageSize"
[totalResults]="displayDefaultStakeholders.length">
</paging-no-load>
</div>
<div class="uk-grid uk-child-width-1-3@l uk-child-width-1-2@m uk-child-width-1-1 uk-grid-match"
uk-grid>
<ng-template ngFor
[ngForOf]="displayDefaultStakeholders.slice((currentTemplatesPage-1)*pageSize, currentTemplatesPage*pageSize)"
let-stakeholder>
<ng-container
*ngTemplateOutlet="stakeholderBox; context: {stakeholder:stakeholder}"></ng-container>
</ng-template>
<div *ngIf="!loading && isCurator()">
<ng-container
*ngTemplateOutlet="newBox; context: {text:'Create a new default profile.', isDefault:true}"></ng-container>
<div class="uk-grid uk-grid-small uk-child-width-1-4@l uk-child-width-1-3@m uk-child-width-1-1 uk-grid-match" uk-grid>
<div *ngFor="let stakeholder of filteredStakeholders.slice((currentPage-1)*pageSize, currentPage*pageSize)">
<div class="uk-card uk-card-default uk-card-body uk-position-relative" [ngClass]="stakeholder.type">
<div class="uk-position-top-right uk-margin-small-right uk-margin-small-top">
<a class="uk-link-reset uk-flex uk-flex-middle">
<icon *ngIf="showVisibility" [flex]="true"
[name]="stakeholderUtils.visibilityIcon.get(stakeholder.visibility)" ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element class="uk-dropdown" uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0;">
<ul class="uk-nav uk-dropdown-nav">
<li>
<a (click)="editStakeholder(stakeholder); hide(element)">Edit</a>
</li>
<li *ngIf="isCurator && stakeholderUtils.isCachingIndicators">
<a (click)="createReport(stakeholder);hide(element)">Cache Indicators</a>
</li>
<li *ngIf="showVisibility" class="uk-nav-divider"></li>
<ng-template *ngIf="showVisibility" ngFor [ngForOf]="stakeholderUtils.visibilities" let-v>
<li [class.uk-active]="stakeholder.visibility === v.value">
<a (click)="changeStakeholderStatus(stakeholder, v.value);hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="stakeholder.visibility === v.value" [flex]="true" name="done"
class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<hr *ngIf="isManager(stakeholder)" class="uk-nav-divider">
<li *ngIf="isManager(stakeholder)"><a
(click)="deleteStakeholderOpen(stakeholder);hide(element)">Delete</a>
</li>
</ul>
</div>
</div>
</div>
<div *ngIf="!isManager()" class="message">
<h4 class="uk-text-center">
No profiles to manage yet
</h4>
</div>
<div *ngIf="tab != 'templates' && isManager()" class="uk-section">
<div class="uk-flex uk-flex-middle uk-flex-between uk-margin-bottom">
<h4 class="uk-margin-remove">Profiles</h4>
<paging-no-load *ngIf="displayStakeholders?.length > pageSize"
(pageChange)="updateCurrentPage($event)"
[currentPage]="currentPage" [size]="pageSize"
[totalResults]="displayStakeholders.length">
</paging-no-load>
</div>
<div class="uk-grid uk-grid-match uk-child-width-1-3@l uk-child-width-1-2@m uk-child-width-1-1"
uk-grid>
<ng-template ngFor
[ngForOf]="displayStakeholders.slice((currentPage-1)*pageSize, currentPage*pageSize)"
let-stakeholder>
<ng-container
*ngTemplateOutlet="stakeholderBox; context: {stakeholder:stakeholder}"></ng-container>
</ng-template>
<div *ngIf="!loading && isCurator()">
<ng-container *ngTemplateOutlet="newBox; context: {text:'Create a new profile by selecting the type ('+typesAsString+') and ' +
'select indicators based on a default or a blank profile.', isDefault:false}"></ng-container>
<a class="uk-display-block uk-text-center uk-link-reset" [routerLink]="'/admin/' + stakeholder.alias">
<div class="titleContainer uk-h6 uk-margin-remove-bottom uk-margin-top multi-line-ellipsis lines-2">
<p *ngIf="stakeholder.name" class="uk-margin-remove">
{{stakeholder.name}}
</p>
</div>
</div>
<div class="logoContainer uk-margin-top uk-flex uk-flex-column uk-flex-center uk-flex-middle">
<img [src]="stakeholder | logoUrl" class="uk-blend-multiply" style="max-height: 60px;">
</div>
</a>
</div>
</div>
<div *ngIf="isCurator()">
<div class="uk-card uk-card-default uk-text-center uk-card-body clickable"
(click)="editStakeholder(null)">
<div class="uk-text-small uk-text-muted">
{{message}}
</div>
<div class="uk-margin-top uk-margin-small-bottom">
<span class="uk-text-secondary">
<icon name="add" [ratio]="3"></icon>
</span>
</div>
</div>
</div>
</div>
</div>
<ng-template #stakeholderBox let-stakeholder="stakeholder">
<div *ngIf="stakeholder">
<div class="uk-card uk-card-default uk-card-body uk-position-relative" [ngClass]="stakeholder.type">
<div class="uk-position-top-right uk-margin-small-right uk-margin-small-top">
<a class="uk-link-reset uk-flex uk-flex-middle">
<icon *ngIf="showVisibility" [flex]="true"
[name]="stakeholderUtils.visibilityIcon.get(stakeholder.visibility)" ratio="0.6"></icon>
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element class="uk-dropdown"
uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0;">
<ul class="uk-nav uk-dropdown-nav">
<li>
<a (click)="editStakeholder(stakeholder, !stakeholder.defaultId); hide(element)">Edit</a>
</li>
<li *ngIf="isCurator && stakeholderUtils.isCachingIndicators">
<a (click)="createReport(stakeholder);hide(element)">Cache Indicators</a>
</li>
<li *ngIf="showVisibility" class="uk-nav-divider"></li>
<ng-template *ngIf="showVisibility" ngFor [ngForOf]="stakeholderUtils.visibilities" let-v>
<li [class.uk-active]="stakeholder.visibility === v.value">
<a (click)="changeStakeholderStatus(stakeholder, v.value);hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" [name]="v.icon" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">{{v.label}}</span>
<icon *ngIf="stakeholder.visibility === v.value" [flex]="true" name="done"
class="uk-text-secondary" ratio="0.8"></icon>
</div>
</a>
</li>
</ng-template>
<hr *ngIf="isProfileManager(stakeholder)" class="uk-nav-divider">
<li *ngIf="isProfileManager(stakeholder)"><a
(click)="deleteStakeholderOpen(stakeholder);hide(element)">Delete</a>
</li>
</ul>
</div>
</div>
<a class="uk-display-block uk-text-center uk-link-reset" [routerLink]="'/admin/' + stakeholder.alias">
<div class="titleContainer uk-h6 uk-margin-remove-bottom uk-margin-top multi-line-ellipsis lines-2">
<p *ngIf="stakeholder.name" class="uk-margin-remove">
{{stakeholder.name}}
</p>
</div>
<div class="logoContainer uk-margin-top uk-flex uk-flex-column uk-flex-center uk-flex-middle">
<img [src]="stakeholder | logoUrl" class="uk-blend-multiply" style="max-height: 80px;">
</div>
</a>
</div>
</div>
</ng-template>
<ng-template #newBox let-text="text" let-isDefault="isDefault">
<ng-container *ngIf="!loading">
<div class="uk-card uk-card-default uk-text-center uk-card-body clickable"
(click)="editStakeholder(null, isDefault)">
<div class="uk-text-small uk-text-muted">
{{text}}
</div>
<div class="uk-margin-medium-top uk-margin-small-bottom">
<span class="uk-text-secondary">
<icon name="add" [ratio]="3"></icon>
</span>
</div>
</div>
</ng-container>
</ng-template>
<modal-alert #editStakeholderModal [large]="true" classTitle="uk-background-primary uk-light"
(alertOutput)="editStakeholderComponent.save(callback)"
(cancelOutput)="editStakeholderComponent.removePhoto()"

View File

@ -1,53 +1,41 @@
import {Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewChild} from "@angular/core";
import {StakeholderService} from "../../monitor/services/stakeholder.service";
import {Stakeholder, Visibility} from "../../monitor/entities/stakeholder";
import {zip} from "rxjs";
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 {ActivatedRoute} from "@angular/router";
import {CacheIndicatorsService} from "../utils/cache-indicators/cache-indicators.service";
import {NotificationHandler} from "../../utils/notification-handler";
import {StakeholderBaseComponent} from "../utils/stakeholder-base.component";
import {FilteredStakeholdersBaseComponent} from "../shared/filtered-stakeholders-base.component";
type Tab = 'all' | 'templates'| 'profiles';
declare var UIkit;
@Component({
selector: 'home',
templateUrl: "./manageStakeholders.component.html",
styleUrls: ["./manageStakeholders.component.less"]
selector: 'manage-stakeholders',
templateUrl: "manageStakeholders.component.html",
styleUrls: ["manageStakeholders.component.less"]
})
export class ManageStakeholdersComponent extends StakeholderBaseComponent implements OnInit, OnDestroy {
public loading: boolean = true;
public deleteLoading: boolean = false;
export class ManageStakeholdersComponent extends FilteredStakeholdersBaseComponent {
@Input()
public defaultStakeholders: Stakeholder[];
public stakeholders: Stakeholder[];
public alias: string[];
/**
* List of aliases
* */
@Input()
public aliases: string[];
@Output()
public aliasesChange = new EventEmitter<string[]>();
/**
* 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 <b>successfully deleted</b>', {
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;
}
}

View File

@ -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) {

View File

@ -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<Stakeholder[]> = new EventEmitter<Stakeholder[]>();
/**
* 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;
}
}

View File

@ -227,8 +227,7 @@
</div>
<ul *ngIf="stakeholder.topics.length > 0 && stakeholder.topics[topicIndex].categories.length > 0 && stakeholder.topics[topicIndex].categories[categoryIndex]"
transition-group class="uk-tab uk-margin-xsmall-top" [id]="'subCategories'">
<ng-template ngFor [ngForOf]=" stakeholder.topics[topicIndex].categories[categoryIndex].subCategories"
let-subCategory let-i="index">
<ng-container *ngFor="let subCategory of stakeholder.topics[topicIndex].categories[categoryIndex].subCategories; let i=index">
<li class="uk-visible-toggle uk-flex" [class.uk-active]="subCategoryIndex === i" transition-group-item>
<a (click)="chooseSubcategory(i)">
<span class="uk-text-uppercase">{{subCategory.name}}</span>
@ -321,7 +320,7 @@
</div>
</span>
</li>
</ng-template>
</ng-container>
<li *ngIf="isCurator && isEditable">
<a (click)="editSubCategoryOpen(); $event.preventDefault()" class="uk-flex uk-flex-middle">
<icon name="add" [flex]="true"></icon>

View File

@ -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: `
<div class="uk-section">
<div class="uk-flex uk-flex-middle uk-flex-between uk-margin-bottom">
<div *ngIf="stakeholderCategory" class="uk-flex uk-flex-middle">
<h4 class="uk-margin-remove">{{ stakeholderCategory.plural }}</h4>
<a *ngIf="stakeholderCategory.tooltip" class="uk-margin-small-left uk-button uk-button-link"
[attr.uk-tooltip]="stakeholderCategory.tooltip">
<icon name="info" [flex]="true" [ratio]="0.8"></icon>
</a>
</div>
<paging-no-load *ngIf="filteredStakeholders?.length > pageSize"
(pageChange)="updateCurrentPage($event)"
[currentPage]="currentPage" [size]="pageSize"
[totalResults]="filteredStakeholders.length">
</paging-no-load>
</div>
<div class="uk-grid uk-grid-small uk-child-width-1-4@l uk-child-width-1-3@m uk-child-width-1-1 uk-grid-match"
uk-grid>
<div *ngFor="let stakeholder of filteredStakeholders.slice((currentPage-1)*pageSize, currentPage*pageSize)">
<div class="uk-card uk-card-default">
<div class="uk-card-body uk-position-relative">
<div class="uk-position-top-right uk-margin-small-right uk-margin-small-top">
<span class="uk-link-reset uk-flex uk-flex-middle">
<icon *ngIf="showVisibility" [flex]="true"
[name]="stakeholderUtils.visibilityIcon.get(stakeholder.visibility)"
ratio="0.6"></icon>
</span>
</div>
<div class="uk-display-block uk-text-center">
<div class="titleContainer uk-h6 uk-margin-remove-bottom uk-margin-top multi-line-ellipsis lines-2">
<p *ngIf="stakeholder.name" class="uk-margin-remove">
{{ stakeholder.name }}
</p>
</div>
<div class="logoContainer uk-margin-top uk-flex uk-flex-column uk-flex-center uk-flex-middle">
<img [src]="stakeholder | logoUrl" class="uk-blend-multiply"
style="max-height: 60px;">
</div>
</div>
</div>
<div *ngIf="(add && !stakeholder['added']) || (remove && stakeholder['added'])" class="uk-card-footer uk-padding-remove-vertical">
<div class="uk-grid uk-grid-small uk-flex-nowrap uk-grid-divider uk-flex-right" uk-grid>
<div *ngIf="add && !stakeholder['added']">
<div class="uk-padding-small uk-padding-remove-horizontal">
<button class="uk-button uk-button-link uk-flex uk-flex-middle"
(click)="added.emit(stakeholder._id)">
<icon name="add" [flex]="true"></icon>
<span class="uk-margin-xsmall-left">Add</span>
</button>
</div>
</div>
<div *ngIf="remove && stakeholder['added']">
<div class="uk-padding-small uk-padding-remove-horizontal">
<button class="uk-button uk-button-link uk-flex uk-flex-middle"
(click)="removed.emit(stakeholder._id)">
<icon name="delete" [flex]="true"></icon>
<span class="uk-margin-xsmall-left">Remove</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="filteredStakeholders.length === 0">
<div class="uk-position-relative uk-section">
<h6 class="uk-position-center">No {{ entities.stakeholders }} have been found.</h6>
</div>
</div>
</div>
`
})
export class FilteredStakeholdersComponent extends FilteredStakeholdersBaseComponent {
@Output()
public added: EventEmitter<string> = new EventEmitter<string>();
@Output()
public removed: EventEmitter<string> = new EventEmitter<string>();
@Input()
public add: boolean = false;
@Input()
public remove: boolean = false;
constructor(protected cdr: ChangeDetectorRef) {
super();
}
}

View File

@ -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: `
<div page-content>
<div header class="uk-margin-medium-top">
<ul *ngIf="umbrella" class="uk-tab uk-margin-xsmall-top">
<ng-container *ngFor="let type of umbrella.types; let i=index">
<li class="uk-visible-toggle uk-flex" [class.uk-active]="activeIndex === i">
<a (click)="chooseType(i)">
<span class="uk-text-uppercase">{{ stakeholderUtils.entities[type] }}</span>
</a>
<div class="uk-flex uk-flex-column uk-flex-center uk-margin-small-left"
[class.uk-invisible-hover]="activeIndex !== i"
(click)="$event.stopPropagation();$event.preventDefault()">
<a class="uk-link-reset uk-flex uk-flex-middle">
<icon [flex]="true" name="more_vert"></icon>
</a>
<div #element
uk-dropdown="mode: click; pos: bottom-left; offset: 5; delay-hide: 0; container: body">
<ul class="uk-nav uk-dropdown-nav">
<li>
<a (click)="deleteTypeOpen(i); hide(element)">
<div class="uk-flex uk-flex-middle">
<icon [flex]="true" name="delete" ratio="0.6"></icon>
<span class="uk-margin-small-left uk-width-expand">Remove</span>
</div>
</a>
</li>
</ul>
</div>
</div>
</li>
</ng-container>
<li *ngIf="isCurator">
<a (click)="addTypeOpen(); $event.preventDefault()" class="uk-flex uk-flex-middle">
<icon name="add" [flex]="true"></icon>
<span class="uk-text-uppercase">Add type</span>
</a>
</li>
</ul>
</div>
<div actions class="uk-flex uk-flex-right@m uk-flex-center">
<button class="uk-button uk-button-primary" (click)="manageStakeholdersOpen()">
Manage {{ entities.stakeholders }}
</button>
</div>
<div inner class="uk-section">
<div *ngIf="children == null" class="message">
<h6>No types yet. Add a type to start.</h6>
</div>
<div *ngIf="children != null" uk-height-match="target: .titleContainer; row: false">
<div uk-height-match="target: .logoContainer; row: false">
<filtered-stakeholders [remove]="true" (removed)="removeStakeholder($event)"
[keyword]="keyword" [stakeholders]="children">
</filtered-stakeholders>
</div>
</div>
</div>
</div>
<modal-alert #deleteTypeModal classTitle="uk-background-primary uk-light" (alertOutput)="deleteType()"
[overflowBody]="false">
<div [class.uk-invisible]="loading" class="uk-position-relative">
<div *ngIf="loading">
<loading class="uk-position-center"></loading>
</div>
You are about to remove <span class="uk-text-bold">{{ entities[umbrella.types[index]] }}</span> from the
umbrella of {{ stakeholder.name }}.
All the profiles added to this type will be removed too.
Are you sure you want to proceed?
</div>
</modal-alert>
<modal-alert #addTypeModal classTitle="uk-background-primary uk-light" (alertOutput)="addType()"
[okDisabled]="updateFb && (updateFb.invalid || updateFb.pristine)">
<div *ngIf="loading" class="uk-position-relative uk-height-large">
<loading class="uk-position-center"></loading>
</div>
<div *ngIf="updateFb" [class.uk-hidden]="loading"
class="uk-grid uk-padding uk-padding-remove-horizontal uk-child-width-1-1" [formGroup]="updateFb"
uk-grid>
<div input [formInput]="updateFb.get('type')" class="uk-width-1-1"
placeholder="Type" [options]="types" type="select"></div>
</div>
</modal-alert>
<fs-modal #manageStakeholdersModal>
<div *ngIf="loading" class="uk-position-relative uk-height-large">
<loading class="uk-position-center"></loading>
</div>
<ng-container *ngIf="!loading && manageStakeholders">
<div class="uk-section-xsmall">
<div class="uk-flex uk-flex-center uk-flex-wrap uk-flex-middle uk-flex-right@m">
<div search-input [(value)]="keyword" [expandable]="true"
[disabled]="loading" [placeholder]="'Search ' + entities.stakeholders"
searchInputClass="outer"
class="uk-width-1-3@xl uk-width-2-5@l uk-width-1-2@m uk-width-1-1 uk-flex uk-flex-right"></div>
</div>
</div>
<div uk-height-match="target: .titleContainer; row: false">
<div uk-height-match="target: .logoContainer; row: false">
<filtered-stakeholders *ngFor="let category of categories" [add]="true" (added)="addStakeholder($event)"
[stakeholderCategory]="category" [remove]="true"
(removed)="removeStakeholder($event)"
[keyword]="keyword" [(stakeholders)]="manageStakeholders[category.value]">
</filtered-stakeholders>
</div>
</div>
</ng-container>
</fs-modal>
`
})
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');
}
}

View File

@ -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 {}

View File

@ -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<string, OAIndicator> = 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;
}
}

View File

@ -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 {

View File

@ -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> | 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<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.check(state.url, route.params.stakeholder, route.params.type, route.params.child);
}
canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.check(state.url, childRoute.data?.stakeholder?childRoute.data.stakeholder:childRoute.params.stakeholder,
childRoute.params.type, childRoute.params.child);
}
}

View File

@ -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<Stakeholder>(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<ManageStakeholder> {
return this.http.get<ManageStakeholder>(url + '/my-stakeholder' + ((type) ? ('?type=' + type) : ''), CustomOptions.registryOptions()).pipe(map(manageStakeholder => {
getMyStakeholders(url: string, type: string = null): Observable<ManageStakeholders> {
return this.http.get<ManageStakeholders>(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<Stakeholder> {
buildStakeholder(url: string, stakeholder: Stakeholder, copyId: string, standalone: boolean = true, umbrella: boolean = false): Observable<Stakeholder> {
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<Stakeholder>(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<Umbrella> {
return this.http.post<Umbrella>(url + '/' + id + '/umbrella', update, CustomOptions.registryOptions()).pipe(map(umbrella => {
return HelperFunctions.copy(umbrella);
}));
}
getStakeholderAsObservable(): Observable<Stakeholder> {
return this.stakeholderSubject.asObservable();
}