From 6b4317361952532e4fa757bd04a5bfa9c1f6d3ef Mon Sep 17 00:00:00 2001 From: desusaiteja Date: Mon, 30 Mar 2026 16:28:49 -0700 Subject: [PATCH 1/3] fix: action buttons and family contact link in seniors table - Fix Graph/Insights/Call action buttons that were broken due to JSON.stringify producing double-quoted strings inside double-quoted HTML onclick attributes; switched to single-quoted safeKey escaping - Fix family contact link by storing the senior's name alongside the contact in _contactMap and displaying it in the contact modal Made-with: Cursor --- frontend/app.js | 91 ++++++++++++++++++++++++++++++++++++++++----- frontend/index.html | 26 ++++++++++++- 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index 446bdcb..f20e5ac 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -20,6 +20,9 @@ function showPage(id) { if (id === 'voice') loadRecentCalls(); } +// ── Contact lookup map (keyed by senior phone) ── +const _contactMap = {}; + // ── Seniors ── async function loadSeniors() { try { @@ -30,27 +33,57 @@ async function loadSeniors() { latest.forEach(c => { latestMap[c.senior_phone] = c; }); const alerts = await fetchJSON('/api/alerts'); + let wellnessMap = {}; + try { + const w = await fetchJSON('/api/seniors/wellness-overview?days_threshold=7'); + document.getElementById('stat-at-risk').textContent = w.at_risk_count ?? '—'; + (w.seniors || []).forEach(x => { wellnessMap[x.phone] = x; }); + } catch (e) { + document.getElementById('stat-at-risk').textContent = '—'; + } + document.getElementById('stat-total').textContent = seniors.length; document.getElementById('stat-alerts').textContent = alerts.length; const scores = latest.filter(c => c.wellness_score > 0).map(c => c.wellness_score); document.getElementById('stat-avg').textContent = scores.length ? (scores.reduce((a,b)=>a+b,0)/scores.length).toFixed(1)+'/10' : '—'; const tbody = document.getElementById('seniors-tbody'); - if (!seniors.length) { tbody.innerHTML = 'No seniors. Click "+ Add Senior".'; return; } + if (!seniors.length) { tbody.innerHTML = 'No seniors. Click "+ Add Senior".'; return; } tbody.innerHTML = seniors.map(s => { const l = latestMap[s.phone]; const mood = l?.mood || 'unknown'; let sc = mood === 'concerning' ? 'danger' : mood === 'sad' ? 'warning' : l ? 'good' : 'neutral'; let st = mood === 'concerning' ? 'Concerning' : mood === 'sad' ? 'Needs attention' : l ? 'OK' : 'Pending'; + const w = wellnessMap[s.phone]; + if (w && w.at_risk) { sc = 'warning'; st = 'Needs outreach'; } const score = l?.wellness_score || '—'; + let checkinHtml = '—'; + if (w) { + if (w.last_checkin_timestamp == null) { + checkinHtml = ' None yet'; + } else if (w.at_risk) { + checkinHtml = ` ${w.days_since_checkin}d`; + } else { + checkinHtml = `${w.days_since_checkin}d ago`; + } + } + const fc = (s.emergency_contacts && s.emergency_contacts[0]) ? s.emergency_contacts[0] : null; + if (fc) _contactMap[s.phone] = { ...fc, seniorName: s.name }; + const safeKey = s.phone.replace(/'/g, "\\'"); + const safeName = s.name.replace(/'/g, "\\'").replace(/\\/g, '\\\\'); + const contactHtml = (fc && fc.phone) + ? ` - - + ${checkinHtml} + ${contactHtml} + ${s.medications.join(', ') || '—'} + + + `; }).join(''); @@ -63,7 +96,44 @@ async function loadSeniors() { `
${a.severity}${a.message}
` ).join(''); } else { banner.style.display = 'none'; badge.style.display = 'none'; } - } catch(e) { console.error(e); document.getElementById('seniors-tbody').innerHTML = 'Failed to load. Is the server running?'; } + } catch(e) { console.error(e); document.getElementById('seniors-tbody').innerHTML = 'Failed to load. Is the server running?'; } +} + +// ── Contact Details Modal ── +function showContactDetails(seniorPhone) { + const fc = _contactMap[seniorPhone] || {}; + const seniorNameEl = document.getElementById('contact-modal-senior-name'); + if (seniorNameEl) seniorNameEl.textContent = fc.seniorName || 'this senior'; + document.getElementById('contact-modal-name').textContent = fc.name || 'Family Contact'; + document.getElementById('contact-modal-phone').textContent = fc.phone || '—'; + document.getElementById('contact-modal-relation').textContent = fc.relation || fc.relationship || '—'; + const callBtn = document.getElementById('contact-modal-call'); + callBtn.href = fc.phone ? `tel:${String(fc.phone).replace(/\s/g, '')}` : '#'; + callBtn.style.display = fc.phone ? 'inline-flex' : 'none'; + document.getElementById('contact-modal').style.display = 'flex'; +} +function closeContactModal() { document.getElementById('contact-modal').style.display = 'none'; } + +// ── Navigate to page and pre-select a senior ── +async function goToPage(page, phone) { + showPage(page); + const selectMap = { + graph: 'graph-senior-select', + insights: 'insight-senior-select', + voice: 'voice-senior-select', + }; + const selId = selectMap[page]; + if (!selId) return; + // Populate options fresh and set value — avoids async race in showPage + try { + const seniors = await fetchJSON('/api/seniors'); + const sel = document.getElementById(selId); + if (!sel) return; + sel.innerHTML = '' + + seniors.map(s => ``).join(''); + sel.value = phone; + } catch(e) {} + if (page === 'graph') loadGraph(); } // ── Interactive Graph View (vis.js) ── @@ -318,10 +388,13 @@ async function addSenior(e) { // ── Alerts Page ── async function loadAlertsPage() { try { - const alerts = await fetchJSON('/api/alerts?acknowledged=true'); + const history = document.getElementById('alerts-show-history')?.checked; + const url = history ? '/api/alerts?acknowledged=true' : '/api/alerts'; + const alerts = await fetchJSON(url); const el = document.getElementById('alerts-full-list'); - if (!alerts.length) { el.innerHTML = '

No alerts.

'; return; } - el.innerHTML = alerts.map(a => `
${a.severity} ${a.alert_type.replace(/_/g,' ')}
${a.message}
${new Date(a.timestamp).toLocaleString()}
${!a.acknowledged ? `` : ''}
`).join(''); + const hint = history ? '' : '

Showing active alerts only. Enable Show acknowledged history to see past items.

'; + if (!alerts.length) { el.innerHTML = hint + '

No alerts.

'; return; } + el.innerHTML = hint + alerts.map(a => `
${a.severity} ${a.alert_type.replace(/_/g,' ')}${a.acknowledged ? ' (acknowledged)' : ''}
${a.message}
${new Date(a.timestamp).toLocaleString()}
${!a.acknowledged ? `` : ''}
`).join(''); } catch(e) { console.error(e); } } async function ackAlert(id) { await fetchJSON(`/api/alerts/${encodeURIComponent(id)}/acknowledge`, { method: 'PUT' }); loadAlertsPage(); loadSeniors(); } diff --git a/frontend/index.html b/frontend/index.html index f58bef7..c204ab2 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -34,12 +34,13 @@

Seniors

Total seniors
0
Active alerts
0
+
Needs outreach
0
Avg. wellness
—/10
Graph nodes
- -
NAMESTATUSSCOREMEDICATIONSACTIONS
Loading...
+ +
NAMESTATUSSCORECHECK-INFAMILYMEDICATIONSACTIONS
Loading...
@@ -126,6 +127,12 @@

Seniors

+
+ +
@@ -141,6 +148,21 @@

Seniors

+ + +