/** * NIS2 Agile - Organigramma (A4 Fase 4.1) * Vista ad albero dei ruoli organizzativi + editor (crea/modifica/elimina). * Client api.orgRole*: ritorna `data`, lancia su success=false (vedi api.js _acn). * Nodo "organo di governance" = Art. 23 D.Lgs. 138/2024. */ 'use strict'; let ORG = { roles: [], users: [], tree: [] }; function el(id) { return document.getElementById(id); } function esc(s) { const d = document.createElement('div'); d.textContent = (s == null ? '' : String(s)); return d.innerHTML; } document.addEventListener('DOMContentLoaded', async function () { if (typeof checkAuth === 'function' && !checkAuth()) return; if (window.I18n && I18n.init) I18n.init('it'); if (typeof loadSidebar === 'function') loadSidebar(); if (window.HelpSystem && HelpSystem.init) HelpSystem.init(); // Chiudi la modale con ESC e con click sull'overlay. document.addEventListener('keydown', function (e) { if (e.key === 'Escape') orgCloseModal(); }); const ov = el('org-modal'); if (ov) ov.addEventListener('click', function (e) { if (e.target === ov) orgCloseModal(); }); await orgLoadAll(); }); async function orgLoadAll() { try { const [data, users] = await Promise.all([ api.orgRolesList(), api.orgRolesAssignableUsers() ]); ORG.roles = data.roles || []; ORG.tree = data.tree || []; ORG.users = Array.isArray(users) ? users : []; orgRender(); } catch (e) { showNotification('Errore nel caricamento dell\'organigramma: ' + (e.message || e), 'error'); } } function orgRender() { const tree = el('org-tree'); const empty = el('org-empty'); const stats = el('org-stats'); if (!ORG.roles.length) { tree.innerHTML = ''; empty.style.display = 'block'; stats.style.display = 'none'; return; } empty.style.display = 'none'; // Statistiche const gov = ORG.roles.filter(r => r.is_governance_body).length; const vacant = ORG.roles.filter(r => !r.holder_user_id).length; el('org-stat-total').textContent = ORG.roles.length; el('org-stat-gov').textContent = gov; el('org-stat-vacant').textContent = vacant; stats.style.display = 'flex'; tree.innerHTML = ORG.tree.map(orgNodeHtml).join(''); } function orgNodeHtml(node) { const gov = node.is_governance_body; const holder = node.holder_name ? '
👤 ' + esc(node.holder_name) + '
' : '
Ruolo vacante
'; const desc = node.description ? '
' + esc(node.description) + '
' : ''; const govBadge = gov ? 'Governance · Art. 23' : ''; const children = (node.children && node.children.length) ? '
' + node.children.map(orgNodeHtml).join('') + '
' : ''; return '' + '
' + '
' + '
' + '
' + esc(node.role_name) + govBadge + '
' + holder + desc + '
' + '
' + '' + '' + '' + '
' + '
' + children + '
'; } /** Insieme degli id discendenti di un ruolo (per escluderli dalla select padre). */ function orgDescendants(roleId) { const out = new Set(); const stack = [roleId]; while (stack.length) { const cur = stack.pop(); ORG.roles.forEach(r => { if (r.parent_role_id === cur && !out.has(r.id)) { out.add(r.id); stack.push(r.id); } }); } return out; } /** * Apre la modale. editId => modifica; parentId => crea un subordinato di parentId. */ function orgOpenModal(editId, parentId) { const role = editId ? ORG.roles.find(r => r.id === editId) : null; el('org-modal-title').textContent = role ? 'Modifica ruolo' : 'Nuovo ruolo'; el('org-f-id').value = role ? role.id : ''; el('org-f-name').value = role ? role.role_name : ''; el('org-f-desc').value = role ? (role.description || '') : ''; el('org-f-gov').checked = role ? !!role.is_governance_body : false; el('org-f-err').textContent = ''; // Select padre — escludi se stesso e i suoi discendenti (anti-ciclo lato UI). const exclude = role ? orgDescendants(role.id) : new Set(); if (role) exclude.add(role.id); const parentSel = el('org-f-parent'); parentSel.innerHTML = '' + ORG.roles.filter(r => !exclude.has(r.id)) .map(r => '').join(''); const selParent = role ? role.parent_role_id : (parentId || ''); parentSel.value = selParent ? String(selParent) : ''; // Select titolare const holderSel = el('org-f-holder'); holderSel.innerHTML = '' + ORG.users.map(u => '').join(''); holderSel.value = (role && role.holder_user_id) ? String(role.holder_user_id) : ''; el('org-modal').classList.add('open'); setTimeout(() => el('org-f-name').focus(), 50); } function orgCloseModal() { const m = el('org-modal'); if (m) m.classList.remove('open'); } async function orgSaveRole() { const id = el('org-f-id').value; const name = el('org-f-name').value.trim(); if (!name) { el('org-f-err').textContent = 'Il nome del ruolo è obbligatorio.'; return; } const payload = { role_name: name, parent_role_id: el('org-f-parent').value || null, holder_user_id: el('org-f-holder').value || null, is_governance_body: el('org-f-gov').checked ? 1 : 0, description: el('org-f-desc').value.trim() }; const btn = el('org-f-save'); btn.disabled = true; try { if (id) { await api.orgRoleUpdate(parseInt(id, 10), payload); showNotification('Ruolo aggiornato.', 'success'); } else { await api.orgRoleCreate(payload); showNotification('Ruolo creato.', 'success'); } orgCloseModal(); await orgLoadAll(); } catch (e) { el('org-f-err').textContent = e.message || 'Errore nel salvataggio.'; } finally { btn.disabled = false; } } async function orgDeleteRole(id) { const role = ORG.roles.find(r => r.id === id); if (!confirm('Eliminare il ruolo "' + (role ? role.role_name : '') + '"?')) return; try { await api.orgRoleDelete(id); showNotification('Ruolo eliminato.', 'success'); await orgLoadAll(); } catch (e) { // 409 = ha ruoli subordinati showNotification(e.message || 'Errore nell\'eliminazione.', 'error'); } } /** Crea una struttura organizzativa di base (solo se l'organigramma è vuoto). */ async function orgScaffold() { if (ORG.roles.length) { showNotification('L\'organigramma non è vuoto.', 'info'); return; } if (!confirm('Creare una struttura di base (Organi di amministrazione, Direzione, CISO, Responsabile IT, DPO)?')) return; try { const board = await api.orgRoleCreate({ role_name: 'Organi di amministrazione e direttivi', is_governance_body: 1, description: 'Approva le misure di gestione del rischio e ne vigila l\'attuazione (art. 23 D.Lgs. 138/2024).' }); const dir = await api.orgRoleCreate({ role_name: 'Direzione / Alta Dirigenza', parent_role_id: board.id }); await api.orgRoleCreate({ role_name: 'Responsabile Sicurezza Informatica (CISO)', parent_role_id: dir.id, description: 'Coordina le misure di sicurezza e l\'attuazione del programma NIS2.' }); await api.orgRoleCreate({ role_name: 'Responsabile IT', parent_role_id: dir.id }); await api.orgRoleCreate({ role_name: 'Responsabile Protezione Dati (DPO)', parent_role_id: dir.id }); showNotification('Struttura di base creata. Personalizzala pure.', 'success'); await orgLoadAll(); } catch (e) { showNotification('Errore nella creazione della struttura: ' + (e.message || e), 'error'); } }