nis2-agile/public/onboarding.html
DevEnv nis2-agile e4e7d94043 [UX] Standardizzazione login/register/onboarding + Test Runner v2
login.html: eye toggle, forgot password, auth-terms footer
register.html: wizard 3-step, 5 ruoli NIS2, invite_token URL, P.IVA lookup
onboarding.html: Font Awesome, brand color cyan (#06B6D4)
test-runner.php: L1-L5 test levels, SIM-06 B2B, tab Coverage/Stats,
  DB row counts, run history (localStorage), 5 tabs totali

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 17:11:25 +01:00

1761 lines
80 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Onboarding - NIS2 Agile</title>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
/* NIS2 brand color override for wizard */
:root { --nis2-cyan: #06B6D4; --nis2-cyan-light: #ecfeff; --nis2-cyan-ring: rgba(6,182,212,.15); }
/* ── Wizard Layout ──────────────────────────────────────────── */
.wizard-page {
min-height: 100vh;
background: var(--gray-50);
display: flex;
flex-direction: column;
}
.wizard-header {
background: var(--card-bg);
border-bottom: 1px solid var(--gray-200);
padding: 16px 32px;
display: flex;
align-items: center;
gap: 12px;
}
.wizard-header-logo {
width: 36px;
height: 36px;
background: var(--primary);
border-radius: var(--border-radius);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.wizard-header-logo svg {
width: 20px;
height: 20px;
fill: #fff;
}
.wizard-header-text h1 {
font-size: 1.125rem;
font-weight: 700;
color: var(--gray-900);
letter-spacing: -0.01em;
}
.wizard-header-text span {
font-size: 0.7rem;
color: var(--gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* ── Stepper ────────────────────────────────────────────────── */
.wizard-stepper {
background: var(--card-bg);
border-bottom: 1px solid var(--gray-200);
padding: 20px 32px;
}
.stepper {
display: flex;
align-items: center;
justify-content: center;
gap: 0;
max-width: 800px;
margin: 0 auto;
}
.stepper-step {
display: flex;
align-items: center;
gap: 10px;
white-space: nowrap;
}
.stepper-number {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--gray-200);
color: var(--gray-500);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: 700;
flex-shrink: 0;
transition: all var(--transition);
}
.stepper-label {
font-size: 0.8rem;
font-weight: 500;
color: var(--gray-400);
transition: color var(--transition);
}
.stepper-step.active .stepper-number {
background: var(--nis2-cyan);
color: #fff;
box-shadow: 0 0 0 4px var(--nis2-cyan-ring);
}
.stepper-step.active .stepper-label {
color: var(--nis2-cyan);
font-weight: 600;
}
.stepper-step.completed .stepper-number {
background: var(--nis2-cyan);
color: #fff;
}
.stepper-step.completed .stepper-label {
color: var(--nis2-cyan);
}
.stepper-connector {
width: 40px;
height: 2px;
background: var(--gray-200);
margin: 0 8px;
flex-shrink: 0;
transition: background var(--transition);
}
.stepper-connector.completed {
background: var(--nis2-cyan);
}
/* ── Wizard Body ────────────────────────────────────────────── */
.wizard-body {
flex: 1;
display: flex;
justify-content: center;
padding: 40px 24px 60px;
}
.wizard-content {
width: 100%;
max-width: 800px;
}
/* ── Step Panels ────────────────────────────────────────────── */
.wizard-step {
display: none;
animation: wizardFadeIn 0.35s ease;
}
.wizard-step.active {
display: block;
}
@keyframes wizardFadeIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.wizard-step-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--gray-900);
margin-bottom: 8px;
letter-spacing: -0.01em;
}
.wizard-step-subtitle {
font-size: 0.9375rem;
color: var(--gray-500);
margin-bottom: 32px;
line-height: 1.6;
}
/* ── Option Cards (Step 1) ──────────────────────────────────── */
.option-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
margin-bottom: 32px;
}
.option-card {
background: var(--card-bg);
border: 2px solid var(--gray-200);
border-radius: var(--border-radius-lg);
padding: 28px 24px;
text-align: center;
cursor: pointer;
transition: all var(--transition);
position: relative;
}
.option-card:hover {
border-color: var(--primary-light);
box-shadow: var(--card-shadow-hover);
transform: translateY(-2px);
}
.option-card.selected {
border-color: var(--nis2-cyan);
background: var(--nis2-cyan-light);
box-shadow: 0 0 0 4px var(--nis2-cyan-ring);
}
.option-card-icon {
width: 56px;
height: 56px;
border-radius: var(--border-radius-lg);
background: var(--primary-bg);
color: var(--primary);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
transition: all var(--transition);
}
.option-card.selected .option-card-icon {
background: var(--nis2-cyan);
color: #fff;
}
.option-card-icon svg {
width: 28px;
height: 28px;
}
.option-card-title {
font-size: 0.9375rem;
font-weight: 700;
color: var(--gray-900);
margin-bottom: 8px;
}
.option-card-desc {
font-size: 0.8125rem;
color: var(--gray-500);
line-height: 1.5;
}
.option-card-check {
position: absolute;
top: 12px;
right: 12px;
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--primary);
color: #fff;
display: none;
align-items: center;
justify-content: center;
}
.option-card.selected .option-card-check {
display: flex;
}
.option-card-check svg {
width: 14px;
height: 14px;
}
/* ── Upload Area ────────────────────────────────────────────── */
.upload-area {
border: 2px dashed var(--gray-300);
border-radius: var(--border-radius-lg);
padding: 48px 24px;
text-align: center;
cursor: pointer;
transition: all var(--transition);
background: var(--card-bg);
position: relative;
}
.upload-area:hover,
.upload-area.dragover {
border-color: var(--primary);
background: var(--primary-bg);
}
.upload-area.has-file {
border-color: var(--secondary);
background: var(--secondary-bg);
border-style: solid;
}
.upload-area-icon {
width: 64px;
height: 64px;
border-radius: 50%;
background: var(--gray-100);
color: var(--gray-400);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
transition: all var(--transition);
}
.upload-area:hover .upload-area-icon,
.upload-area.dragover .upload-area-icon {
background: var(--primary);
color: #fff;
}
.upload-area.has-file .upload-area-icon {
background: var(--secondary);
color: #fff;
}
.upload-area-icon svg {
width: 32px;
height: 32px;
}
.upload-area-title {
font-size: 1rem;
font-weight: 600;
color: var(--gray-700);
margin-bottom: 4px;
}
.upload-area-hint {
font-size: 0.8125rem;
color: var(--gray-500);
}
.upload-area-formats {
font-size: 0.75rem;
color: var(--gray-400);
margin-top: 12px;
}
.upload-file-info {
display: none;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 12px;
font-size: 0.875rem;
font-weight: 600;
color: var(--secondary);
}
.upload-file-info svg {
width: 18px;
height: 18px;
}
.upload-area.has-file .upload-file-info {
display: flex;
}
.upload-area input[type="file"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
/* ── Loading State ──────────────────────────────────────────── */
.wizard-loading {
display: none;
text-align: center;
padding: 48px 24px;
}
.wizard-loading.active {
display: block;
}
.wizard-loading .spinner-lg {
margin-bottom: 16px;
}
.wizard-loading-text {
font-size: 1rem;
font-weight: 600;
color: var(--gray-700);
margin-bottom: 4px;
}
.wizard-loading-subtext {
font-size: 0.8125rem;
color: var(--gray-500);
}
/* ── CertiSource Badge ──────────────────────────────────────── */
.certisource-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
background: var(--gray-50);
border: 1px solid var(--gray-200);
border-radius: 100px;
font-size: 0.75rem;
font-weight: 600;
color: var(--gray-600);
margin-top: 16px;
}
.certisource-badge svg {
width: 16px;
height: 16px;
color: var(--primary);
}
/* ── Data Source Banner ──────────────────────────────────────── */
.data-source-banner {
display: none;
align-items: center;
gap: 10px;
padding: 12px 20px;
background: var(--secondary-bg);
border: 1px solid #bbf7d0;
border-radius: var(--border-radius);
margin-bottom: 24px;
font-size: 0.875rem;
font-weight: 500;
color: #15803d;
}
.data-source-banner.visible {
display: flex;
}
.data-source-banner svg {
width: 20px;
height: 20px;
flex-shrink: 0;
}
/* ── Company Form Grid ──────────────────────────────────────── */
.form-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0 20px;
}
.form-grid-2 .form-group-full {
grid-column: 1 / -1;
}
/* ── Profile Form ───────────────────────────────────────────── */
.profile-form-wrapper {
max-width: 520px;
margin: 0 auto;
}
/* ── Classification Result (Step 5) ─────────────────────────── */
.classification-result {
padding: 32px;
border-radius: var(--border-radius-lg);
border: 2px solid var(--gray-200);
text-align: center;
margin-bottom: 24px;
transition: all var(--transition);
}
.classification-result.essential {
border-color: #1e40af;
background: linear-gradient(135deg, #eff6ff, #dbeafe);
}
.classification-result.important {
border-color: #d97706;
background: linear-gradient(135deg, #fffbeb, #fef3c7);
}
.classification-result.not-applicable {
border-color: var(--gray-300);
background: var(--gray-50);
}
.classification-result-icon {
width: 64px;
height: 64px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
}
.classification-result.essential .classification-result-icon {
background: #1e40af;
color: #fff;
}
.classification-result.important .classification-result-icon {
background: #d97706;
color: #fff;
}
.classification-result.not-applicable .classification-result-icon {
background: var(--gray-300);
color: #fff;
}
.classification-result-icon svg {
width: 32px;
height: 32px;
}
.classification-result-label {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 8px;
}
.classification-result.essential .classification-result-label {
color: #1e40af;
}
.classification-result.important .classification-result-label {
color: #d97706;
}
.classification-result.not-applicable .classification-result-label {
color: var(--gray-500);
}
.classification-result-desc {
font-size: 0.875rem;
color: var(--gray-600);
line-height: 1.6;
max-width: 600px;
margin: 0 auto;
}
/* ── Summary Card ───────────────────────────────────────────── */
.summary-card {
background: var(--card-bg);
border-radius: var(--border-radius-lg);
box-shadow: var(--card-shadow);
overflow: hidden;
margin-bottom: 32px;
}
.summary-card-header {
padding: 16px 24px;
border-bottom: 1px solid var(--gray-100);
font-size: 0.9375rem;
font-weight: 600;
color: var(--gray-900);
}
.summary-card-body {
padding: 24px;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.summary-item {
padding: 8px 0;
}
.summary-item-label {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--gray-500);
margin-bottom: 4px;
}
.summary-item-value {
font-size: 0.9375rem;
font-weight: 500;
color: var(--gray-800);
}
/* ── Wizard Footer/Actions ──────────────────────────────────── */
.wizard-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 32px;
border-top: 1px solid var(--gray-200);
margin-top: 32px;
}
.wizard-actions-right {
display: flex;
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 {
padding: 12px 16px;
}
.wizard-stepper {
padding: 16px;
overflow-x: auto;
}
.stepper {
min-width: 600px;
}
.stepper-label {
display: none;
}
.wizard-body {
padding: 24px 16px 40px;
}
.option-cards {
grid-template-columns: 1fr;
}
.form-grid-2 {
grid-template-columns: 1fr;
}
.summary-grid {
grid-template-columns: 1fr;
}
.wizard-step-title {
font-size: 1.25rem;
}
}
</style>
</head>
<body>
<div class="wizard-page">
<!-- Header -->
<div class="wizard-header">
<div class="wizard-header-logo">
<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>
</div>
<div class="wizard-header-text">
<h1>NIS2 Agile</h1>
<span>Configurazione Iniziale</span>
</div>
</div>
<!-- Stepper -->
<div class="wizard-stepper">
<div class="stepper" id="stepper">
<div class="stepper-step active" data-step="1">
<div class="stepper-number">1</div>
<span class="stepper-label">Partita IVA</span>
</div>
<div class="stepper-connector" data-after="1"></div>
<div class="stepper-step" data-step="2">
<div class="stepper-number">2</div>
<span class="stepper-label">Dati Aziendali</span>
</div>
<div class="stepper-connector" data-after="2"></div>
<div class="stepper-step" data-step="3">
<div class="stepper-number">3</div>
<span class="stepper-label">Il Tuo Profilo</span>
</div>
<div class="stepper-connector" data-after="3"></div>
<div class="stepper-step" data-step="4">
<div class="stepper-number">4</div>
<span class="stepper-label">Classificazione</span>
</div>
</div>
</div>
<!-- Wizard Body -->
<div class="wizard-body">
<div class="wizard-content">
<!-- ═══ STEP 1: Partita IVA ═══ -->
<div class="wizard-step active" id="step-1">
<h2 class="wizard-step-title">Inserisci la Partita IVA dell'azienda</h2>
<p class="wizard-step-subtitle">Inserisci la Partita IVA dell'azienda da configurare. I dati verranno recuperati automaticamente dal registro delle imprese.</p>
<div class="card" style="max-width:520px; margin:0 auto;">
<div class="card-body">
<div class="form-group">
<label class="form-label" for="s1-vat">Partita IVA <span class="required">*</span></label>
<div style="display:flex; gap:.5rem;">
<span style="display:flex;align-items:center;padding:0 12px;background:var(--gray-100);border:1px solid var(--gray-300);border-radius:var(--border-radius);font-weight:600;color:var(--gray-600);">IT</span>
<input type="text" id="s1-vat" class="form-input" placeholder="12345678901" maxlength="11"
style="flex:1;" oninput="this.value=this.value.replace(/\D/g,'')">
</div>
<div class="form-help">11 cifre numeriche senza prefisso IT</div>
</div>
<div id="s1-result" style="display:none; padding:.75rem; border-radius:8px; margin-bottom:1rem;"></div>
<button class="btn btn-primary btn-lg w-full" id="s1-fetch-btn" onclick="step1FetchCompany()">
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg>
Cerca Azienda
</button>
</div>
</div>
<div class="wizard-loading" id="s1-loading">
<div class="spinner spinner-lg"></div>
<div class="wizard-loading-text">Ricerca in corso...</div>
<div class="wizard-loading-subtext">Interrogazione registro imprese</div>
</div>
<div style="text-align:center; margin-top:1.5rem;">
<button class="btn btn-link" onclick="step1ManualEntry()" style="color:var(--gray-500);font-size:.85rem;">
Non trovi l'azienda? Inserisci manualmente
</button>
</div>
<div class="wizard-actions" style="margin-top:2rem;">
<div></div>
<div class="wizard-actions-right">
<button class="btn btn-primary btn-lg" id="s1-continue-btn" onclick="goToStep(2)" style="display:none;">
Continua
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>
</button>
</div>
</div>
</div>
<!-- ═══ STEP 3: Dati Aziendali ═══ -->
<div class="wizard-step" id="step-2">
<h2 class="wizard-step-title">Verifica e Completa i Dati Aziendali</h2>
<p class="wizard-step-subtitle">Controlla i dati qui sotto e completa le informazioni mancanti.</p>
<div class="data-source-banner" id="data-source-banner">
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
<span id="data-source-text"></span>
</div>
<div class="card">
<div class="card-body">
<div class="form-grid-2">
<div class="form-group form-group-full">
<label class="form-label" for="company-name">Ragione Sociale <span class="required">*</span></label>
<input type="text" id="company-name" class="form-input" placeholder="Es. Acme S.r.l." required>
</div>
<div class="form-group">
<label class="form-label" for="vat-number">Partita IVA</label>
<input type="text" id="vat-number" class="form-input" placeholder="IT12345678901" maxlength="16">
</div>
<div class="form-group">
<label class="form-label" for="fiscal-code">Codice Fiscale</label>
<input type="text" id="fiscal-code" class="form-input" placeholder="Es. 12345678901" maxlength="16">
</div>
<div class="form-group form-group-full">
<label class="form-label" for="address">Indirizzo</label>
<input type="text" id="address" class="form-input" placeholder="Via Roma, 1">
</div>
<div class="form-group">
<label class="form-label" for="city">Citta'</label>
<input type="text" id="city" class="form-input" placeholder="Es. Milano">
</div>
<div class="form-group">
<label class="form-label" for="website">Sito Web</label>
<input type="text" id="website" class="form-input" placeholder="https://www.esempio.it">
</div>
<div class="form-group">
<label class="form-label" for="company-email">Email Aziendale</label>
<input type="email" id="company-email" class="form-input" placeholder="info@azienda.it">
</div>
<div class="form-group">
<label class="form-label" for="company-phone">Telefono Aziendale</label>
<input type="text" id="company-phone" class="form-input" placeholder="+39 02 1234567">
</div>
<div class="form-group form-group-full">
<label class="form-label" for="sector">Settore NIS2 <span class="required">*</span></label>
<select id="sector" class="form-select" required>
<option value="">-- Seleziona settore --</option>
<optgroup label="Settori ad Alta Criticita' (Allegato I)">
<option value="energy_electricity">Energia - Elettricita'</option>
<option value="energy_district_heating">Energia - Teleriscaldamento</option>
<option value="energy_oil">Energia - Petrolio</option>
<option value="energy_gas">Energia - Gas</option>
<option value="energy_hydrogen">Energia - Idrogeno</option>
<option value="transport_air">Trasporti - Aereo</option>
<option value="transport_rail">Trasporti - Ferroviario</option>
<option value="transport_water">Trasporti - Marittimo/Fluviale</option>
<option value="transport_road">Trasporti - Stradale</option>
<option value="banking">Banche</option>
<option value="financial_markets">Infrastrutture Mercati Finanziari</option>
<option value="health">Sanita'</option>
<option value="drinking_water">Acqua Potabile</option>
<option value="waste_water">Acque Reflue</option>
<option value="digital_infrastructure">Infrastruttura Digitale</option>
<option value="ict_service_management">Gestione Servizi ICT (B2B)</option>
<option value="public_administration">Pubblica Amministrazione</option>
<option value="space">Spazio</option>
</optgroup>
<optgroup label="Altri Settori Critici (Allegato II)">
<option value="postal_courier">Servizi Postali e Corrieri</option>
<option value="waste_management">Gestione Rifiuti</option>
<option value="chemicals">Fabbricazione Prodotti Chimici</option>
<option value="food">Produzione e Distribuzione Alimentare</option>
<option value="manufacturing_medical">Fabbricazione - Dispositivi Medici</option>
<option value="manufacturing_computers">Fabbricazione - Computer/Elettronica</option>
<option value="manufacturing_electrical">Fabbricazione - Apparecchiature Elettriche</option>
<option value="manufacturing_machinery">Fabbricazione - Macchinari</option>
<option value="manufacturing_vehicles">Fabbricazione - Autoveicoli</option>
<option value="manufacturing_transport">Fabbricazione - Altri Mezzi di Trasporto</option>
<option value="digital_providers">Fornitori Servizi Digitali</option>
<option value="research">Organizzazioni di Ricerca</option>
</optgroup>
<optgroup label="Altro">
<option value="other">Altro Settore</option>
</optgroup>
</select>
</div>
<div class="form-group">
<label class="form-label" for="employee-count">Numero Dipendenti <span class="required">*</span></label>
<input type="number" id="employee-count" class="form-input" placeholder="Es. 150" min="1" required>
</div>
<div class="form-group">
<label class="form-label" for="annual-turnover">Fatturato Annuo EUR <span class="required">*</span></label>
<input type="number" id="annual-turnover" class="form-input" placeholder="Es. 15000000" min="0" required>
</div>
</div>
</div>
</div>
<div class="wizard-actions">
<button class="btn btn-secondary" onclick="goToStep(1)">
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd"/></svg>
Indietro
</button>
<div class="wizard-actions-right">
<button class="btn btn-primary btn-lg" onclick="submitStep3()">
Continua
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>
</button>
</div>
</div>
</div>
<!-- ═══ STEP 4: Il Tuo Profilo ═══ -->
<div class="wizard-step" id="step-3">
<h2 class="wizard-step-title">Completa il tuo profilo</h2>
<p class="wizard-step-subtitle">Inserisci le informazioni sul tuo ruolo in azienda.</p>
<div class="profile-form-wrapper">
<div class="card">
<div class="card-body">
<div class="form-group">
<label class="form-label" for="full-name">Nome Completo</label>
<input type="text" id="full-name" class="form-input" placeholder="Mario Rossi">
</div>
<div class="form-group">
<label class="form-label" for="role">Ruolo in Azienda <span class="required">*</span></label>
<select id="role" class="form-select" required>
<option value="">-- Seleziona ruolo --</option>
<option value="ceo">CEO/Amministratore Delegato</option>
<option value="cto">CTO/Direttore Tecnico</option>
<option value="ciso">CISO/Responsabile Sicurezza</option>
<option value="dpo">DPO/Data Protection Officer</option>
<option value="compliance_manager">Compliance Manager</option>
<option value="it_manager">IT Manager</option>
<option value="legal">Responsabile Legale</option>
<option value="board_member">Membro CDA</option>
<option value="consultant">Consulente Esterno</option>
<option value="other">Altro</option>
</select>
</div>
<div class="form-group mb-0">
<label class="form-label" for="user-phone">Telefono</label>
<input type="text" id="user-phone" class="form-input" placeholder="+39 333 1234567">
</div>
</div>
</div>
</div>
<div class="wizard-actions">
<button class="btn btn-secondary" onclick="goToStep(2)">
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd"/></svg>
Indietro
</button>
<div class="wizard-actions-right">
<button class="btn btn-primary btn-lg" onclick="submitStep4()">
Continua
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>
</button>
</div>
</div>
</div>
<!-- ═══ STEP 5: Classificazione NIS2 e Riepilogo ═══ -->
<div class="wizard-step" id="step-4">
<h2 class="wizard-step-title">Classificazione NIS2</h2>
<p class="wizard-step-subtitle">In base ai dati forniti, ecco la classificazione della tua organizzazione secondo la Direttiva NIS2.</p>
<!-- Classification Result -->
<div class="classification-result" id="classification-result">
<div class="classification-result-icon" id="classification-icon">
<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"/>
</svg>
</div>
<div class="classification-result-label" id="classification-label"></div>
<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>
<div class="summary-card-body">
<div class="summary-grid">
<div class="summary-item">
<div class="summary-item-label">Ragione Sociale</div>
<div class="summary-item-value" id="sum-company">-</div>
</div>
<div class="summary-item">
<div class="summary-item-label">Partita IVA</div>
<div class="summary-item-value" id="sum-vat">-</div>
</div>
<div class="summary-item">
<div class="summary-item-label">Settore</div>
<div class="summary-item-value" id="sum-sector">-</div>
</div>
<div class="summary-item">
<div class="summary-item-label">Tipologia Soggetto</div>
<div class="summary-item-value" id="sum-entity-type">-</div>
</div>
<div class="summary-item">
<div class="summary-item-label">Dipendenti</div>
<div class="summary-item-value" id="sum-employees">-</div>
</div>
<div class="summary-item">
<div class="summary-item-label">Fatturato Annuo</div>
<div class="summary-item-value" id="sum-turnover">-</div>
</div>
</div>
</div>
</div>
<div class="wizard-actions">
<button class="btn btn-secondary" onclick="goToStep(3)">
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z" clip-rule="evenodd"/></svg>
Indietro
</button>
<div class="wizard-actions-right">
<button class="btn btn-primary btn-lg" id="complete-btn" onclick="completeOnboarding()">
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
Completa e Vai alla Dashboard
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="js/api.js"></script>
<script src="js/common.js"></script>
<script>
// ── Auth check ──────────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
// ── Extend API with onboarding methods ──────────────────────────
api.uploadVisura = async function(file) {
const formData = new FormData();
formData.append('visura', file);
const response = await fetch(this.baseUrl + '/onboarding/upload-visura', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + this.token,
'X-Organization-Id': this.orgId || ''
},
body: formData
});
return response.json();
};
api.fetchCompany = function(vatNumber) {
return this.post('/onboarding/fetch-company', { vat_number: vatNumber });
};
api.completeOnboarding = function(data) {
return this.post('/onboarding/complete', data);
};
// ── Wizard State ────────────────────────────────────────────────
const wizardState = {
currentStep: 1,
method: null, // 'visura' | 'certisource' | 'manual'
dataSource: null, // label for banner
visuraFile: null,
company: {
name: '',
vat_number: '',
fiscal_code: '',
address: '',
city: '',
website: '',
email: '',
phone: '',
sector: '',
employee_count: '',
annual_turnover: ''
},
profile: {
full_name: '',
role: '',
phone: ''
},
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: P.IVA Fetch ─────────────────────────────────────────
async function step1FetchCompany() {
const vat = document.getElementById('s1-vat').value.trim();
if (!vat || vat.length !== 11) {
showNotification('Inserisci una Partita IVA valida (11 cifre numeriche).', 'warning');
return;
}
const btn = document.getElementById('s1-fetch-btn');
const loading = document.getElementById('s1-loading');
const resultDiv = document.getElementById('s1-result');
btn.disabled = true;
loading.classList.add('active');
resultDiv.style.display = 'none';
try {
const result = await api.fetchCompany(vat);
if (result.success && result.data) {
const d = result.data;
wizardState.company.name = d.company_name || d.ragione_sociale || d.name || '';
wizardState.company.vat_number = vat;
wizardState.company.fiscal_code = d.fiscal_code || d.codice_fiscale || '';
wizardState.company.address = d.address || d.indirizzo || '';
wizardState.company.city = d.city || d.citta || '';
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.suggested_sector || d.sector || '';
wizardState.company.employee_count = parseEmployeesRange(d.employees_range) || d.employee_count || d.employees || '';
wizardState.company.annual_turnover = '';
wizardState.dataSource = 'Dati precompilati da CertiSource';
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');
resultDiv.style.cssText = 'display:block; padding:.75rem; border-radius:8px; background:var(--success-bg); color:var(--secondary);';
resultDiv.innerHTML = '<strong>✓ ' + (wizardState.company.name || 'Azienda trovata') + '</strong><br><small>' + (d.address || d.city || '') + '</small>';
document.getElementById('s1-continue-btn').style.display = 'inline-flex';
showNotification('Azienda trovata: ' + (wizardState.company.name || vat), 'success');
} else {
resultDiv.style.cssText = 'display:block; padding:.75rem; border-radius:8px; background:var(--warning-bg,#fff3cd); color:#856404;';
resultDiv.textContent = 'Azienda non trovata. Puoi inserire i dati manualmente.';
step1ManualEntry(vat);
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
} finally {
btn.disabled = false;
loading.classList.remove('active');
}
}
function step1ManualEntry(vat) {
wizardState.company.vat_number = vat || document.getElementById('s1-vat').value.trim();
wizardState.dataSource = null;
wizardState.autoFilledFields.clear();
document.getElementById('s1-continue-btn').style.display = 'inline-flex';
showNotification('Inserisci i dati aziendali manualmente.', 'info');
}
// ── Step navigation ─────────────────────────────────────────────
function goToStep(step) {
wizardState.currentStep = step;
// Hide all steps
document.querySelectorAll('.wizard-step').forEach(s => s.classList.remove('active'));
// Show target step
const targetStep = document.getElementById('step-' + step);
if (targetStep) targetStep.classList.add('active');
// Step 2: populate company form from wizard state
if (step === 2) {
populateCompanyForm();
}
// Step 3: load user profile
if (step === 3) {
loadUserProfile();
}
// Step 4: run classification and populate summary
if (step === 4) {
runClassificationAndSummary();
}
// Update stepper
updateStepper(step);
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function updateStepper(currentStep) {
document.querySelectorAll('.stepper-step').forEach(el => {
const s = parseInt(el.dataset.step);
el.classList.remove('active', 'completed');
const numEl = el.querySelector('.stepper-number');
if (s < currentStep) {
el.classList.add('completed');
numEl.innerHTML = '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
} else if (s === currentStep) {
el.classList.add('active');
numEl.textContent = s;
} else {
numEl.textContent = s;
}
});
// Update connectors
document.querySelectorAll('.stepper-connector').forEach(el => {
const afterStep = parseInt(el.dataset.after);
el.classList.toggle('completed', afterStep < currentStep);
});
}
// ── Step 2a: Visura Upload (legacy, kept for API completeness) ──
function handleVisuraFile(file) {
// Validate type
if (file.type !== 'application/pdf') {
showNotification('Il file deve essere in formato PDF.', 'warning');
return;
}
// Validate size (10 MB)
if (file.size > 10 * 1024 * 1024) {
showNotification('Il file non deve superare i 10 MB.', 'warning');
return;
}
wizardState.visuraFile = file;
uploadArea.classList.add('has-file');
document.getElementById('upload-file-name').textContent = file.name;
document.getElementById('analyze-visura-btn').disabled = false;
}
async function analyzeVisura() {
if (!wizardState.visuraFile) return;
const btn = document.getElementById('analyze-visura-btn');
const loading = document.getElementById('visura-loading');
const btnArea = document.getElementById('visura-upload-btn-area');
btn.disabled = true;
btnArea.style.display = 'none';
loading.classList.add('active');
try {
const result = await api.uploadVisura(wizardState.visuraFile);
if (result.success && result.data) {
// Map extracted data to wizard state
const d = result.data;
wizardState.company.name = d.company_name || d.ragione_sociale || d.name || '';
wizardState.company.vat_number = d.vat_number || d.partita_iva || '';
wizardState.company.fiscal_code = d.fiscal_code || d.codice_fiscale || '';
wizardState.company.address = d.address || d.indirizzo || '';
wizardState.company.city = d.city || d.citta || '';
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.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
setTimeout(() => goToStep(3), 600);
} else {
showNotification(result.message || 'Errore nell\'analisi della visura.', 'error');
btnArea.style.display = 'block';
btn.disabled = false;
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
btnArea.style.display = 'block';
btn.disabled = false;
} finally {
loading.classList.remove('active');
}
}
// ── Step 2b: CertiSource ────────────────────────────────────────
async function fetchCompany() {
const vatInput = document.getElementById('cs-vat');
const vat = vatInput.value.trim();
if (!vat) {
showNotification('Inserisci la Partita IVA.', 'warning');
return;
}
const btn = document.getElementById('fetch-company-btn');
const loading = document.getElementById('certisource-loading');
btn.disabled = true;
btn.innerHTML = '<div class="spinner" style="width:18px;height:18px;border-width:2px;margin:0;"></div> Ricerca...';
loading.classList.add('active');
try {
const result = await api.fetchCompany(vat);
if (result.success && result.data) {
const d = result.data;
wizardState.company.name = d.company_name || d.ragione_sociale || d.name || '';
wizardState.company.vat_number = d.vat_number || d.partita_iva || vat;
wizardState.company.fiscal_code = d.fiscal_code || d.codice_fiscale || '';
wizardState.company.address = d.address || d.indirizzo || '';
wizardState.company.city = d.city || d.citta || '';
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.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
setTimeout(() => goToStep(2), 600);
} else {
showNotification(result.message || 'Impossibile recuperare i dati. Verifica la Partita IVA.', 'error');
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
} finally {
loading.classList.remove('active');
btn.disabled = false;
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg> Recupera Dati';
}
}
// ── Step 3: Company Form ────────────────────────────────────────
function populateCompanyForm() {
const c = wizardState.company;
document.getElementById('company-name').value = c.name || '';
document.getElementById('vat-number').value = c.vat_number || '';
document.getElementById('fiscal-code').value = c.fiscal_code || '';
document.getElementById('address').value = c.address || '';
document.getElementById('city').value = c.city || '';
document.getElementById('website').value = c.website || '';
document.getElementById('company-email').value = c.email || '';
document.getElementById('company-phone').value = c.phone || '';
document.getElementById('employee-count').value = c.employee_count || '';
document.getElementById('annual-turnover').value = c.annual_turnover || '';
// Set sector if available
if (c.sector) {
const sectorSelect = document.getElementById('sector');
if (sectorSelect.querySelector('option[value="' + c.sector + '"]')) {
sectorSelect.value = c.sector;
}
}
// Show data source banner
const banner = document.getElementById('data-source-banner');
const bannerText = document.getElementById('data-source-text');
if (wizardState.dataSource) {
bannerText.textContent = wizardState.dataSource;
banner.classList.add('visible');
} else {
banner.classList.remove('visible');
}
// Apply auto-fill visual indicators
const autoFields = wizardState.autoFilledFields;
document.querySelectorAll('#step-2 .form-input, #step-2 .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 = wizardState.dataSource ? 'auto-compilato' : 'da visura';
const label = wrapper.querySelector('.form-label');
if (label) label.appendChild(badge);
}
});
}
function submitStep3() {
// Read form values into state
const name = document.getElementById('company-name').value.trim();
const sector = document.getElementById('sector').value;
const employees = document.getElementById('employee-count').value;
const turnover = document.getElementById('annual-turnover').value;
// Validate required fields
if (!name) {
showNotification('Inserisci la ragione sociale.', 'warning');
document.getElementById('company-name').focus();
return;
}
if (!sector) {
showNotification('Seleziona un settore NIS2.', 'warning');
document.getElementById('sector').focus();
return;
}
if (!employees || parseInt(employees) <= 0) {
showNotification('Inserisci il numero di dipendenti.', 'warning');
document.getElementById('employee-count').focus();
return;
}
if (!turnover || parseInt(turnover) < 0) {
showNotification('Inserisci il fatturato annuo.', 'warning');
document.getElementById('annual-turnover').focus();
return;
}
// Save all values to state
wizardState.company.name = name;
wizardState.company.vat_number = document.getElementById('vat-number').value.trim();
wizardState.company.fiscal_code = document.getElementById('fiscal-code').value.trim();
wizardState.company.address = document.getElementById('address').value.trim();
wizardState.company.city = document.getElementById('city').value.trim();
wizardState.company.website = document.getElementById('website').value.trim();
wizardState.company.email = document.getElementById('company-email').value.trim();
wizardState.company.phone = document.getElementById('company-phone').value.trim();
wizardState.company.sector = sector;
wizardState.company.employee_count = parseInt(employees);
wizardState.company.annual_turnover = parseInt(turnover);
goToStep(3);
}
// ── Step 4: User Profile ────────────────────────────────────────
async function loadUserProfile() {
try {
const result = await api.getMe();
if (result.success && result.data) {
const nameInput = document.getElementById('full-name');
if (!nameInput.value && result.data.full_name) {
nameInput.value = result.data.full_name;
wizardState.profile.full_name = result.data.full_name;
}
}
} catch (e) {
// Silently ignore
}
}
function submitStep4() {
const role = document.getElementById('role').value;
if (!role) {
showNotification('Seleziona il tuo ruolo in azienda.', 'warning');
document.getElementById('role').focus();
return;
}
wizardState.profile.full_name = document.getElementById('full-name').value.trim();
wizardState.profile.role = role;
wizardState.profile.phone = document.getElementById('user-phone').value.trim();
goToStep(4);
}
// ── Step 5: Classification & Summary ────────────────────────────
// NIS2 Classification Logic (from setup-org.html)
function classifyLocally(sector, employees, turnover) {
const annexI = [
'energy_electricity', 'energy_district_heating', 'energy_oil', 'energy_gas',
'energy_hydrogen', 'transport_air', 'transport_rail', 'transport_water',
'transport_road', 'banking', 'financial_markets', 'health', 'drinking_water',
'waste_water', 'digital_infrastructure', 'ict_service_management',
'public_administration', 'space'
];
const annexII = [
'postal_courier', 'waste_management', 'chemicals', 'food',
'manufacturing_medical', 'manufacturing_computers', 'manufacturing_electrical',
'manufacturing_machinery', 'manufacturing_vehicles', 'manufacturing_transport',
'digital_providers', 'research'
];
const isAnnexI = annexI.includes(sector);
const isAnnexII = annexII.includes(sector);
const isLarge = employees >= 250 || turnover >= 50000000;
const isMedium = employees >= 50 || turnover >= 10000000;
if (isAnnexI && isLarge) {
return {
classification: 'essential',
label: 'Soggetto Essenziale',
description: 'La vostra organizzazione rientra tra i soggetti essenziali ai sensi della Direttiva NIS2, in quanto opera in un settore ad alta criticita\' (Allegato I) e supera le soglie dimensionali per la classificazione come grande impresa. Siete tenuti al rispetto completo degli obblighi di sicurezza con vigilanza ex ante.'
};
} else if ((isAnnexI || isAnnexII) && isMedium) {
return {
classification: 'important',
label: 'Soggetto Importante',
description: 'La vostra organizzazione rientra tra i soggetti importanti ai sensi della Direttiva NIS2. Siete tenuti a rispettare gli obblighi di sicurezza e di notifica degli incidenti, con un regime di vigilanza ex post.'
};
} else if (sector === 'other' || (!isAnnexI && !isAnnexII)) {
return {
classification: 'not_applicable',
label: 'Non Applicabile',
description: 'In base ai dati forniti, la vostra organizzazione non sembra rientrare nell\'ambito di applicazione della Direttiva NIS2. Consigliamo comunque di adottare misure di cybersicurezza adeguate e di verificare con le autorita\' competenti.'
};
} else {
return {
classification: 'not_applicable',
label: 'Sotto le Soglie',
description: 'La vostra organizzazione opera in un settore coperto dalla NIS2 ma non raggiunge le soglie dimensionali minime (50 dipendenti o 10M EUR di fatturato). Potreste comunque essere designati dalle autorita\' nazionali.'
};
}
}
function runClassificationAndSummary() {
const c = wizardState.company;
const sector = c.sector;
const employees = parseInt(c.employee_count) || 0;
const turnover = parseInt(c.annual_turnover) || 0;
// Run classification
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');
const descEl = document.getElementById('classification-desc');
const iconEl = document.getElementById('classification-icon');
container.className = 'classification-result';
if (result.classification === 'essential') {
container.classList.add('essential');
iconEl.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>';
} else if (result.classification === 'important') {
container.classList.add('important');
iconEl.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"/></svg>';
} else {
container.classList.add('not-applicable');
iconEl.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 = result.label;
descEl.textContent = result.description;
// Get the display text for the sector
const sectorSelect = document.getElementById('sector');
const sectorOption = sectorSelect.querySelector('option[value="' + c.sector + '"]');
const sectorText = sectorOption ? sectorOption.textContent : c.sector;
// Populate summary
document.getElementById('sum-company').textContent = c.name || '-';
document.getElementById('sum-vat').textContent = c.vat_number || '-';
document.getElementById('sum-sector').textContent = sectorText || '-';
document.getElementById('sum-entity-type').textContent = result.label || '-';
document.getElementById('sum-employees').textContent = employees ? employees.toLocaleString('it-IT') : '-';
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');
btn.disabled = true;
btn.innerHTML = '<div class="spinner" style="width:18px;height:18px;border-width:2px;margin:0;"></div> Completamento in corso...';
const c = wizardState.company;
const p = wizardState.profile;
const cl = wizardState.classification;
const payload = {
// Company data (field names match backend OnboardingController)
name: c.name,
vat_number: c.vat_number,
fiscal_code: c.fiscal_code,
address: c.address,
city: c.city,
website: c.website,
contact_email: c.email,
contact_phone: c.phone,
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,
// Country
country: 'IT',
};
try {
const result = await api.completeOnboarding(payload);
if (result.success && result.data) {
const orgId = result.data.id || result.data.organization_id;
if (orgId) {
api.setOrganization(orgId);
}
showNotification('Configurazione completata con successo!', 'success');
setTimeout(() => {
const dest = api.isConsultant() ? 'companies.html' : 'dashboard.html';
window.location.href = dest;
}, 1000);
} else {
showNotification(result.message || 'Errore nel completamento.', 'error');
btn.disabled = false;
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg> Completa e Vai alla Dashboard';
}
} catch (err) {
showNotification('Errore di connessione al server.', 'error');
btn.disabled = false;
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg> Completa e Vai alla Dashboard';
}
}
// ── Allow P.IVA input to submit with Enter ───────────────────────
document.getElementById('s1-vat').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
step1FetchCompany();
}
});
</script>
</body>
</html>