Files
nis2-agile/public/calendario.html
T
DevEnv nis2-agileandClaude Opus 4.8 8799620fba [FIX] Rischi 12 col: titoli gruppo "Rischio Inerente/Residuo" + soglie alert 9-12 (feedback Simon)
Riscontro Simon sulla tabella Gestione Rischi:
- Titoli di gruppo sui tripletti Probabilità/Impatto/Valutazione: da
  "VALUTAZIONE RISCHIO INERENTE/RESIDUO" a "Rischio Inerente"/"Rischio Residuo"
  (risks.html thead + i18n.js grp_inherent/grp_residual IT+EN).
- Ritirata eccezione #3: banda Alert riportata a spec Simon 15-25/9-12/3-8/1-2.
  Simon ha ragione: con P,I in 1-5 i prodotti 7/11/13/14 non esistono → nessun
  buco. alertBand resta >=9 (=9-12 in pratica); aggiornati commento, help.js,
  manuale, KB (9-14 -> 9-12).
- Cache-buster help.js/i18n.js -> 20260730b (i18n va bustato o riscrive il titolo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 07:36:43 +02:00

444 lines
26 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>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">
<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 -->
<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>
<!-- 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 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>
<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>
<div class="legend">
<p>Filtra per tipo</p>
<div class="cal-chips" id="cal-type-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" 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 class="cal-list-card" id="cal-list-wrap"></div>
</section>
</section>
</div>
</main>
<!-- 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-modal-x" type="button" aria-label="Chiudi" onclick="calPopClose()">&times;</button>
</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=20260730a"></script>
<script src="/js/common.js?v=20260729b"></script>
<script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260729"></script>
<script src="/js/i18n.js?v=20260730b"></script>
<script src="/js/help.js?v=20260730b"></script>
<script>
'use strict';
/*
* NIS2 Agile - Calendario unico delle scadenze (Modulo C) — UI V3
* Aggregatore READ-ONLY: griglia mensile + lista + filtri (tipo/stato) + deep-link.
* 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',
cursor: new Date(), // mese visualizzato (giorno 1)
events: [], // tutti gli eventi della finestra caricata
rangeFrom: null, rangeTo: null,
activeTypes: null, // null = tutti
activeStatuses: null, // null = tutti
};
var TYPE_META = {
incident_early_warning: { label: 'Incidente - Early Warning', color: '#dc2626' },
incident_notification: { label: 'Incidente - Notifica CSIRT', color: '#ea580c' },
incident_final_report: { label: 'Incidente - Report finale', color: '#f59e0b' },
policy_review: { label: 'Revisione policy', color: '#2563eb' },
risk_treatment: { label: 'Trattamento rischio', color: '#7c3aed' },
control_review: { label: 'Revisione controllo', color: '#0891b2' },
nc_target_close: { label: 'Chiusura non conformita', color: '#be123c' },
capa_action: { label: 'Azione correttiva', color: '#c026d3' },
training_due: { label: 'Formazione', color: '#16a34a' },
stakeholder_activity: { label: 'Attivita stakeholder', color: '#0d9488' },
review_schedule: { label: 'Scadenziario revisioni', color: '#4f46e5' },
internal_audit: { label: 'Audit interno', color: '#9333ea' },
management_review_decision: { label: 'Decisione riesame', color: '#0369a1' },
periodic_control: { label: 'Controllo periodico', color: '#65a30d' }
};
var STATUS_META = {
overdue: { label: 'In ritardo', color: '#dc2626' },
due_soon: { label: 'In scadenza', color: '#d97706' },
upcoming: { label: 'Futura', color: '#2563eb' },
done: { label: 'Assolta', color: '#16a34a' }
};
var WEEKDAYS = ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom'];
var MONTHS = ['gennaio','febbraio','marzo','aprile','maggio','giugno','luglio','agosto','settembre','ottobre','novembre','dicembre'];
function el(id) { return document.getElementById(id); }
function esc(s) { var d = document.createElement('div'); d.textContent = (s == null ? '' : String(s)); return d.innerHTML; }
function escAttr(s) { return esc(s).replace(/"/g, '&quot;'); }
function ymd(d) {
var m = String(d.getMonth() + 1).padStart(2, '0');
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 (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);
renderFilters();
await calLoad();
});
// ── Caricamento dati (finestra ampia attorno al cursore) ──
async function calLoad() {
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('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('mv-grid').innerHTML = '<div class="mc-empty">Errore nel caricamento: ' + esc(e.message || '') + '</div>';
}
await loadSummary();
render();
}
async function loadSummary() {
try {
var s = await api.calendarSummary();
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="l">' + d[1] + '</div><div class="n">' + (bs[d[0]] || 0) + '</div></div>';
}).join('');
} catch (e) { el('cal-summary').innerHTML = ''; }
}
// ── Filtri (chip checkbox per tipo e per stato) ──
function renderFilters() {
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><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><span class="lbl">' + esc(m.label) + '</span></label>';
}).join('');
}
function onTypeFilter() {
var all = Array.prototype.slice.call(document.querySelectorAll('.f-type'));
var checked = all.filter(function (c) { return c.checked; }).map(function (c) { return c.value; });
CAL.activeTypes = (checked.length === all.length) ? null : checked;
render();
}
function onStatusFilter() {
var all = Array.prototype.slice.call(document.querySelectorAll('.f-status'));
var checked = all.filter(function (c) { return c.checked; }).map(function (c) { return c.value; });
CAL.activeStatuses = (checked.length === all.length) ? null : checked;
render();
}
function visibleEvents() {
return CAL.events.filter(function (e) {
if (CAL.activeTypes && CAL.activeTypes.indexOf(e.type) < 0) return false;
if (CAL.activeStatuses && CAL.activeStatuses.indexOf(e.status) < 0) return false;
return true;
});
}
// ── Navigazione mese / vista ──
function calPrevMonth() { CAL.cursor.setMonth(CAL.cursor.getMonth() - 1); maybeReload(); }
function calNextMonth() { CAL.cursor.setMonth(CAL.cursor.getMonth() + 1); maybeReload(); }
function calToday() { CAL.cursor = new Date(); CAL.cursor.setDate(1); maybeReload(); }
function maybeReload() {
// Ricarica dal server solo se il mese esce dalla finestra gia' caricata.
var first = ymd(new Date(CAL.cursor.getFullYear(), CAL.cursor.getMonth(), 1));
var last = ymd(new Date(CAL.cursor.getFullYear(), CAL.cursor.getMonth() + 1, 0));
if (!CAL.rangeFrom || first < CAL.rangeFrom || last > CAL.rangeTo) { calLoad(); }
else { render(); }
}
function calSetView(v) {
CAL.view = v;
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');
el('pane-list').classList.toggle('hidden', v !== 'list');
render();
}
function render() {
el('cal-month-label').textContent = MONTHS[CAL.cursor.getMonth()] + ' ' + CAL.cursor.getFullYear();
if (CAL.view === 'grid') renderGrid(); else renderList();
}
// ── Vista griglia mensile (.mv-grid del mockup) ──
function renderGrid() {
var year = CAL.cursor.getFullYear(), month = CAL.cursor.getMonth();
var firstOfMonth = new Date(year, month, 1);
// settimana inizia lunedi (getDay: 0=dom)
var startOffset = (firstOfMonth.getDay() + 6) % 7;
var gridStart = new Date(year, month, 1 - startOffset);
var todayStr = ymd(new Date());
// raggruppa eventi visibili per giorno
var byDay = {};
visibleEvents().forEach(function (e) { (byDay[e.date] = byDay[e.date] || []).push(e); });
var html = '';
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>';
}
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) ──
function renderList() {
var evs = visibleEvents().slice().sort(function (a, b) {
var oa = a.status === 'overdue' ? 0 : 1, ob = b.status === 'overdue' ? 0 : 1;
if (oa !== ob) return oa - ob;
return a.date < b.date ? -1 : (a.date > b.date ? 1 : 0);
});
if (!evs.length) { el('cal-list-wrap').innerHTML = '<div class="cal-empty">Nessuna scadenza per i filtri selezionati.</div>'; return; }
var rows = 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 '<tr class="row-' + esc(e.status) + '">' +
'<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>';
}).join('');
el('cal-list-wrap').innerHTML =
'<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 calFmtDate(s) {
var p = String(s).split('-');
if (p.length !== 3) return s;
return p[2] + '/' + p[1] + '/' + p[0];
}
// ── Popover eventi di un giorno ──
function calPopDay(dateKey) {
var evs = visibleEvents().filter(function (e) { return e.date === dateKey; });
if (!evs.length) return;
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="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:.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('');
el('cal-pop').classList.add('open');
}
function calPopClose() { el('cal-pop').classList.remove('open'); }
</script>
</body>
</html>