nis2-agile/public/onboarding.html
Cristiano Benassati 9aa2788c68 [FEAT] Add onboarding wizard with visura camerale and CertiSource integration
- New 5-step onboarding wizard (onboarding.html) replacing setup-org.html
- Step 1: Choose data source (Upload Visura / CertiSource / Manual)
- Step 2: PDF upload with AI extraction or CertiSource P.IVA lookup
- Step 3: Verify/complete company data with NIS2 sector mapping
- Step 4: User profile completion
- Step 5: NIS2 classification (Essential/Important) with summary
- OnboardingController with upload-visura, fetch-company, complete endpoints
- VisuraService with Claude AI PDF extraction and ATECO-to-NIS2 mapping
- CertiSource API integration for automatic company data retrieval
- Updated login/register redirects to point to new onboarding wizard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:01:34 +01:00

1662 lines
77 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">
<style>
/* ── 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(--primary);
color: #fff;
box-shadow: 0 0 0 4px rgba(26, 115, 232, 0.15);
}
.stepper-step.active .stepper-label {
color: var(--primary);
font-weight: 600;
}
.stepper-step.completed .stepper-number {
background: var(--secondary);
color: #fff;
}
.stepper-step.completed .stepper-label {
color: var(--secondary);
}
.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(--secondary);
}
/* ── 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(--primary);
background: var(--primary-bg);
box-shadow: 0 0 0 4px rgba(26, 115, 232, 0.12);
}
.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(--primary);
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;
}
/* ── 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">Benvenuto</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">Acquisizione Dati</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">Dati Aziendali</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">Il Tuo Profilo</span>
</div>
<div class="stepper-connector" data-after="4"></div>
<div class="stepper-step" data-step="5">
<div class="stepper-number">5</div>
<span class="stepper-label">Classificazione</span>
</div>
</div>
</div>
<!-- Wizard Body -->
<div class="wizard-body">
<div class="wizard-content">
<!-- ═══ STEP 1: Benvenuto ═══ -->
<div class="wizard-step active" id="step-1">
<h2 class="wizard-step-title">Configura il tuo ambiente NIS2</h2>
<p class="wizard-step-subtitle">Per iniziare, abbiamo bisogno dei dati della tua azienda. Puoi fornirli in tre modi:</p>
<div class="option-cards">
<!-- Carica Visura -->
<div class="option-card" data-method="visura" onclick="selectMethod('visura')">
<div class="option-card-check">
<svg 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>
</div>
<div class="option-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="12" y1="18" x2="12" y2="12"/>
<line x1="9" y1="15" x2="15" y2="15"/>
</svg>
</div>
<div class="option-card-title">Carica Visura Camerale</div>
<div class="option-card-desc">Carica il PDF della visura camerale e i dati verranno estratti automaticamente con l'AI</div>
</div>
<!-- CertiSource -->
<div class="option-card" data-method="certisource" onclick="selectMethod('certisource')">
<div class="option-card-check">
<svg 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>
</div>
<div class="option-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
<path d="M11 8v6"/>
<path d="M8 11h6"/>
</svg>
</div>
<div class="option-card-title">Recupera con CertiSource</div>
<div class="option-card-desc">Inserisci la Partita IVA e recupereremo automaticamente i dati aziendali tramite CertiSource</div>
</div>
<!-- Manuale -->
<div class="option-card" data-method="manual" onclick="selectMethod('manual')">
<div class="option-card-check">
<svg 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>
</div>
<div class="option-card-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</div>
<div class="option-card-title">Inserisci Manualmente</div>
<div class="option-card-desc">Compila tu stesso i dati dell'azienda</div>
</div>
</div>
<div class="wizard-actions" id="step1-actions" style="display:none;">
<div></div>
<div class="wizard-actions-right">
<button class="btn btn-primary btn-lg" onclick="goToStep(2)">
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 2: Acquisizione Dati ═══ -->
<div class="wizard-step" id="step-2">
<!-- 2a: Visura Upload -->
<div id="step2-visura" style="display:none;">
<h2 class="wizard-step-title">Carica la Visura Camerale</h2>
<p class="wizard-step-subtitle">Carica il file PDF della visura camerale. I dati aziendali verranno estratti automaticamente tramite intelligenza artificiale.</p>
<div class="upload-area" id="upload-area">
<input type="file" id="visura-file" accept=".pdf" />
<div class="upload-area-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>
</div>
<div class="upload-area-title">Trascina qui il file PDF oppure clicca per selezionarlo</div>
<div class="upload-area-hint">Rilascia il file per caricarlo</div>
<div class="upload-area-formats">Formato accettato: PDF - Dimensione massima: 10 MB</div>
<div class="upload-file-info" id="upload-file-info">
<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="upload-file-name"></span>
</div>
</div>
<div style="margin-top:24px; text-align:center;" id="visura-upload-btn-area">
<button class="btn btn-primary btn-lg" id="analyze-visura-btn" disabled onclick="analyzeVisura()">
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zm7-10a1 1 0 01.967.744L14.146 7.2 17.5 8.512a1 1 0 010 1.836l-3.354 1.311-1.18 4.456a1 1 0 01-1.932 0L9.854 11.66 6.5 10.348a1 1 0 010-1.836l3.354-1.311 1.18-4.456A1 1 0 0112 2z"/></svg>
Analizza Visura
</button>
</div>
<!-- Loading -->
<div class="wizard-loading" id="visura-loading">
<div class="spinner spinner-lg"></div>
<div class="wizard-loading-text">Analisi AI in corso...</div>
<div class="wizard-loading-subtext">Stiamo estraendo i dati dalla visura camerale</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></div>
</div>
</div>
<!-- 2b: CertiSource -->
<div id="step2-certisource" style="display:none;">
<h2 class="wizard-step-title">Recupera Dati con CertiSource</h2>
<p class="wizard-step-subtitle">Inserisci la Partita IVA dell'azienda e recupereremo automaticamente i dati aziendali 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="cs-vat">Partita IVA <span class="required">*</span></label>
<input type="text" id="cs-vat" class="form-input" placeholder="Es. 12345678901 o IT12345678901" maxlength="16">
<div class="form-help">Inserisci la Partita IVA con o senza prefisso paese</div>
</div>
<button class="btn btn-primary btn-lg w-full" id="fetch-company-btn" onclick="fetchCompany()">
<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
</button>
</div>
</div>
<!-- Loading -->
<div class="wizard-loading" id="certisource-loading">
<div class="spinner spinner-lg"></div>
<div class="wizard-loading-text">Recupero dati da CertiSource...</div>
<div class="wizard-loading-subtext">Stiamo interrogando il registro delle imprese</div>
</div>
<div style="text-align:center; margin-top:24px;">
<div class="certisource-badge">
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 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>
Powered by CertiSource
</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></div>
</div>
</div>
</div>
<!-- ═══ STEP 3: Dati Aziendali ═══ -->
<div class="wizard-step" id="step-3">
<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(wizardState.method === 'manual' ? 1 : 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="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-4">
<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(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" 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-5">
<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>
<!-- 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(4)">
<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
};
// ── Step 1: Method selection ────────────────────────────────────
function selectMethod(method) {
wizardState.method = method;
// Update UI
document.querySelectorAll('.option-card').forEach(card => {
card.classList.toggle('selected', card.dataset.method === method);
});
// Show continue button
document.getElementById('step1-actions').style.display = 'flex';
}
// ── Step navigation ─────────────────────────────────────────────
function goToStep(step) {
const prevStep = wizardState.currentStep;
// If going from step 1 to step 2, handle method routing
if (prevStep === 1 && step === 2) {
if (!wizardState.method) {
showNotification('Seleziona un metodo per procedere.', 'warning');
return;
}
if (wizardState.method === 'manual') {
// Skip step 2, go directly to step 3
step = 3;
}
}
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');
// If step 2, show the appropriate sub-panel
if (step === 2) {
document.getElementById('step2-visura').style.display = wizardState.method === 'visura' ? 'block' : 'none';
document.getElementById('step2-certisource').style.display = wizardState.method === 'certisource' ? 'block' : 'none';
}
// If step 3, populate form from state
if (step === 3) {
populateCompanyForm();
}
// If step 4, load user profile
if (step === 4) {
loadUserProfile();
}
// If step 5, run classification and populate summary
if (step === 5) {
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 ──────────────────────────────────────
const uploadArea = document.getElementById('upload-area');
const visuraFileInput = document.getElementById('visura-file');
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleVisuraFile(files[0]);
}
});
visuraFileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleVisuraFile(e.target.files[0]);
}
});
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.sector || '';
wizardState.company.employee_count = d.employee_count || d.employees || '';
wizardState.company.annual_turnover = d.annual_turnover || d.turnover || '';
wizardState.dataSource = 'Dati precompilati dalla visura camerale';
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.sector || '';
wizardState.company.employee_count = d.employee_count || d.employees || '';
wizardState.company.annual_turnover = d.annual_turnover || d.turnover || '';
wizardState.dataSource = 'Dati recuperati da CertiSource';
showNotification('Dati aziendali recuperati con successo!', 'success');
// Auto-advance to step 3
setTimeout(() => goToStep(3), 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');
}
}
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(4);
}
// ── 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(5);
}
// ── 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;
// 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') : '-';
}
// ── 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
name: c.name,
vat_number: c.vat_number,
fiscal_code: c.fiscal_code,
address: c.address,
city: c.city,
website: c.website,
company_email: c.email,
company_phone: c.phone,
sector: c.sector,
employee_count: parseInt(c.employee_count) || 0,
annual_turnover: parseInt(c.annual_turnover) || 0,
// Profile data
user_full_name: p.full_name,
user_role: p.role,
user_phone: p.phone,
// Classification
classification: cl ? cl.classification : null,
classification_label: cl ? cl.label : null,
// Method used
data_source: wizardState.method
};
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(() => {
window.location.href = 'dashboard.html';
}, 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 CertiSource VAT input to submit with Enter ────────────
document.getElementById('cs-vat').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
fetchCompany();
}
});
</script>
</body>
</html>