[FEAT] UI V3 — flotta: chrome top-nav + design system su 32 pagine app
Migrazione UI dal V2 (Bootstrap Italia, sidebar) al V3 (top-nav mega-menu navy/oro, Inter+Lucide self-host) fedele ai mockup docs/mockup Ui/. - 32 pagine app convertite (incl. admin/*): head Inter+v3.css, chrome topnav-v3, contenuti in card V3, dati REALI preservati (id/data-* intatti), i18n/help/FAB reali invariati, nessun CDN esterno. - v3.css: aggiunti widget condivisi (modal/toast/form/btn-variant/spinner/ progress/tag/badge) + utility (.btn base, .text-muted, .sr-only) + alias variabili V2->token V3, così modali/form/toast di common.js funzionano ovunque senza style.css. - maintenance.html: finestra estesa a ~12:15. - docs/UI_V3_FLEET_BRIEF.md: ricetta operativa della flotta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b0fc190704
commit
a4150b5389
+202
-163
@@ -3,65 +3,10 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Calendario scadenze - NIS2 Agile</title>
|
||||
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260618">
|
||||
<style>
|
||||
.cal-intro { background:#eff6ff; border-left:4px solid var(--primary,#2563eb); padding:14px 18px; border-radius:8px; margin-bottom:12px; font-size:.92rem; line-height:1.6; }
|
||||
.cal-note { font-size:.82rem; color:#3730a3; background:#eef2ff; border:1px solid #c7d2fe; border-radius:8px; padding:10px 12px; margin:0 0 16px; }
|
||||
.cal-bar { display:flex; flex-wrap:wrap; gap:12px; align-items:center; justify-content:space-between; margin-bottom:14px; }
|
||||
.cal-nav { display:flex; align-items:center; gap:8px; }
|
||||
.cal-nav h3 { margin:0; font-size:1.05rem; min-width:170px; text-align:center; text-transform:capitalize; }
|
||||
.cal-views { display:flex; gap:6px; }
|
||||
.cal-view-btn { padding:7px 14px; font-weight:600; font-size:.85rem; cursor:pointer; border:1px solid var(--gray-200,#e5e7eb); background:#fff; color:var(--gray-600,#4b5563); border-radius:8px; }
|
||||
.cal-view-btn.active { background:var(--primary,#2563eb); color:#fff; border-color:var(--primary,#2563eb); }
|
||||
.cal-summary { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:14px; }
|
||||
.cal-kpi { flex:1 1 0; min-width:120px; background:#fff; border:1px solid var(--gray-200,#e5e7eb); border-radius:10px; padding:10px 14px; }
|
||||
.cal-kpi .n { font-size:1.5rem; font-weight:800; line-height:1; }
|
||||
.cal-kpi .l { font-size:.74rem; text-transform:uppercase; letter-spacing:.04em; color:var(--gray-500,#6b7280); margin-top:4px; }
|
||||
.cal-kpi.k-overdue .n { color:#b91c1c; } .cal-kpi.k-due_soon .n { color:#b45309; }
|
||||
.cal-kpi.k-upcoming .n { color:#1d4ed8; } .cal-kpi.k-done .n { color:#15803d; }
|
||||
.cal-filters { background:#f8fafc; border:1px solid var(--gray-200,#e5e7eb); border-radius:10px; padding:12px 14px; margin-bottom:14px; }
|
||||
.cal-filters h4 { margin:0 0 8px; font-size:.78rem; text-transform:uppercase; letter-spacing:.04em; color:var(--gray-500,#6b7280); }
|
||||
.cal-chips { display:flex; flex-wrap:wrap; gap:8px; }
|
||||
.cal-chip { display:inline-flex; align-items:center; gap:6px; font-size:.82rem; padding:5px 10px; border:1px solid var(--gray-200,#e5e7eb); border-radius:20px; background:#fff; cursor:pointer; user-select:none; }
|
||||
.cal-chip input { margin:0; cursor:pointer; }
|
||||
.cal-chip .dot { width:10px; height:10px; border-radius:50%; display:inline-block; }
|
||||
.cal-filter-row { margin-top:10px; }
|
||||
table.cal-grid { width:100%; border-collapse:collapse; table-layout:fixed; }
|
||||
table.cal-grid th { font-size:.72rem; text-transform:uppercase; letter-spacing:.04em; color:var(--gray-500,#6b7280); padding:6px 4px; text-align:center; }
|
||||
table.cal-grid td { border:1px solid var(--gray-100,#f3f4f6); vertical-align:top; height:96px; padding:4px; background:#fff; }
|
||||
table.cal-grid td.cal-out { background:#fafafa; }
|
||||
table.cal-grid td.cal-today { background:#fffbeb; outline:2px solid #fbbf24; outline-offset:-2px; }
|
||||
.cal-daynum { font-size:.78rem; font-weight:600; color:var(--gray-600,#4b5563); }
|
||||
.cal-day-dots { display:flex; flex-wrap:wrap; gap:3px; margin-top:4px; }
|
||||
.cal-dot { width:9px; height:9px; border-radius:50%; cursor:pointer; }
|
||||
.cal-more { font-size:.7rem; color:var(--gray-500,#6b7280); cursor:pointer; margin-top:3px; }
|
||||
.st-overdue { background:#dc2626; } .st-due_soon { background:#d97706; }
|
||||
.st-upcoming { background:#2563eb; } .st-done { background:#16a34a; }
|
||||
.cal-list { width:100%; border-collapse:collapse; font-size:.88rem; }
|
||||
.cal-list th, .cal-list td { text-align:left; padding:9px 12px; border-top:1px solid var(--gray-100,#f3f4f6); vertical-align:top; }
|
||||
.cal-list th { font-size:.72rem; text-transform:uppercase; letter-spacing:.04em; color:var(--gray-500,#6b7280); }
|
||||
.cal-list tr.row-overdue td { background:#fef2f2; }
|
||||
.cal-list a.cal-link { color:var(--primary,#2563eb); text-decoration:none; font-weight:600; }
|
||||
.cal-list a.cal-link:hover { text-decoration:underline; }
|
||||
.st-badge { display:inline-block; font-size:.7rem; font-weight:700; padding:2px 8px; border-radius:6px; white-space:nowrap; color:#fff; }
|
||||
.st-badge.st-overdue { background:#dc2626; } .st-badge.st-due_soon { background:#d97706; }
|
||||
.st-badge.st-upcoming { background:#2563eb; } .st-badge.st-done { background:#16a34a; }
|
||||
.cal-empty { text-align:center; padding:22px 16px; color:var(--gray-500,#6b7280); font-size:.86rem; }
|
||||
.hidden { display:none !important; }
|
||||
.type-tag { display:inline-block; font-size:.7rem; padding:2px 7px; border-radius:6px; background:#eef2ff; color:#3730a3; }
|
||||
/* popover giorno */
|
||||
.cal-pop { position:fixed; inset:0; background:rgba(15,23,42,.4); z-index:1050; display:none; align-items:flex-start; justify-content:center; padding:40px 16px; overflow:auto; }
|
||||
.cal-pop.open { display:flex; }
|
||||
.cal-pop-card { background:#fff; border-radius:12px; width:100%; max-width:520px; box-shadow:0 20px 50px rgba(0,0,0,.25); }
|
||||
.cal-pop-head { display:flex; justify-content:space-between; align-items:center; padding:14px 18px; border-bottom:1px solid var(--gray-100,#f3f4f6); }
|
||||
.cal-pop-head h3 { margin:0; font-size:1rem; }
|
||||
.cal-pop-body { padding:14px 18px; }
|
||||
.cal-pop-item { display:flex; gap:8px; align-items:flex-start; padding:8px 0; border-top:1px solid var(--gray-100,#f3f4f6); }
|
||||
.cal-pop-item:first-child { border-top:none; }
|
||||
.cal-pop-close { background:none; border:none; font-size:1.4rem; line-height:1; cursor:pointer; color:var(--gray-400,#9ca3af); }
|
||||
</style>
|
||||
<title>Calendario · NIS2 Agile</title>
|
||||
<!-- UI V3: Inter self-host + design system v3.css. Self-host only: no external font/script hosts. -->
|
||||
<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">
|
||||
@@ -75,94 +20,189 @@
|
||||
<meta name="application-name" content="NIS2 Agile">
|
||||
<script src="/js/pwa.js?v=20260614" defer></script>
|
||||
<!-- PWA:end -->
|
||||
<style>
|
||||
/* ── Calendario V3 — shell app full-page (fonte: docs/mockup Ui/calendario.html).
|
||||
Solo classi NON coperte da v3.css; token condivisi via v3.css. Il calendario reale
|
||||
è un AGGREGATORE READ-ONLY (api.calendarEvents/calendarSummary): niente create/edit
|
||||
eventi → dal mockup riusiamo .cal-app/.cal-side/.cal-main/.mv-grid/.view-switch/.legend,
|
||||
wiring sui dati reali. Le due viste reali sono Griglia (mese) e Lista. */
|
||||
.cal-app{display:grid;grid-template-columns:250px 1fr;gap:18px;align-items:start}
|
||||
.cal-side{display:flex;flex-direction:column;gap:16px}
|
||||
|
||||
/* KPI sintetici (by_status) */
|
||||
.cal-kpis{display:grid;gap:10px}
|
||||
.cal-kpi{background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px 14px;box-shadow:var(--shadow);display:flex;align-items:center;justify-content:space-between;gap:10px}
|
||||
.cal-kpi .n{font-size:1.5rem;font-weight:800;line-height:1}
|
||||
.cal-kpi .l{font-size:.74rem;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
|
||||
.cal-kpi.k-overdue .n{color:var(--c-red)} .cal-kpi.k-due_soon .n{color:#b45309}
|
||||
.cal-kpi.k-upcoming .n{color:var(--c-blue)} .cal-kpi.k-done .n{color:#15803d}
|
||||
|
||||
/* legenda categorie + filtri */
|
||||
.legend{background:#fff;border:1px solid var(--line);border-radius:12px;padding:14px;box-shadow:var(--shadow)}
|
||||
.legend p{margin:0 0 9px;font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#8093A8}
|
||||
.cal-chips{display:flex;flex-direction:column;gap:4px}
|
||||
.cal-chip{display:inline-flex;align-items:center;gap:9px;font-size:12.5px;padding:5px 4px;border-radius:7px;cursor:pointer;user-select:none}
|
||||
.cal-chip:hover{background:#F3F7FC}
|
||||
.cal-chip input{margin:0;cursor:pointer;accent-color:var(--c-blue);flex:0 0 auto}
|
||||
.cal-chip .dot{width:11px;height:11px;border-radius:3px;display:inline-block;flex:0 0 auto}
|
||||
.cal-chip .lbl{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
|
||||
/* main */
|
||||
.cal-main{min-width:0}
|
||||
.cal-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px;flex-wrap:wrap}
|
||||
.tb-left{display:flex;align-items:center;gap:10px}
|
||||
.tb-nav{display:flex;gap:2px}
|
||||
.tb-title{font-size:19px;font-weight:800;text-transform:capitalize;letter-spacing:-.3px;margin-left:4px}
|
||||
.view-switch{display:inline-flex;background:#E7EDF4;border-radius:9px;padding:3px}
|
||||
.view-switch button{border:0;background:transparent;font:inherit;font-weight:600;font-size:12.5px;color:var(--muted);padding:6px 14px;border-radius:7px;cursor:pointer}
|
||||
.view-switch button.on{background:#fff;color:var(--ink);box-shadow:0 1px 2px rgba(16,40,80,.12)}
|
||||
|
||||
/* vista mese (griglia) */
|
||||
.mv{background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:var(--shadow)}
|
||||
.mv-dow{display:grid;grid-template-columns:repeat(7,1fr);background:#F6F9FC;border-bottom:1px solid var(--line)}
|
||||
.mv-dow div{padding:9px 8px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#7a8aa0}
|
||||
.mv-grid{display:grid;grid-template-columns:repeat(7,1fr)}
|
||||
.mc{min-height:108px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);padding:6px 7px;position:relative;overflow:hidden}
|
||||
.mc:nth-child(7n){border-right:0}
|
||||
.mc.has{cursor:pointer}
|
||||
.mc.has:hover{background:#FAFCFE}
|
||||
.mc .dnum{font-size:12.5px;font-weight:600;width:25px;height:25px;display:flex;align-items:center;justify-content:center;border-radius:50%;color:var(--ink)}
|
||||
.mc.other{background:#FAFBFD}.mc.other .dnum{color:#AEBAC8}
|
||||
.mc.today .dnum{background:var(--c-blue);color:#fff}
|
||||
.mc .chip{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--ink);border-radius:5px;padding:2px 6px;margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer}
|
||||
.mc .chip:hover{background:#F0F4F9}
|
||||
.mc .chip .cdot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
|
||||
.mc .more{font-size:10.5px;color:var(--muted);margin-top:3px;font-weight:600;cursor:pointer}
|
||||
.mc-empty{grid-column:1 / -1;text-align:center;padding:30px 16px;color:var(--muted);font-size:13px}
|
||||
|
||||
/* vista lista */
|
||||
.cal-list-card{background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:var(--shadow)}
|
||||
.cal-list{width:100%;border-collapse:collapse;font-size:13px}
|
||||
.cal-list th,.cal-list td{text-align:left;padding:10px 14px;border-top:1px solid var(--line);vertical-align:middle}
|
||||
.cal-list thead th{border-top:0;font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:#8093A8;font-weight:700;background:#F6F9FC}
|
||||
.cal-list tr.row-overdue td{background:#FEF2F2}
|
||||
.cal-list a.cal-link{color:var(--c-blue);font-weight:600}
|
||||
.cal-list a.cal-link:hover{text-decoration:underline}
|
||||
.type-tag{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;padding:3px 9px;border-radius:6px}
|
||||
.type-tag .cdot{width:8px;height:8px;border-radius:50%}
|
||||
.st-badge{display:inline-block;font-size:10px;font-weight:700;padding:3px 9px;border-radius:6px;white-space:nowrap;color:#fff;text-transform:uppercase;letter-spacing:.03em}
|
||||
.st-badge.st-overdue{background:#dc2626}.st-badge.st-due_soon{background:#d97706}
|
||||
.st-badge.st-upcoming{background:#2563eb}.st-badge.st-done{background:#16a34a}
|
||||
.cal-empty{text-align:center;padding:30px 16px;color:var(--muted);font-size:13px}
|
||||
.hidden{display:none !important}
|
||||
|
||||
/* popover giorno (modale) */
|
||||
.cal-modal{position:fixed;inset:0;background:rgba(13,28,52,.45);display:none;align-items:flex-start;justify-content:center;z-index:90;padding:40px 16px;overflow:auto}
|
||||
.cal-modal.open{display:flex}
|
||||
.cal-modal-card{background:#fff;border-radius:14px;width:100%;max-width:520px;box-shadow:0 24px 60px rgba(11,30,60,.35)}
|
||||
.cal-modal-head{display:flex;align-items:center;justify-content:space-between;padding:15px 18px;border-bottom:1px solid var(--line)}
|
||||
.cal-modal-head h3{margin:0;font-size:15px;font-weight:700}
|
||||
.cal-modal-x{border:0;background:transparent;cursor:pointer;color:var(--muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:1.4rem;line-height:1}
|
||||
.cal-modal-x:hover{background:#F0F4F9}
|
||||
.cal-modal-body{padding:14px 18px}
|
||||
.cal-pop-item{display:flex;gap:10px;align-items:flex-start;padding:9px 0;border-top:1px solid var(--line)}
|
||||
.cal-pop-item:first-child{border-top:none}
|
||||
.cal-pop-item .cdot{width:10px;height:10px;border-radius:50%;margin-top:4px;flex:0 0 auto}
|
||||
|
||||
@media (max-width:900px){
|
||||
.cal-app{grid-template-columns:1fr}
|
||||
.cal-side{flex-direction:row;flex-wrap:wrap}
|
||||
.cal-kpis{grid-template-columns:repeat(2,1fr);flex:1;min-width:220px}
|
||||
.legend{flex:1;min-width:220px}
|
||||
.mc{min-height:84px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-layout">
|
||||
<aside class="sidebar" id="sidebar"></aside>
|
||||
<main class="main-content">
|
||||
<header class="content-header">
|
||||
<h2 data-i18n="cal.title">Calendario scadenze</h2>
|
||||
</header>
|
||||
<div class="content-body">
|
||||
<div class="cal-intro">
|
||||
<strong>Tutte le scadenze in un solo posto.</strong>
|
||||
Il calendario aggrega <em>in tempo reale</em> ogni scadenza del sistema — incidenti, revisioni policy e controlli,
|
||||
trattamenti di rischio, non conformita e azioni correttive, formazione, attivita stakeholder, scadenziario revisioni,
|
||||
audit interni e decisioni del riesame — con stato e link diretto all'elemento.
|
||||
</div>
|
||||
<div class="cal-note">
|
||||
Ancoraggio: buona prassi di monitoraggio scadenze e revisioni periodiche (<strong>ISO 9.1/9.3</strong>,
|
||||
NIS2 <strong>GV.PO-02 / GV.SC-07 / DE.CM</strong>). Strumento di supporto operativo, non un parere legale.
|
||||
</div>
|
||||
<!-- Chrome V3 (appbar + mega-menu) iniettata da topnav-v3.js in questo host -->
|
||||
<div id="topnav"></div>
|
||||
|
||||
<!-- KPI sintetici -->
|
||||
<div class="cal-summary" id="cal-summary" aria-live="polite"></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 data-i18n="cal.title">Calendario</span>
|
||||
</nav>
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1 data-i18n="cal.title">Calendario scadenze</h1>
|
||||
<p class="sub">Vista temporale aggregata di scadenze, revisioni e controlli del sistema</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Barra: navigazione mese + selettore vista -->
|
||||
<div class="cal-bar">
|
||||
<div class="cal-nav">
|
||||
<button class="btn btn-outline-primary btn-sm" type="button" onclick="calPrevMonth()" aria-label="Mese precedente">‹</button>
|
||||
<h3 id="cal-month-label">—</h3>
|
||||
<button class="btn btn-outline-primary btn-sm" type="button" onclick="calNextMonth()" aria-label="Mese successivo">›</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="button" onclick="calToday()">Oggi</button>
|
||||
</div>
|
||||
<div class="cal-views" role="tablist" aria-label="Vista calendario">
|
||||
<button class="cal-view-btn active" id="view-grid" role="tab" aria-selected="true" aria-controls="pane-grid" onclick="calSetView('grid')">Griglia</button>
|
||||
<button class="cal-view-btn" id="view-list" role="tab" aria-selected="false" aria-controls="pane-list" onclick="calSetView('list')">Lista</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cal-app">
|
||||
<!-- Sidebar: KPI + legenda/filtri categoria + stato -->
|
||||
<aside class="cal-side">
|
||||
<div class="cal-kpis" id="cal-summary" aria-live="polite"></div>
|
||||
|
||||
<!-- Filtri -->
|
||||
<div class="cal-filters">
|
||||
<h4>Filtra per tipo</h4>
|
||||
<div class="legend">
|
||||
<p>Filtra per tipo</p>
|
||||
<div class="cal-chips" id="cal-type-chips"></div>
|
||||
<div class="cal-filter-row">
|
||||
<h4>Filtra per stato</h4>
|
||||
<div class="cal-chips" id="cal-status-chips"></div>
|
||||
</div>
|
||||
|
||||
<div class="legend">
|
||||
<p>Filtra per stato</p>
|
||||
<div class="cal-chips" id="cal-status-chips"></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main: toolbar + viste -->
|
||||
<section class="cal-main">
|
||||
<div class="cal-toolbar">
|
||||
<div class="tb-left">
|
||||
<button class="ctrl-today" id="tb-today" onclick="calToday()"><i data-lucide="calendar-check" class="ic"></i>Oggi</button>
|
||||
<div class="tb-nav">
|
||||
<button class="ctrl-btn" id="tb-prev" title="Mese precedente" aria-label="Mese precedente" onclick="calPrevMonth()"><i data-lucide="chevron-left" class="ic"></i></button>
|
||||
<button class="ctrl-btn" id="tb-next" title="Mese successivo" aria-label="Mese successivo" onclick="calNextMonth()"><i data-lucide="chevron-right" class="ic"></i></button>
|
||||
</div>
|
||||
<span class="tb-title" id="cal-month-label">—</span>
|
||||
</div>
|
||||
<div class="view-switch" role="tablist" aria-label="Vista calendario">
|
||||
<button data-v="grid" class="on" id="view-grid" role="tab" aria-selected="true" aria-controls="pane-grid" onclick="calSetView('grid')">Griglia</button>
|
||||
<button data-v="list" id="view-list" role="tab" aria-selected="false" aria-controls="pane-list" onclick="calSetView('list')">Lista</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vista griglia mensile -->
|
||||
<section id="pane-grid" role="tabpanel" aria-labelledby="view-grid" aria-live="polite">
|
||||
<table class="cal-grid">
|
||||
<thead>
|
||||
<tr id="cal-weekhead"></tr>
|
||||
</thead>
|
||||
<tbody id="cal-grid-body"></tbody>
|
||||
</table>
|
||||
<section id="pane-grid" class="mv" role="tabpanel" aria-labelledby="view-grid" aria-live="polite">
|
||||
<div class="mv-dow"><div>Lun</div><div>Mar</div><div>Mer</div><div>Gio</div><div>Ven</div><div>Sab</div><div>Dom</div></div>
|
||||
<div class="mv-grid" id="mv-grid"></div>
|
||||
</section>
|
||||
|
||||
<!-- Vista lista -->
|
||||
<section id="pane-list" class="hidden" role="tabpanel" aria-labelledby="view-list" aria-live="polite">
|
||||
<div id="cal-list-wrap"></div>
|
||||
<div class="cal-list-card" id="cal-list-wrap"></div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Popover eventi di un giorno -->
|
||||
<div class="cal-pop" id="cal-pop" role="dialog" aria-modal="true" aria-labelledby="cal-pop-title">
|
||||
<div class="cal-pop-card">
|
||||
<div class="cal-pop-head">
|
||||
<!-- Popover eventi di un giorno (modale) -->
|
||||
<div class="cal-modal" id="cal-pop" role="dialog" aria-modal="true" aria-labelledby="cal-pop-title">
|
||||
<div class="cal-modal-card">
|
||||
<div class="cal-modal-head">
|
||||
<h3 id="cal-pop-title">Eventi</h3>
|
||||
<button class="cal-pop-close" type="button" aria-label="Chiudi" onclick="calPopClose()">×</button>
|
||||
<button class="cal-modal-x" type="button" aria-label="Chiudi" onclick="calPopClose()">×</button>
|
||||
</div>
|
||||
<div class="cal-pop-body" id="cal-pop-body"></div>
|
||||
<div class="cal-modal-body" id="cal-pop-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260624g"></script>
|
||||
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></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>
|
||||
'use strict';
|
||||
/*
|
||||
* NIS2 Agile - Calendario unico delle scadenze (Modulo C)
|
||||
* NIS2 Agile - Calendario unico delle scadenze (Modulo C) — UI V3
|
||||
* Aggregatore READ-ONLY: griglia mensile + lista + filtri (tipo/stato) + deep-link.
|
||||
* Dependency-free: nessuna libreria calendario, render in JS puro.
|
||||
* Dati reali via api.calendarEvents / api.calendarSummary. Dependency-free.
|
||||
* Veste fedele a docs/mockup Ui/calendario.html (.cal-app/.mv-grid/.view-switch/.legend).
|
||||
*/
|
||||
var CAL = {
|
||||
view: 'grid',
|
||||
@@ -206,12 +246,14 @@
|
||||
var day = String(d.getDate()).padStart(2, '0');
|
||||
return d.getFullYear() + '-' + m + '-' + day;
|
||||
}
|
||||
function refreshIcons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
if (typeof checkAuth === 'function' && !checkAuth()) return;
|
||||
if (window.I18n && I18n.init) I18n.init('it');
|
||||
if (typeof loadSidebar === 'function') loadSidebar();
|
||||
if (typeof loadSidebar === 'function') loadSidebar(); // = loadTopnavV3()
|
||||
if (window.I18n && I18n.init) I18n.init();
|
||||
if (window.HelpSystem && HelpSystem.init) HelpSystem.init();
|
||||
refreshIcons();
|
||||
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') calPopClose(); });
|
||||
var pop = el('cal-pop'); if (pop) pop.addEventListener('click', function (e) { if (e.target === pop) calPopClose(); });
|
||||
CAL.cursor = new Date(); CAL.cursor.setDate(1);
|
||||
@@ -224,13 +266,13 @@
|
||||
var from = new Date(CAL.cursor.getFullYear(), CAL.cursor.getMonth() - 2, 1);
|
||||
var to = new Date(CAL.cursor.getFullYear(), CAL.cursor.getMonth() + 3, 0);
|
||||
CAL.rangeFrom = ymd(from); CAL.rangeTo = ymd(to);
|
||||
el('cal-grid-body').innerHTML = '<tr><td colspan="7" class="cal-empty">Caricamento…</td></tr>';
|
||||
el('mv-grid').innerHTML = '<div class="mc-empty"><span class="v3-loading"><span class="v3-spin"></span>Caricamento…</span></div>';
|
||||
try {
|
||||
var data = await api.calendarEvents({ from: CAL.rangeFrom, to: CAL.rangeTo });
|
||||
CAL.events = (data && data.events) ? data.events : [];
|
||||
} catch (e) {
|
||||
CAL.events = [];
|
||||
el('cal-grid-body').innerHTML = '<tr><td colspan="7" class="cal-empty">Errore nel caricamento: ' + esc(e.message || '') + '</td></tr>';
|
||||
el('mv-grid').innerHTML = '<div class="mc-empty">Errore nel caricamento: ' + esc(e.message || '') + '</div>';
|
||||
}
|
||||
await loadSummary();
|
||||
render();
|
||||
@@ -242,7 +284,7 @@
|
||||
var bs = (s && s.by_status) || {};
|
||||
var defs = [['overdue', 'In ritardo'], ['due_soon', 'In scadenza'], ['upcoming', 'Future'], ['done', 'Assolte']];
|
||||
el('cal-summary').innerHTML = defs.map(function (d) {
|
||||
return '<div class="cal-kpi k-' + d[0] + '"><div class="n">' + (bs[d[0]] || 0) + '</div><div class="l">' + d[1] + '</div></div>';
|
||||
return '<div class="cal-kpi k-' + d[0] + '"><div class="l">' + d[1] + '</div><div class="n">' + (bs[d[0]] || 0) + '</div></div>';
|
||||
}).join('');
|
||||
} catch (e) { el('cal-summary').innerHTML = ''; }
|
||||
}
|
||||
@@ -252,12 +294,12 @@
|
||||
el('cal-type-chips').innerHTML = Object.keys(TYPE_META).map(function (t) {
|
||||
var m = TYPE_META[t];
|
||||
return '<label class="cal-chip"><input type="checkbox" class="f-type" value="' + escAttr(t) + '" checked onchange="onTypeFilter()">' +
|
||||
'<span class="dot" style="background:' + m.color + '"></span>' + esc(m.label) + '</label>';
|
||||
'<span class="dot" style="background:' + m.color + '"></span><span class="lbl">' + esc(m.label) + '</span></label>';
|
||||
}).join('');
|
||||
el('cal-status-chips').innerHTML = Object.keys(STATUS_META).map(function (s) {
|
||||
var m = STATUS_META[s];
|
||||
return '<label class="cal-chip"><input type="checkbox" class="f-status" value="' + escAttr(s) + '" checked onchange="onStatusFilter()">' +
|
||||
'<span class="dot" style="background:' + m.color + '"></span>' + esc(m.label) + '</label>';
|
||||
'<span class="dot" style="background:' + m.color + '"></span><span class="lbl">' + esc(m.label) + '</span></label>';
|
||||
}).join('');
|
||||
}
|
||||
function onTypeFilter() {
|
||||
@@ -294,8 +336,8 @@
|
||||
}
|
||||
function calSetView(v) {
|
||||
CAL.view = v;
|
||||
el('view-grid').classList.toggle('active', v === 'grid');
|
||||
el('view-list').classList.toggle('active', v === 'list');
|
||||
el('view-grid').classList.toggle('on', v === 'grid');
|
||||
el('view-list').classList.toggle('on', v === 'list');
|
||||
el('view-grid').setAttribute('aria-selected', v === 'grid' ? 'true' : 'false');
|
||||
el('view-list').setAttribute('aria-selected', v === 'list' ? 'true' : 'false');
|
||||
el('pane-grid').classList.toggle('hidden', v !== 'grid');
|
||||
@@ -308,10 +350,8 @@
|
||||
if (CAL.view === 'grid') renderGrid(); else renderList();
|
||||
}
|
||||
|
||||
// ── Vista griglia mensile ──
|
||||
// ── Vista griglia mensile (.mv-grid del mockup) ──
|
||||
function renderGrid() {
|
||||
el('cal-weekhead').innerHTML = WEEKDAYS.map(function (w) { return '<th>' + w + '</th>'; }).join('');
|
||||
|
||||
var year = CAL.cursor.getFullYear(), month = CAL.cursor.getMonth();
|
||||
var firstOfMonth = new Date(year, month, 1);
|
||||
// settimana inizia lunedi (getDay: 0=dom)
|
||||
@@ -324,32 +364,31 @@
|
||||
visibleEvents().forEach(function (e) { (byDay[e.date] = byDay[e.date] || []).push(e); });
|
||||
|
||||
var html = '';
|
||||
for (var w = 0; w < 6; w++) {
|
||||
html += '<tr>';
|
||||
for (var d = 0; d < 7; d++) {
|
||||
var cur = new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + (w * 7 + d));
|
||||
var key = ymd(cur);
|
||||
var inMonth = (cur.getMonth() === month);
|
||||
var cls = inMonth ? '' : ' cal-out';
|
||||
if (key === todayStr) cls += ' cal-today';
|
||||
var evs = byDay[key] || [];
|
||||
var dots = '';
|
||||
var shown = evs.slice(0, 6);
|
||||
shown.forEach(function (e) {
|
||||
var c = (TYPE_META[e.type] || {}).color || '#64748b';
|
||||
var tip = escAttr((TYPE_META[e.type] || { label: e.type }).label + ' — ' + e.title);
|
||||
dots += '<span class="cal-dot st-' + esc(e.status) + '" style="background:' + c + '" title="' + tip + '" onclick="calPopDay(\'' + key + '\')"></span>';
|
||||
});
|
||||
var more = evs.length > shown.length ? '<div class="cal-more" onclick="calPopDay(\'' + key + '\')">+' + (evs.length - shown.length) + ' altri</div>' : '';
|
||||
var clickDay = evs.length ? ' style="cursor:pointer" role="button" tabindex="0" aria-label="' + escAttr(cur.getDate() + ': ' + evs.length + ' scadenze') + '" onclick="calPopDay(\'' + key + '\')" onkeydown="if(event.key===\'Enter\'||event.key===\' \'){event.preventDefault();calPopDay(\'' + key + '\');}"' : '';
|
||||
html += '<td class="' + cls + '">' +
|
||||
'<div class="cal-daynum"' + clickDay + '>' + cur.getDate() + '</div>' +
|
||||
(evs.length ? '<div class="cal-day-dots">' + dots + '</div>' + more : '') +
|
||||
'</td>';
|
||||
}
|
||||
html += '</tr>';
|
||||
for (var i = 0; i < 42; i++) {
|
||||
var cur = new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i);
|
||||
var key = ymd(cur);
|
||||
var inMonth = (cur.getMonth() === month);
|
||||
var evs = byDay[key] || [];
|
||||
var cls = 'mc' + (inMonth ? '' : ' other') + (key === todayStr ? ' today' : '') + (evs.length ? ' has' : '');
|
||||
var chips = '';
|
||||
evs.slice(0, 3).forEach(function (e) {
|
||||
var c = (TYPE_META[e.type] || {}).color || '#64748b';
|
||||
var tip = escAttr((TYPE_META[e.type] || { label: e.type }).label + ' — ' + e.title);
|
||||
chips += '<div class="chip" title="' + tip + '"><span class="cdot" style="background:' + c + '"></span>' + esc(e.title) + '</div>';
|
||||
});
|
||||
var more = evs.length > 3 ? '<div class="more">+' + (evs.length - 3) + ' altri</div>' : '';
|
||||
var click = evs.length ? ' role="button" tabindex="0" aria-label="' + escAttr(cur.getDate() + ': ' + evs.length + ' scadenze') + '" data-day="' + key + '"' : '';
|
||||
html += '<div class="' + cls + '"' + click + '>' +
|
||||
'<div class="dnum">' + cur.getDate() + '</div>' + chips + more + '</div>';
|
||||
}
|
||||
el('cal-grid-body').innerHTML = html;
|
||||
var grid = el('mv-grid');
|
||||
grid.innerHTML = html;
|
||||
// wiring click/keyboard sui giorni con eventi
|
||||
grid.querySelectorAll('.mc.has').forEach(function (cell) {
|
||||
var key = cell.getAttribute('data-day');
|
||||
cell.addEventListener('click', function () { calPopDay(key); });
|
||||
cell.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); calPopDay(key); } });
|
||||
});
|
||||
}
|
||||
|
||||
// ── Vista lista (overdue in cima, poi per data) ──
|
||||
@@ -364,8 +403,8 @@
|
||||
var m = TYPE_META[e.type] || { label: e.type, color: '#64748b' };
|
||||
var sm = STATUS_META[e.status] || { label: e.status };
|
||||
return '<tr class="row-' + esc(e.status) + '">' +
|
||||
'<td>' + esc(formatDate(e.date)) + '</td>' +
|
||||
'<td><span class="type-tag" style="background:' + m.color + '22;color:' + m.color + '">' + esc(m.label) + '</span></td>' +
|
||||
'<td>' + esc(calFmtDate(e.date)) + '</td>' +
|
||||
'<td><span class="type-tag" style="background:' + m.color + '22;color:' + m.color + '"><span class="cdot" style="background:' + m.color + '"></span>' + esc(m.label) + '</span></td>' +
|
||||
'<td><a class="cal-link" href="' + escAttr(e.link || '#') + '">' + esc(e.title) + '</a></td>' +
|
||||
'<td><span class="st-badge st-' + esc(e.status) + '">' + esc(sm.label) + '</span></td>' +
|
||||
'</tr>';
|
||||
@@ -374,7 +413,7 @@
|
||||
'<table class="cal-list"><thead><tr><th>Data</th><th>Tipo</th><th>Elemento</th><th>Stato</th></tr></thead><tbody>' + rows + '</tbody></table>';
|
||||
}
|
||||
|
||||
function formatDate(s) {
|
||||
function calFmtDate(s) {
|
||||
var p = String(s).split('-');
|
||||
if (p.length !== 3) return s;
|
||||
return p[2] + '/' + p[1] + '/' + p[0];
|
||||
@@ -384,15 +423,15 @@
|
||||
function calPopDay(dateKey) {
|
||||
var evs = visibleEvents().filter(function (e) { return e.date === dateKey; });
|
||||
if (!evs.length) return;
|
||||
el('cal-pop-title').textContent = 'Scadenze del ' + formatDate(dateKey);
|
||||
el('cal-pop-title').textContent = 'Scadenze del ' + calFmtDate(dateKey);
|
||||
el('cal-pop-body').innerHTML = evs.map(function (e) {
|
||||
var m = TYPE_META[e.type] || { label: e.type, color: '#64748b' };
|
||||
var sm = STATUS_META[e.status] || { label: e.status };
|
||||
return '<div class="cal-pop-item">' +
|
||||
'<span class="cal-dot st-' + esc(e.status) + '" style="background:' + m.color + ';margin-top:4px;flex:0 0 auto"></span>' +
|
||||
'<div style="flex:1 1 auto">' +
|
||||
'<span class="cdot" style="background:' + m.color + '"></span>' +
|
||||
'<div style="flex:1 1 auto;min-width:0">' +
|
||||
'<div><a class="cal-link" href="' + escAttr(e.link || '#') + '">' + esc(e.title) + '</a></div>' +
|
||||
'<div style="font-size:.78rem;color:#6b7280;margin-top:2px">' + esc(m.label) +
|
||||
'<div style="font-size:.8rem;color:var(--muted);margin-top:3px">' + esc(m.label) +
|
||||
' · <span class="st-badge st-' + esc(e.status) + '">' + esc(sm.label) + '</span></div>' +
|
||||
'</div></div>';
|
||||
}).join('');
|
||||
|
||||
Reference in New Issue
Block a user