[FEAT] UI V3 — flotta: chrome top-nav + design system su 32 pagine app
Migrazione UI dal V2 (Bootstrap Italia, sidebar) al V3 (top-nav mega-menu navy/oro, Inter+Lucide self-host) fedele ai mockup docs/mockup Ui/. - 32 pagine app convertite (incl. admin/*): head Inter+v3.css, chrome topnav-v3, contenuti in card V3, dati REALI preservati (id/data-* intatti), i18n/help/FAB reali invariati, nessun CDN esterno. - v3.css: aggiunti widget condivisi (modal/toast/form/btn-variant/spinner/ progress/tag/badge) + utility (.btn base, .text-muted, .sr-only) + alias variabili V2->token V3, così modali/form/toast di common.js funzionano ovunque senza style.css. - maintenance.html: finestra estesa a ~12:15. - docs/UI_V3_FLEET_BRIEF.md: ricetta operativa della flotta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b0fc190704
commit
a4150b5389
+107
-62
@@ -3,32 +3,78 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Segnalazioni Sicurezza - NIS2 Agile</title>
|
||||
<!-- Bootstrap Italia v2.18.1 self-hostato (regola: MAI CDN). Caricato PRIMA di
|
||||
style.css cosi' il design system esistente resta autorevole per i widget
|
||||
renderizzati via JS (tabelle/modali/timeline) -> parita' funzionale assoluta.
|
||||
BI veste la chrome (sidebar + font istituzionale). -->
|
||||
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260618">
|
||||
<title>Segnalazioni Sicurezza · NIS2 Agile</title>
|
||||
<!-- UI V3: Inter self-host + design system v3.css (NO Google Fonts / NO unpkg / NO Bootstrap Italia CDN). -->
|
||||
<link rel="stylesheet" href="/vendor/inter/inter.css?v=20260627">
|
||||
<link rel="stylesheet" href="/css/v3.css?v=20260627">
|
||||
<style>
|
||||
.priority-badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; }
|
||||
.priority-critical { background: var(--danger-bg); color: var(--danger); }
|
||||
.priority-high { background: #fff7ed; color: #c2410c; }
|
||||
.priority-medium { background: var(--warning-bg); color: #a16207; }
|
||||
.priority-low { background: var(--gray-100); color: var(--gray-600); }
|
||||
.status-badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; }
|
||||
.status-received { background: rgba(6,182,212,0.1); color: #0891b2; }
|
||||
.status-under_review { background: var(--warning-bg); color: #a16207; }
|
||||
.status-investigating { background: #f3e8ff; color: #7c3aed; }
|
||||
.status-resolved, .status-closed { background: var(--success-bg); color: var(--success); }
|
||||
.status-rejected { background: var(--danger-bg); color: var(--danger); }
|
||||
.anonymous-shield { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; background: rgba(16,185,129,0.1); border-radius: 12px; color: var(--success); font-size: 0.75rem; font-weight: 600; }
|
||||
.art32-banner { background: linear-gradient(135deg, rgba(6,182,212,0.1), rgba(16,185,129,0.1)); border: 1px solid rgba(6,182,212,0.2); border-radius: var(--border-radius-lg); padding: 20px 24px; margin-bottom: 28px; }
|
||||
.art32-banner h3 { font-size: 0.9375rem; font-weight: 700; color: var(--gray-800); margin-bottom: 6px; }
|
||||
.art32-banner p { font-size: 0.8125rem; color: var(--gray-600); }
|
||||
.track-form { background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: var(--border-radius); padding: 20px; margin-bottom: 24px; }
|
||||
/* Icone sprite BI dimensionate inline per i bottoni azione */
|
||||
.icon-inline { width: 16px; height: 16px; fill: currentColor; vertical-align: text-bottom; }
|
||||
/* ── Compat V3: token + classi emesse dal JS (ex style.css, niente CDN) ── */
|
||||
:root{
|
||||
--primary:#1F5FA8;--primary-bg:#E7F0FB;--secondary:#1E8A5A;--secondary-bg:#E6F4EC;
|
||||
--success:#1E8A5A;--success-bg:#E6F4EC;--warning:#D98324;--warning-bg:#FBF1E2;
|
||||
--danger:#C62828;--danger-bg:#FBE9E9;--info:#1F5FA8;--info-bg:#E7F0FB;
|
||||
--gray-50:#F8FAFC;--gray-100:#F1F5F9;--gray-200:#E3E9F0;--gray-300:#CBD5E1;
|
||||
--gray-400:#94A3B8;--gray-500:#64748B;--gray-600:#475569;--gray-700:#334155;--gray-800:#15263C;
|
||||
--card-bg:#fff;--card-shadow:var(--shadow);--border-radius:8px;--border-radius-lg:12px;--border-radius-sm:6px;
|
||||
--font-mono:'SF Mono','Fira Code','Consolas',monospace;--transition-fast:150ms ease;
|
||||
}
|
||||
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
||||
.mb-16{margin-bottom:16px}.mb-24{margin-bottom:24px}
|
||||
/* Buttons (parità con .btn-primary/.btn-ghost di v3.css) */
|
||||
.btn{display:inline-flex;align-items:center;gap:7px;border:1px solid transparent;border-radius:8px;padding:8px 14px;font:inherit;font-weight:600;font-size:12.5px;cursor:pointer;line-height:1.2}
|
||||
.btn:disabled{opacity:.55;cursor:not-allowed}
|
||||
.btn-primary{background:var(--c-blue);color:#fff}.btn-primary:hover:not(:disabled){background:#1A5494}
|
||||
.btn-secondary,.btn-outline{background:#fff;color:var(--ink);border-color:var(--line)}
|
||||
.btn-secondary:hover:not(:disabled),.btn-outline:hover:not(:disabled){background:#F5F8FC;border-color:#c7d4e4}
|
||||
.btn-sm{padding:5px 10px;font-size:11.5px}
|
||||
.icon-inline{width:16px;height:16px;fill:currentColor;vertical-align:text-bottom}
|
||||
/* Forms */
|
||||
.form-group{margin-bottom:16px}
|
||||
.form-label{display:block;font-size:12px;font-weight:600;color:var(--ink);margin-bottom:6px}
|
||||
.form-control,.form-input,.form-select{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:8px;font:inherit;font-size:13px;background:#fff;color:var(--ink)}
|
||||
.form-control:focus,.form-input:focus,.form-select:focus{outline:none;border-color:var(--c-blue);box-shadow:0 0 0 3px rgba(31,95,168,.12)}
|
||||
textarea.form-control{resize:vertical;min-height:80px}
|
||||
/* Cards (override v3 .card already exists; add header/body) */
|
||||
.card-header{padding:16px 20px;border-bottom:1px solid var(--line)}
|
||||
.card-header h3{margin:0;font-size:15px;font-weight:700}
|
||||
.card-body{padding:20px}
|
||||
/* Stats grid (KPI) */
|
||||
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px}
|
||||
.stat-card{background:#fff;border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow);padding:16px 18px;border-left:4px solid var(--c-blue)}
|
||||
.stat-label{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#7a8aa0}
|
||||
.stat-value{font-size:25px;font-weight:800;line-height:1.1;margin-top:4px;color:var(--ink)}
|
||||
/* Table */
|
||||
.table-container{overflow-x:auto;border:1px solid var(--line);border-radius:12px}
|
||||
.table-container table{width:100%;border-collapse:collapse;font-size:13px;background:#fff}
|
||||
.table-container thead{background:#F5F8FC}
|
||||
.table-container th{text-align:left;padding:11px 14px;font-size:10.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:#7a8aa0;border-bottom:1px solid var(--line)}
|
||||
.table-container td{padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:middle}
|
||||
.table-container tbody tr:last-child td{border-bottom:0}
|
||||
.table-container tbody tr:hover td{background:#FAFCFE}
|
||||
/* Badge / empty / spinner */
|
||||
.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:6px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}
|
||||
.badge-neutral{background:#EEF2F7;color:#5b6b80}
|
||||
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:40px 20px;text-align:center;color:var(--muted)}
|
||||
.empty-state h4{margin:0;font-size:14px;font-weight:700;color:var(--ink)}
|
||||
.empty-state p{margin:0;font-size:12.5px}
|
||||
.spinner{width:30px;height:30px;border:3px solid var(--line);border-top-color:var(--c-blue);border-radius:50%;animation:v3spin .7s linear infinite}
|
||||
@media (prefers-reduced-motion:reduce){.spinner{animation:none}}
|
||||
/* Page-specific */
|
||||
.priority-badge{display:inline-flex;align-items:center;padding:2px 9px;border-radius:6px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}
|
||||
.priority-critical{background:var(--danger-bg);color:var(--danger)}
|
||||
.priority-high{background:#FBF1E2;color:#9A6418}
|
||||
.priority-medium{background:var(--warning-bg);color:#9A6418}
|
||||
.priority-low{background:var(--gray-100);color:var(--gray-600)}
|
||||
.status-badge{display:inline-flex;align-items:center;padding:2px 9px;border-radius:6px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}
|
||||
.status-received{background:#E7F0FB;color:#1F5FA8}
|
||||
.status-under_review{background:var(--warning-bg);color:#9A6418}
|
||||
.status-investigating{background:#f3e8ff;color:#7c3aed}
|
||||
.status-resolved,.status-closed{background:var(--success-bg);color:var(--success)}
|
||||
.status-rejected{background:var(--danger-bg);color:var(--danger)}
|
||||
.anonymous-shield{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;background:var(--success-bg);border-radius:6px;color:var(--success);font-size:11px;font-weight:600}
|
||||
.art32-banner{background:linear-gradient(135deg,rgba(31,95,168,.07),rgba(30,138,90,.07));border:1px solid var(--line);border-radius:12px;padding:18px 22px;margin-bottom:18px}
|
||||
.art32-banner h3{font-size:14px;font-weight:700;color:var(--ink);margin:0 0 6px}
|
||||
.art32-banner p{font-size:12.5px;color:var(--muted);margin:0}
|
||||
</style>
|
||||
<!-- PWA:start -->
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
@@ -45,25 +91,29 @@
|
||||
<!-- PWA:end -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<aside class="sidebar" id="sidebar"></aside>
|
||||
<main class="main-content">
|
||||
<div class="page-header">
|
||||
<div class="page-header-content">
|
||||
<h1 class="page-title">Segnalazioni Sicurezza</h1>
|
||||
<p class="page-subtitle">Canale interno Art.32 NIS2 — Segnala violazioni e anomalie di sicurezza</p>
|
||||
</div>
|
||||
<div class="page-header-actions">
|
||||
<button class="btn btn-secondary" onclick="switchView('track')">
|
||||
<svg class="icon-inline" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-search"></use></svg>
|
||||
Traccia Segnalazione Anonima
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="switchView('submit')">
|
||||
<svg class="icon-inline" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-plus"></use></svg>
|
||||
Nuova Segnalazione
|
||||
</button>
|
||||
</div>
|
||||
<!-- Chrome V3 (appbar + mega-menu) iniettata da topnav-v3.js in questo host -->
|
||||
<div id="topnav"></div>
|
||||
|
||||
<main class="wrap">
|
||||
<nav class="crumb" aria-label="breadcrumb">
|
||||
<i data-lucide="home" class="ic"></i><a href="dashboard.html">Home</a>
|
||||
<i data-lucide="chevron-right" class="ic"></i><span>Monitoraggio</span>
|
||||
<i data-lucide="chevron-right" class="ic"></i><span>Segnalazioni</span>
|
||||
</nav>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Segnalazioni Sicurezza</h1>
|
||||
<p class="sub">Canale interno Art.32 NIS2 — Segnala violazioni e anomalie di sicurezza</p>
|
||||
</div>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
||||
<button class="btn-ghost" onclick="switchView('track')">
|
||||
<i data-lucide="search" class="ic"></i>Traccia segnalazione anonima
|
||||
</button>
|
||||
<button class="btn-primary" onclick="switchView('submit')">
|
||||
<i data-lucide="plus" class="ic"></i>Nuova segnalazione
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Art.32 Banner -->
|
||||
<div class="art32-banner">
|
||||
@@ -124,8 +174,8 @@
|
||||
<p style="font-size:0.8125rem; color:var(--gray-500); margin-top:4px;">Segnala anonimamente o con il tuo nome. L'anonimato è garantito dalla piattaforma.</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div style="margin-bottom:20px; padding:12px 16px; background:rgba(16,185,129,0.08); border:1px solid rgba(16,185,129,0.2); border-radius:var(--border-radius); display:flex; align-items:center; gap:10px;">
|
||||
<svg class="icon-inline" style="width:18px; height:18px; color:var(--success); flex-shrink:0;" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-lock"></use></svg>
|
||||
<div style="margin-bottom:20px; padding:12px 16px; background:var(--success-bg); border:1px solid var(--line); border-radius:var(--border-radius); display:flex; align-items:center; gap:10px;">
|
||||
<i data-lucide="lock" class="ic" style="width:18px; height:18px; color:var(--success); flex-shrink:0;"></i>
|
||||
<label style="display:flex; align-items:center; gap:8px; font-size:0.875rem; cursor:pointer;">
|
||||
<input type="checkbox" id="is-anonymous" checked style="accent-color:var(--success);">
|
||||
<span><strong>Segnalazione anonima</strong> — La tua identità non verrà registrata</span>
|
||||
@@ -202,30 +252,24 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Report Detail Modal handled by common.js showModal -->
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260624g"></script>
|
||||
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
|
||||
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
|
||||
riusando tutti gli helper di common.js (zero backend). -->
|
||||
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
|
||||
<script>
|
||||
if (window.bootstrap && bootstrap.loadFonts) {
|
||||
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
I18n.init();
|
||||
if (typeof I18n !== 'undefined') I18n.init();
|
||||
if (typeof HelpSystem !== 'undefined') HelpSystem.init();
|
||||
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
||||
|
||||
let currentView = 'list';
|
||||
|
||||
@@ -302,10 +346,11 @@
|
||||
if (!reports.length) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<svg class="icon-inline" style="width:40px; height:40px; color:var(--gray-300);" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-warning-circle"></use></svg>
|
||||
<i data-lucide="megaphone" style="width:34px;height:34px;color:var(--gray-300);" aria-hidden="true"></i>
|
||||
<h4>Nessuna segnalazione trovata</h4>
|
||||
<p>Il canale di segnalazione è attivo. Le segnalazioni appariranno qui.</p>
|
||||
</div>`;
|
||||
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch(e){} }
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -319,7 +364,7 @@
|
||||
<td><strong style="font-size:0.875rem;">${escapeHtml(r.title)}</strong></td>
|
||||
<td><span class="priority-badge priority-${r.priority}">${escapeHtml(priorityLabels[r.priority] || r.priority)}</span></td>
|
||||
<td><span class="status-badge status-${r.status}">${escapeHtml(statusLabels[r.status] || r.status)}</span></td>
|
||||
<td>${r.is_anonymous ? '<span class="anonymous-shield"><svg class="icon-inline" style="width:12px;height:12px;" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-lock"></use></svg>Anonima</span>' : '<span style="color:var(--gray-400); font-size:0.75rem;">Firmata</span>'}</td>
|
||||
<td>${r.is_anonymous ? '<span class="anonymous-shield"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px" aria-hidden="true"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>Anonima</span>' : '<span style="color:var(--gray-400); font-size:0.75rem;">Firmata</span>'}</td>
|
||||
<td>${r.assigned_to_name ? escapeHtml(r.assigned_to_name) : '<span style="color:var(--gray-400);">—</span>'}</td>
|
||||
<td style="font-size:0.8rem;">${formatDate(r.created_at)}</td>
|
||||
<td><button class="btn btn-sm btn-secondary" onclick="viewReport(${r.id})">Gestisci</button></td>
|
||||
@@ -348,7 +393,7 @@
|
||||
<div style="display:flex; gap:10px; flex-wrap:wrap; margin-bottom:16px;">
|
||||
<span class="priority-badge priority-${r.priority}">${escapeHtml(priorityLabels[r.priority] || r.priority)}</span>
|
||||
<span class="status-badge status-${r.status}">${escapeHtml(statusLabels[r.status] || r.status)}</span>
|
||||
${r.is_anonymous ? '<span class="anonymous-shield"><svg class="icon-inline" style="width:12px;height:12px;" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-lock"></use></svg>Anonima</span>' : ''}
|
||||
${r.is_anonymous ? '<span class="anonymous-shield"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-1px" aria-hidden="true"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>Anonima</span>' : ''}
|
||||
</div>
|
||||
<div style="padding:12px 16px; background:var(--gray-50); border-radius:var(--border-radius); margin-bottom:16px; font-size:0.875rem; line-height:1.6;">
|
||||
${escapeHtml(r.description)}
|
||||
|
||||
Reference in New Issue
Block a user