[FEAT] Dashboard: card Documenti istituzionali funzionante + UI (ticket #499)
- loadDocs: 5 voci standard sempre presenti + voci custom + 'Aggiungi voce' - modale add/edit con descrizione OBBLIGATORIA (richiesta punto 1), link doc opzionale - delete voci custom; controlli visibili a org_admin/compliance_manager/super_admin - CSS .doc-* in v3.css; cache-buster dashboard.js/v3.css su dashboard.html; version 1.25.1 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1b65321ffb
commit
896663d24c
@@ -302,6 +302,17 @@ a.stat:hover{box-shadow:0 8px 20px rgba(16,40,80,.13);transform:translateY(-1px)
|
||||
.btn-outline:hover:not(:disabled){background:var(--navy-nav);color:#fff}
|
||||
.btn-secondary:disabled,.btn-success:disabled,.btn-danger:disabled,.btn-warning:disabled,.btn-outline:disabled{opacity:.5;cursor:not-allowed}
|
||||
.btn-sm{padding:6px 14px;font-size:.8rem}
|
||||
/* Documenti istituzionali (dashboard) — ticket #499 */
|
||||
.doc-list{display:flex;flex-direction:column;gap:2px}
|
||||
.doc-item{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:10px 4px;border-bottom:1px solid var(--v3-border,#e6e8ee)}
|
||||
.doc-item:last-of-type{border-bottom:none}
|
||||
.doc-main{display:flex;flex-direction:column;gap:2px;min-width:0}
|
||||
.doc-main b{font-size:.92rem}
|
||||
.doc-desc{font-size:.82rem;color:var(--v3-muted,#5b6472);white-space:pre-line;word-break:break-word}
|
||||
.doc-empty-desc{font-style:italic;opacity:.75}
|
||||
.doc-file{font-size:.78rem;font-weight:500;margin-left:6px}
|
||||
.doc-actions{display:flex;gap:2px;flex:0 0 auto}
|
||||
.doc-add{margin-top:8px}
|
||||
.btn-lg{padding:14px 28px;font-size:1rem}
|
||||
.btn-icon{padding:8px;width:36px;height:36px}
|
||||
.btn-group{display:flex;gap:8px}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>Dashboard · NIS2 Agile</title>
|
||||
<!-- UI V3: Inter self-host + design system v3.css (NO Google Fonts / NO unpkg CDN). -->
|
||||
<link rel="stylesheet" href="/vendor/inter/inter.css?v=20260627">
|
||||
<link rel="stylesheet" href="/css/v3.css?v=20260627">
|
||||
<link rel="stylesheet" href="/css/v3.css?v=20260724a">
|
||||
<!-- PWA:start -->
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<meta name="theme-color" content="#0066CC">
|
||||
@@ -118,7 +118,7 @@
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260627p"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/dashboard.js?v=20260627"></script>
|
||||
<script src="/js/dashboard.js?v=20260724a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+113
-2
@@ -290,10 +290,121 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── 5) Documenti istituzionali — nessun endpoint reale → stato vuoto onesto
|
||||
// ── 5) Documenti istituzionali — /api/institutional-docs (ticket #499) ──
|
||||
// Le 5 voci standard sono SEMPRE presenti (Codice Etico, Mission, Vision,
|
||||
// Statuto, Piano Sanzionatorio); ogni documento richiede una descrizione.
|
||||
function canEditDocs() {
|
||||
var r = (localStorage.getItem('nis2_user_role') || '').toLowerCase();
|
||||
return r === 'org_admin' || r === 'compliance_manager' || r === 'super_admin';
|
||||
}
|
||||
function docRow(item, stdLabel) {
|
||||
var filled = item && item.description && String(item.description).trim() !== '';
|
||||
var title = esc((item && item.title) || stdLabel || 'Documento');
|
||||
var editBtn = canEditDocs()
|
||||
? '<button class="btn-ghost btn-sm doc-edit" type="button"><i data-lucide="pencil" class="ic"></i></button>' : '';
|
||||
var delBtn = (canEditDocs() && item && item.id && Number(item.is_standard) !== 1)
|
||||
? '<button class="btn-ghost btn-sm doc-del" type="button"><i data-lucide="trash-2" class="ic"></i></button>' : '';
|
||||
var body = filled
|
||||
? '<span class="doc-desc">' + esc(item.description) + '</span>'
|
||||
: '<span class="doc-desc doc-empty-desc">Nessuna descrizione — ' + (canEditDocs() ? 'compila la voce.' : 'da compilare.') + '</span>';
|
||||
var fileLink = (item && item.file_url)
|
||||
? ' <a class="doc-file" href="' + esc(item.file_url) + '" target="_blank" rel="noopener">documento</a>' : '';
|
||||
return '<div class="doc-item" data-id="' + esc((item && item.id) || '') +
|
||||
'" data-key="' + esc((item && item.doc_key) || (stdLabel ? '' : '')) + '">' +
|
||||
'<div class="doc-main"><b>' + title + fileLink + '</b>' + body + '</div>' +
|
||||
'<div class="doc-actions">' + editBtn + delBtn + '</div></div>';
|
||||
}
|
||||
function stdKeyOf(label, standards) {
|
||||
for (var i = 0; i < standards.length; i++) if (standards[i].label === label) return standards[i].key;
|
||||
return null;
|
||||
}
|
||||
function loadDocs() {
|
||||
var card = document.getElementById('doc-card'); if (!card) return;
|
||||
setEmpty('doc-card', 'Documenti istituzionali', 'Gestisci Mission, Vision ed Etica nelle Impostazioni.', 'file-text');
|
||||
setLoading('doc-card');
|
||||
api.request('GET', '/institutional-docs/list').then(function (res) {
|
||||
var data = (res && res.data) || res || {};
|
||||
var docs = data.docs || [];
|
||||
var standards = data.standards || [];
|
||||
var byKey = {}; var customs = [];
|
||||
docs.forEach(function (d) {
|
||||
if (d.doc_key) byKey[d.doc_key] = d; else customs.push(d);
|
||||
});
|
||||
var html = '';
|
||||
// 5 voci standard sempre presenti
|
||||
standards.forEach(function (s) {
|
||||
var item = byKey[s.key] || { doc_key: s.key, title: s.label, is_standard: 1 };
|
||||
html += docRow(item, s.label);
|
||||
});
|
||||
// voci custom
|
||||
customs.forEach(function (d) { html += docRow(d, null); });
|
||||
if (canEditDocs()) {
|
||||
html += '<button class="btn-ghost doc-add" type="button" style="margin-top:8px">' +
|
||||
'<i data-lucide="plus" class="ic"></i>Aggiungi voce</button>';
|
||||
}
|
||||
card.innerHTML = '<div class="doc-list">' + html + '</div>';
|
||||
refreshIcons();
|
||||
wireDocEvents(card, standards);
|
||||
}).catch(function (e) {
|
||||
setError('doc-card', (e && e.message) || 'Riprova tra poco.');
|
||||
});
|
||||
}
|
||||
function docModal(opts, standards) {
|
||||
var isStd = !!opts.doc_key;
|
||||
var titleField = isStd
|
||||
? '<input type="text" id="doc-f-title" class="form-control" value="' + esc(opts.title || '') + '" readonly>'
|
||||
: '<input type="text" id="doc-f-title" class="form-control" value="' + esc(opts.title || '') + '" maxlength="200" placeholder="Titolo del documento">';
|
||||
var body =
|
||||
'<div class="form-group"><label for="doc-f-title">Titolo</label>' + titleField + '</div>' +
|
||||
'<div class="form-group"><label for="doc-f-desc">Descrizione <span style="color:#c00">*</span></label>' +
|
||||
'<textarea id="doc-f-desc" class="form-control" rows="4" maxlength="2000" required placeholder="Descrizione del documento (obbligatoria)">' + esc(opts.description || '') + '</textarea></div>' +
|
||||
'<div class="form-group"><label for="doc-f-file">Link al documento (opzionale)</label>' +
|
||||
'<input type="url" id="doc-f-file" class="form-control" value="' + esc(opts.file_url || '') + '" placeholder="https://…"></div>';
|
||||
var footer = '<button class="btn-ghost" type="button" onclick="closeModal()">Annulla</button>' +
|
||||
'<button class="btn-primary" type="button" id="doc-f-save">Salva</button>';
|
||||
showModal(opts.id ? 'Modifica documento' : (isStd ? 'Compila: ' + (opts.title || '') : 'Aggiungi documento'), body, { footer: footer });
|
||||
var saveBtn = document.getElementById('doc-f-save');
|
||||
saveBtn.addEventListener('click', function () {
|
||||
var title = document.getElementById('doc-f-title').value.trim();
|
||||
var desc = document.getElementById('doc-f-desc').value.trim();
|
||||
var file = document.getElementById('doc-f-file').value.trim();
|
||||
if (!title) { showNotification('Il titolo è obbligatorio', 'error'); return; }
|
||||
if (!desc) { showNotification('La descrizione è obbligatoria', 'error'); return; }
|
||||
saveBtn.disabled = true;
|
||||
api.request('POST', '/institutional-docs/save', {
|
||||
id: opts.id || undefined, doc_key: opts.doc_key || undefined,
|
||||
title: title, description: desc, file_url: file || undefined
|
||||
}).then(function () {
|
||||
closeModal(); showNotification('Documento salvato', 'success'); loadDocs();
|
||||
}).catch(function (e) {
|
||||
saveBtn.disabled = false;
|
||||
showNotification((e && e.message) || 'Salvataggio non riuscito', 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
function wireDocEvents(card, standards) {
|
||||
card.querySelectorAll('.doc-edit').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var el = btn.closest('.doc-item');
|
||||
docModal({
|
||||
id: el.getAttribute('data-id') || null,
|
||||
doc_key: el.getAttribute('data-key') || null,
|
||||
title: el.querySelector('b') ? el.querySelector('b').childNodes[0].textContent.trim() : '',
|
||||
description: el.querySelector('.doc-empty-desc') ? '' : (el.querySelector('.doc-desc') ? el.querySelector('.doc-desc').textContent : ''),
|
||||
file_url: el.querySelector('.doc-file') ? el.querySelector('.doc-file').getAttribute('href') : ''
|
||||
}, standards);
|
||||
});
|
||||
});
|
||||
card.querySelectorAll('.doc-del').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var el = btn.closest('.doc-item'); var id = el.getAttribute('data-id');
|
||||
if (!id || !confirm('Eliminare questa voce?')) return;
|
||||
api.request('DELETE', '/institutional-docs/' + id).then(function () {
|
||||
showNotification('Documento eliminato', 'success'); loadDocs();
|
||||
}).catch(function (e) { showNotification((e && e.message) || 'Eliminazione non riuscita', 'error'); });
|
||||
});
|
||||
});
|
||||
var addBtn = card.querySelector('.doc-add');
|
||||
if (addBtn) addBtn.addEventListener('click', function () { docModal({}, standards); });
|
||||
}
|
||||
|
||||
// ── Init pubblico (chiamato dall'init pagina dopo checkAuth/loadSidebar) ─
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"version": "1.25.0", "build": "2026-06-27-v1.25.0", "date": "2026-06-27", "changelog": "Nuova interfaccia (UI V3): la navigazione e' ora un menu IN ALTO a tendina (al posto della barra laterale a sinistra), con un design piu' moderno (tema blu/oro, nuovi font e icone). Tutte le funzioni e i dati restano gli stessi: cambia la veste e dove trovi le voci. L'assistente ARIA, l'aiuto contestuale (?), le traduzioni IT/EN e la knowledge base sono allineati alla nuova navigazione."}
|
||||
{"version": "1.25.1", "build": "2026-07-24-v1.25.1", "date": "2026-07-24", "changelog": "Dashboard - card 'Documenti istituzionali' ora funzionante: puoi aggiungere documenti (ognuno con una descrizione obbligatoria) e trovi sempre presenti le 5 voci standard Codice Etico, Mission, Vision, Statuto e Piano Sanzionatorio da compilare."}
|
||||
|
||||
Reference in New Issue
Block a user