[FEAT] Visura auto-fill, adesione volontaria, modulo NCR/CAPA
1. Fix auto-fill visura: mapping corretto suggested_sector e employees_range, indicatori visivi verdi sui campi auto-compilati, fatturato sempre manuale 2. Adesione volontaria: colonna voluntary_compliance, checkbox in onboarding step 5 quando not_applicable, toggle in settings, reset su ri-classificazione 3. Modulo NCR/CAPA: NonConformityController con 10 endpoint API, tabelle non_conformities + capa_actions, generazione NCR dai gap assessment, predisposizione integrazione SistemiG.agile (webhook + sync) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,25 @@
|
||||
</div>
|
||||
<div class="card-body" id="ai-analysis-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- Generate NCR from gaps -->
|
||||
<div class="card mb-24" id="ncr-generation-card">
|
||||
<div class="card-header">
|
||||
<h3>Non Conformita'</h3>
|
||||
</div>
|
||||
<div class="card-body" style="text-align:center; padding:32px;">
|
||||
<p style="color:var(--gray-600); margin-bottom:16px; font-size:0.875rem;">
|
||||
Genera automaticamente le non conformita' (NCR) dai gap identificati nell'assessment.
|
||||
I controlli con stato "Non Implementato" saranno classificati come <strong>major</strong>,
|
||||
quelli "Parzialmente Implementato" come <strong>minor</strong>.
|
||||
</p>
|
||||
<button class="btn btn-primary" id="btn-generate-ncr" onclick="generateNCRs()">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414A2 2 0 0015.414 6L12 2.586A2 2 0 0010.586 2H6zm5 6a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V8z" clip-rule="evenodd"/></svg>
|
||||
Genera Non Conformita' dai Gap
|
||||
</button>
|
||||
<div id="ncr-generation-result" style="display:none; margin-top:16px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -590,6 +609,54 @@
|
||||
showNotification('Errore di connessione.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Generate NCRs from Assessment Gaps ─────────────────────
|
||||
async function generateNCRs() {
|
||||
if (!currentAssessmentId) {
|
||||
showNotification('Nessun assessment selezionato.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('btn-generate-ncr');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<div class="spinner" style="width:16px;height:16px;border-width:2px;margin:0;"></div> Generazione in corso...';
|
||||
|
||||
try {
|
||||
const result = await api.createNCRsFromAssessment(currentAssessmentId);
|
||||
const resultDiv = document.getElementById('ncr-generation-result');
|
||||
resultDiv.style.display = 'block';
|
||||
|
||||
if (result.success && result.data) {
|
||||
const total = result.data.total || 0;
|
||||
if (total > 0) {
|
||||
const majorCount = (result.data.created || []).filter(n => n.severity === 'major').length;
|
||||
const minorCount = total - majorCount;
|
||||
resultDiv.innerHTML = `
|
||||
<div style="background:var(--secondary-bg); border:1px solid #bbf7d0; border-radius:var(--border-radius); padding:16px; text-align:left;">
|
||||
<strong style="color:#15803d;">${total} non conformita' generate con successo</strong>
|
||||
<div style="margin-top:8px; font-size:0.8125rem; color:var(--gray-600);">
|
||||
${majorCount > 0 ? `<span style="color:var(--danger); font-weight:600;">${majorCount} major</span> ` : ''}
|
||||
${minorCount > 0 ? `<span style="color:var(--warning); font-weight:600;">${minorCount} minor</span>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
showNotification(`${total} non conformita' generate.`, 'success');
|
||||
} else {
|
||||
resultDiv.innerHTML = '<p style="color:var(--gray-500);">Nessun gap trovato — tutti i controlli sono implementati.</p>';
|
||||
showNotification('Nessun gap trovato.', 'info');
|
||||
}
|
||||
btn.style.display = 'none';
|
||||
} else {
|
||||
resultDiv.innerHTML = `<p style="color:var(--danger);">${escapeHtml(result.message || 'Errore nella generazione.')}</p>`;
|
||||
showNotification(result.message || 'Errore.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414A2 2 0 0015.414 6L12 2.586A2 2 0 0010.586 2H6zm5 6a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V8z" clip-rule="evenodd"/></svg> Genera Non Conformita\' dai Gap';
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Errore di connessione.', 'error');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<svg viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414A2 2 0 0015.414 6L12 2.586A2 2 0 0010.586 2H6zm5 6a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V8z" clip-rule="evenodd"/></svg> Genera Non Conformita\' dai Gap';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -98,6 +98,7 @@ $controllerMap = [
|
||||
'audit' => 'AuditController',
|
||||
'admin' => 'AdminController',
|
||||
'onboarding' => 'OnboardingController',
|
||||
'ncr' => 'NonConformityController',
|
||||
];
|
||||
|
||||
if (!isset($controllerMap[$controllerName])) {
|
||||
@@ -275,6 +276,20 @@ $actionMap = [
|
||||
'POST:fetchCompany' => 'fetchCompany',
|
||||
'POST:complete' => 'complete',
|
||||
],
|
||||
|
||||
// ── NonConformityController (NCR & CAPA) ───────
|
||||
'ncr' => [
|
||||
'POST:create' => 'create',
|
||||
'GET:list' => 'list',
|
||||
'GET:{id}' => 'get',
|
||||
'PUT:{id}' => 'update',
|
||||
'POST:{id}/capa' => 'addCapa',
|
||||
'PUT:capa/{subId}' => 'updateCapa',
|
||||
'POST:fromAssessment' => 'fromAssessment',
|
||||
'GET:stats' => 'stats',
|
||||
'POST:{id}/sync' => 'syncExternal',
|
||||
'POST:webhook' => 'webhook',
|
||||
],
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -264,6 +264,19 @@ class NIS2API {
|
||||
|
||||
fetchCompany(vatNumber) { return this.post('/onboarding/fetch-company', { vat_number: vatNumber }); }
|
||||
completeOnboarding(data) { return this.post('/onboarding/complete', data); }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Non-Conformity & CAPA
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
listNCRs(params = {}) { return this.get('/ncr/list?' + new URLSearchParams(params)); }
|
||||
createNCR(data) { return this.post('/ncr/create', data); }
|
||||
getNCR(id) { return this.get(`/ncr/${id}`); }
|
||||
updateNCR(id, data) { return this.put(`/ncr/${id}`, data); }
|
||||
addCapa(ncrId, data) { return this.post(`/ncr/${ncrId}/capa`, data); }
|
||||
updateCapa(capaId, data) { return this.put(`/ncr/capa/${capaId}`, data); }
|
||||
createNCRsFromAssessment(assessmentId) { return this.post('/ncr/from-assessment', { assessment_id: assessmentId }); }
|
||||
getNCRStats() { return this.get('/ncr/stats'); }
|
||||
}
|
||||
|
||||
// Singleton globale
|
||||
|
||||
+211
-7
@@ -598,6 +598,91 @@
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ── Auto-fill indicators ──────────────────────────────────── */
|
||||
.auto-filled .form-input,
|
||||
.auto-filled .form-select {
|
||||
border-color: #86efac;
|
||||
background: #f0fdf4;
|
||||
}
|
||||
|
||||
.auto-fill-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
margin-left: 8px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
color: #15803d;
|
||||
background: #dcfce7;
|
||||
border-radius: 100px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* ── Voluntary Compliance ──────────────────────────────────── */
|
||||
.voluntary-section {
|
||||
display: none;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.voluntary-section.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.voluntary-card {
|
||||
border: 2px solid var(--primary-light);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
background: var(--card-bg);
|
||||
transition: all var(--transition);
|
||||
}
|
||||
|
||||
.voluntary-card.checked {
|
||||
border-color: var(--primary);
|
||||
background: var(--primary-bg);
|
||||
}
|
||||
|
||||
.voluntary-card input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
accent-color: var(--primary);
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.voluntary-card h4 {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 700;
|
||||
color: var(--gray-900);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.voluntary-card p {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--gray-600);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.classification-result.voluntary {
|
||||
border-color: var(--primary);
|
||||
background: linear-gradient(135deg, #eff6ff, #dbeafe);
|
||||
}
|
||||
|
||||
.classification-result.voluntary .classification-result-icon {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.classification-result.voluntary .classification-result-label {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* ── Responsive ─────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.wizard-header {
|
||||
@@ -1040,6 +1125,22 @@
|
||||
<div class="classification-result-desc" id="classification-desc"></div>
|
||||
</div>
|
||||
|
||||
<!-- Voluntary Compliance Option -->
|
||||
<div class="voluntary-section" id="voluntary-section">
|
||||
<div class="voluntary-card" id="voluntary-card">
|
||||
<input type="checkbox" id="voluntary-checkbox" onchange="onVoluntaryChange(this.checked)">
|
||||
<div>
|
||||
<h4>Adesione Volontaria alla NIS2</h4>
|
||||
<p>
|
||||
Anche se la tua organizzazione non rientra nell'ambito di applicazione obbligatorio della
|
||||
Direttiva NIS2, puoi scegliere di aderire volontariamente adottando le misure di sicurezza
|
||||
previste dalla normativa. Questo consente di migliorare la postura di cybersicurezza e di
|
||||
prepararsi a possibili future designazioni.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary -->
|
||||
<div class="summary-card">
|
||||
<div class="summary-card-header">Riepilogo Dati Aziendali</div>
|
||||
@@ -1142,9 +1243,22 @@
|
||||
role: '',
|
||||
phone: ''
|
||||
},
|
||||
classification: null
|
||||
classification: null,
|
||||
autoFilledFields: new Set(),
|
||||
voluntaryCompliance: false
|
||||
};
|
||||
|
||||
// ── Helper: parse employees range string to number ────────
|
||||
function parseEmployeesRange(range) {
|
||||
if (!range) return '';
|
||||
if (typeof range === 'number') return range;
|
||||
const str = String(range).trim();
|
||||
if (str.endsWith('+')) return parseInt(str);
|
||||
const parts = str.split('-');
|
||||
if (parts.length === 2) return parseInt(parts[0]);
|
||||
return parseInt(str) || '';
|
||||
}
|
||||
|
||||
// ── Step 1: Method selection ────────────────────────────────────
|
||||
function selectMethod(method) {
|
||||
wizardState.method = method;
|
||||
@@ -1308,11 +1422,24 @@
|
||||
wizardState.company.website = d.website || d.sito_web || '';
|
||||
wizardState.company.email = d.email || d.pec || '';
|
||||
wizardState.company.phone = d.phone || d.telefono || '';
|
||||
wizardState.company.sector = d.sector || '';
|
||||
wizardState.company.employee_count = d.employee_count || d.employees || '';
|
||||
wizardState.company.annual_turnover = d.annual_turnover || d.turnover || '';
|
||||
wizardState.company.sector = d.suggested_sector || d.sector || '';
|
||||
wizardState.company.employee_count = parseEmployeesRange(d.employees_range) || d.employee_count || d.employees || '';
|
||||
// Fatturato resta SEMPRE manuale — non auto-compilare dalla visura
|
||||
wizardState.company.annual_turnover = '';
|
||||
wizardState.dataSource = 'Dati precompilati dalla visura camerale';
|
||||
|
||||
// Track auto-filled fields
|
||||
wizardState.autoFilledFields.clear();
|
||||
if (wizardState.company.name) wizardState.autoFilledFields.add('company-name');
|
||||
if (wizardState.company.vat_number) wizardState.autoFilledFields.add('vat-number');
|
||||
if (wizardState.company.fiscal_code) wizardState.autoFilledFields.add('fiscal-code');
|
||||
if (wizardState.company.address) wizardState.autoFilledFields.add('address');
|
||||
if (wizardState.company.city) wizardState.autoFilledFields.add('city');
|
||||
if (wizardState.company.email) wizardState.autoFilledFields.add('company-email');
|
||||
if (wizardState.company.phone) wizardState.autoFilledFields.add('company-phone');
|
||||
if (wizardState.company.sector) wizardState.autoFilledFields.add('sector');
|
||||
if (wizardState.company.employee_count) wizardState.autoFilledFields.add('employee-count');
|
||||
|
||||
showNotification('Visura analizzata con successo!', 'success');
|
||||
|
||||
// Auto-advance to step 3
|
||||
@@ -1361,11 +1488,24 @@
|
||||
wizardState.company.website = d.website || d.sito_web || '';
|
||||
wizardState.company.email = d.email || d.pec || '';
|
||||
wizardState.company.phone = d.phone || d.telefono || '';
|
||||
wizardState.company.sector = d.sector || '';
|
||||
wizardState.company.employee_count = d.employee_count || d.employees || '';
|
||||
wizardState.company.annual_turnover = d.annual_turnover || d.turnover || '';
|
||||
wizardState.company.sector = d.suggested_sector || d.sector || '';
|
||||
wizardState.company.employee_count = parseEmployeesRange(d.employees_range) || d.employee_count || d.employees || '';
|
||||
// Fatturato resta SEMPRE manuale
|
||||
wizardState.company.annual_turnover = '';
|
||||
wizardState.dataSource = 'Dati recuperati da CertiSource';
|
||||
|
||||
// Track auto-filled fields
|
||||
wizardState.autoFilledFields.clear();
|
||||
if (wizardState.company.name) wizardState.autoFilledFields.add('company-name');
|
||||
if (wizardState.company.vat_number) wizardState.autoFilledFields.add('vat-number');
|
||||
if (wizardState.company.fiscal_code) wizardState.autoFilledFields.add('fiscal-code');
|
||||
if (wizardState.company.address) wizardState.autoFilledFields.add('address');
|
||||
if (wizardState.company.city) wizardState.autoFilledFields.add('city');
|
||||
if (wizardState.company.email) wizardState.autoFilledFields.add('company-email');
|
||||
if (wizardState.company.phone) wizardState.autoFilledFields.add('company-phone');
|
||||
if (wizardState.company.sector) wizardState.autoFilledFields.add('sector');
|
||||
if (wizardState.company.employee_count) wizardState.autoFilledFields.add('employee-count');
|
||||
|
||||
showNotification('Dati aziendali recuperati con successo!', 'success');
|
||||
|
||||
// Auto-advance to step 3
|
||||
@@ -1413,6 +1553,27 @@
|
||||
} else {
|
||||
banner.classList.remove('visible');
|
||||
}
|
||||
|
||||
// Apply auto-fill visual indicators
|
||||
const autoFields = wizardState.autoFilledFields;
|
||||
document.querySelectorAll('#step-3 .form-input, #step-3 .form-select').forEach(el => {
|
||||
const wrapper = el.closest('.form-group');
|
||||
if (!wrapper) return;
|
||||
|
||||
// Remove previous indicators
|
||||
wrapper.classList.remove('auto-filled');
|
||||
const existing = wrapper.querySelector('.auto-fill-badge');
|
||||
if (existing) existing.remove();
|
||||
|
||||
if (autoFields.has(el.id)) {
|
||||
wrapper.classList.add('auto-filled');
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'auto-fill-badge';
|
||||
badge.textContent = 'da visura';
|
||||
const label = wrapper.querySelector('.form-label');
|
||||
if (label) label.appendChild(badge);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function submitStep3() {
|
||||
@@ -1553,6 +1714,21 @@
|
||||
const result = classifyLocally(sector, employees, turnover);
|
||||
wizardState.classification = result;
|
||||
|
||||
// Reset voluntary state when re-entering step 5
|
||||
wizardState.voluntaryCompliance = false;
|
||||
const voluntaryCheckbox = document.getElementById('voluntary-checkbox');
|
||||
if (voluntaryCheckbox) voluntaryCheckbox.checked = false;
|
||||
const voluntaryCard = document.getElementById('voluntary-card');
|
||||
if (voluntaryCard) voluntaryCard.classList.remove('checked');
|
||||
|
||||
// Show/hide voluntary compliance section
|
||||
const voluntarySection = document.getElementById('voluntary-section');
|
||||
if (result.classification === 'not_applicable') {
|
||||
voluntarySection.classList.add('visible');
|
||||
} else {
|
||||
voluntarySection.classList.remove('visible');
|
||||
}
|
||||
|
||||
// Update classification UI
|
||||
const container = document.getElementById('classification-result');
|
||||
const labelEl = document.getElementById('classification-label');
|
||||
@@ -1589,6 +1765,32 @@
|
||||
document.getElementById('sum-turnover').textContent = turnover ? '\u20AC ' + turnover.toLocaleString('it-IT') : '-';
|
||||
}
|
||||
|
||||
// ── Voluntary Compliance Toggle ───────────────────────────────
|
||||
function onVoluntaryChange(checked) {
|
||||
wizardState.voluntaryCompliance = checked;
|
||||
|
||||
const card = document.getElementById('voluntary-card');
|
||||
const container = document.getElementById('classification-result');
|
||||
const labelEl = document.getElementById('classification-label');
|
||||
const sumEntityType = document.getElementById('sum-entity-type');
|
||||
|
||||
card.classList.toggle('checked', checked);
|
||||
|
||||
if (checked) {
|
||||
container.className = 'classification-result voluntary';
|
||||
container.querySelector('.classification-result-icon').innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/><path d="M10 12.5l-2-2-1.41 1.41L10 15.32l5.41-5.41L14 8.5l-4 4z"/></svg>';
|
||||
labelEl.textContent = 'Adesione Volontaria';
|
||||
document.getElementById('classification-desc').textContent = 'La tua organizzazione aderisce volontariamente alla Direttiva NIS2, adottando le misure di sicurezza previste dalla normativa per migliorare la propria postura di cybersicurezza.';
|
||||
sumEntityType.textContent = 'Adesione Volontaria';
|
||||
} else {
|
||||
container.className = 'classification-result not-applicable';
|
||||
container.querySelector('.classification-result-icon').innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
|
||||
labelEl.textContent = wizardState.classification.label;
|
||||
document.getElementById('classification-desc').textContent = wizardState.classification.description;
|
||||
sumEntityType.textContent = wizardState.classification.label;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Complete Onboarding ─────────────────────────────────────────
|
||||
async function completeOnboarding() {
|
||||
const btn = document.getElementById('complete-btn');
|
||||
@@ -1612,6 +1814,8 @@
|
||||
sector: c.sector,
|
||||
employee_count: parseInt(c.employee_count) || 0,
|
||||
annual_turnover_eur: parseInt(c.annual_turnover) || 0,
|
||||
// Voluntary compliance
|
||||
voluntary_compliance: wizardState.voluntaryCompliance ? 1 : 0,
|
||||
// Profile data
|
||||
full_name: p.full_name,
|
||||
phone: p.phone,
|
||||
|
||||
+44
-4
@@ -79,6 +79,11 @@
|
||||
color: var(--gray-600);
|
||||
border: 1px solid var(--gray-300);
|
||||
}
|
||||
.entity-badge.voluntary {
|
||||
background: var(--primary-bg);
|
||||
color: var(--primary);
|
||||
border: 1px solid var(--primary-light);
|
||||
}
|
||||
.plan-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -573,10 +578,14 @@
|
||||
|
||||
function renderEntityClassification(org) {
|
||||
const type = org.entity_type || 'not_applicable';
|
||||
const label = entityTypeLabels[type] || 'Non Applicabile';
|
||||
const isVoluntary = org.voluntary_compliance == 1 && type === 'not_applicable';
|
||||
const displayLabel = isVoluntary ? 'Adesione Volontaria' : (entityTypeLabels[type] || 'Non Applicabile');
|
||||
const badgeClass = isVoluntary ? 'voluntary' : type;
|
||||
|
||||
let description = '';
|
||||
if (type === 'essential') {
|
||||
if (isVoluntary) {
|
||||
description = 'L\'organizzazione ha scelto di aderire <strong>volontariamente</strong> ai requisiti della Direttiva NIS2, pur non rientrando nell\'ambito di applicazione obbligatorio. Le misure di sicurezza sono adottate su base volontaria.';
|
||||
} else if (type === 'essential') {
|
||||
description = 'L\'organizzazione rientra tra le entita\' <strong>Essenziali</strong> ai sensi della Direttiva NIS2. Soggetta a supervisione proattiva con sanzioni fino a EUR 10M o 2% del fatturato globale.';
|
||||
} else if (type === 'important') {
|
||||
description = 'L\'organizzazione rientra tra le entita\' <strong>Importanti</strong> ai sensi della Direttiva NIS2. Soggetta a supervisione reattiva con sanzioni fino a EUR 7M o 1,4% del fatturato globale.';
|
||||
@@ -584,15 +593,46 @@
|
||||
description = 'In base ai parametri attuali, l\'organizzazione <strong>non rientra</strong> nell\'ambito di applicazione della Direttiva NIS2. Si consiglia comunque di adottare le best practice di cybersecurity.';
|
||||
}
|
||||
|
||||
// Voluntary toggle (only visible for not_applicable entities)
|
||||
const voluntaryToggle = type === 'not_applicable' ? `
|
||||
<div style="margin-top:16px; padding:16px; background:var(--gray-50); border-radius:var(--border-radius); border:1px solid var(--gray-200);">
|
||||
<label style="display:flex; align-items:center; gap:10px; cursor:pointer;">
|
||||
<input type="checkbox" id="org-voluntary" ${isVoluntary ? 'checked' : ''}
|
||||
onchange="toggleVoluntaryCompliance(this.checked)"
|
||||
style="accent-color:var(--primary); width:18px; height:18px; flex-shrink:0;">
|
||||
<span style="font-size:0.875rem; font-weight:600; color:var(--gray-700);">Adesione Volontaria alla NIS2</span>
|
||||
</label>
|
||||
<p style="font-size:0.75rem; color:var(--gray-500); margin-top:6px; margin-left:28px;">
|
||||
Aderisci volontariamente ai requisiti NIS2 anche se non obbligato dalla normativa.
|
||||
</p>
|
||||
</div>` : '';
|
||||
|
||||
document.getElementById('entity-classification').innerHTML = `
|
||||
<div class="entity-badge ${type}">
|
||||
<div class="entity-badge ${badgeClass}">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="20" height="20"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>
|
||||
Entita' ${label}
|
||||
Entita': ${displayLabel}
|
||||
</div>
|
||||
<p style="font-size:0.8125rem; color:var(--gray-600); line-height:1.6; max-width:640px;">${description}</p>
|
||||
${voluntaryToggle}
|
||||
`;
|
||||
}
|
||||
|
||||
async function toggleVoluntaryCompliance(checked) {
|
||||
if (!currentOrg) return;
|
||||
try {
|
||||
const result = await api.updateOrganization(currentOrg.id, { voluntary_compliance: checked ? 1 : 0 });
|
||||
if (result.success) {
|
||||
currentOrg.voluntary_compliance = checked ? 1 : 0;
|
||||
renderEntityClassification(currentOrg);
|
||||
showNotification(checked ? 'Adesione volontaria attivata.' : 'Adesione volontaria disattivata.', 'success');
|
||||
} else {
|
||||
showNotification(result.message || 'Errore.', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Errore di connessione.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function renderSubscriptionPlan(org) {
|
||||
const plan = org.subscription_plan || 'free';
|
||||
const planLabels = { free: 'Free', professional: 'Professional', enterprise: 'Enterprise' };
|
||||
|
||||
Reference in New Issue
Block a user