← back to AbramsOS
views/medications.ejs
127 lines
<%- include('partials/header', { title: 'Medications' }) %>
<section class="page-head">
<div>
<h1>Medications</h1>
<p class="subtle"><%= meds.length %> tracked · recall checks query the <strong>FDA (openFDA)</strong> drug-enforcement database by drug name.</p>
</div>
<div class="grid-controls">
<label>Sort
<select data-sort-for="med">
<option value="name-asc">Name A→Z</option>
<option value="recall-desc">Recalls first</option>
<option value="created-desc">Newest added</option>
</select>
</label>
<label>Density<input data-density-for="med" type="range" min="240" max="480" step="10" value="340"></label>
<button type="button" class="btn" id="checkAll">🔎 Check all recalls</button>
<button type="button" class="btn" id="toggleAdd">+ Add medication</button>
</div>
</section>
<section class="glass" style="padding:10px 14px;margin:0 0 14px;font-size:12px;color:var(--text-dim)">
🔒 <strong>Health data.</strong> Medications are stored locally in your own AbramsOS DB (single-user), not shared. Recall checks send only the drug name to the FDA's public API — no identity. Entering a med records your consent (audit-logged).
</section>
<form id="addForm" class="add-form glass" hidden>
<div class="row">
<label>Medication name*<input name="name" required placeholder="Lipitor"></label>
<label>Generic<input name="generic_name" placeholder="atorvastatin"></label>
<label>For
<select name="person_id">
<option value="">Me (account owner)</option>
<% people.forEach(p => { %><option value="<%= p.id %>"><%= p.full_name %> (<%= p.relation %>)</option><% }) %>
</select>
</label>
</div>
<div class="row">
<label>Dosage<input name="dosage" placeholder="20 mg"></label>
<label>Frequency<input name="frequency" placeholder="once daily"></label>
<label>Form<input name="form" placeholder="tablet"></label>
<label>Start date<input name="start_date" type="date"></label>
</div>
<div class="row">
<label>Prescriber<input name="prescriber" placeholder="Dr. …"></label>
<label>Pharmacy<input name="pharmacy" placeholder="optional"></label>
<label>Rx #<input name="rx_number" placeholder="optional"></label>
<label class="grow">Notes<input name="notes" placeholder="optional"></label>
</div>
<div class="row"><button type="submit" class="btn primary">Save medication</button></div>
</form>
<% if (!meds.length) { %>
<section class="empty glass"><p>No medications yet. Click <strong>+ Add medication</strong> — then run a recall check.</p></section>
<% } %>
<section data-grid="med" class="purchase-grid" style="grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));">
<% meds.forEach(m => { %>
<article class="purchase glass<%= m.recall_count ? ' overdue' : '' %><%= m.is_active ? '' : ' inactive' %>"
data-name="<%= (m.name||'').toLowerCase() %>"
data-recall="<%= m.recall_count || 0 %>"
data-created="<%= new Date(m.created_at).toISOString() %>">
<header>
<h3><%= m.name %><% if (m.generic_name) { %> <span class="subtle">(<%= m.generic_name %>)</span><% } %></h3>
<% if (m.recall_count) { %><span class="badge red"><%= m.recall_count %> recall<%= m.recall_count>1?'s':'' %></span><% } else if (m.last_checked) { %><span class="badge">✓ clear</span><% } %>
</header>
<div class="amount" style="font-size:14px;font-weight:500">
<%= m.dosage || '' %><% if (m.frequency) { %> · <%= m.frequency %><% } %>
<% if (!m.is_active) { %><span class="subtle"> · inactive</span><% } %>
</div>
<dl class="meta">
<dt>For</dt><dd><%= m.person_name ? m.person_name + ' (' + m.person_relation + ')' : 'Me' %></dd>
<% if (m.form) { %><dt>Form</dt><dd><%= m.form %></dd><% } %>
<% if (m.prescriber) { %><dt>Prescriber</dt><dd><%= m.prescriber %></dd><% } %>
<% if (m.pharmacy) { %><dt>Pharmacy</dt><dd><%= m.pharmacy %></dd><% } %>
<% if (m.last_checked) { %><dt>Checked</dt><dd><%= new Date(m.last_checked).toLocaleDateString() %></dd><% } %>
</dl>
<% if (m.recalls && m.recalls.length) { %>
<div class="recalls">
<% m.recalls.forEach(rc => { %>
<div class="recall-item">
<strong><%= rc.classification || 'Recall' %></strong> <span class="subtle"><%= rc.status || '' %><% if (rc.recall_initiation_date) { %> · <%= rc.recall_initiation_date %><% } %></span>
<div class="subtle"><%= (rc.reason || '').slice(0,180) %></div>
<% if (rc.url) { %><a href="<%= rc.url %>" target="_blank" rel="noopener noreferrer">FDA notice →</a><% } %>
</div>
<% }) %>
</div>
<% } %>
<div class="when" title="<%= new Date(m.created_at).toISOString() %>">🕓 added <%= new Date(m.created_at).toLocaleString(undefined, { year:'numeric', month:'short', day:'numeric', hour:'numeric', minute:'2-digit' }) %></div>
<div class="card-actions">
<button class="btn small" data-action="check" data-id="<%= m.id %>">Check recalls</button>
<button class="btn small danger" data-action="delete" data-id="<%= m.id %>">Delete</button>
</div>
</article>
<% }) %>
</section>
<script>
const $ = (s, r = document) => r.querySelector(s);
$('#toggleAdd')?.addEventListener('click', () => { const f = $('#addForm'); f.hidden = !f.hidden; });
$('#addForm')?.addEventListener('submit', async (e) => {
e.preventDefault();
const body = Object.fromEntries(new FormData(e.target).entries());
const res = await fetch('/api/medications', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
if (res.ok) location.reload(); else alert('Save failed: ' + (await res.text()));
});
$('#checkAll')?.addEventListener('click', async () => {
$('#checkAll').textContent = 'Checking FDA…'; $('#checkAll').disabled = true;
const res = await fetch('/api/medications/check-all-recalls', { method: 'POST' });
const j = await res.json().catch(() => ({}));
if (res.ok) { const hits = (j.results || []).reduce((s, r) => s + r.hits, 0); alert(`Checked ${j.checked} med(s) against the FDA. ${hits} recall record(s) matched.`); location.reload(); }
else { alert('Check failed: ' + (j.error || res.status)); $('#checkAll').textContent = '🔎 Check all recalls'; $('#checkAll').disabled = false; }
});
document.querySelectorAll('[data-action]').forEach((btn) => {
btn.addEventListener('click', async () => {
const id = btn.dataset.id, action = btn.dataset.action;
if (action === 'delete') { if (!confirm('Delete this medication?')) return; const r = await fetch(`/api/medications/${id}/delete`, { method: 'POST' }); return r.ok ? location.reload() : alert('Delete failed'); }
btn.textContent = 'Checking…'; btn.disabled = true;
const r = await fetch(`/api/medications/${id}/check-recalls`, { method: 'POST' });
const j = await r.json().catch(() => ({}));
if (r.ok) { alert(`${j.name}: ${j.hits} FDA recall record(s) found.`); location.reload(); }
else { alert('Check failed: ' + (j.error || r.status)); btn.textContent = 'Check recalls'; btn.disabled = false; }
});
});
</script>
<%- include('partials/footer', { gridScript: true }) %>