Skip to content

Instantly share code, notes, and snippets.

@ivanferrer
Created January 6, 2025 14:49
Show Gist options
  • Save ivanferrer/50e85a3cd4433a9cf0c2db9062eda874 to your computer and use it in GitHub Desktop.
Save ivanferrer/50e85a3cd4433a9cf0c2db9062eda874 to your computer and use it in GitHub Desktop.
Menu vertical
import { HttpClient } from '@angular/common/http';
import { OnInit, OnChanges, AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, Output, SimpleChanges, ViewChild, ViewChildren } from '@angular/core';
import { AuthorizationService } from '@shared/authorization.service';
import { environment } from '@shared/environments/environment';
import * as _ from 'lodash';
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
import { SHARED_LOCALIZATION } from '@shared/shared-localization';
import { TranslateService } from '@ngx-translate/core';
import { MenuVerticalService } from './menu-vertical.service';
@Component({
selector: 'menu-vertical',
templateUrl: './menu-vertical.component.html',
styleUrls: ['./menu-vertical.component.scss'],
})
export class MenuVerticalComponent implements OnInit, OnChanges, AfterViewInit {
@Input() activatedMenu: boolean;
@Input() user: any;
@Input() org: any;
@Input() activeAnalytics: boolean;
@Input() translationOKR: string;
@Input() balanceData: any;
@Input() countListUsers: any;
@Output() toggleCollapse = new EventEmitter<boolean>();
@Output() accessUrl = new EventEmitter<any>();
@ViewChild('sidebarAdmin', {static: false}) sidebarAdmin: ElementRef;
@ViewChildren('appraisalsHeader') appraisalsHeader: ElementRef;
@ViewChildren('surveysHeader') surveysHeader: ElementRef;
@ViewChildren('learningHeader') learningHeader: ElementRef;
@ViewChildren('feedbacksHeader') feedbacksHeader: ElementRef;
@ViewChildren('pdiHeader') pdiHeader: ElementRef;
@ViewChildren('meetingsHeader') meetingsHeader: ElementRef;
@ViewChildren('actionsHeader') actionsHeader: ElementRef;
@ViewChildren('okHeader') okrHeader: ElementRef;
@ViewChildren('rvHeader') rvHeader: ElementRef;
@ViewChildren('usersHeader') usersHeader: ElementRef;
@ViewChildren('scrollableMenu') scrollableMenu: ElementRef;
@HostListener('window:resize', ['$event'])
onResize(event) {
this.toggleCollapseMenu(event);
}
private features = environment.features;
menuShow: boolean = false;
isCollapsed: boolean = false;
dataMenuEvent: any;
queryParams: any = {};
featureEnabled: any;
enableRV: boolean = false;
isScrollable: boolean = false;
idRoute: string;
routePath: string;
constructor(private http: HttpClient,
private authorizationService: AuthorizationService,
private router: Router,
private translateService: TranslateService,
private route: ActivatedRoute,
private cdr: ChangeDetectorRef,
private menuStateService: MenuVerticalService) { }
private routerParams = {
'appraisals-competencies':{type:'competencies'},
'appraisals-experiences':{type:'experiences'},
'ninebox':{mode: 'SectionScoresNineBox'},
'insights': {mode: 'DistributionScores', tab: 'INSIGHTS'},
'ia-assistant': {mode: 'INSIGHTS_TEST', tab: 'INSIGHTS_TEST'},
'chart-distribution': {mode: 'DistributionScores', tab: 'DISTRIBUTION_RANGE'},
'pulse': {type: 'PULSE'},
'enps': {type: 'eNPS'},
'surveys': {type: 'FORM'},
'learning-courses': {tab: 'COURSES'},
'learning-classes': {tab: 'CLASSES'},
'learning-subscribes': {tab: 'SUBSCRIBES'},
'learning-settings': {tab: 'SETTINGS'},
'feedbacks-settings': {tab: 'CONFIGURATION'},
'feedbacks-notifications': {tab: 'NOTIFICATION'},
'jobs': {tab: 'JOB_POSITIONS'},
'initiatives': {tab: 'INITIATIVE'},
'actions-settings': {tab: 'CONFIGURATION'},
'rv-results': {tab: 'RESULTS'},
'pdi-library-actions': {tab: 'ACTION_LIBRARY'},
'pdi-settings': {tab: 'CONFIGURATION'},
'pdi-notifications': {tab: 'NOTIFICATION'},
'meetings-settings': {tab: 'CONFIGURATION'},
'meetings-notifications': {tab: 'NOTIFICATION'},
'campaigns': {tab: 'REGISTRATION_CAMPAIGN'}
};
ngOnInit(): void {
console.log('init...')
this.http
.get<any>('/api/insight/appraisal/current/organization/is-feature-enabled')
.subscribe((data) => {
this.featureEnabled = data;
});
this.route.data.subscribe((data) => {
this.idRoute = data.id || null;
this.routePath = data.path || null;
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.activatedMenu && this.sidebarAdmin &&this.sidebarAdmin.nativeElement) {
if (!this.activatedMenu) {
this.sidebarAdmin.nativeElement.remove();
}
}
}
ngAfterViewInit(): void {
this.menuRender()
if (this.sidebarAdmin && this.sidebarAdmin.nativeElement) {
this.sidebarAdmin.nativeElement
.addEventListener('mouseover', (e) => {
this.isScrollable = true;
setTimeout(() => {
this.changeScrollingStyle();
}, 1)
});
this.sidebarAdmin.nativeElement
.addEventListener('mouseout', (e) => {
this.isScrollable = false;
});
}
}
isActiveRoute(type: string): boolean {
// Gera a rota esperada em formato string
const targetRoute = this.routerLink(type);
const targetRouteString = Array.isArray(targetRoute) ? targetRoute
.filter(segment => typeof segment !== 'object')
.join('/')
.replace(/\/\//gi, '/') : targetRoute;
const currentRoute = this.router.url.split('?')[0].split(';')[0];
const children = (this.route.snapshot.routeConfig.children || [])
.filter(child => child.data && child.data.id && child.data.id === type);
// Verifica se algum dos filhos corresponde à URL atual
const isMatchingPath = (child: any): boolean => {
if (child.path) {
const regexPattern = '^/dashboard/' + child.path.replace(/:[^/]+/g, '[^/]+') + '$';
const regex = new RegExp(regexPattern);
return regex.test(currentRoute);
}
return false;
};
// Procura por um filho que tenha a ID e um caminho correspondente
const matchingChild = children.find(child => isMatchingPath(child));
if (matchingChild) {
return true;
}
// Verifica se a rota atual contém a ID e o path da rota ativa
return currentRoute.includes(targetRouteString);
}
toggleCollapseMenu(event = null) {
const sidebar = this.dataMenuEvent.sidebar;
const toggleBtn = this.dataMenuEvent.toggleBtn;
const eventChange = (event != null) ? event.target.innerWidth : window.innerWidth;
if (eventChange < 1200) {
this.isCollapsed = true;
toggleBtn.classList.add('collapsed');
sidebar.classList.add('collapsed');
toggleBtn.children[0].classList.add('fa-chevron-right');
toggleBtn.children[0].classList.remove('fa-chevron-left');
if (eventChange < 768) {
sidebar.classList.add('fixed-menu');
} else {
sidebar.classList.remove('fixed-menu');
}
} else {
this.isCollapsed = false;
toggleBtn.classList.remove('collapsed');
sidebar.classList.remove('fixed-menu');
sidebar.classList.remove('collapsed');
toggleBtn.children[0].classList.remove('fa-chevron-right');
toggleBtn.children[0].classList.add('fa-chevron-left');
if (sidebar.classList.contains('fixed-menu')) {
sidebar.classList.remove('fixed-menu');
}
}
this.isCollapsed = toggleBtn.classList.contains('collapsed') ? true : false;
this.menuStateService.setIsCollapsed(this.isCollapsed);
this.toggleCollapse.emit(this.isCollapsed);
this.createMenuCollapsed(this.isCollapsed);
}
toArrayType(type) {
if (typeof type === 'string' && /^\[.*\]$/.test(type)) {
try {
return JSON.parse(type.replace(/'/g, '"'));
} catch (error) {
console.error('Erro ao converter string para array:', error);
return null;
}
}
return type;
}
settingsDefaultActiveDropdown(accordionHeaders, sidebar, currentHeader = null) {
const settingsActive = (header) => {
let type = header.getAttribute('data-item');
type = this.toArrayType(type);
if (type && _.isArray(type) && type.length > 0) {
type.forEach(t => {
if (this.isCurrentRoute(t)) {
header.classList.add('active');
} else {
header.classList.remove('active');
}
});
}
}
if (!sidebar.classList.contains('collapsed')) {
if (currentHeader) {
accordionHeaders.forEach(h => (h !== currentHeader) ? h.parentElement.classList.remove('active') : settingsActive(h.parentElement));
} else {
accordionHeaders.forEach(h => (h !== settingsActive(h.parentElement)));
}
}
}
menuRender() {
const sidebar = this.sidebarAdmin.nativeElement;
if (sidebar) {
this.menuShow = this.activatedMenu;
const toggleBtn = document.querySelector('.toggle-btn');
const accordionHeaders = document.querySelectorAll('.accordion-header');
const menuItems = document.querySelectorAll('.menu-item');
this.settingsDefaultActiveDropdown(accordionHeaders, sidebar);
if (toggleBtn) {
this.dataMenuEvent = {
sidebar:sidebar,
toggleBtn:toggleBtn,
accordionHeaders: accordionHeaders
}
this.toggleCollapseMenu();
toggleBtn.addEventListener('click', () => {
this.collapseMenu();
this.menuStateService.setIsCollapsed(this.isCollapsed);
this.toggleCollapse.emit(this.isCollapsed);
});
this.isCollapsed = toggleBtn.classList.contains('collapsed') ? true : false;
}
if (accordionHeaders) {
accordionHeaders.forEach(header => {
header.addEventListener('click', () => {
this.settingsDefaultActiveDropdown(accordionHeaders, sidebar, header);
accordionHeaders.forEach(h => (h !== header) ? h.parentElement.classList.remove('active') : null);
const accordionItem = header.parentElement;
accordionItem.classList.toggle('active');
let type = header.getAttribute('data-item');
const items = this.toArrayType(type);
if (items && _.isArray(items) && items.length > 0 && !items.some(t => this.isCurrentRoute(t))) {
let routerKey = items[0];
/* Esse método faz a navegação para o primeiro item do menu ao abrir o
dropdown (caso a página atual não corresponda a nenhum item do mesmo pai)
*/
const firstLink = accordionItem.querySelector('.accordion-content .menu-item');
if (firstLink && firstLink.classList) {
firstLink.classList.add('active');
const dataKey = firstLink.getAttribute('data-key');
if(dataKey) {
routerKey = dataKey;
}
}
this.clickLink(routerKey);
const route: any = this.routerLink(routerKey)
if (this.queryParams && Object.keys(this.queryParams).length > 0) {
//Essa condição é fundamental para links que possuem parâmetros
this.router.navigate(route, {queryParams: this.queryParams});
} else {
this.router.navigate(route);
}
}
});
if (sidebar.classList.contains('collapsed')) {
this.isCollapsed = true;
accordionHeaders.forEach(header => {
const accordionItem = header.parentElement;
if (!accordionItem.classList.contains('active-dropdown')) {
accordionItem.classList.toggle('active-dropdown');
}
});
}
});
}
if (menuItems) {
menuItems.forEach(item => {
item.addEventListener('click', (e) => {
this.closewHeaderWithItemParent(item, accordionHeaders);
e.preventDefault();
menuItems.forEach(i => i.classList.remove('active'));
item.classList.add('active');
if (window.innerWidth < 768) {
this.collapseMenu();
}
});
});
}
setTimeout(() => {
//essa regra expande o menu ao carregar a página
this.expandActiveMenu(menuItems);
}, 200)
}
}
changeScrollingStyle() {
const element = document.querySelectorAll('.container-menu.scrollable')[0] as any;
if (element) {
const hasScroll = element.offsetWidth > element.clientWidth;
if (!hasScroll) {
element.classList.add('no-scroll');
} else {
element.classList.remove('no-scroll');
}
}
}
closewHeaderWithItemParent(item, accordionHeaders) {
if (item.classList.contains('parent')) {
accordionHeaders.forEach(h => (h.parentElement.classList.contains('active')) ? h.parentElement.classList.remove('active') : null);
}
}
isCurrentRoute(type: string): boolean {
const targetRoute: any = this.routerLink(type); // Obtém a rota completa com parâmetros
if (_.isArray(targetRoute)) {
const urlTree: UrlTree = this.router.createUrlTree(targetRoute);
const targetUrl = this.router.serializeUrl(urlTree);
const currentUrl = this.router.url.split(';')[0]; // Parte antes do ';'
const targetUrlBase = targetUrl.split(';')[0]; // Parte antes do ';'
if (currentUrl !== targetUrlBase) {
return false; // Se a base da URL não for igual, retorna false
}
return this.checkQueryParams(currentUrl, targetUrl);
}
return false;
}
expandActiveMenu(menuItems) {
try {
const activeItem = document.querySelector('.menu-item.active')
if(activeItem) {
const parent = activeItem.closest('.accordion-item');
if (!parent.classList.contains('active')) {
parent.classList.add('active');
} else {
menuItems.forEach(item => {
if (item.classList.contains('active')) {
const parent = item.closest('.accordion-item');
if (parent) {
parent.classList.add('active');
}
}
});
}
} else {
menuItems.forEach(item => {
if (item.classList.contains('active')) {
const parent = item.closest('.accordion-item');
if (parent) {
parent.classList.add('active');
}
}
});
}
} catch (error) {
}
this.clearDuplicateActive();
}
checkQueryParams(currentUrl: string, targetUrl: string): boolean {
const currentParams = this.getQueryParamsFromUrl(currentUrl);
const targetParams = this.getQueryParamsFromUrl(targetUrl);
return Object.keys(currentParams).every(key => currentParams[key] === targetParams[key]);
}
getQueryParamsFromUrl(url: string): { [key: string]: string } {
const params: { [key: string]: string } = {};
const paramString = url.split(';')[1];
if (paramString) {
paramString.split('&').forEach(param => {
const [key, value] = param.split('=');
if (key && value) {
params[key] = value;
}
});
}
return params;
}
isCurrentActive(type, queryParams) {
if (this.queryParams && Object.keys(this.queryParams).length > 0 && queryParams && Object.keys(queryParams).length > 0) {
return this.routerParams[type] === queryParams[type] && this.isCurrentRoute(type);
} else {
if (queryParams) {
return false;
}
return this.isCurrentRoute(type);
}
}
createMenuCollapsed(status: boolean) {
const accordionHeaders = this.dataMenuEvent.accordionHeaders;
setTimeout(() => {
accordionHeaders
.forEach(header => {
const accordionItem = header.parentElement;
if (status) {
accordionItem.classList.add('active-dropdown');
} else {
if (accordionItem.classList.contains('active-dropdown')) {
accordionItem.classList.remove('active-dropdown');
}
}
});
}, 300);
}
collapseMenu() {
const sidebar = this.dataMenuEvent.sidebar;
const toggleBtn = this.dataMenuEvent.toggleBtn;
sidebar.classList.toggle('collapsed');
toggleBtn.children[0].classList.toggle('fa-chevron-right');
toggleBtn.children[0].classList.toggle('fa-chevron-left');
if (sidebar.classList.contains('collapsed')) {
this.isCollapsed = true;
} else {
this.isCollapsed = false;
}
this.createMenuCollapsed(this.isCollapsed);
}
canShowParentMenu(attrCanvies: any[]) {
return this.user.isFullAdmin || attrCanvies.some(cv => this.canView(cv));
}
canView(attrCanView) {
return this.user.adminPermission[attrCanView];
}
goToPlaymentPlan() {
this.router.navigate(['/','dashboard','payment-plan', {credit: 'true'}]);
}
goToUserActivesPlan() {
this.router.navigate(['/','dashboard','payment-plan', {user: 'true'}]);
}
clearDuplicateActive() {
//este método irá impedir que dois items fiquem ativos ao mesmo tempo
const activeItems = Array.from(document.querySelectorAll('.menu-item.active'));
if (activeItems.length > 1) {
activeItems.forEach((item, index) => {
if (index > 0) {
item.classList.remove('active');
}
});
}
}
clickLink(type: any) {
this.clearDuplicateActive();
if (['appraisals','appraisals-competences','appraisals-experiences'].includes(type)) {
this.viewAppraisalHubSpot()
}
if (type === 'pdi') {
this.viewPdisHubSpot();
}
if (type === 'okr-cycles') {
window.location.href = '/okr/admin/cycles';
}
if (type === 'okr') {
window.location.href = '/okr/admin/objectives';
}
if (type === 'tree') {
window.location.href = '/okr/admin/tree-view/cycles';
}
if (type === 'okr-settings') {
window.location.href = '/okr/admin/home';
}
if (type === 'okr-results') {
window.location.href = '/okr/admin/people;involvement=everyone';
}
if (type === 'help') {
window.open('https://impulseup.movidesk.com/kb', '_blank');
}
if (type === 'surveys') {
//TODO: checar se será necessário fazer também uma implementação de análise do hubspot para o módulo de pesquisa
//this.viewSurveysHubSpot()
}
if (['feedbacks','feedbacks-settings','feedbacks-notifications'].includes(type)) {
this.viewFeedbacksHubSpot()
}
const url = this.routerLink(type);
this.accessUrl.emit({url: url, type: type});
}
routerLink(type: string) {
this.queryParams = this.routerParams[type];
switch (type) {
case 'dashboard':
return ['/', 'dashboard', 'admin-dashboard'];
case 'appraisals-competencies':
return ['/', 'dashboard', 'appraisals','competencies', this.queryParams];
case 'appraisals-experiences':
return ['/', 'dashboard', 'appraisals','experiences', this.queryParams];
case 'appraisals':
return ['/', 'dashboard', 'appraisals'];
case 'surveys':
return ['/', 'dashboard', 'surveys', 'form', this.queryParams];
case 'enps':
return ['/', 'dashboard', 'surveys', 'enps', this.queryParams];
case 'pulse':
return ['/', 'dashboard', 'surveys', 'pulse', this.queryParams];
case 'analytics':
return ['/', 'dashboard', 'appraisal-results'];
case 'ninebox':
return ['/', 'dashboard', 'appraisal-results', 'ninebox', this.queryParams];
case 'chart-distribution':
return ['/', 'dashboard', 'appraisal-results', 'chart-distribution', this.queryParams];
case 'insights':
return ['/', 'dashboard', 'appraisal-results', 'insights', this.queryParams];
case 'ia-assistant':
return ['/', 'dashboard', 'appraisal-results', 'ia-assistant', this.queryParams];
case 'analytics-survey':
return ['/', 'dashboard', 'survey-results'];
case 'succession-plan':
return ['/', 'dashboard', 'succession-plan'];
case 'pdi':
return ['/', 'dashboard', 'pdi'];
case 'pdi-library-actions':
return ['/', 'dashboard', 'pdi', 'library-actions', this.queryParams];
case 'pdi-settings':
return ['/', 'dashboard', 'pdi', 'settings', this.queryParams];
case 'pdi-notifications':
return ['/', 'dashboard', 'pdi', 'notifications', this.queryParams];
case 'feedbacks':
return ['/', 'dashboard', 'feedback'];
case 'feedbacks-settings':
return ['/', 'dashboard', 'feedback', 'settings', this.queryParams];
case 'feedbacks-notifications':
return ['/', 'dashboard', 'feedback', 'notifications', this.queryParams];
case 'meetings':
return ['/', 'dashboard', 'one-on-one'];
case 'meetings-settings':
return ['/', 'dashboard', 'one-on-one', 'settings', this.queryParams];
case 'meetings-notifications':
return ['/', 'dashboard', 'one-on-one', 'notifications', this.queryParams];
case 'okr-cycles':
return '/okr/admin/cycles';
case 'okr':
return '/okr/admin/objectives';
case 'okr-settings':
return '/okr/admin/home';
case 'okr-results':
return '/okr/admin/people;involvement=everyone';
case 'tree':
return '/okr/admin/tree-view/cycles';
case 'learning':
return ['/', 'dashboard', 'learn-admin'];
case 'learning-courses':
return ['/', 'dashboard', 'learn-admin', 'courses', this.queryParams];
case 'learning-classes':
return ['/', 'dashboard', 'learn-admin', 'classes', this.queryParams];
case 'learning-subscribes':
return ['/', 'dashboard', 'learn-admin', 'subscribes', this.queryParams];
case 'learning-settings':
return ['/', 'dashboard', 'learn-admin', 'settings', this.queryParams];
case 'users':
return ['/', 'dashboard', 'people'];
case 'users-settings':
return ['/', 'dashboard', 'people-configuration'];
case 'jobs':
return ['/', 'dashboard', 'people','jobs', this.queryParams];
case 'competencies':
return ['/', 'dashboard', 'competency'];
case 'actions-settings':
return ['/', 'dashboard', 'action','settings', this.queryParams];
case 'actions':
return ['/', 'dashboard', 'action'];
case 'initiatives':
return ['/', 'dashboard', 'initiatives','action', this.queryParams];
case 'payment-plan':
return ['/', 'dashboard', 'payment-plan'];
case 'rv':
return ['/', 'dashboard', 'variable-compensation'];
case 'rv-results':
return ['/', 'dashboard', 'variable-compensation', this.queryParams];
case 'campaigns':
return ['/', 'dashboard', 'people-configuration', this.queryParams];
default:
return ['/'];
}
}
viewAppraisalHubSpot() {
this.http
.get<any>(`/api/appraisals/hubspot/view-appraisal`)
.subscribe();
}
viewAnalyticsHubSpot() {
this.http.get<any>(`/api/analytics/hubspot/view-analytics`).subscribe();
}
viewFeedbacksHubSpot() {
this.http.get<any>(`/api/feedback/hubspot/view`).subscribe();
}
viewPdisHubSpot() {
this.http.get<any>(`/api/pdi/hubspot/view`).subscribe();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment