Critical fixes discovered during end-to-end testing:
Router (index.php):
- Rewrote route resolution engine to properly handle /{id}/subAction patterns
- All routes like GET /assessments/{id}/questions, POST /incidents/{id}/early-warning,
GET /organizations/{id}/members now resolve correctly
- Routes with kebab-case sub-actions (early-warning, ai-analyze) now convert to camelCase
- Controller methods receive correct arguments via spread operator
EmailService.php:
- Fix PHP parse error: ?? operator cannot be used inside string interpolation {}
- Extract incident_code to variable before interpolation (3 occurrences)
assessment.html:
- Fix data structure handling: API returns categories with nested questions array
- Fix field names: question_code (not question_id), response_value (not compliance_level)
- Fix answer enum values: not_implemented/partial/implemented (not Italian)
- Fix question text field: question_text (not text/question/title)
- Show NIS2 article and ISO 27001 control references
- Fix response restoration from existing answers
dashboard.html:
- Fix data mapping from overview API response structure
- risks.total instead of open_risks, policies array instead of approved_policies
- Calculate training completion percentage from training object
- Load deadlines/activity from dedicated endpoints (not included in overview)
onboarding.html:
- Fix field name mismatches: annual_turnover_eur, contact_email, contact_phone,
full_name, phone (matching OnboardingController expected params)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1658 lines
77 KiB
HTML
1658 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 (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,
|
|
// 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(() => {
|
|
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>
|