[FEAT] Gap Analysis ACN: frontend (acn-gap.html + sidebar + api.js)

- public/acn-gap.html: catalogo requisiti consultabile, wizard per funzione FW
  con accordion misure/requisiti, opzioni attuato/parziale/non attuato/N.A.,
  autosave batch debounce, risultati con punteggio per funzione + piano d'azione
  gap + analisi AI. Badge livello soggetto (importante/essenziale). IT/EN inline.
- api.js: metodi acn* (catalog/list/create/requirements/respond/complete/report/
  aiAnalyze) che spacchettano l'envelope e lanciano errore su success=false.
- common.js: voce sidebar "Gap Analysis ACN".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-01 08:10:50 +02:00
co-authored by Claude Opus 4.8
parent ea2a291325
commit 91043d7391
3 changed files with 435 additions and 0 deletions
+25
View File
@@ -179,6 +179,31 @@ class NIS2API {
getAssessmentReport(id) { return this.get(`/assessments/${id}/report`); }
aiAnalyzeAssessment(id) { return this.post(`/assessments/${id}/ai-analyze`, {}); }
// ═══════════════════════════════════════════════════════════════════
// Gap Analysis ACN (Determinazione 164179/2025 - misure/requisiti)
// I metodi acn* ritornano direttamente il payload `data` e lanciano
// un errore {message, error_code} se success=false (per il try/catch UI).
// ═══════════════════════════════════════════════════════════════════
async _acn(promise) {
const r = await promise;
if (!r || !r.success) {
const err = new Error((r && r.message) || 'Errore');
err.error_code = r && (r.error_code || (r.data && r.data.error_code));
err.data = r && r.data;
throw err;
}
return r.data;
}
acnCatalog() { return this._acn(this.get('/acn-gap/catalog')); }
acnList() { return this._acn(this.get('/acn-gap/list')); }
acnCreate(data) { return this._acn(this.post('/acn-gap/create', data || {})); }
acnGet(id) { return this._acn(this.get(`/acn-gap/${id}`)); }
acnRequirements(id) { return this._acn(this.get(`/acn-gap/${id}/requirements`)); }
acnRespond(id, data) { return this._acn(this.post(`/acn-gap/${id}/respond`, data)); }
acnComplete(id) { return this._acn(this.post(`/acn-gap/${id}/complete`, {})); }
acnReport(id) { return this._acn(this.get(`/acn-gap/${id}/report`)); }
acnAiAnalyze(id) { return this._acn(this.post(`/acn-gap/${id}/aiAnalyze`, {})); }
// ═══════════════════════════════════════════════════════════════════
// Dashboard
// ═══════════════════════════════════════════════════════════════════
+1
View File
@@ -188,6 +188,7 @@ function loadSidebar() {
{ name: 'Dashboard', href: 'dashboard.html', icon: iconGrid(), i18nKey: 'nav.dashboard' },
{ name: 'Compliance Journey', href: 'workflow.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/></svg>` },
{ name: 'Gap Analysis', href: 'assessment.html', icon: iconClipboardCheck(), i18nKey: 'nav.gap_analysis' },
{ name: 'Gap Analysis ACN', href: 'acn-gap.html', icon: iconClipboardCheck(), i18nKey: 'nav.acn_gap' },
]
},
{