[FEAT] UI V3 — foundation (v3.css + topnav-v3.js + Inter/Lucide self-host) + dashboard pilota fedele al mockup
Fase 0 della migrazione UI V3 (sidebar V2 → top-nav navy/oro, font Inter, icone Lucide). - public/css/v3.css: design system portato fedelmente dal mockup index.html (token :root, chrome appbar+mega-menu, card/stat/calendario/anag/doc/fab, @media ≤900/≤560), + aggiunte minime V3+ per stati loading/empty/error, mobile nav toggle e touch≥44px (AGID/WCAG). - public/vendor/inter/: Inter self-host (variable woff2 latin+latin-ext) + inter.css + README (NO Google CDN). - public/vendor/lucide/lucide.min.js: Lucide UMD self-host (NO unpkg CDN). - public/js/topnav-v3.js: appbar + mega-menu data-driven dalla mappa IA (§6, 9 gruppi), aria-current sulla voce attiva, riusa gli helper di common.js (utente/logout/versione/org-switcher, branding, impersonate) e i 3 FAB reali (ARIA + bug-reporter + notifiche). window.loadSidebar=loadTopnavV3. - public/dashboard.html: convertita al V3 fedele al mockup, cablata sui DATI REALI via api.js (overview/compliance-score/upcoming-deadlines/recent-activity/organizations-current); monitor giornaliero/settimanale alimentati dagli eventi reali; documenti istituzionali = stato vuoto onesto. - public/js/dashboard.js (nuovo): wiring dati reali + render calendario/scadenze/attività/anagrafica; stati loading/empty/error; zero dati finti del mockup. node --check OK su topnav-v3.js e dashboard.js. Solo UI: nessun PHP/DB/api.js toccato. 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
2a5d90f8bb
commit
b0fc190704
+105
-324
@@ -3,12 +3,10 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Dashboard - NIS2 Agile</title>
|
||||
<!-- Bootstrap Italia v2.18.1 self-hostato (regola: MAI CDN). Caricato PRIMA di style.css
|
||||
cosi' style.css (design system esistente) resta autorevole per i widget renderizzati
|
||||
via JS (card/gauge/stat/modali) -> parita' funzionale assoluta. BI veste la chrome. -->
|
||||
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260618">
|
||||
<title>Dashboard · NIS2 Agile</title>
|
||||
<!-- UI V3: Inter self-host + design system v3.css (NO Google Fonts / NO unpkg CDN). -->
|
||||
<link rel="stylesheet" href="/vendor/inter/inter.css?v=20260627">
|
||||
<link rel="stylesheet" href="/css/v3.css?v=20260627">
|
||||
<!-- PWA:start -->
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<meta name="theme-color" content="#0066CC">
|
||||
@@ -24,338 +22,121 @@
|
||||
<!-- PWA:end -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar" id="sidebar"></aside>
|
||||
<!-- Chrome V3 (appbar + mega-menu) iniettata da topnav-v3.js in questo host -->
|
||||
<div id="topnav"></div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<header class="content-header">
|
||||
<h1 class="h2" data-i18n="dashboard.title">Dashboard</h1>
|
||||
<div class="content-header-actions">
|
||||
<span class="text-muted" id="header-date"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content-body">
|
||||
<!-- Compliance Score + Stats -->
|
||||
<div class="grid-3 mb-24" style="grid-template-columns: 1fr 2fr;">
|
||||
<!-- Gauge -->
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div id="compliance-gauge">
|
||||
<div class="spinner-lg" style="margin:40px auto;"></div>
|
||||
</div>
|
||||
<p class="text-muted mt-8" style="font-size:0.8rem;">Avanzamento implementazione misure Art.21 NIS2</p>
|
||||
<p class="text-muted" style="font-size:0.7rem; margin-top:2px;">Misura l'implementazione tecnica, non la conformita' legale</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div>
|
||||
<div class="stats-grid" id="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon danger">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
<h4>Rischi Aperti</h4>
|
||||
<div class="stat-card-value" id="stat-risks">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon warning">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z"/></svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
<h4>Incidenti Attivi</h4>
|
||||
<div class="stat-card-value" id="stat-incidents">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon success">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/></svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
<h4>Policy Approvate</h4>
|
||||
<div class="stat-card-value" id="stat-policies">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon primary">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0z"/></svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
<h4>Formazione</h4>
|
||||
<div class="stat-card-value" id="stat-training">--%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="card mb-24" id="nis2-dashboard-quick-actions">
|
||||
<div class="card-header">
|
||||
<h3>Azioni Rapide</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="quick-actions">
|
||||
<button class="quick-action-btn" id="nis2-dashboard-qa-assessment" onclick="window.location.href='assessment.html'">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="20" height="20"><path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd"/></svg>
|
||||
Nuovo Assessment
|
||||
</button>
|
||||
<button class="quick-action-btn" id="nis2-dashboard-qa-incidente" onclick="window.location.href='incidents.html'">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="20" height="20"><path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6z"/></svg>
|
||||
Registra Incidente
|
||||
</button>
|
||||
<button class="quick-action-btn" onclick="generateAIPolicy()">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor" width="20" height="20"><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>
|
||||
Genera Policy AI
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deadlines + Activity -->
|
||||
<div class="grid-2">
|
||||
<!-- Scadenze -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Prossime Scadenze</h3>
|
||||
</div>
|
||||
<div class="card-body" id="deadlines-list">
|
||||
<div class="spinner" style="margin:20px auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attivita' Recenti -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Attivita' Recenti</h3>
|
||||
</div>
|
||||
<div class="card-body" id="activity-list">
|
||||
<div class="spinner" style="margin:20px auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</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>Dashboard</span>
|
||||
</nav>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1 data-i18n="dashboard.title">Dashboard</h1>
|
||||
<p class="sub">Stato di conformità NIS2 del tuo perimetro</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<div class="date"><i data-lucide="calendar" class="ic"></i><span id="header-date"></span></div>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
<!-- Monitor Giornaliero (eventi reali: scadenze + attività) -->
|
||||
<section class="card card-pad">
|
||||
<div class="cal-head">
|
||||
<h3>Monitor Giornaliero</h3>
|
||||
<div class="cal-ctrl">
|
||||
<button class="ctrl-btn" id="d-prev" title="Giorni precedenti" aria-label="Giorni precedenti"><i data-lucide="chevron-left" class="ic"></i></button>
|
||||
<button class="ctrl-today" id="d-today"><i data-lucide="calendar-check" class="ic"></i>Oggi</button>
|
||||
<button class="ctrl-btn" id="d-next" title="Giorni successivi" aria-label="Giorni successivi"><i data-lucide="chevron-right" class="ic"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="daystrip" id="daystrip"></div>
|
||||
</section>
|
||||
|
||||
<!-- Monitor Settimanale -->
|
||||
<section class="card card-pad">
|
||||
<div class="cal-head">
|
||||
<h3>Monitor Settimanale</h3>
|
||||
<div class="cal-ctrl">
|
||||
<button class="ctrl-btn" id="w-prev" title="Settimana precedente" aria-label="Settimana precedente"><i data-lucide="chevron-left" class="ic"></i></button>
|
||||
<button class="ctrl-today" id="w-today"><i data-lucide="calendar-check" class="ic"></i>Questa settimana</button>
|
||||
<button class="ctrl-btn" id="w-next" title="Settimana successiva" aria-label="Settimana successiva"><i data-lucide="chevron-right" class="ic"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="weekstrip" id="weekstrip"></div>
|
||||
</section>
|
||||
|
||||
<!-- 3 stat card (dati reali) -->
|
||||
<div class="grid3">
|
||||
<a href="assessment.html" class="card card-pad stat blue" id="stat-gap">
|
||||
<div class="ibox"><i data-lucide="git-compare" class="ic"></i></div>
|
||||
<div style="flex:1;min-width:0"><div class="lbl">Gap analysis in corso</div><div class="val">…</div><div class="sub">caricamento…</div></div>
|
||||
</a>
|
||||
<a href="training.html" class="card card-pad stat green" id="stat-training">
|
||||
<div class="ibox"><i data-lucide="graduation-cap" class="ic"></i></div>
|
||||
<div style="flex:1;min-width:0"><div class="lbl">Formazione</div><div class="val">…</div><div class="sub">caricamento…</div></div>
|
||||
</a>
|
||||
<a href="risks.html" class="card card-pad stat amber" id="stat-risk">
|
||||
<div class="ibox"><i data-lucide="shield-check" class="ic"></i></div>
|
||||
<div style="flex:1;min-width:0"><div class="lbl">Rischi valutati</div><div class="val">…</div><div class="sub">caricamento…</div><div class="bar"><i style="width:0%"></i></div></div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Scadenze + Attività aperte -->
|
||||
<div class="split2">
|
||||
<section class="card card-pad">
|
||||
<div class="sec-head"><h3>Scadenze · prossimi 10 giorni</h3><span class="hint" id="sca-hint"></span></div>
|
||||
<div id="sca-list"><div class="v3-loading"><span class="v3-spin"></span>Caricamento…</div></div>
|
||||
</section>
|
||||
<section class="card card-pad">
|
||||
<div class="sec-head"><h3>Attività aperte</h3>
|
||||
<a class="btn-primary" href="stakeholder-activities.html"><i data-lucide="plus" class="ic"></i>Nuova attività</a></div>
|
||||
<div class="act-scroll" id="act-scroll"><div class="v3-loading"><span class="v3-spin"></span>Caricamento…</div></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Anagrafica + Documenti istituzionali -->
|
||||
<div class="split-anag">
|
||||
<section class="card card-pad">
|
||||
<div class="sec-head"><h3>Anagrafica aziendale</h3>
|
||||
<a class="btn-ghost" href="settings.html"><i data-lucide="pencil" class="ic"></i>Modifica</a></div>
|
||||
<div id="anag-card"><div class="v3-loading"><span class="v3-spin"></span>Caricamento…</div></div>
|
||||
</section>
|
||||
<section class="card card-pad">
|
||||
<div class="sec-head"><h3>Documenti istituzionali</h3></div>
|
||||
<div id="doc-card"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
||||
i18n → help → dashboard.js -->
|
||||
<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 src="/js/dashboard.js?v=20260627"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
// ── Init ─────────────────────────────────────────────────
|
||||
loadSidebar();
|
||||
I18n.init();
|
||||
HelpSystem.init();
|
||||
// ── Init chrome + i18n + help ────────────────────────────
|
||||
loadSidebar(); // = loadTopnavV3() (definito in topnav-v3.js)
|
||||
if (typeof I18n !== 'undefined') I18n.init();
|
||||
if (typeof HelpSystem !== 'undefined') HelpSystem.init();
|
||||
|
||||
// Data corrente nell'header
|
||||
document.getElementById('header-date').textContent = new Date().toLocaleDateString('it-IT', {
|
||||
weekday: 'long', day: 'numeric', month: 'long', year: 'numeric'
|
||||
});
|
||||
var hd = document.getElementById('header-date');
|
||||
if (hd) hd.textContent = new Date().toLocaleDateString('it-IT', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
|
||||
|
||||
// ── Caricamento dati ─────────────────────────────────────
|
||||
loadDashboard();
|
||||
// Icone Lucide (chrome + pagina)
|
||||
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
||||
|
||||
async function loadDashboard() {
|
||||
try {
|
||||
const result = await api.getDashboardOverview();
|
||||
|
||||
if (result.success && result.data) {
|
||||
const data = result.data;
|
||||
|
||||
// Compliance gauge
|
||||
const score = data.compliance_score != null ? Math.round(data.compliance_score) : 0;
|
||||
document.getElementById('compliance-gauge').innerHTML = renderScoreGauge(score, 180);
|
||||
|
||||
// Stats - map backend response structure to UI
|
||||
const openRisks = data.risks ? (parseInt(data.risks.total) || 0) : 0;
|
||||
const activeIncidents = data.active_incidents || 0;
|
||||
const approvedPolicies = Array.isArray(data.policies)
|
||||
? data.policies.reduce((sum, p) => p.status === 'approved' || p.status === 'published' ? sum + parseInt(p.count) : sum, 0)
|
||||
: 0;
|
||||
const trainingTotal = data.training ? (parseInt(data.training.total) || 0) : 0;
|
||||
const trainingCompleted = data.training ? (parseInt(data.training.completed) || 0) : 0;
|
||||
const trainingPct = trainingTotal > 0 ? Math.round((trainingCompleted / trainingTotal) * 100) : 0;
|
||||
|
||||
document.getElementById('stat-risks').textContent = openRisks;
|
||||
document.getElementById('stat-incidents').textContent = activeIncidents;
|
||||
document.getElementById('stat-policies').textContent = approvedPolicies;
|
||||
document.getElementById('stat-training').textContent = trainingPct + '%';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Dashboard load error:', err);
|
||||
}
|
||||
|
||||
// Always load deadlines and activity from dedicated endpoints
|
||||
loadIndividualData();
|
||||
}
|
||||
|
||||
async function loadIndividualData() {
|
||||
// Compliance score
|
||||
try {
|
||||
const scoreRes = await api.getComplianceScore();
|
||||
if (scoreRes.success && scoreRes.data) {
|
||||
// Preferisci la % controlli implementati; se 0/assente (org con gap
|
||||
// analysis fatta ma senza modulo controlli) ripiega sul punteggio assessment.
|
||||
const d = scoreRes.data;
|
||||
const score = (d.avg_implementation && d.avg_implementation > 0) ? d.avg_implementation : (d.score || 0);
|
||||
document.getElementById('compliance-gauge').innerHTML = renderScoreGauge(score, 180);
|
||||
} else {
|
||||
document.getElementById('compliance-gauge').innerHTML = renderScoreGauge(0, 180);
|
||||
}
|
||||
} catch (e) {
|
||||
document.getElementById('compliance-gauge').innerHTML = renderScoreGauge(0, 180);
|
||||
}
|
||||
|
||||
// Deadlines
|
||||
try {
|
||||
const dlRes = await api.getUpcomingDeadlines();
|
||||
renderDeadlines(dlRes.success ? (dlRes.data || []) : []);
|
||||
} catch (e) {
|
||||
renderDeadlines([]);
|
||||
}
|
||||
|
||||
// Activity
|
||||
try {
|
||||
const actRes = await api.getRecentActivity();
|
||||
renderActivity(actRes.success ? (actRes.data || []) : []);
|
||||
} catch (e) {
|
||||
renderActivity([]);
|
||||
}
|
||||
}
|
||||
|
||||
function renderDeadlines(deadlines) {
|
||||
const container = document.getElementById('deadlines-list');
|
||||
|
||||
if (!deadlines || deadlines.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"/></svg>
|
||||
<h4>Nessuna scadenza imminente</h4>
|
||||
<p>Le prossime scadenze appariranno qui.</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const months = ['GEN','FEB','MAR','APR','MAG','GIU','LUG','AGO','SET','OTT','NOV','DIC'];
|
||||
let html = '';
|
||||
|
||||
deadlines.slice(0, 5).forEach(dl => {
|
||||
const d = new Date(dl.due_date || dl.date);
|
||||
const now = new Date();
|
||||
const diffDays = Math.ceil((d - now) / 86400000);
|
||||
let urgency = 'normal';
|
||||
if (diffDays <= 3) urgency = 'urgent';
|
||||
else if (diffDays <= 7) urgency = 'soon';
|
||||
|
||||
html += `
|
||||
<div class="deadline-item">
|
||||
<div class="deadline-date ${urgency}">
|
||||
<span class="day">${d.getDate()}</span>
|
||||
<span class="month">${months[d.getMonth()]}</span>
|
||||
</div>
|
||||
<div class="deadline-info">
|
||||
<div class="deadline-title">${escapeHtml(dl.title || dl.description || 'Scadenza')}</div>
|
||||
<div class="deadline-desc">${escapeHtml(dl.category || dl.type || '')}</div>
|
||||
</div>
|
||||
${diffDays <= 3 ? '<span class="badge badge-danger">Urgente</span>' : diffDays <= 7 ? '<span class="badge badge-warning">Prossima</span>' : ''}
|
||||
</div>`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderActivity(activities) {
|
||||
const container = document.getElementById('activity-list');
|
||||
|
||||
if (!activities || activities.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>
|
||||
<h4>Nessuna attivita' recente</h4>
|
||||
<p>Le attivita' appariranno qui quando inizierai ad usare la piattaforma.</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<ul class="activity-list">';
|
||||
|
||||
activities.slice(0, 8).forEach(act => {
|
||||
html += `
|
||||
<li class="activity-item">
|
||||
<div class="activity-icon">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg>
|
||||
</div>
|
||||
<div class="activity-content">
|
||||
<div class="activity-text">${escapeHtml(act.description || act.message || act.action || '')}</div>
|
||||
<div class="activity-time">${timeAgo(act.created_at || act.timestamp || act.date)}</div>
|
||||
</div>
|
||||
</li>`;
|
||||
});
|
||||
|
||||
html += '</ul>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
async function generateAIPolicy() {
|
||||
showModal('Genera Policy con AI', `
|
||||
<p style="margin-bottom:16px;">Seleziona la categoria per cui generare una policy automatica tramite intelligenza artificiale.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Categoria Policy</label>
|
||||
<select class="form-select" id="ai-policy-category">
|
||||
<option value="risk_management">Gestione del Rischio</option>
|
||||
<option value="incident_response">Risposta agli Incidenti</option>
|
||||
<option value="business_continuity">Continuita' Operativa</option>
|
||||
<option value="supply_chain">Sicurezza Supply Chain</option>
|
||||
<option value="access_control">Controllo degli Accessi</option>
|
||||
<option value="encryption">Crittografia</option>
|
||||
<option value="hr_security">Sicurezza del Personale</option>
|
||||
<option value="asset_management">Gestione degli Asset</option>
|
||||
</select>
|
||||
</div>
|
||||
`, {
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="closeModal()">Annulla</button>
|
||||
<button class="btn btn-primary" onclick="doGeneratePolicy()">Genera</button>
|
||||
`
|
||||
});
|
||||
}
|
||||
|
||||
async function doGeneratePolicy() {
|
||||
const category = document.getElementById('ai-policy-category').value;
|
||||
closeModal();
|
||||
showNotification('Generazione policy in corso...', 'info');
|
||||
|
||||
try {
|
||||
const result = await api.aiGeneratePolicy(category);
|
||||
if (result.success) {
|
||||
showNotification('Policy generata con successo!', 'success');
|
||||
setTimeout(() => window.location.href = 'policies.html', 1500);
|
||||
} else {
|
||||
showNotification(result.message || 'Errore nella generazione.', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Errore di connessione.', 'error');
|
||||
}
|
||||
}
|
||||
// ── Dati reali ───────────────────────────────────────────
|
||||
if (typeof initDashboard === 'function') initDashboard();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user