← back to AbramsOS
views/warranties.ejs
106 lines
<%- include('partials/header', { title: 'Warranties' }) %>
<section class="page-head">
<div>
<h1>Warranties & Guarantees</h1>
<p class="subtle"><%= warranties.length %> tracked · manual entries live alongside anything the receipt reader finds · expiry windows flow into <a href="/deadlines">Deadlines</a>.</p>
</div>
<div class="grid-controls">
<label>Sort
<select data-sort-for="warranty">
<option value="due-asc">Expires soonest</option>
<option value="due-desc">Expires latest</option>
<option value="provider-asc">Provider A→Z</option>
<option value="type-asc">Type</option>
<option value="created-desc">Newest added</option>
</select>
</label>
<label>Density
<input data-density-for="warranty" type="range" min="240" max="460" step="10" value="320">
</label>
<button type="button" class="btn" id="toggleAdd">+ Add warranty</button>
</div>
</section>
<form id="addForm" class="add-form glass" hidden>
<div class="row">
<label>Provider / item*<input name="provider_name" required placeholder="LG fridge / AppleCare"></label>
<label>Type
<select name="commitment_type">
<% types.forEach(t => { %><option value="<%= t %>" <%= t==='warranty'?'selected':'' %>><%= t %></option><% }) %>
</select>
</label>
<label>Expires<input name="refund_window_ends_at" type="date"></label>
</div>
<div class="row">
<label class="grow">What it covers<input name="guarantee_text" placeholder="1-yr parts & labor; extended to 3-yr via AppleCare"></label>
<label>Window (days)<input name="window_days" type="number" placeholder="365"></label>
</div>
<div class="row">
<label class="grow">Promised outcome<input name="promised_outcome" placeholder="free repair or replacement"></label>
<label class="grow">Conditions / notes<input name="conditions" placeholder="keep receipt; excludes accidental damage"></label>
</div>
<div class="row"><button type="submit" class="btn primary">Save warranty</button></div>
</form>
<% if (!warranties.length) { %>
<section class="empty glass"><p>No warranties yet. Click <strong>+ Add warranty</strong> to log one.</p></section>
<% } %>
<section data-grid="warranty" class="purchase-grid" style="grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));">
<% warranties.forEach(w => { %>
<article class="purchase glass<%= w.is_expired ? ' overdue' : '' %>"
data-provider="<%= (w.provider_name||'').toLowerCase() %>"
data-type="<%= w.commitment_type || '' %>"
data-due="<%= w.refund_window_ends_at ? new Date(w.refund_window_ends_at).toISOString().slice(0,10) : '' %>"
data-created="<%= new Date(w.created_at).toISOString() %>">
<header>
<h3><%= w.provider_name || 'Warranty' %></h3>
<span class="chip cat"><%= w.commitment_type || 'warranty' %></span>
</header>
<div class="amount" style="font-size:14px;font-weight:500">
<% if (w.refund_window_ends_at) { %>
expires <%= new Date(w.refund_window_ends_at).toLocaleDateString() %>
<% if (w.is_expired) { %><span class="badge red">expired</span>
<% } else if (w.is_expiring_soon) { %><span class="badge amber">in <%= w.days_until %>d</span>
<% } else { %><span class="badge">in <%= w.days_until %>d</span><% } %>
<% } else if (w.window_days) { %><span class="subtle"><%= w.window_days %>-day window</span>
<% } else { %><span class="subtle">no expiry set</span><% } %>
</div>
<dl class="meta">
<% if (w.guarantee_text) { %><dt>Covers</dt><dd><%= w.guarantee_text %></dd><% } %>
<% if (w.promised_outcome) { %><dt>Outcome</dt><dd><%= w.promised_outcome %></dd><% } %>
<% if (w.conditions) { %><dt>Notes</dt><dd><%= w.conditions %></dd><% } %>
<% if (w.source && w.source !== 'manual') { %><dt>Source</dt><dd><%= w.source %><% if (w.confidence != null) { %> (<%= Math.round(Number(w.confidence)*100) %>%)<% } %></dd><% } %>
</dl>
<div class="when" title="<%= new Date(w.created_at).toISOString() %>">🕓 added <%= new Date(w.created_at).toLocaleString(undefined, { year:'numeric', month:'short', day:'numeric', hour:'numeric', minute:'2-digit' }) %></div>
<div class="card-actions">
<% if (w.commitment_type !== 'return') { %><a class="btn small" href="/claims">File claim →</a><% } %>
<button class="btn small danger" data-action="delete" data-id="<%= w.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/warranties', { 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 warranty?')) return;
const res = await fetch(`/api/warranties/${btn.dataset.id}/delete`, { method: 'POST' });
if (res.ok) location.reload(); else alert('Delete failed');
});
});
</script>
<%- include('partials/footer', { gridScript: true }) %>