← back to AbramsOS

views/reorders.ejs

116 lines

<%- include('partials/header', { title: 'Reorders' }) %>

<section class="page-head">
  <div>
    <h1>Reorders &amp; 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 &amp; 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 }) %>