ligbox-ops-platform/projects/ops-desk/frontend/assets/topnav.js
Ligbox Spec Hub b03bb2c37c feat(desk): UI/API Spec 039-041 + deploy atómico e smoke GREEN
Commita governance, user-wizard, operational-feed e catálogo RBAC;
adiciona deploy-desk-full.sh, smoke-desk.sh e regra anti-deploy parcial;
documenta credencial VM112 @betinplace.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 13:32:15 +00:00

165 lines
5.2 KiB
JavaScript

/** Desk chrome nav — tabs planas por zona + sidebar contextual (v0.11). */
const TOPNAV_VIEW_GROUP = {
'agentic-ops': 'operacoes',
dashboard: 'operacoes',
tickets: 'operacoes',
events: 'operacoes',
'overview-home': 'comercial',
leads: 'comercial',
infra: 'infra',
infra2: 'infra',
'email-migration': 'infra',
overview: 'sistema',
account: 'sistema',
messages: 'sistema',
admin: 'sistema',
'access-matrix': 'sistema',
modules: 'sistema',
};
const CONTEXT_TOOLBARS = {
events: { title: 'Eventos', host: '#view-events', label: 'Vistas', toolbarId: 'events-toolbar' },
};
function resolveContextToolbar(cfg) {
if (cfg.toolbarId) return document.getElementById(cfg.toolbarId);
const view = document.querySelector(cfg.host);
return view?.querySelector('.toolbar') || null;
}
function parkContextToolbars() {
Object.values(CONTEXT_TOOLBARS).forEach((cfg) => {
const view = document.querySelector(cfg.host);
const toolbar = resolveContextToolbar(cfg);
if (toolbar && view && toolbar.parentElement !== view) {
view.insertBefore(toolbar, view.firstChild);
}
});
const matrixRoleNav = document.getElementById('context-matrix-roles');
if (matrixRoleNav && matrixRoleNav.dataset.parkedFrom) {
const slot = document.querySelector(matrixRoleNav.dataset.parkedFrom);
if (slot) slot.appendChild(matrixRoleNav);
delete matrixRoleNav.dataset.parkedFrom;
}
const adminHost = document.getElementById('context-admin-tabs');
if (adminHost) adminHost.innerHTML = '';
}
function updateTopnavActive(view) {
document.querySelectorAll('[data-desk-nav]').forEach((btn) => {
const active = btn.dataset.view === view;
btn.classList.toggle('active', active);
if (active) btn.setAttribute('aria-current', 'page');
else btn.removeAttribute('aria-current');
});
document.querySelectorAll('.nav-zone[data-topnav-group]').forEach((zone) => {
zone.classList.toggle('active', TOPNAV_VIEW_GROUP[view] === zone.dataset.topnavGroup);
});
}
function mountMatrixRoleSidebar() {
const roleNav = document.querySelector('#access-matrix-content .am-role-list');
if (!roleNav) return false;
const nav = document.getElementById('context-nav');
if (!nav) return false;
nav.querySelectorAll('.am-role-list').forEach((el) => {
if (el !== roleNav) el.remove();
});
if (roleNav.parentElement === nav) return true;
roleNav.dataset.parkedFrom = '#access-matrix-content .am-layout';
nav.appendChild(roleNav);
return true;
}
function mountEventsToolbar() {
const cfg = CONTEXT_TOOLBARS.events;
const nav = document.getElementById('context-nav');
const sidebar = document.getElementById('context-sidebar');
const shell = document.querySelector('.shell');
const titleEl = document.getElementById('context-sidebar-title');
const labelEl = document.getElementById('context-sidebar-label');
if (!cfg || !nav || !sidebar) return false;
const toolbar = resolveContextToolbar(cfg);
if (!toolbar) return false;
sidebar.hidden = false;
shell?.classList.remove('shell--no-context');
if (titleEl) titleEl.textContent = cfg.title;
if (labelEl) labelEl.textContent = cfg.label;
toolbar.classList.add('context-toolbar');
if (toolbar.parentElement !== nav) nav.appendChild(toolbar);
return true;
}
function initTopnavDropdowns() {
/* v0.11 — tabs planas; sem dropdowns */
}
function updateContextSidebar(view) {
const shell = document.querySelector('.shell');
const sidebar = document.getElementById('context-sidebar');
const nav = document.getElementById('context-nav');
const titleEl = document.getElementById('context-sidebar-title');
const labelEl = document.getElementById('context-sidebar-label');
if (!shell || !sidebar || !nav) return;
parkContextToolbars();
nav.innerHTML = '';
// Spec 032 — Tickets RWD tem header próprio; sidebar contextual duplica título e embola layout.
if (view === 'tickets') {
sidebar.hidden = true;
shell.classList.add('shell--no-context');
return;
}
// Controle de acesso Suporte — layout ACS com rail próprio (sem sidebar contextual).
if (view === 'admin') {
sidebar.hidden = true;
shell.classList.add('shell--no-context');
return;
}
if (view === 'access-matrix') {
sidebar.hidden = false;
shell.classList.remove('shell--no-context');
if (titleEl) titleEl.textContent = '';
if (labelEl) labelEl.textContent = 'Funções';
requestAnimationFrame(() => mountMatrixRoleSidebar());
return;
}
const cfg = CONTEXT_TOOLBARS[view];
if (!cfg) {
sidebar.hidden = true;
shell.classList.add('shell--no-context');
return;
}
if (view === 'events') {
mountEventsToolbar();
return;
}
const toolbar = resolveContextToolbar(cfg);
if (!toolbar) {
sidebar.hidden = true;
shell.classList.add('shell--no-context');
return;
}
sidebar.hidden = false;
shell.classList.remove('shell--no-context');
if (titleEl) titleEl.textContent = cfg.title;
if (labelEl) labelEl.textContent = cfg.label;
toolbar.classList.add('context-toolbar');
nav.appendChild(toolbar);
}
window.DeskTopnav = {
initTopnavDropdowns,
updateTopnavActive,
updateContextSidebar,
remountMatrixRoles: mountMatrixRoleSidebar,
remountEventsToolbar: mountEventsToolbar,
};