[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
+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) ─
|
||||
|
||||
Reference in New Issue
Block a user