← back to AbramsOS
views/household.ejs
86 lines
<%- include('partials/header', { title: 'Household' }) %>
<section class="page-head">
<div>
<h1>Household</h1>
<p class="subtle"><%= people.length %> <%= people.length === 1 ? 'person' : 'people' %> · add your spouse / dependents; their <a href="/medications">medications</a> and records attach to them.</p>
</div>
<div class="grid-controls">
<label>Sort
<select data-sort-for="person">
<option value="name-asc">Name A→Z</option>
<option value="relation-asc">Relation</option>
<option value="created-desc">Newest added</option>
</select>
</label>
<label>Density<input data-density-for="person" type="range" min="240" max="460" step="10" value="300"></label>
<button type="button" class="btn" id="toggleAdd">+ Add person</button>
</div>
</section>
<form id="addForm" class="add-form glass" hidden>
<div class="row">
<label>Full name*<input name="full_name" required placeholder="Jane Abrams"></label>
<label>Relation
<select name="relation"><% relations.forEach(r => { %><option value="<%= r %>" <%= r==='spouse'?'selected':'' %>><%= r %></option><% }) %></select>
</label>
<label>Nickname<input name="nickname" placeholder="optional"></label>
<label>DOB<input name="dob" type="date"></label>
</div>
<div class="row">
<label>Phone<input name="phone" placeholder="optional"></label>
<label>Email<input name="email" type="email" placeholder="optional"></label>
<label class="grow">Notes<input name="notes" placeholder="optional"></label>
</div>
<div class="row"><button type="submit" class="btn primary">Save person</button></div>
</form>
<% if (!people.length) { %>
<section class="empty glass"><p>No one added yet. Click <strong>+ Add person</strong> to add your spouse or a dependent.</p></section>
<% } %>
<section data-grid="person" class="purchase-grid" style="grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));">
<% people.forEach(p => { %>
<article class="purchase glass"
data-name="<%= (p.full_name||'').toLowerCase() %>"
data-relation="<%= p.relation %>"
data-created="<%= new Date(p.created_at).toISOString() %>">
<header>
<h3><%= p.full_name %><% if (p.nickname) { %> <span class="subtle">(<%= p.nickname %>)</span><% } %></h3>
<span class="chip cat"><%= p.relation %></span>
</header>
<dl class="meta">
<% if (p.dob) { %><dt>DOB</dt><dd><%= p.dob %></dd><% } %>
<% if (p.phone) { %><dt>Phone</dt><dd><%= p.phone %></dd><% } %>
<% if (p.email) { %><dt>Email</dt><dd><%= p.email %></dd><% } %>
<% if (p.notes) { %><dt>Notes</dt><dd><%= p.notes %></dd><% } %>
<dt>Meds</dt><dd><%= p.med_count %> active <% if (p.med_count) { %><a href="/medications">view →</a><% } %></dd>
</dl>
<div class="when" title="<%= new Date(p.created_at).toISOString() %>">🕓 added <%= new Date(p.created_at).toLocaleString(undefined, { year:'numeric', month:'short', day:'numeric', hour:'numeric', minute:'2-digit' }) %></div>
<div class="card-actions">
<button class="btn small danger" data-action="delete" data-id="<%= p.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/people', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
if (res.ok) location.reload(); else alert('Save failed: ' + (await res.text()));
});
document.querySelectorAll('[data-action="delete"]').forEach((btn) => {
btn.addEventListener('click', async () => {
if (!confirm('Delete this person? (their medications stay but unlink)')) return;
const res = await fetch(`/api/people/${btn.dataset.id}/delete`, { method: 'POST' });
if (res.ok) location.reload(); else alert('Delete failed');
});
});
</script>
<%- include('partials/footer', { gridScript: true }) %>