mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
feat: complete calendar integration + planner widget + 298 new KB intents
- Add calendar sync route to api.php (/api/calendar → calendar_sync.php) - Add CALENDAR SYNC tab to admin with feed CRUD (add/edit/delete Google/ICS feeds) - Add cal_sync_now action to admin for on-demand iCloud/Google sync - Add cron: calendar_sync.php every 15 min (iCloud CalDAV + ICS feeds) - Add PLANNER mini panel to index.html (left panel, shows today tasks + appointments) - Update loadPlannerSummary() to render tasks/appts with priority dots and times - Seed 298 new KB intents across 37 categories: science, history, tech, geography, math, health, food, space, philosophy, psychology, sports, music, film, travel, language, literature, finance, productivity, nature, facts, home automation, architecture, geopolitics, and more — 543 total intents
This commit is contained in:
+64
-9
@@ -388,6 +388,16 @@ body::after{
|
||||
}
|
||||
#contextClear:hover{color:var(--red)}
|
||||
|
||||
/* Planner mini panel */
|
||||
#plannerMiniPanel .task-item{display:flex;align-items:center;gap:6px;padding:2px 0;border-bottom:1px solid rgba(0,212,255,0.06);cursor:default}
|
||||
#plannerMiniPanel .task-item:last-child{border-bottom:none}
|
||||
#plannerMiniPanel .pri-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
||||
#plannerMiniPanel .pri-urgent{background:#f44}
|
||||
#plannerMiniPanel .pri-high{background:#fa0}
|
||||
#plannerMiniPanel .pri-normal{background:var(--cyan)}
|
||||
#plannerMiniPanel .pri-low{background:var(--text-dim)}
|
||||
#plannerMiniPanel .appt-row{color:var(--text-dim);font-size:0.58rem;padding:2px 0;display:flex;gap:6px;border-bottom:1px solid rgba(0,212,255,0.04)}
|
||||
#plannerMiniPanel .appt-time{color:var(--cyan);min-width:42px}
|
||||
/* Clickable panel items */
|
||||
.vm-card{cursor:pointer;transition:background 0.15s,border-color 0.15s}
|
||||
.vm-card:hover{background:rgba(0,212,255,0.07);border-color:rgba(0,212,255,0.4)}
|
||||
@@ -718,6 +728,16 @@ body::after{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PLANNER MINI PANEL -->
|
||||
<div id="plannerMiniPanel" class="panel" style="flex:0 0 auto;max-height:220px;overflow:hidden;display:flex;flex-direction:column">
|
||||
<div class="panel-title" style="display:flex;align-items:center;gap:8px">
|
||||
PLANNER
|
||||
<span id="planner-badge" style="font-size:0.55rem;color:var(--cyan);margin-left:auto"></span>
|
||||
<a href="/admin" target="_blank" style="font-size:0.55rem;color:var(--text-dim);text-decoration:none;letter-spacing:1px">ADMIN ↗</a>
|
||||
</div>
|
||||
<div id="planner-tasks" style="overflow-y:auto;flex:1;font-size:0.62rem;line-height:1.7"></div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER: Arc Reactor + Chat -->
|
||||
<div id="centerPanel">
|
||||
<div id="arcReactor">
|
||||
@@ -1543,15 +1563,50 @@ 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 = '';
|
||||
if (el && tx) {
|
||||
const tasksDue = (d.tasks_today || []).length + (d.tasks_overdue || []).length;
|
||||
const appts = (d.appts_today || []).length;
|
||||
if (!tasksDue && !appts) { el.style.display = 'none'; }
|
||||
else {
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Render planner mini panel
|
||||
const pEl = document.getElementById('planner-tasks');
|
||||
const badge = document.getElementById('planner-badge');
|
||||
if (!pEl) return;
|
||||
|
||||
const priClass = {urgent:'pri-urgent',high:'pri-high',normal:'pri-normal',low:'pri-low'};
|
||||
const fmtTime = s => { if(!s) return ''; const d=new Date(s); return d.toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',hour12:true}); };
|
||||
const fmtDate = s => { if(!s) return ''; const d=new Date(s+'T00:00:00'); return d.toLocaleDateString('en-US',{month:'short',day:'numeric'}); };
|
||||
|
||||
const tasks = [...(d.tasks_overdue||[]).map(t=>({...t,_overdue:true})), ...(d.tasks_today||[])];
|
||||
const appts = d.appts_today || [];
|
||||
|
||||
let html = '';
|
||||
if (!tasks.length && !appts.length) {
|
||||
html = '<div style="color:var(--text-dim);font-size:0.6rem;padding:4px 0">No tasks or appointments today.</div>';
|
||||
} else {
|
||||
if (appts.length) {
|
||||
html += '<div style="color:var(--cyan);font-size:0.55rem;letter-spacing:2px;margin-bottom:3px">TODAY'S SCHEDULE</div>';
|
||||
html += appts.map(a => `<div class="appt-row"><span class="appt-time">${fmtTime(a.start_at)}</span><span>${a.title}</span>${a.location?'<span style="color:var(--text-dim);font-size:0.55rem"> · '+a.location+'</span>':''}</div>`).join('');
|
||||
}
|
||||
if (tasks.length) {
|
||||
html += '<div style="color:var(--cyan);font-size:0.55rem;letter-spacing:2px;margin:5px 0 3px">TASKS DUE</div>';
|
||||
html += tasks.map(t => `<div class="task-item"><span class="pri-dot ${priClass[t.priority]||'pri-normal'}"></span><span style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${t.title}</span>${t._overdue?'<span style="color:#f66;font-size:0.55rem">OVERDUE</span>':''}</div>`).join('');
|
||||
}
|
||||
if (d.pending_count > tasks.length) {
|
||||
html += `<div style="color:var(--text-dim);font-size:0.55rem;padding:3px 0">${d.pending_count} pending total</div>`;
|
||||
}
|
||||
}
|
||||
pEl.innerHTML = html;
|
||||
const total = tasks.length + appts.length;
|
||||
if (badge) badge.textContent = total ? total + ' TODAY' : '';
|
||||
}
|
||||
|
||||
// ── ALERTS ────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user