feat: JARVIS Planner — tasks/appointments with voice intents, status bar badge, admin CRUD

This commit is contained in:
2026-05-31 16:53:21 +00:00
parent 02d62cbe53
commit f122de483a
5 changed files with 545 additions and 0 deletions
+18
View File
@@ -658,6 +658,7 @@ body::after{
<div class="tb-stat">MEM&nbsp;<span id="tb-mem">--</span>%</div>
<div class="tb-stat">DO SERVER&nbsp;<span id="tb-do" class="text-dim">--</span></div>
<div class="tb-stat"><span id="tb-alerts" class="text-green">NO ALERTS</span></div>
<div class="tb-stat" id="tb-planner" style="display:none"><span id="tb-planner-text" class="text-yellow"></span></div>
</div>
<div class="tb-right">
<div>
@@ -1202,6 +1203,7 @@ async function refreshAll() {
try { await loadAlerts(); } catch(e) {}
try { await loadAgents(); } catch(e) {}
try { await loadProxmox(); } catch(e) {}
try { await loadPlannerSummary(); } catch(e) {}
}
// Refresh weather + news every 18th tick (~3 min — cache updates every 30 min)
if (_refreshTick % 18 === 0) {
@@ -1521,6 +1523,22 @@ async function toggleHA(entityId, domain, currentState) {
} catch(e) {}
}
// ── PLANNER SUMMARY (top bar badge only) ─────────────────────────────────
async function loadPlannerSummary() {
const d = await api('planner/today');
const el = document.getElementById('tb-planner');
const tx = document.getElementById('tb-planner-text');
if (!el || !tx) return;
const tasksDue = (d.tasks_today || []).length + (d.tasks_overdue || []).length;
const appts = (d.appts_today || []).length;
if (!tasksDue && !appts) { el.style.display = 'none'; return; }
const parts = [];
if (tasksDue) parts.push(tasksDue + ' TASK' + (tasksDue > 1 ? 'S' : ''));
if (appts) parts.push(appts + ' APPT' + (appts > 1 ? 'S' : ''));
tx.textContent = parts.join(' · ');
el.style.display = '';
}
// ── ALERTS ────────────────────────────────────────────────────────────
async function loadAlerts() {
const data = await api('alerts');