[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
// ═══════════════════════════════════════════════════════════════════