← back to AbramsOS
views/reorders.ejs
116 lines
<%- include('partials/header', { title: 'Reorders' }) %>
<section class="page-head">
<div>
<h1>Reorders & Savings</h1>
<p class="subtle">
<%= items.length %> items you buy a lot ·
<% if (totalSavings) { %>USD <%= totalSavings.toFixed(2) %> saved by buying at your best-known price<% } else { %>log a typical & best price to see savings<% } %>
</p>
</div>
<div class="grid-controls">
<label>Sort
<select data-sort-for="reorder">
<option value="due-asc">Reorder soonest</option>
<option value="savings-desc">Biggest savings</option>
<option value="name-asc">Name A→Z</option>
<option value="merchant-asc">Merchant A→Z</option>
<option value="created-desc">Newest added</option>
</select>
</label>
<label>Density
<input data-density-for="reorder" type="range" min="240" max="460" step="10" value="320">
</label>
<button type="button" class="btn" id="toggleAdd">+ Add item</button>
</div>
</section>
<form id="addForm" class="add-form glass" hidden>
<div class="row">
<label>Name*<input name="name" required placeholder="Paper towels"></label>
<label>Merchant<input name="merchant" placeholder="Costco / Amazon"></label>
<label>Category<input name="category" placeholder="household"></label>
<label>Unit<input name="unit" placeholder="12-pack"></label>
</div>
<div class="row">
<label>Typical price<input name="typical_price" type="number" step="0.01" placeholder="0.00"></label>
<label>Best price<input name="best_price" type="number" step="0.01" placeholder="0.00"></label>
<label>Best price source<input name="best_price_source" placeholder="Costco 6/26"></label>
</div>
<div class="row">
<label>Reorder every (days)<input name="reorder_cadence_days" type="number" placeholder="30"></label>
<label>Last ordered<input name="last_ordered_at" type="date"></label>
<label>Link<input name="url" type="url" placeholder="https://…"></label>
<label class="grow">Notes<input name="notes" placeholder="optional"></label>
</div>
<div class="row"><button type="submit" class="btn primary">Save item</button></div>
</form>
<% if (!items.length) { %>
<section class="empty glass"><p>Nothing tracked yet. Click <strong>+ Add item</strong> to log something you reorder.</p></section>
<% } %>
<section data-grid="reorder" class="purchase-grid" style="grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));">
<% items.forEach(it => { %>
<article class="purchase glass<%= it.is_due ? ' overdue' : '' %>"
data-name="<%= (it.name||'').toLowerCase() %>"
data-merchant="<%= (it.merchant||'').toLowerCase() %>"
data-savings="<%= it.savings || 0 %>"
data-due="<%= it.next_due_date || '' %>"
data-created="<%= new Date(it.created_at).toISOString() %>">
<header>
<h3><%= it.name %></h3>
<% if (it.savings) { %><span class="chip save">save <%= it.currency %> <%= it.savings.toFixed(2) %></span><% } %>
</header>
<div class="amount">
<% if (it.best_price) { %><%= it.currency %> <%= Number(it.best_price).toFixed(2) %> <span class="subtle">best</span>
<% } else if (it.typical_price) { %><%= it.currency %> <%= Number(it.typical_price).toFixed(2) %> <span class="subtle">typical</span>
<% } else { %><span class="subtle">no price yet</span><% } %>
<% if (it.unit) { %><span class="subtle">/ <%= it.unit %></span><% } %>
</div>
<dl class="meta">
<% if (it.merchant) { %><dt>Buy from</dt><dd><%= it.merchant %></dd><% } %>
<% if (it.typical_price && it.best_price) { %><dt>Typical</dt><dd><%= it.currency %> <%= Number(it.typical_price).toFixed(2) %></dd><% } %>
<% if (it.best_price_source) { %><dt>Best at</dt><dd><%= it.best_price_source %></dd><% } %>
<dt>Reorder</dt>
<dd>
<% if (it.next_due_date) { %>
<%= it.next_due_date %>
<% if (it.is_due) { %><span class="badge amber">due now</span><% } else { %><span class="badge">in <%= it.days_until %>d</span><% } %>
<% } else if (it.reorder_cadence_days) { %>every <%= it.reorder_cadence_days %>d<% } else { %><span class="subtle">as needed</span><% } %>
</dd>
<% if (it.url) { %><dt>Link</dt><dd><a href="<%= it.url %>" target="_blank" rel="noopener noreferrer">buy →</a></dd><% } %>
</dl>
<div class="when" title="<%= new Date(it.created_at).toISOString() %>">🕓 added <%= new Date(it.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="ordered" data-id="<%= it.id %>">Ordered today</button>
<button class="btn small danger" data-action="delete" data-id="<%= it.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/reorders', { 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]').forEach((btn) => {
btn.addEventListener('click', async () => {
const id = btn.dataset.id, action = btn.dataset.action;
if (action === 'delete' && !confirm('Delete this item?')) return;
const url = action === 'ordered' ? `/api/reorders/${id}/ordered` : `/api/reorders/${id}/delete`;
const res = await fetch(url, { method: 'POST' });
if (res.ok) location.reload(); else alert('Action failed');
});
});
</script>
<%- include('partials/footer', { gridScript: true }) %>