← back to AbramsOS

views/assets.ejs

117 lines

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

<% const fmt = (d) => new Date(d).toLocaleString(undefined, { year:'numeric', month:'short', day:'numeric', hour:'numeric', minute:'2-digit' });
   const money = (n) => (n == null ? '—' : '$' + Number(n).toLocaleString(undefined, { maximumFractionDigits: 0 }));
   const kindIcon = { property:'🏠', vehicle:'🚗', account:'🏦', valuable:'💎', other:'📦' }; %>

<section class="page-head">
  <div>
    <h1>Assets &amp; Net Worth</h1>
    <p class="subtle">
      <% if (netWorth) { %>Net worth (entered): <strong><%= money(netWorth) %></strong> · <% } %>
      <%= assets.length %> asset<%= assets.length===1?'':'s' %> · values you enter
    </p>
  </div>
  <div class="grid-controls">
    <button type="button" class="btn primary" id="toggleAdd">+ Add asset</button>
  </div>
</section>

<% if (Object.keys(byCategory).length) { %>
  <section class="stat-row">
    <% Object.entries(byCategory).forEach(([c, v]) => { %>
      <div class="stat glass"><div class="num" style="font-size:1.4rem"><%= money(v) %></div><div class="label"><%= c.replace(/_/g,' ') %></div></div>
    <% }) %>
  </section>
<% } %>

<form id="addForm" class="add-form glass" hidden>
  <div class="row">
    <label>Type
      <select name="kind" id="kindSel">
        <option value="property">🏠 Property</option>
        <option value="vehicle">🚗 Vehicle</option>
        <option value="account">🏦 Account</option>
        <option value="valuable">💎 Valuable</option>
        <option value="other">📦 Other</option>
      </select>
    </label>
    <label class="grow">Name*<input name="name" required placeholder="Primary residence"></label>
    <label>Category<input name="category" placeholder="real_estate" list="catList"></label>
    <datalist id="catList"><option value="real_estate"><option value="vehicle"><option value="cash"><option value="investment"><option value="retirement"><option value="other"></datalist>
  </div>
  <div class="row addr-row">
    <label class="grow">Address<input name="address" placeholder="123 Main St, City, ST 91436"></label>
  </div>
  <div class="row">
    <label>Current value ($)<input name="current_value" type="number" step="0.01" placeholder="0"></label>
    <label>As of<input name="valued_at" type="date"></label>
  </div>
  <div class="row">
    <label class="grow">Details<input name="details" placeholder="4 bed / 3 bath, 2,400 sqft"></label>
  </div>
  <div class="row">
    <label class="grow">Notes<input name="notes" placeholder="optional"></label>
    <button type="submit" class="btn primary">Save asset</button>
  </div>
</form>

<% if (!assets.length) { %>
  <section class="empty glass">
    <p>No assets yet. Click <strong>+ Add asset</strong> to enter your home (by address), a vehicle, or an account with its current value and a finance category. Property assets link to <a href="/neighborhood">Neighborhood Watch</a>.</p>
  </section>
<% } %>

<section class="purchase-grid" style="grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));">
  <% assets.forEach(a => { %>
    <article class="purchase glass" data-id="<%= a.id %>">
      <header>
        <h3><%= kindIcon[a.kind] || '📦' %> <%= a.name %></h3>
        <% if (a.category) { %><span class="chip" style="background:rgba(128,128,128,.15)"><%= a.category.replace(/_/g,' ') %></span><% } %>
      </header>
      <div class="amount"><%= money(a.current_value) %><% if (a.valued_at) { %> <span class="subtle">as of <%= new Date(a.valued_at).toLocaleDateString() %></span><% } %></div>
      <dl class="meta">
        <% if (a.address) { %><dt>Address</dt><dd><%= a.address %></dd><% } %>
        <% if (a.details) { %><dt>Details</dt><dd><%= a.details %></dd><% } %>
        <% if (a.notes) { %><dt>Notes</dt><dd class="subtle"><%= a.notes %></dd><% } %>
      </dl>
      <footer class="card-foot">
        <span class="when" title="<%= new Date(a.created_at).toISOString() %>">🕓 <%= fmt(a.created_at) %></span>
        <span class="actions">
          <% if (a.kind === 'property' && a.address) { %>
            <button type="button" class="btn tiny" data-estimate="<%= a.id %>" title="<%= valuationConfigured ? 'Estimate value via RentCast' : 'Add RENTCAST_API_KEY to enable' %>">$ Estimate</button>
            <a class="btn tiny" href="/neighborhood?asset=<%= a.id %>">🛡 Watch</a>
          <% } %>
          <button type="button" class="btn tiny ghost" data-del="<%= a.id %>">Remove</button>
        </span>
      </footer>
    </article>
  <% }) %>
</section>

<script>
  window.__csrf = '<%= csrfToken %>';
  async function post(url, body){ const r=await fetch(url,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(Object.assign({_csrf:window.__csrf},body||{}))}); return r.json(); }
  const kindSel=document.getElementById('kindSel');
  function syncAddr(){ document.querySelector('.addr-row').style.display = (kindSel.value==='property')?'':'none'; }
  kindSel?.addEventListener('change', syncAddr); if(kindSel) syncAddr();
  document.getElementById('toggleAdd')?.addEventListener('click', ()=>{ const f=document.getElementById('addForm'); f.hidden=!f.hidden; });
  document.getElementById('addForm')?.addEventListener('submit', async (e)=>{
    e.preventDefault(); const fd=Object.fromEntries(new FormData(e.target).entries());
    const out=await post('/api/assets', fd); if(out.ok) location.reload(); else alert(out.error||'error');
  });
  document.querySelectorAll('[data-del]').forEach(b=>b.addEventListener('click', async (e)=>{
    if(!confirm('Remove this asset?')) return;
    await post('/api/assets/'+e.currentTarget.dataset.del+'/delete'); e.currentTarget.closest('article').remove();
  }));
  document.querySelectorAll('[data-estimate]').forEach(b=>b.addEventListener('click', async (e)=>{
    const btn=e.currentTarget; btn.disabled=true; const t=btn.textContent; btn.textContent='…';
    const out=await post('/api/assets/'+btn.dataset.estimate+'/estimate');
    if(out.ok){ btn.textContent='$'+out.price.toLocaleString(); setTimeout(()=>location.reload(),900); }
    else if(out.reason==='no_key'){ alert('Add RENTCAST_API_KEY (free at rentcast.io) via the secrets skill to enable estimates.'); btn.textContent=t; btn.disabled=false; }
    else { alert('Estimate unavailable: '+(out.reason||out.error||'?')); btn.textContent=t; btn.disabled=false; }
  }));
</script>

<%- include('partials/footer', {}) %>