La pagina Gestione Rischi mostra come tabella primaria (read-only) i requisiti del framework Lex&ISO come rischi, codice RSK-<misura>Com.N via /api/risks/derived, filtrati per classe (importante All.1 / essenziale All.2). Framing normativo (validato nis2-expert): ogni voce = "rischio derivante dal mancato/parziale soddisfacimento del requisito", mai "requisito = rischio"; RSK e "92" NON come codici/numeri ufficiali ACN; matrice = strumento di rappresentazione (art.24). - Rischi CUSTOM mantenuti in sezione "Rischi aggiuntivi (all-hazard)". - Tasti "Nuovo Rischio"/"AI Suggerisci" limitati a super_admin/org_admin (non rimossi): l'inserimento libero serve al multirischio (art.24 D.Lgs.138/2024). - api.js derivedRisks(); help.js + i18n aggiornati; version 1.25.4; SW v1.25.4. - Cache-buster api/help/i18n ?v=20260727r. Backend/DB non toccati. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
451 lines
27 KiB
HTML
451 lines
27 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<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>
|
|
<!-- 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>
|
|
/* ── 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; }
|
|
.search-box svg { position:absolute; left:12px; top:50%; transform:translateY(-50%); color:var(--muted); pointer-events:none; }
|
|
.search-box input { width:100%; padding:9px 12px 9px 38px; border:1px solid var(--line); border-radius:8px; font:inherit; font-size:13px; background:#fff; color:var(--ink); outline:none; }
|
|
.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: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; }
|
|
.company-card-info { flex:1; min-width:0; }
|
|
.company-card-name { font-size:1rem; font-weight:800; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-bottom:4px; }
|
|
.company-card-vat { font-size:.8125rem; color:var(--muted); font-family:'Inter',monospace; }
|
|
|
|
.entity-badge { display:inline-flex; align-items:center; gap:5px; padding:3px 9px; border-radius:6px; font-size:10px; font-weight:700; text-transform:uppercase; letter-spacing:.03em; white-space:nowrap; }
|
|
.entity-badge.essential { background:#FBE9E9; color:#B23636; }
|
|
.entity-badge.important { background:#FBF1E2; color:#9A6418; }
|
|
.entity-badge.not_applicable, .entity-badge.voluntary { background:#EEF2F7; color:#5b6b80; }
|
|
|
|
.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-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); }
|
|
|
|
.company-card-footer { border-top:1px solid var(--line); padding-top:16px; display:flex; align-items:center; justify-content:space-between; gap:8px; }
|
|
.company-card-sector { font-size:.8125rem; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; }
|
|
.btn-sm { padding:6px 11px; font-size:11.5px; }
|
|
|
|
.companies-empty { text-align:center; padding:60px 24px; color:var(--muted); grid-column:1 / -1; }
|
|
.companies-empty svg { width:56px; height:56px; color:var(--line); margin:0 auto 18px; display:block; }
|
|
.companies-empty h3 { font-size:1.05rem; font-weight:700; color:var(--ink); margin-bottom:8px; }
|
|
.companies-empty p { font-size:.9375rem; margin-bottom:22px; line-height:1.6; }
|
|
|
|
/* Skeleton loader */
|
|
.skeleton { background:linear-gradient(90deg,#EEF2F7 25%,#F6F9FC 50%,#EEF2F7 75%); background-size:200% 100%; animation:shimmer 1.5s infinite; border-radius:6px; }
|
|
@keyframes shimmer { 0%{background-position:200% 0;} 100%{background-position:-200% 0;} }
|
|
@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; }
|
|
|
|
/* 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; }
|
|
.conn-row input:focus { border-color:#9FBEDD; }
|
|
</style>
|
|
<!-- PWA:start -->
|
|
<link rel="manifest" href="/manifest.webmanifest">
|
|
<meta name="theme-color" content="#0066CC">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
<meta name="apple-mobile-web-app-title" content="NIS2 Agile">
|
|
<meta name="application-name" content="NIS2 Agile">
|
|
<script src="/js/pwa.js?v=20260614" defer></script>
|
|
<!-- PWA:end -->
|
|
</head>
|
|
<body>
|
|
<!-- 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>Le Mie Aziende</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>
|
|
</div>
|
|
<a href="onboarding.html" class="btn-primary" id="nis2-companies-add-btn">
|
|
<i data-lucide="plus" class="ic"></i>Aggiungi Cliente
|
|
</a>
|
|
</div>
|
|
|
|
<section class="card card-pad">
|
|
<div class="companies-toolbar">
|
|
<div class="search-box">
|
|
<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/></svg>
|
|
<input type="text" id="search-input" placeholder="Cerca azienda..." oninput="filterCompanies(this.value)" aria-label="Cerca azienda">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Companies Grid -->
|
|
<div class="companies-grid" id="companies-grid">
|
|
<!-- Skeleton loaders -->
|
|
<div class="skeleton-card">
|
|
<div style="display:flex;gap:12px;align-items:center">
|
|
<div class="skeleton" style="width:48px;height:48px;border-radius:11px;flex-shrink:0"></div>
|
|
<div style="flex:1"><div class="skeleton" style="height:16px;margin-bottom:8px"></div><div class="skeleton" style="height:12px;width:60%"></div></div>
|
|
</div>
|
|
<div class="skeleton" style="height:7px"></div>
|
|
<div style="display:flex;gap:16px"><div class="skeleton" style="height:40px;flex:1"></div><div class="skeleton" style="height:40px;flex:1"></div></div>
|
|
<div class="skeleton" style="height:36px"></div>
|
|
</div>
|
|
<div class="skeleton-card">
|
|
<div style="display:flex;gap:12px;align-items:center">
|
|
<div class="skeleton" style="width:48px;height:48px;border-radius:11px;flex-shrink:0"></div>
|
|
<div style="flex:1"><div class="skeleton" style="height:16px;margin-bottom:8px"></div><div class="skeleton" style="height:12px;width:60%"></div></div>
|
|
</div>
|
|
<div class="skeleton" style="height:7px"></div>
|
|
<div style="display:flex;gap:16px"><div class="skeleton" style="height:40px;flex:1"></div><div class="skeleton" style="height:40px;flex:1"></div></div>
|
|
<div class="skeleton" style="height:36px"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty state (hidden initially) -->
|
|
<div class="companies-empty" id="companies-empty" style="display:none">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008z"/>
|
|
</svg>
|
|
<h3>Nessuna azienda ancora</h3>
|
|
<p>Aggiungi il tuo primo cliente per iniziare a monitorare la compliance NIS2.</p>
|
|
<a href="onboarding.html" class="btn-primary" id="nis2-companies-add-first-btn" style="display:inline-flex">
|
|
<i data-lucide="plus" class="ic"></i>Aggiungi Primo Cliente
|
|
</a>
|
|
</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/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=20260727r"></script>
|
|
<script src="/js/help.js?v=20260727r"></script>
|
|
<script>
|
|
if (!checkAuth()) throw new Error('Not authenticated');
|
|
|
|
// Check that user is consultant, else redirect
|
|
if (!api.isConsultant()) {
|
|
window.location.href = 'dashboard.html';
|
|
throw new Error('Not consultant');
|
|
}
|
|
|
|
loadSidebar();
|
|
if (typeof I18n !== 'undefined') I18n.init();
|
|
if (typeof HelpSystem !== 'undefined') HelpSystem.init();
|
|
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
|
|
|
// ── Data ────────────────────────────────────────────────────────
|
|
let allCompanies = [];
|
|
|
|
// ── Load companies ───────────────────────────────────────────────
|
|
async function loadCompanies() {
|
|
const result = await api.listOrganizations();
|
|
const grid = document.getElementById('companies-grid');
|
|
const emptyState = document.getElementById('companies-empty');
|
|
|
|
if (!result.success) {
|
|
showNotification('Errore nel caricamento delle aziende.', 'error');
|
|
grid.innerHTML = '';
|
|
emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
const orgs = result.data || [];
|
|
allCompanies = orgs;
|
|
|
|
if (orgs.length === 0) {
|
|
grid.innerHTML = '';
|
|
emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
emptyState.style.display = 'none';
|
|
renderCompanies(orgs);
|
|
|
|
// Load compliance scores in background
|
|
orgs.forEach(org => loadOrgScore(org.id || org.organization_id));
|
|
}
|
|
|
|
// ── Render companies ─────────────────────────────────────────────
|
|
function renderCompanies(orgs) {
|
|
const grid = document.getElementById('companies-grid');
|
|
grid.innerHTML = orgs.map(org => renderCompanyCard(org)).join('');
|
|
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
|
}
|
|
|
|
function renderCompanyCard(org) {
|
|
const orgId = org.id || org.organization_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 initials = name.split(' ').map(w => w[0]).slice(0, 2).join('').toUpperCase();
|
|
|
|
const entityBadge = renderEntityBadge(entityType, voluntary);
|
|
const sectorLabel = getSectorLabel(sector);
|
|
|
|
return `
|
|
<div class="company-card" data-name="${escapeHtml(name.toLowerCase())}" data-org-id="${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>
|
|
${entityBadge}
|
|
</div>
|
|
|
|
<!-- Score bar (loaded async) -->
|
|
<div class="company-score-row" id="score-row-${orgId}" style="display:none">
|
|
<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>
|
|
<span class="company-score-value" id="score-val-${orgId}">-</span>
|
|
</div>
|
|
|
|
<!-- Stats row (loaded async) -->
|
|
<div class="company-stats" id="stats-row-${orgId}" style="display:none">
|
|
<div class="company-stat">
|
|
<span class="company-stat-value" id="stat-risks-${orgId}">-</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>
|
|
</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">
|
|
Connettori
|
|
</button>
|
|
<button class="btn-primary btn-sm" onclick="manageCompany(${orgId})">
|
|
Gestisci
|
|
<i data-lucide="arrow-right" class="ic"></i>
|
|
</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
// ── Connettori per-azienda (Evidence Automation) ─────────────
|
|
const CONNECTOR_LABELS = {
|
|
m365: 'Microsoft 365 / Entra ID', google: 'Google Workspace', aws: 'Amazon Web Services',
|
|
azure: 'Microsoft Azure', idp: 'Identity Provider', edr: 'EDR / XDR',
|
|
siem: 'SIEM / SOC', ticketing: 'Ticketing (Jira/ServiceNow)'
|
|
};
|
|
let _connOrgId = null;
|
|
|
|
async function openConnectors(orgId, orgName) {
|
|
_connOrgId = orgId;
|
|
try {
|
|
const res = await fetch(`/api/organizations/${orgId}/connectors`, {
|
|
headers: { 'Authorization': `Bearer ${api.token}`, 'X-Organization-Id': orgId }
|
|
});
|
|
const data = await res.json();
|
|
if (!data.success) { showConnModal(orgName, `<p style="color:var(--c-red);">${escapeHtml(data.message || 'Errore')}</p>`); return; }
|
|
const existing = {};
|
|
(data.data.connectors || []).forEach(c => existing[c.connector_type] = c);
|
|
const rows = (data.data.available_types || []).map(t => {
|
|
const c = existing[t] || {};
|
|
const cfg = (c.config && typeof c.config === 'object') ? c.config : {};
|
|
const ss = c.secret_status || 'not_set';
|
|
const ssBadge = { not_set: ['Segreto non impostato', '#9ca3af'], pending: ['Segreto in attesa', '#D98324'], configured: ['Segreto configurato', '#1E8A5A'] }[ss];
|
|
return `<div class="conn-row" data-type="${t}">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
<strong>${escapeHtml(CONNECTOR_LABELS[t] || t)}</strong>
|
|
<label style="font-size:.8rem;"><input type="checkbox" class="c-en" ${c.enabled ? 'checked' : ''}> Attivo</label>
|
|
</div>
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:8px;">
|
|
<input class="c-tenant" placeholder="tenant_id / account_id" value="${escapeHtml(cfg.tenant_id || cfg.account_id || '')}">
|
|
<input class="c-client" placeholder="client_id / app_id" value="${escapeHtml(cfg.client_id || cfg.app_id || '')}">
|
|
</div>
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px;">
|
|
<span style="font-size:.72rem;color:${ssBadge[1]};">● ${ssBadge[0]}</span>
|
|
<button class="btn-primary btn-sm" onclick="saveConn('${t}', this)">Salva</button>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
showConnModal(orgName, `
|
|
<p style="font-size:.82rem;color:var(--muted);margin-bottom:12px;">
|
|
Configura i connettori di <strong>Evidence Automation</strong>. Qui si salvano solo i parametri
|
|
NON segreti (tenant/client id). Il <strong>client secret</strong> va caricato nel vault dall'amministratore
|
|
(il comando viene mostrato dopo il salvataggio).</p>${rows}`);
|
|
} catch (e) { showConnModal(orgName, '<p style="color:var(--c-red);">Errore di connessione</p>'); }
|
|
}
|
|
|
|
function showConnModal(orgName, body) {
|
|
const old = document.getElementById('conn-modal'); if (old) old.remove();
|
|
document.body.insertAdjacentHTML('beforeend', `
|
|
<div id="conn-modal" onclick="if(event.target===this)closeConn()" style="position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px;">
|
|
<div style="background:var(--card);border-radius:12px;max-width:640px;width:100%;max-height:85vh;overflow-y:auto;padding:22px;">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;">
|
|
<h3 style="margin:0;">Connettori — ${escapeHtml(orgName)}</h3>
|
|
<button class="btn-ghost btn-sm" onclick="closeConn()">✕</button>
|
|
</div>${body}
|
|
</div></div>`);
|
|
}
|
|
function closeConn() { const m = document.getElementById('conn-modal'); if (m) m.remove(); }
|
|
|
|
async function saveConn(type, btn) {
|
|
const row = btn.closest('.conn-row');
|
|
const payload = {
|
|
type,
|
|
enabled: row.querySelector('.c-en').checked,
|
|
config: { tenant_id: row.querySelector('.c-tenant').value.trim(), client_id: row.querySelector('.c-client').value.trim() },
|
|
secret_status: 'pending'
|
|
};
|
|
btn.disabled = true; btn.textContent = '...';
|
|
try {
|
|
const res = await fetch(`/api/organizations/${_connOrgId}/connectors`, {
|
|
method: 'PUT',
|
|
headers: { 'Authorization': `Bearer ${api.token}`, 'X-Organization-Id': _connOrgId, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
const j = await res.json();
|
|
btn.disabled = false; btn.textContent = 'Salva';
|
|
if (j.success) {
|
|
if (typeof showNotification === 'function') showNotification('Connettore salvato', 'success');
|
|
if (j.data && j.data.cli_hint) alert('Connettore salvato.\n\nPer caricare il SEGRETO nel vault, l\'amministratore esegue:\n\n' + j.data.cli_hint);
|
|
} else if (typeof showNotification === 'function') showNotification(j.message || 'Errore', 'error');
|
|
} catch (e) {
|
|
btn.disabled = false; btn.textContent = 'Salva';
|
|
if (typeof showNotification === 'function') showNotification('Errore di connessione', 'error');
|
|
}
|
|
}
|
|
|
|
function renderEntityBadge(type, voluntary) {
|
|
if (voluntary) {
|
|
return `<span class="entity-badge voluntary">Volontaria</span>`;
|
|
}
|
|
if (type === 'essential') {
|
|
return `<span class="entity-badge essential">Essenziale</span>`;
|
|
}
|
|
if (type === 'important') {
|
|
return `<span class="entity-badge important">Importante</span>`;
|
|
}
|
|
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);
|
|
window.location.href = 'dashboard.html';
|
|
}
|
|
|
|
// ── Search / Filter ──────────────────────────────────────────────
|
|
function filterCompanies(query) {
|
|
const q = query.toLowerCase().trim();
|
|
const cards = document.querySelectorAll('.company-card');
|
|
let anyVisible = false;
|
|
|
|
cards.forEach(card => {
|
|
const name = card.dataset.name || '';
|
|
const visible = !q || name.includes(q);
|
|
card.style.display = visible ? '' : 'none';
|
|
if (visible) anyVisible = true;
|
|
});
|
|
|
|
const emptyState = document.getElementById('companies-empty');
|
|
if (emptyState) {
|
|
emptyState.style.display = (!anyVisible && allCompanies.length > 0) ? 'block' : 'none';
|
|
}
|
|
}
|
|
|
|
// ── Sector labels ────────────────────────────────────────────────
|
|
function getSectorLabel(sector) {
|
|
const labels = {
|
|
energy_electricity: 'Energia - Elettricità',
|
|
energy_oil: 'Energia - Petrolio', energy_gas: 'Energia - Gas',
|
|
energy_hydrogen: 'Energia - Idrogeno',
|
|
transport_air: 'Trasporti - Aereo', transport_rail: 'Trasporti - Ferroviario',
|
|
transport_water: 'Trasporti - Marittimo', transport_road: 'Trasporti - Stradale',
|
|
banking: 'Banche', financial_markets: 'Mercati Finanziari',
|
|
health: 'Sanità', drinking_water: 'Acqua Potabile',
|
|
waste_water: 'Acque Reflue', digital_infrastructure: 'Infrastruttura Digitale',
|
|
ict_service_management: 'Gestione ICT B2B', public_administration: 'PA',
|
|
space: 'Spazio', postal_courier: 'Postale/Corrieri',
|
|
waste_management: 'Gestione Rifiuti', chemicals: 'Chimica',
|
|
food: 'Alimentare', manufacturing_medical: 'Dispositivi Medici',
|
|
manufacturing_computers: 'Computer/Elettronica',
|
|
manufacturing_electrical: 'App. Elettriche',
|
|
manufacturing_machinery: 'Macchinari', manufacturing_vehicles: 'Autoveicoli',
|
|
manufacturing_transport: 'Mezzi di Trasporto',
|
|
digital_providers: 'Fornitori Digitali', research: 'Ricerca', other: 'Altro'
|
|
};
|
|
return labels[sector] || sector || 'Settore non specificato';
|
|
}
|
|
|
|
// ── Init ─────────────────────────────────────────────────────────
|
|
loadCompanies();
|
|
</script>
|
|
</body>
|
|
</html>
|