[FEAT] Cruscotto Studio (frontend): companies.html vista d'insieme + menu
companies.html diventa il cruscotto dello studio consulente: barra KPI (aziende, compliance media, scadenze in ritardo/in arrivo), griglia aziende (compliance/ rischi/incidenti + badge ritardo, clic = entra nell'azienda) e sezione "Scadenze generali" aggregata su TUTTE le aziende ordinata per data. Una sola chiamata api.consultantPortfolio(). Voce menu V3 "Le mie aziende" (icona briefcase) gata a consultant/super_admin. help.js + i18n (nav.studio + companies.*) aggiornati. Cache-buster api/topnav-v3/i18n/help ?v=20260729. version 1.25.9. 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
2bd97a6bd8
commit
15010f7dc3
@@ -70,7 +70,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della app: api.js + common.js (helper) + common-bi.js (override BI) -->
|
||||
<script src="js/api.js?v=20260727r"></script>
|
||||
<script src="js/api.js?v=20260729"></script>
|
||||
<script src="js/common.js?v=20260624g"></script>
|
||||
<script src="js/common-bi.js?v=20260625d"></script>
|
||||
<script>
|
||||
|
||||
@@ -162,12 +162,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -85,12 +85,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -92,12 +92,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -1122,12 +1122,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar(); // = loadTopnavV3()
|
||||
|
||||
@@ -270,12 +270,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica
|
||||
pagina (wizard GAP) resta inline: usa gli stessi id/funzioni di prima. -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+4
-4
@@ -377,12 +377,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -190,12 +190,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica del
|
||||
calendario (aggregatore read-only) resta inline e usa gli stessi id/funzioni. -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
+221
-83
@@ -3,11 +3,16 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Le Mie Aziende · NIS2 Agile</title>
|
||||
<title>Cruscotto Studio · NIS2 Agile</title>
|
||||
<!-- UI V3: Inter self-host + design system v3.css (NO Bootstrap Italia / NO CDN). -->
|
||||
<link rel="stylesheet" href="/vendor/inter/inter.css?v=20260627">
|
||||
<link rel="stylesheet" href="/css/v3.css?v=20260627">
|
||||
<style>
|
||||
/* ── Barra KPI (griglia 4 col responsive, riusa .stat di v3.css) ── */
|
||||
.kpi-grid { display:grid; grid-template-columns:repeat(4, 1fr); gap:18px; margin-bottom:22px; }
|
||||
@media (max-width:900px){ .kpi-grid{ grid-template-columns:repeat(2,1fr); } }
|
||||
@media (max-width:520px){ .kpi-grid{ grid-template-columns:1fr; } }
|
||||
|
||||
/* ── Card aziende (non coperte da v3.css; nessun CDN) ───────────── */
|
||||
.companies-toolbar { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:18px; }
|
||||
.search-box { position:relative; min-width:240px; flex:1; max-width:340px; }
|
||||
@@ -16,7 +21,7 @@
|
||||
.search-box input:focus { border-color:#9FBEDD; }
|
||||
|
||||
.companies-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(300px, 1fr)); gap:18px; }
|
||||
.company-card { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:20px; transition:box-shadow .12s, transform .12s; display:flex; flex-direction:column; gap:16px; }
|
||||
.company-card { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:20px; transition:box-shadow .12s, transform .12s; display:flex; flex-direction:column; gap:16px; cursor:pointer; }
|
||||
.company-card:hover { box-shadow:0 8px 20px rgba(16,40,80,.13); border-color:#9FBEDD; transform:translateY(-1px); }
|
||||
.company-card-header { display:flex; align-items:flex-start; gap:14px; }
|
||||
.company-card-avatar { width:48px; height:48px; border-radius:11px; background:linear-gradient(135deg,#143A5C,#1F5FA8); color:#fff; display:flex; align-items:center; justify-content:center; font-weight:800; font-size:1.05rem; flex-shrink:0; }
|
||||
@@ -29,13 +34,15 @@
|
||||
.entity-badge.important { background:#FBF1E2; color:#9A6418; }
|
||||
.entity-badge.not_applicable, .entity-badge.voluntary { background:#EEF2F7; color:#5b6b80; }
|
||||
|
||||
.overdue-badge { display:inline-flex; align-items:center; gap:5px; padding:3px 9px; border-radius:6px; font-size:11px; font-weight:700; background:#FBE9E9; color:#B23636; white-space:nowrap; }
|
||||
|
||||
.company-score-row { display:flex; align-items:center; gap:12px; }
|
||||
.company-score-label { font-size:.8125rem; color:var(--muted); white-space:nowrap; }
|
||||
.company-score-bar { flex:1; height:7px; background:#EAF0F6; border-radius:99px; overflow:hidden; }
|
||||
.company-score-bar-fill { height:100%; border-radius:99px; transition:width .6s ease; }
|
||||
.company-score-value { font-size:.875rem; font-weight:700; min-width:36px; text-align:right; }
|
||||
|
||||
.company-stats { display:flex; gap:16px; }
|
||||
.company-stats { display:flex; gap:16px; align-items:center; }
|
||||
.company-stat { display:flex; flex-direction:column; gap:2px; }
|
||||
.company-stat-value { font-size:1.125rem; font-weight:800; color:var(--ink); }
|
||||
.company-stat-label { font-size:.75rem; color:var(--muted); }
|
||||
@@ -55,6 +62,25 @@
|
||||
@media (prefers-reduced-motion:reduce) { .skeleton{animation:none;} }
|
||||
.skeleton-card { background:var(--card); border:1px solid var(--line); border-radius:var(--radius); padding:20px; display:flex; flex-direction:column; gap:16px; }
|
||||
|
||||
/* Sezione scadenze generali */
|
||||
.deadlines-wrap { max-height:520px; overflow-y:auto; }
|
||||
.deadline-row { display:grid; grid-template-columns:1.4fr 1.8fr 1.1fr .9fr auto; gap:14px; align-items:center; padding:11px 4px; border-bottom:1px solid var(--line); font-size:13px; }
|
||||
.deadline-row:last-child { border-bottom:0; }
|
||||
.deadline-org { font-weight:700; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.deadline-title { color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.deadline-type { color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.deadline-date { color:var(--muted); font-variant-numeric:tabular-nums; white-space:nowrap; }
|
||||
.deadline-status { justify-self:end; }
|
||||
.dl-badge { display:inline-flex; align-items:center; padding:2px 9px; border-radius:99px; font-size:11px; font-weight:700; white-space:nowrap; }
|
||||
.dl-badge.overdue { background:#FBE9E9; color:#B23636; }
|
||||
.dl-badge.soon { background:#FBF1E2; color:#9A6418; }
|
||||
.dl-badge.ok { background:#EAF1FA; color:#1F5FA8; }
|
||||
.deadlines-empty { text-align:center; color:var(--muted); padding:36px 12px; font-size:14px; }
|
||||
@media (max-width:720px){
|
||||
.deadline-row { grid-template-columns:1fr auto; grid-row-gap:2px; }
|
||||
.deadline-type, .deadline-date { display:none; }
|
||||
}
|
||||
|
||||
/* Modale connettori (reskin sobrio, nessun CDN) */
|
||||
.conn-row { border:1px solid var(--line); border-radius:8px; padding:12px; margin-bottom:10px; }
|
||||
.conn-row input { padding:7px 9px; border:1px solid var(--line); border-radius:6px; font:inherit; font-size:13px; outline:none; }
|
||||
@@ -81,18 +107,39 @@
|
||||
<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>Le Mie Aziende</span>
|
||||
<i data-lucide="chevron-right" class="ic"></i><span>Cruscotto Studio</span>
|
||||
</nav>
|
||||
<div class="page-head" id="nis2-companies-header">
|
||||
<div>
|
||||
<h1>Le Mie Aziende</h1>
|
||||
<p class="sub">Gestisci il portafoglio clienti e monitora la compliance NIS2</p>
|
||||
<h1 data-i18n="companies.title">Cruscotto Studio</h1>
|
||||
<p class="sub" data-i18n="companies.subtitle">Portafoglio clienti, compliance e scadenze in un colpo d'occhio</p>
|
||||
</div>
|
||||
<a href="onboarding.html" class="btn-primary" id="nis2-companies-add-btn">
|
||||
<i data-lucide="plus" class="ic"></i>Aggiungi Cliente
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- ── Barra KPI ────────────────────────────────────────────── -->
|
||||
<div class="kpi-grid" id="kpi-grid">
|
||||
<div class="card card-pad stat blue" id="kpi-total">
|
||||
<div class="ibox"><i data-lucide="building-2" class="ic"></i></div>
|
||||
<div style="flex:1;min-width:0"><div class="lbl" data-i18n="companies.kpi_total">Aziende gestite</div><div class="val">…</div></div>
|
||||
</div>
|
||||
<div class="card card-pad stat green" id="kpi-avg">
|
||||
<div class="ibox"><i data-lucide="gauge" class="ic"></i></div>
|
||||
<div style="flex:1;min-width:0"><div class="lbl" data-i18n="companies.kpi_avg">Compliance media</div><div class="val">…</div></div>
|
||||
</div>
|
||||
<div class="card card-pad stat amber" id="kpi-overdue">
|
||||
<div class="ibox"><i data-lucide="alarm-clock-off" class="ic"></i></div>
|
||||
<div style="flex:1;min-width:0"><div class="lbl" data-i18n="companies.kpi_overdue">Scadenze in ritardo</div><div class="val">…</div></div>
|
||||
</div>
|
||||
<div class="card card-pad stat blue" id="kpi-upcoming">
|
||||
<div class="ibox"><i data-lucide="calendar-clock" class="ic"></i></div>
|
||||
<div style="flex:1;min-width:0"><div class="lbl" data-i18n="companies.kpi_upcoming">Scadenze in arrivo</div><div class="val">…</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Griglia aziende ──────────────────────────────────────── -->
|
||||
<section class="card card-pad">
|
||||
<div class="companies-toolbar">
|
||||
<div class="search-box">
|
||||
@@ -136,20 +183,32 @@
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Scadenze generali (tutte le aziende) ─────────────────── -->
|
||||
<section class="card card-pad" style="margin-top:22px">
|
||||
<div class="sec-head">
|
||||
<h3 data-i18n="companies.deadlines_title">Scadenze generali (tutte le aziende)</h3>
|
||||
<span class="hint" id="deadlines-hint"></span>
|
||||
</div>
|
||||
<div class="deadlines-wrap" id="deadlines-wrap">
|
||||
<div class="deadlines-empty"><span class="v3-spin"></span> Caricamento…</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
// Check that user is consultant, else redirect
|
||||
if (!api.isConsultant()) {
|
||||
// Accesso riservato a consulenti / super_admin (menu gated + backend gate).
|
||||
var _role = api.getUserRole();
|
||||
if (['consultant', 'super_admin'].indexOf(_role) === -1) {
|
||||
window.location.href = 'dashboard.html';
|
||||
throw new Error('Not consultant');
|
||||
}
|
||||
@@ -162,33 +221,75 @@
|
||||
// ── Data ────────────────────────────────────────────────────────
|
||||
let allCompanies = [];
|
||||
|
||||
// ── Load companies ───────────────────────────────────────────────
|
||||
async function loadCompanies() {
|
||||
const result = await api.listOrganizations();
|
||||
// Etichette leggibili tipi scadenza (dal contratto backend).
|
||||
const DEADLINE_TYPE_LABELS = {
|
||||
incident_early_warning: 'Early Warning',
|
||||
incident_notification: 'Notifica CSIRT',
|
||||
incident_final_report: 'Report finale',
|
||||
policy_review: 'Revisione policy',
|
||||
risk_treatment: 'Trattamento rischio',
|
||||
training_due: 'Formazione'
|
||||
};
|
||||
|
||||
// Colore barra compliance: <50 rosso · 50-74 giallo · ≥75 verde.
|
||||
function complianceColor(score) {
|
||||
if (score === null || score === undefined) return '#C7D2DE';
|
||||
if (score < 50) return 'var(--c-red)';
|
||||
if (score < 75) return 'var(--c-amber)';
|
||||
return 'var(--c-green)';
|
||||
}
|
||||
|
||||
// ── Load portfolio (una sola chiamata) ───────────────────────────
|
||||
async function loadPortfolio() {
|
||||
const grid = document.getElementById('companies-grid');
|
||||
const emptyState = document.getElementById('companies-empty');
|
||||
const dlWrap = document.getElementById('deadlines-wrap');
|
||||
|
||||
if (!result.success) {
|
||||
showNotification('Errore nel caricamento delle aziende.', 'error');
|
||||
const result = await api.consultantPortfolio();
|
||||
|
||||
if (!result || !result.success) {
|
||||
if (typeof showNotification === 'function') showNotification('Errore nel caricamento del cruscotto.', 'error');
|
||||
grid.innerHTML = '';
|
||||
emptyState.style.display = 'block';
|
||||
dlWrap.innerHTML = '<div class="deadlines-empty">Impossibile caricare le scadenze.</div>';
|
||||
renderKpis(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const orgs = result.data || [];
|
||||
allCompanies = orgs;
|
||||
const d = result.data || {};
|
||||
renderKpis(d.kpis || {});
|
||||
|
||||
if (orgs.length === 0) {
|
||||
allCompanies = d.companies || [];
|
||||
if (allCompanies.length === 0) {
|
||||
grid.innerHTML = '';
|
||||
emptyState.style.display = 'block';
|
||||
return;
|
||||
} else {
|
||||
emptyState.style.display = 'none';
|
||||
renderCompanies(allCompanies);
|
||||
}
|
||||
|
||||
emptyState.style.display = 'none';
|
||||
renderCompanies(orgs);
|
||||
renderDeadlines(d.deadlines || []);
|
||||
}
|
||||
|
||||
// Load compliance scores in background
|
||||
orgs.forEach(org => loadOrgScore(org.id || org.organization_id));
|
||||
// ── KPI bar ───────────────────────────────────────────────────────
|
||||
function renderKpis(k) {
|
||||
k = k || {};
|
||||
const setVal = (id, txt) => { const el = document.querySelector('#' + id + ' .val'); if (el) el.textContent = txt; };
|
||||
setVal('kpi-total', (k.total_companies != null ? k.total_companies : 0));
|
||||
setVal('kpi-avg', (k.avg_compliance != null ? k.avg_compliance + '%' : '—'));
|
||||
setVal('kpi-overdue', (k.overdue_total != null ? k.overdue_total : 0));
|
||||
setVal('kpi-upcoming', (k.upcoming_total != null ? k.upcoming_total : 0));
|
||||
|
||||
// Scadenze in ritardo: rossa se > 0.
|
||||
const over = document.getElementById('kpi-overdue');
|
||||
if (over) {
|
||||
const hot = (k.overdue_total || 0) > 0;
|
||||
over.classList.toggle('amber', !hot);
|
||||
over.style.borderLeftColor = hot ? 'var(--c-red)' : '';
|
||||
const v = over.querySelector('.val'); if (v) v.style.color = hot ? 'var(--c-red)' : '';
|
||||
const ib = over.querySelector('.ibox');
|
||||
if (ib) { ib.style.background = hot ? '#FBE9E9' : ''; ib.style.color = hot ? '#B23636' : ''; }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Render companies ─────────────────────────────────────────────
|
||||
@@ -199,63 +300,139 @@
|
||||
}
|
||||
|
||||
function renderCompanyCard(org) {
|
||||
const orgId = org.id || org.organization_id;
|
||||
const orgId = org.id;
|
||||
const name = org.name || 'Azienda';
|
||||
const vat = org.vat_number ? 'IT' + org.vat_number : '-';
|
||||
const sector = org.sector || '';
|
||||
const entityType = org.entity_type || 'not_applicable';
|
||||
const voluntary = org.voluntary_compliance ? true : false;
|
||||
const score = (org.compliance_score === null || org.compliance_score === undefined) ? null : Number(org.compliance_score);
|
||||
const risks = org.open_risks || 0;
|
||||
const incidents = org.open_incidents || 0;
|
||||
const overdue = org.deadlines_overdue || 0;
|
||||
|
||||
const initials = name.split(' ').map(w => w[0]).slice(0, 2).join('').toUpperCase();
|
||||
|
||||
const initials = name.split(' ').map(w => w[0]).filter(Boolean).slice(0, 2).join('').toUpperCase() || '?';
|
||||
const entityBadge = renderEntityBadge(entityType, voluntary);
|
||||
const sectorLabel = getSectorLabel(sector);
|
||||
const nameEsc = escapeHtml(name).replace(/'/g, "\\'");
|
||||
|
||||
const scoreColor = complianceColor(score);
|
||||
const scoreText = (score === null) ? '—' : (score + '%');
|
||||
const scoreWidth = (score === null) ? 0 : Math.max(0, Math.min(100, score));
|
||||
|
||||
const overdueBadge = overdue > 0
|
||||
? `<span class="overdue-badge" title="Scadenze in ritardo"><i data-lucide="alarm-clock-off" class="ic" style="width:13px;height:13px"></i>${overdue} in ritardo</span>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="company-card" data-name="${escapeHtml(name.toLowerCase())}" data-org-id="${orgId}">
|
||||
<div class="company-card" data-name="${escapeHtml(name.toLowerCase())}" data-org-id="${orgId}" onclick="manageCompany(${orgId})" role="button" tabindex="0"
|
||||
onkeydown="if(event.key==='Enter'){manageCompany(${orgId});}">
|
||||
<div class="company-card-header">
|
||||
<div class="company-card-avatar">${escapeHtml(initials)}</div>
|
||||
<div class="company-card-info">
|
||||
<div class="company-card-name" title="${escapeHtml(name)}">${escapeHtml(name)}</div>
|
||||
<div class="company-card-vat">${escapeHtml(vat)}</div>
|
||||
<div class="company-card-vat">${escapeHtml(sectorLabel)}</div>
|
||||
</div>
|
||||
${entityBadge}
|
||||
</div>
|
||||
|
||||
<!-- Score bar (loaded async) -->
|
||||
<div class="company-score-row" id="score-row-${orgId}" style="display:none">
|
||||
<!-- Score bar -->
|
||||
<div class="company-score-row">
|
||||
<span class="company-score-label">Compliance</span>
|
||||
<div class="company-score-bar">
|
||||
<div class="company-score-bar-fill" id="score-fill-${orgId}" style="width:0%;background:#ccc"></div>
|
||||
<div class="company-score-bar-fill" style="width:${scoreWidth}%;background:${scoreColor}"></div>
|
||||
</div>
|
||||
<span class="company-score-value" id="score-val-${orgId}">-</span>
|
||||
<span class="company-score-value" style="color:${scoreColor}">${scoreText}</span>
|
||||
</div>
|
||||
|
||||
<!-- Stats row (loaded async) -->
|
||||
<div class="company-stats" id="stats-row-${orgId}" style="display:none">
|
||||
<!-- Stats row -->
|
||||
<div class="company-stats">
|
||||
<div class="company-stat">
|
||||
<span class="company-stat-value" id="stat-risks-${orgId}">-</span>
|
||||
<span class="company-stat-value">${risks}</span>
|
||||
<span class="company-stat-label">Rischi aperti</span>
|
||||
</div>
|
||||
<div class="company-stat">
|
||||
<span class="company-stat-value" id="stat-incidents-${orgId}">-</span>
|
||||
<span class="company-stat-label">Incidenti attivi</span>
|
||||
<span class="company-stat-value">${incidents}</span>
|
||||
<span class="company-stat-label">Incidenti aperti</span>
|
||||
</div>
|
||||
${overdueBadge ? `<div style="margin-left:auto">${overdueBadge}</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="company-card-footer">
|
||||
<span class="company-card-sector">${escapeHtml(sectorLabel)}</span>
|
||||
<button class="btn-ghost btn-sm" onclick="openConnectors(${orgId}, '${escapeHtml(name).replace(/'/g, "\\'")}')" title="Connettori Evidence Automation">
|
||||
<button class="btn-ghost btn-sm" onclick="event.stopPropagation(); openConnectors(${orgId}, '${nameEsc}')" title="Connettori Evidence Automation">
|
||||
Connettori
|
||||
</button>
|
||||
<button class="btn-primary btn-sm" onclick="manageCompany(${orgId})">
|
||||
Gestisci
|
||||
<button class="btn-primary btn-sm" onclick="event.stopPropagation(); manageCompany(${orgId})">
|
||||
Entra
|
||||
<i data-lucide="arrow-right" class="ic"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ── Render scadenze generali (tutte le aziende) ──────────────────
|
||||
function renderDeadlines(deadlines) {
|
||||
const wrap = document.getElementById('deadlines-wrap');
|
||||
const hint = document.getElementById('deadlines-hint');
|
||||
if (!deadlines || deadlines.length === 0) {
|
||||
wrap.innerHTML = `<div class="deadlines-empty" data-i18n="companies.deadlines_empty">Nessuna scadenza nei prossimi 30 giorni</div>`;
|
||||
if (hint) hint.textContent = '';
|
||||
if (typeof I18n !== 'undefined') I18n.applyTranslations();
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = deadlines.slice(0, 50).map(dl => {
|
||||
const orgName = escapeHtml(dl.org_name || '—');
|
||||
const title = escapeHtml(dl.title || '—');
|
||||
const typeLabel = escapeHtml(DEADLINE_TYPE_LABELS[dl.type] || dl.type || '—');
|
||||
const dateTxt = formatDate(dl.due_date);
|
||||
const badge = deadlineBadge(dl);
|
||||
return `<div class="deadline-row">
|
||||
<span class="deadline-org" title="${orgName}">${orgName}</span>
|
||||
<span class="deadline-title" title="${title}">${title}</span>
|
||||
<span class="deadline-type">${typeLabel}</span>
|
||||
<span class="deadline-date">${dateTxt}</span>
|
||||
<span class="deadline-status">${badge}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
wrap.innerHTML = rows;
|
||||
if (hint) hint.textContent = deadlines.length + (deadlines.length === 1 ? ' scadenza' : ' scadenze');
|
||||
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
||||
}
|
||||
|
||||
function deadlineBadge(dl) {
|
||||
if (dl.overdue) {
|
||||
return `<span class="dl-badge overdue">In ritardo</span>`;
|
||||
}
|
||||
const days = daysUntil(dl.due_date);
|
||||
if (days === null) return '';
|
||||
if (days <= 0) return `<span class="dl-badge soon">Oggi</span>`;
|
||||
const cls = days <= 7 ? 'soon' : 'ok';
|
||||
return `<span class="dl-badge ${cls}">Tra ${days} ${days === 1 ? 'giorno' : 'giorni'}</span>`;
|
||||
}
|
||||
|
||||
// "YYYY-MM-DD HH:MM:SS" → Date (local). Ritorna null se non parsabile.
|
||||
function parseDue(str) {
|
||||
if (!str) return null;
|
||||
const d = new Date(String(str).replace(' ', 'T'));
|
||||
return isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
function formatDate(str) {
|
||||
const d = parseDue(str);
|
||||
if (!d) return '—';
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
return `${dd}/${mm}/${d.getFullYear()}`;
|
||||
}
|
||||
function daysUntil(str) {
|
||||
const d = parseDue(str);
|
||||
if (!d) return null;
|
||||
const today = new Date(); today.setHours(0, 0, 0, 0);
|
||||
const target = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
||||
return Math.round((target - today) / 86400000);
|
||||
}
|
||||
|
||||
// ── Connettori per-azienda (Evidence Automation) ─────────────
|
||||
const CONNECTOR_LABELS = {
|
||||
m365: 'Microsoft 365 / Entra ID', google: 'Google Workspace', aws: 'Amazon Web Services',
|
||||
@@ -355,45 +532,6 @@
|
||||
return `<span class="entity-badge not_applicable">Non NIS2</span>`;
|
||||
}
|
||||
|
||||
// ── Load compliance score per org ────────────────────────────────
|
||||
async function loadOrgScore(orgId) {
|
||||
try {
|
||||
// Temporarily switch to this org for the API call
|
||||
const savedOrgId = api.orgId;
|
||||
api.orgId = orgId;
|
||||
const result = await api.getDashboardOverview();
|
||||
api.orgId = savedOrgId;
|
||||
|
||||
if (result.success && result.data) {
|
||||
const score = result.data.compliance_score || 0;
|
||||
const risks = result.data.open_risks || 0;
|
||||
const incidents = result.data.active_incidents || 0;
|
||||
|
||||
const scoreRow = document.getElementById('score-row-' + orgId);
|
||||
const scoreVal = document.getElementById('score-val-' + orgId);
|
||||
const scoreFill = document.getElementById('score-fill-' + orgId);
|
||||
const statsRow = document.getElementById('stats-row-' + orgId);
|
||||
const statRisks = document.getElementById('stat-risks-' + orgId);
|
||||
const statIncidents = document.getElementById('stat-incidents-' + orgId);
|
||||
|
||||
if (scoreRow) scoreRow.style.display = 'flex';
|
||||
if (scoreVal) {
|
||||
scoreVal.textContent = score + '%';
|
||||
scoreVal.style.color = getScoreColor(score);
|
||||
}
|
||||
if (scoreFill) {
|
||||
scoreFill.style.width = score + '%';
|
||||
scoreFill.style.background = getScoreColor(score);
|
||||
}
|
||||
if (statsRow) statsRow.style.display = 'flex';
|
||||
if (statRisks) statRisks.textContent = risks;
|
||||
if (statIncidents) statIncidents.textContent = incidents;
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently ignore per-org errors
|
||||
}
|
||||
}
|
||||
|
||||
// ── Manage company (switch org + go to dashboard) ─────────────────
|
||||
function manageCompany(orgId) {
|
||||
api.setOrganization(orgId);
|
||||
@@ -444,7 +582,7 @@
|
||||
}
|
||||
|
||||
// ── Init ─────────────────────────────────────────────────────────
|
||||
loadCompanies();
|
||||
loadPortfolio();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -192,12 +192,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts V3. competenze.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script src="/js/competenze.js?v=20260627"></script>
|
||||
<script>
|
||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||
|
||||
@@ -98,12 +98,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script src="/js/connettori-discovery.js?v=20260627"></script>
|
||||
<script>
|
||||
if (typeof checkAuth === 'function' && !checkAuth()) {
|
||||
|
||||
@@ -155,12 +155,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */
|
||||
|
||||
@@ -222,12 +222,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Icone inline (Lucide stroke) usate nelle stringhe HTML generate dal JS.
|
||||
// Renderizzate immediatamente (no createIcons re-run), self-host, niente sprite BI. ──
|
||||
|
||||
@@ -112,12 +112,12 @@
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script src="/js/dashboard.js?v=20260724a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della forgot-password.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260727r"></script>
|
||||
<script src="js/api.js?v=20260729"></script>
|
||||
<script>
|
||||
const form = document.getElementById('forgot-form');
|
||||
const err = document.getElementById('err');
|
||||
|
||||
+4
-4
@@ -1177,12 +1177,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// La guida è accessibile anche da non autenticati (utile per onboarding)
|
||||
// ma se sei loggato carichi chrome + i18n normalmente. ZERO modifiche backend.
|
||||
|
||||
@@ -359,12 +359,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -931,12 +931,12 @@ curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar(); // = loadTopnavV3()
|
||||
|
||||
@@ -160,12 +160,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
+4
-4
@@ -199,12 +199,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → isms.js.
|
||||
isms.js si auto-inizializza su DOMContentLoaded (checkAuth/I18n/loadSidebar/Help/render). -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script src="/js/isms.js?v=20260627v3"></script>
|
||||
<script>
|
||||
// Icone Lucide della chrome + crumb/page-head statici. L'init dati e il wiring
|
||||
|
||||
@@ -177,6 +177,9 @@ class NIS2API {
|
||||
updateOrganization(id, data) { return this.put(`/organizations/${id}`, data); }
|
||||
classifyEntity(data) { return this.post('/organizations/classify', data); }
|
||||
|
||||
// Cruscotto Studio (dashboard consulente) — portfolio aggregato read-only.
|
||||
consultantPortfolio() { return this.get('/consultant/portfolio'); }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Assessments
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
+15
-8
@@ -1389,28 +1389,35 @@ const HelpSystem = (function () {
|
||||
|
||||
// ─── Aziende (consulente) ────────────────────────────────────
|
||||
'companies': {
|
||||
title: 'Guida - Aziende Gestite',
|
||||
intro: 'Il modulo Aziende è dedicato a consulenti e studi: permette di gestire più organizzazioni clienti e passare rapidamente dall\'una all\'altra mantenendo i dati separati.',
|
||||
title: 'Guida - Cruscotto Studio',
|
||||
intro: 'Il Cruscotto Studio è la dashboard dedicata a consulenti e studi: in un\'unica pagina raccoglie tutte le aziende clienti che gestisci con il loro stato di compliance, i rischi e gli incidenti aperti, e l\'elenco aggregato di tutte le scadenze in arrivo o in ritardo.',
|
||||
sections: [
|
||||
{
|
||||
heading: 'Gestione del portafoglio clienti',
|
||||
heading: 'Panoramica dello studio',
|
||||
items: [
|
||||
'Visualizzi l\'elenco delle <strong>aziende che gestisci</strong> con il relativo livello di compliance.',
|
||||
'Puoi <strong>aggiungere</strong> una nuova azienda cliente e accedere ai suoi moduli.',
|
||||
'Il pulsante <strong>"Entra"</strong> imposta l\'organizzazione attiva: tutti i moduli (rischi, incidenti, policy, fornitori...) mostrano i dati di quell\'azienda.'
|
||||
'La <strong>barra KPI</strong> in alto riepiloga il portafoglio: numero di aziende, compliance media, scadenze in ritardo (in rosso) e scadenze in arrivo.',
|
||||
'Ogni <strong>scheda azienda</strong> mostra settore, classificazione NIS2 (essenziale/importante/volontaria), barra di compliance, rischi e incidenti aperti e un badge rosso se ci sono scadenze scadute.',
|
||||
'Il <strong>clic su una scheda</strong> imposta l\'organizzazione attiva e apre la sua dashboard: tutti i moduli (rischi, incidenti, policy, fornitori...) mostrano poi i dati di quell\'azienda.'
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: 'Scadenze generali (tutte le aziende)',
|
||||
items: [
|
||||
'La sezione in fondo elenca <strong>tutte le scadenze</strong> del portafoglio ordinate per data: per ciascuna vedi azienda, tipo di adempimento (Early Warning, Notifica CSIRT, Report finale, Revisione policy, Trattamento rischio, Formazione), data e stato.',
|
||||
'Le scadenze <strong>scadute</strong> sono evidenziate in rosso; le altre indicano tra quanti giorni cadono.'
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: 'Separazione dei dati',
|
||||
items: [
|
||||
'Ogni azienda è un\'organizzazione isolata: i dati non si mescolano mai tra clienti diversi (multi-tenancy).',
|
||||
'Il cambio di contesto è tracciato a fini di audit.'
|
||||
'Il cambio di contesto (ingresso in un\'azienda) è tracciato a fini di audit.'
|
||||
]
|
||||
}
|
||||
],
|
||||
references: [
|
||||
'Architettura multi-tenant: ogni organizzazione ha dati isolati',
|
||||
'Ruolo consulente con accesso a più organizzazioni'
|
||||
'Ruolo consulente/super admin con accesso a più organizzazioni'
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ const I18n = (function () {
|
||||
'nav.integrations': { it: 'Integrazioni Esterne', en: 'External Integrations' },
|
||||
'nav.workflow': { it: 'Compliance Journey', en: 'Compliance Journey' },
|
||||
'nav.audit_acn': { it: 'Audit ACN', en: 'ACN Audit' },
|
||||
'nav.studio': { it: 'Le mie aziende', en: 'My companies' },
|
||||
// ── Etichette gruppi mega-menu V3 ──
|
||||
'nav.grp_stakeholders': { it: 'Stakeholders', en: 'Stakeholders' },
|
||||
'nav.grp_internal': { it: 'Struttura interna', en: 'Internal structure' },
|
||||
@@ -116,6 +117,15 @@ const I18n = (function () {
|
||||
'nav.grp_lexiso': { it: 'Lex e ISO', en: 'Law & ISO' },
|
||||
'nav.grp_ai': { it: 'AI', en: 'AI' },
|
||||
'nav.grp_guide': { it: 'Guida', en: 'Guide' },
|
||||
// ── Cruscotto Studio (dashboard consulente) ──
|
||||
'companies.title': { it: 'Cruscotto Studio', en: 'Studio Dashboard' },
|
||||
'companies.subtitle': { it: 'Portafoglio clienti, compliance e scadenze in un colpo d\'occhio', en: 'Client portfolio, compliance and deadlines at a glance' },
|
||||
'companies.kpi_total': { it: 'Aziende gestite', en: 'Managed companies' },
|
||||
'companies.kpi_avg': { it: 'Compliance media', en: 'Average compliance' },
|
||||
'companies.kpi_overdue': { it: 'Scadenze in ritardo', en: 'Overdue deadlines' },
|
||||
'companies.kpi_upcoming': { it: 'Scadenze in arrivo', en: 'Upcoming deadlines' },
|
||||
'companies.deadlines_title': { it: 'Scadenze generali (tutte le aziende)', en: 'General deadlines (all companies)' },
|
||||
'companies.deadlines_empty': { it: 'Nessuna scadenza nei prossimi 30 giorni', en: 'No deadlines in the next 30 days' },
|
||||
// ── Titoli pagina aggiunti dalla flotta V3 (mancavano → si vedeva la chiave) ──
|
||||
'mr.title': { it: 'Misure e Requisiti', en: 'Measures & Requirements' },
|
||||
'normative.title': { it: 'Aggiornamenti Normativi', en: 'Regulatory Updates' },
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
// top-level: voce diretta (href) oppure gruppo con mega-menu (cols).
|
||||
var NAV_GROUPS = [
|
||||
{ label: 'Dashboard', i18n: 'nav.dashboard', icon: 'layout-dashboard', href: 'dashboard.html' },
|
||||
// Voce riservata a consulenti/super_admin — gate applicato in loadTopnavV3().
|
||||
{ label: 'Le mie aziende', i18n: 'nav.studio', icon: 'briefcase', href: 'companies.html', studioOnly: true },
|
||||
{ label: 'Calendario', i18n: 'nav.calendar', icon: 'calendar', href: 'calendario.html' },
|
||||
{
|
||||
label: 'Stakeholders', icon: 'users',
|
||||
@@ -220,6 +222,16 @@
|
||||
'</div></nav>' +
|
||||
'</header>';
|
||||
|
||||
// ── Gate voce "Le mie aziende": visibile solo a consulenti / super_admin ──
|
||||
try {
|
||||
var role = (typeof api !== 'undefined' && api.getUserRole) ? api.getUserRole() : null;
|
||||
if (['consultant', 'super_admin'].indexOf(role) === -1) {
|
||||
var studioLink = host.querySelector('#topnav-bar a[data-nav="companies"]');
|
||||
var studioItem = studioLink && studioLink.closest('.nav-item');
|
||||
if (studioItem) studioItem.remove();
|
||||
}
|
||||
} catch (e) { /* in caso di errore lasciamo la voce (backend gate comunque presente) */ }
|
||||
|
||||
// ── Riuso helper common.js (utente/avatar/ruolo + org-switcher + versione + branding + impersonate) ──
|
||||
if (typeof _loadUserInfo === 'function') _loadUserInfo();
|
||||
if (typeof _loadVersionFooter === 'function') _loadVersionFooter();
|
||||
|
||||
+4
-4
@@ -175,12 +175,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → kb.js -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script src="/js/kb.js"></script>
|
||||
<script>
|
||||
// Gate auth + chrome come le altre pagine V3.
|
||||
|
||||
@@ -515,7 +515,7 @@ body { background: var(--bg-main); }
|
||||
|
||||
<div id="toast"></div>
|
||||
|
||||
<script src="js/api.js?v=20260727r"></script>
|
||||
<script src="js/api.js?v=20260729"></script>
|
||||
<script>
|
||||
// ══════════════════════════════════════════════════════
|
||||
// CONFIG
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della login attuale: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260727r"></script>
|
||||
<script src="js/api.js?v=20260729"></script>
|
||||
<script src="js/common.js?v=20260624g"></script>
|
||||
<script>
|
||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||
|
||||
@@ -147,12 +147,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -175,12 +175,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -137,12 +137,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth + chrome + i18n + help ──────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -494,7 +494,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della onboarding.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260727r"></script>
|
||||
<script src="js/api.js?v=20260729"></script>
|
||||
<script src="js/common.js?v=20260624g"></script>
|
||||
<script>
|
||||
// ── Auth check ──────────────────────────────────────────────────
|
||||
|
||||
@@ -151,12 +151,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → organigramma.js.
|
||||
organigramma.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script src="/js/organigramma.js?v=20260627"></script>
|
||||
<script>
|
||||
// Render icone Lucide (chrome + crumb + bottoni). organigramma.js fa render albero
|
||||
|
||||
@@ -340,12 +340,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+4
-4
@@ -141,12 +141,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3. raci.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script src="/js/raci.js?v=20260627"></script>
|
||||
<script>
|
||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||
|
||||
@@ -268,7 +268,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della register.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260727r"></script>
|
||||
<script src="js/api.js?v=20260729"></script>
|
||||
<script src="js/common.js?v=20260624g"></script>
|
||||
<script>
|
||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||
|
||||
+4
-4
@@ -232,12 +232,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -157,12 +157,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
+4
-4
@@ -487,12 +487,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -735,12 +735,12 @@
|
||||
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
||||
i18n → help → init pagina (inline, invariato). -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -215,12 +215,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -210,12 +210,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -542,12 +542,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -190,12 +190,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali+showModal/showNotification) →
|
||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help.
|
||||
La logica della pagina e' inline (nessun training.js): preservata invariata. -->
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth & Init (ordine V3) ─────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"version": "1.25.8", "build": "2026-07-28-v1.25.8", "date": "2026-07-28", "changelog": "Gestione Rischi: valutando un rischio-requisito con il tasto 'Valuta', Probabilita' e Impatto vengono ora pre-compilati come SUGGERIMENTO in base allo stato di conformita' (conforme -> basso, parziale -> medio, non conforme -> alto). E' solo un suggerimento chiaramente modificabile: velocizza la valutazione senza imporre valori (art. 24 D.Lgs. 138/2024 non impone una metodologia)."}
|
||||
{"version": "1.25.9", "build": "2026-07-29-v1.25.9", "date": "2026-07-29", "changelog": "Nuovo: Cruscotto Studio (per consulenti e societa' di consulenza). Dalla voce di menu 'Le mie aziende' vedi in un colpo solo tutte le aziende che gestisci con la loro compliance, rischi e incidenti aperti, e soprattutto le SCADENZE GENERALI aggregate di TUTTE le aziende ordinate per data (in ritardo / in arrivo). Clic su un'azienda per entrarci. Visibile ai profili consulente e amministratore."}
|
||||
|
||||
@@ -258,12 +258,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
|
||||
@@ -143,12 +143,12 @@
|
||||
|
||||
<!-- 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=20260727r"></script>
|
||||
<script src="/js/api.js?v=20260729"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260727b"></script>
|
||||
<script src="/js/help.js?v=20260727b"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729"></script>
|
||||
<script>
|
||||
// ── Auth + chrome + i18n + help ──────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
Reference in New Issue
Block a user