import { AfterViewInit, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit, PLATFORM_ID, QueryList, ViewChild, ViewChildren } from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {Title} from '@angular/platform-browser'; import {Category, Stakeholder, SubCategory, Topic, Visibility} from "../../monitor/entities/stakeholder"; import {StakeholderService} from "../../monitor/services/stakeholder.service"; import {HelperFunctions} from "../../utils/HelperFunctions.class"; import {AlertModal} from "../../utils/modal/alert"; import {BehaviorSubject, Subject, Subscriber, Subscription} from "rxjs"; import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; import {StringUtils} from "../../utils/string-utils.class"; import {IDeactivateComponent} from "../../utils/can-exit.guard"; import {Option} from "../../sharedComponents/input/input.component"; import {properties} from "src/environments/environment"; import {Session, User} from "../../login/utils/helper.class"; import {UserManagementService} from "../../services/user-management.service"; import {TransitionGroupComponent} from "../../utils/transition-group/transition-group.component"; import {NotificationHandler} from "../../utils/notification-handler"; import {StakeholderBaseComponent} from "../utils/stakeholder-base.component"; declare var UIkit; @Component({ selector: 'topic', templateUrl: './topic.component.html', }) export class TopicComponent extends StakeholderBaseComponent implements OnInit, OnDestroy, AfterViewInit, IDeactivateComponent { private topicSubscriptions: any[] = []; public offset: number; public loading: boolean = false; public stakeholder: Stakeholder; public showLogo: boolean = false; public user: User; /** * Stakeholder change event * */ public change: Subject = new Subject(); /** * Current topic **/ public topicIndexSubject: BehaviorSubject = new BehaviorSubject(0); public topicIndex: number = 0; /** * Current category */ public categoryIndexSubject: BehaviorSubject = new BehaviorSubject(0); public categoryIndex: number = 0; /** * Current Subcategory */ public subCategoryIndexSubject: BehaviorSubject = new BehaviorSubject(0); public subCategoryIndex: number = 0; /** * Current element and index of topic, category or subcategory to be deleted. */ public form: UntypedFormGroup; public element: Topic | Category | SubCategory; public type: 'topic' | 'category' | 'subcategory' = "topic"; public index: number = -1; public visibility: Visibility; @ViewChild('deleteModal', {static: true}) deleteModal: AlertModal; @ViewChild('editModal', {static: true}) editModal: AlertModal; @ViewChild('visibilityModal', {static: true}) visibilityModal: AlertModal; @ViewChildren(TransitionGroupComponent) transitions: QueryList; public elementChildrenActionOnDelete: string; public filters: UntypedFormGroup; public all: Option = { value: 'all', label: 'All' }; constructor( protected _route: ActivatedRoute, protected _router: Router, protected _title: Title, private fb: UntypedFormBuilder, private stakeholderService: StakeholderService, private userManagementService: UserManagementService, protected cdr: ChangeDetectorRef, @Inject(PLATFORM_ID) private platformId) { super(); } public ngOnInit() { if(this._route.snapshot.data?.showLogo === true) { this.showLogo = true; } if (typeof document !== "undefined") { this.offset = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-height')); } let subscription: Subscription; this.subscriptions.push(this.topicIndexSubject.asObservable().subscribe(index => { this.topicChanged(() => { this.topicIndex = index; }); })); this.subscriptions.push(this.categoryIndexSubject.asObservable().subscribe(index => { this.categoryChanged(() => { this.categoryIndex = index; }); })); this.subscriptions.push(this.subCategoryIndexSubject.asObservable().subscribe(index => { this.subCategoryChanged(() => { this.subCategoryIndex = index; }); })); this.subscriptions.push(this._route.params.subscribe(params => { if (subscription) { subscription.unsubscribe(); } subscription = this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => { if (stakeholder) { this.stakeholder = stakeholder; if (params['topic']) { this.chooseTopic(this.stakeholder.topics.findIndex(topic => topic.alias === params['topic'])); } else { this.chooseTopic(0); } this.chooseCategory(0); this.filters = this.fb.group({ chartType: this.fb.control('all'), status: this.fb.control('all'), keyword: this.fb.control('') }); if (this.topicIndex === -1) { this.navigateToError(); } else { this.title = stakeholder.name + " | Indicators" this.setMetadata(); } } }); this.topicSubscriptions.push(subscription); })); this.topicSubscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { this.user = user; })) } ngAfterViewInit() { if(this.topics) { let activeIndex = UIkit.nav(this.topics.element.nativeElement).items.findIndex(item => item.classList.contains('uk-open')); if(activeIndex !== this.topicIndex) { setTimeout(() => { UIkit.nav(this.topics.element.nativeElement).toggle(this.topicIndex, true); }); } } } get isBrowser() { return this.platformId === 'browser'; } public ngOnDestroy() { super.ngOnDestroy(); this.topicSubscriptions.forEach(value => { if (value instanceof Subscriber) { value.unsubscribe(); } }); } canExit(): boolean { this.topicSubscriptions.forEach(value => { if (value instanceof Subscriber) { value.unsubscribe(); } }); this.stakeholderService.setStakeholder(this.stakeholder); return true; } private findById(id: string) { return this.transitions?this.transitions.find(item => item.id === id):null; } get topics(): TransitionGroupComponent { return this.findById('topics'); } get categories(): TransitionGroupComponent { return this.findById('categories-' + this.topicIndex); } get subCategories(): TransitionGroupComponent { return this.findById('subCategories'); } hide(element: any) { UIkit.dropdown(element).hide(); } stakeholderChanged() { this.change.next(); } public saveElement() { if (this.type === "topic") { this.saveTopic(); } else if (this.type === "category") { this.saveCategory(); } else { this.saveSubCategory(); } } public deleteElement() { if (this.type === "topic") { this.deleteTopic(); } else if (this.type === "category") { this.deleteCategory(); } else { this.deleteSubcategory(); } } public changeElementStatus(propagate: boolean = false) { if (this.type === "topic") { this.changeTopicStatus(propagate); } else if (this.type === "category") { this.changeCategoryStatus(propagate); } else { this.changeSubcategoryStatus(propagate); } } public chooseTopic(topicIndex: number) { this.topicIndexSubject.next(topicIndex); } topicChanged(callback: Function, save: boolean = false) { if(this.topics && save) { this.topics.disable(); } if(this.categories) { this.categories.disable(); } if(this.subCategories) { this.subCategories.disable(); } if(callback) { callback(); } this.cdr.detectChanges(); if(this.topics && save) { this.topics.init(); this.topics.enable(); } if(this.categories) { this.categories.init(); this.categories.enable(); } if(this.subCategories) { this.subCategories.init(); this.subCategories.enable(); } } private buildTopic(topic: Topic) { let topics = this.stakeholder.topics.filter(element => element._id !== topic._id); this.form = this.fb.group({ _id: this.fb.control(topic._id), name: this.fb.control(topic.name, Validators.required), description: this.fb.control(topic.description), creationDate: this.fb.control(topic.creationDate), alias: this.fb.control(topic.alias, [ Validators.required, this.stakeholderUtils.aliasValidator(topics) ] ), visibility: this.fb.control(topic.visibility), defaultId: this.fb.control(topic.defaultId), categories: this.fb.control(topic.categories), icon: this.fb.control(topic.icon) }); this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { let i = 1; value = this.stakeholderUtils.generateAlias(value); this.form.controls['alias'].setValue(value); while (this.form.get('alias').invalid) { this.form.controls['alias'].setValue(value + i); i++; } })); } public editTopicOpen(index = -1) { this.index = index; this.type = 'topic'; if (index === -1) { this.buildTopic(new Topic(null, null, null, "PUBLIC")); } else { this.buildTopic(this.stakeholder.topics[index]); } this.editOpen(); } public saveTopic() { if (!this.form.invalid) { let path = [this.stakeholder._id]; let callback = (topic: Topic): void => { this.topicChanged(() => { if (this.index === -1) { this.stakeholder.topics.push(topic); } else { this.stakeholder.topics[this.index] = HelperFunctions.copy(topic); } }, true); }; if (this.index === -1) { this.save('Topic has been successfully created', path, this.form.value, callback); } else { this.save('Topic has been successfully saved', path, this.form.value, callback); } } } public changeTopicStatus(propagate: boolean = false) { let path = [ this.stakeholder._id, this.stakeholder.topics[this.index]._id ]; let callback = (topic: Topic): void => { this.topicChanged(() => { this.stakeholder.topics[this.index] = HelperFunctions.copy(topic); }, true); } this.changeStatus(this.stakeholder.topics[this.index], path, this.visibility, callback, propagate); this.visibilityModal.cancel(); } public deleteTopicOpen(index = this.topicIndex, childrenAction: string = null) { this.type = 'topic'; this.index = index; this.element = this.stakeholder.topics[this.index]; this.deleteOpen(childrenAction); } public deleteTopic() { let path: string[] = [ this.stakeholder._id, this.stakeholder.topics[this.index]._id ]; let callback = (): void => { this.topicChanged(() => { this.stakeholder.topics.splice(this.index, 1); if(this.topicIndex === this.index) { this.chooseTopic(Math.max(0, this.index - 1)); } else if(this.topicIndex > this.index) { this.chooseTopic(this.topicIndex - 1); } }, true); }; this.delete('Topic has been successfully be deleted', path, callback, (this.topicIndex === this.index)); } public moveTopic(index: number, newIndex: number = index - 1) { this.topics.init(); let path = [this.stakeholder._id]; let ids = this.stakeholder.topics.map(topic => topic._id); HelperFunctions.swap(ids, index, newIndex); this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { HelperFunctions.swap(this.stakeholder.topics, index, newIndex); if(this.topicIndex === index) { this.chooseTopic(newIndex); } else if(this.topicIndex === newIndex) { this.chooseTopic(index); } }, error => { NotificationHandler.rise(error.error.message) }); } public chooseCategory(index: number) { this.categoryIndexSubject.next(index); this.chooseSubcategory(0); } categoryChanged(callback: Function, save: boolean = false) { if(this.categories && save) { this.categories.disable(); } if(this.subCategories) { this.subCategories.disable(); } if(callback) { callback(); } this.cdr.detectChanges(); if(this.categories && save) { this.categories.init(); this.categories.enable(); } if(this.subCategories) { this.subCategories.init(); this.subCategories.enable(); } } private buildCategory(category: Category) { let categories = this.stakeholder.topics[this.topicIndex].categories.filter(element => element._id !== category._id); this.form = this.fb.group({ _id: this.fb.control(category._id), name: this.fb.control(category.name, Validators.required), description: this.fb.control(category.description), creationDate: this.fb.control(category.creationDate), alias: this.fb.control(category.alias, [ Validators.required, this.stakeholderUtils.aliasValidator(categories) ] ), visibility: this.fb.control(category.visibility), defaultId: this.fb.control(category.defaultId), subCategories: this.fb.control(category.subCategories) }); this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { let i = 1; value = this.stakeholderUtils.generateAlias(value); this.form.controls['alias'].setValue(value); while (this.form.get('alias').invalid) { this.form.controls['alias'].setValue(value + i); i++; } })); } public editCategoryOpen(index: number = -1) { this.index = index; this.type = 'category'; if (index === -1) { this.buildCategory(new Category(null, null, null, "PUBLIC")); } else { this.buildCategory(this.stakeholder.topics[this.topicIndex].categories[index]); } this.editOpen(); } public saveCategory() { if (!this.form.invalid) { let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id]; let callback = (category: Category): void => { this.categoryChanged(() => { if (this.index === -1) { this.stakeholder.topics[this.topicIndex].categories.push(category); this.categories.init(); } else { this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category); } }, true); }; if (this.index === -1) { this.save('Category has been successfully created', path, this.form.value, callback); } else { this.save('Category has been successfully saved', path, this.form.value, callback); } } } public changeCategoryStatus(propagate: boolean = false) { let path = [ this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.index]._id ]; let callback = (category: Category): void => { this.categoryChanged(() => { this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category); }, true); } this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.index], path, this.visibility, callback, propagate); this.visibilityModal.cancel(); } public deleteCategoryOpen(index: number, childrenAction: string = null) { this.type = 'category'; this.index = index; this.element = this.stakeholder.topics[this.topicIndex].categories[this.index]; this.deleteOpen(childrenAction); } public deleteCategory() { let path: string[] = [ this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.index]._id ]; let callback = (): void => { this.categoryChanged(() => { this.stakeholder.topics[this.topicIndex].categories.splice(this.index, 1); if(this.categoryIndex === this.index) { this.chooseCategory(Math.max(0, this.index - 1)); } else if(this.categoryIndex > this.index) { this.chooseCategory(this.categoryIndex - 1); } }, true); }; this.delete('Category has been successfully be deleted', path, callback); } public moveCategory(index: number, newIndex: number = index - 1) { this.categories.init(); let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id]; let ids = this.stakeholder.topics[this.topicIndex].categories.map(category => category._id); HelperFunctions.swap(ids, index, newIndex); this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories, index, newIndex); if(this.categoryIndex === index) { this.chooseCategory(newIndex); } else if(this.categoryIndex === newIndex) { this.chooseCategory(index); } }, error => { NotificationHandler.rise(error.error.message) }); } chooseSubcategory(subcategoryIndex: number) { this.subCategoryIndexSubject.next(subcategoryIndex); } subCategoryChanged(callback: Function, save: boolean = false) { if(this.subCategories && save) { this.subCategories.disable(); } if(callback) { callback(); } this.cdr.detectChanges(); if(this.subCategories && save) { this.subCategories.init(); this.subCategories.enable(); } } private buildSubcategory(subCategory: SubCategory) { let subCategories = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.filter(element => element._id !== subCategory._id); this.form = this.fb.group({ _id: this.fb.control(subCategory._id), name: this.fb.control(subCategory.name, Validators.required), description: this.fb.control(subCategory.description), creationDate: this.fb.control(subCategory.creationDate), alias: this.fb.control(subCategory.alias, [ Validators.required, this.stakeholderUtils.aliasValidator(subCategories) ] ), visibility: this.fb.control(subCategory.visibility), defaultId: this.fb.control(subCategory.defaultId), charts: this.fb.control(subCategory.charts), numbers: this.fb.control(subCategory.numbers) }); this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { let i = 1; value = this.stakeholderUtils.generateAlias(value); this.form.controls['alias'].setValue(value); while (this.form.get('alias').invalid) { this.form.controls['alias'].setValue(value + i); i++; } })); } public editSubCategoryOpen(index: number = -1) { this.index = index; this.type = 'subcategory'; if (index === -1) { this.buildSubcategory(new SubCategory(null, null, null, "PUBLIC")); } else { this.buildSubcategory(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[index]); } this.editOpen(); } public saveSubCategory() { if (!this.form.invalid) { let path: string[] = [ this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id ]; let callback = (subCategory: SubCategory): void => { this.subCategoryChanged(() => { if (this.index === -1) { this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.push(subCategory); } else { this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subCategory); } }, true); }; if (this.index === -1) { this.save('Subcategory has been successfully created', path, this.form.value, callback); } else { this.save('Subcategory has been successfully saved', path, this.form.value, callback); } } } public changeSubcategoryStatus(propagate: boolean = false) { let path = [ this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id ]; let callback = (subcategory: SubCategory): void => { this.subCategoryChanged(() => { this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subcategory); }, true); } this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index], path, this.visibility, callback, propagate); this.visibilityModal.cancel(); } public deleteSubcategoryOpen(index, childrenAction: string = null) { this.type = 'subcategory'; this.index = index; this.element = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]; this.deleteOpen(childrenAction); } public deleteSubcategory() { let path: string[] = [ this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id ]; let callback = (): void => { this.subCategoryChanged(() => { this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.splice(this.index, 1); if(this.subCategoryIndex === this.index) { this.chooseSubcategory(Math.max(0, this.index - 1)); } else if(this.subCategoryIndex > this.index) { this.chooseSubcategory(this.subCategoryIndex - 1); } }, true); }; this.delete('Subcategory has been successfully be deleted', path, callback); } public moveSubCategory(index: number, newIndex: number = index - 1) { this.subCategories.init(); let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id]; let ids = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.map(subCategory => subCategory._id); HelperFunctions.swap(ids, index, newIndex); this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories, index, newIndex); if(this.subCategoryIndex === index) { this.chooseSubcategory(newIndex); } else if(this.subCategoryIndex === newIndex) { this.chooseSubcategory(index); } }, error => { NotificationHandler.rise(error.error.message) }); } get isCurator(): boolean { return Session.isPortalAdministrator(this.user) || Session.isCurator(this.stakeholder.type, this.user); } private editOpen() { this.editModal.cancelButtonText = 'Cancel'; this.editModal.okButtonLeft = false; this.editModal.alertMessage = false; if (this.index === -1) { this.editModal.alertTitle = 'Create a new ' + this.type; this.editModal.okButtonText = 'Create'; } else { this.editModal.alertTitle = 'Edit ' + this.type + '\'s information '; this.editModal.okButtonText = 'Save'; } this.editModal.stayOpen = true; this.editModal.open(); } private deleteOpen(childrenAction: string = null) { this.elementChildrenActionOnDelete = null; if (childrenAction == "delete") { this.elementChildrenActionOnDelete = childrenAction; } else if (childrenAction == "disconnect") { this.elementChildrenActionOnDelete = childrenAction; } this.deleteModal.alertTitle = 'Delete ' + this.type; this.deleteModal.cancelButtonText = 'No'; this.deleteModal.okButtonText = 'Yes'; // this.deleteModal.cancelButton = false; // this.deleteModal.okButton = false; this.deleteModal.stayOpen = true; this.deleteModal.open(); } private save(message: string, path: string[], saveElement: any, callback: Function, redirect = false) { this.loading = true; this.topicSubscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, saveElement, path).subscribe(saveElement => { callback(saveElement); this.stakeholderChanged(); this.loading = false; this.editModal.cancel(); NotificationHandler.rise(message); if (redirect) { this._router.navigate(['../' + saveElement.alias], { relativeTo: this._route }); } }, error => { this.loading = false; this.editModal.cancel(); NotificationHandler.rise(error.error.message, 'danger'); })); } private delete(message: string, path: string[], callback: Function, redirect = false) { this.loading = true; this.topicSubscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.elementChildrenActionOnDelete).subscribe(() => { callback(); this.stakeholderChanged(); this.loading = false; this.deleteModal.cancel(); NotificationHandler.rise(message); if (redirect) { this.back(); } }, error => { this.loading = false; this.deleteModal.cancel(); NotificationHandler.rise(error.error.message, 'danger'); })); } private changeStatus(element: Topic | Category | SubCategory, path: string[], visibility: Visibility, callback: Function = null, propagate: boolean = false) { this.topicSubscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility, propagate).subscribe(returnedElement => { if(propagate) { callback(returnedElement); NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been successfully changed to ' + returnedElement.visibility.toLowerCase()); } else { element.visibility = returnedElement.visibility; NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been successfully changed to ' + element.visibility.toLowerCase()); } }, error => { NotificationHandler.rise(error.error.message, 'danger'); })); } back() { this._router.navigate(['../'], { relativeTo: this._route }); } public getPluralTypeName(): string { if (this.type == "topic") { return "Topics"; } else if (this.type == "category") { return "Categories"; } else if (this.type == "subcategory") { return "Subcategories"; } else { return this.type; } } public openVisibilityModal(index: number, visibility: Visibility, type: any) { this.index = index; this.visibility = visibility; this.type = type; this.visibilityModal.alertTitle = 'Visibility Status'; this.visibilityModal.alertFooter = false; this.visibilityModal.open(); } get isEditable(): boolean { return this.stakeholder.copy || this.stakeholder.defaultId == null || this.stakeholder.defaultId == '-1'; } }