[object Object]

← back to Rentv 2026

auto-save: 2026-07-31T16:29:30 (1 files) — public/watchlist.html

b4f527159d78534fda955b47c88b0735d4eb368f · 2026-07-31 16:29:31 -0700 · Steve Abrams

Files touched

Diff

commit b4f527159d78534fda955b47c88b0735d4eb368f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 31 16:29:31 2026 -0700

    auto-save: 2026-07-31T16:29:30 (1 files) — public/watchlist.html
---
 public/watchlist.html | 41 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 38 insertions(+), 3 deletions(-)

diff --git a/public/watchlist.html b/public/watchlist.html
index 33e2bc90..43e47425 100644
--- a/public/watchlist.html
+++ b/public/watchlist.html
@@ -46,7 +46,11 @@
   .bar .acts{display:flex;gap:10px}
   .bar .acts button,.bar .acts a{border:1.5px solid var(--line);background:#fff;border-radius:8px;padding:8px 14px;font:700 12.5px 'Inter';color:var(--ink);cursor:pointer}
   .bar .acts .alert{background:var(--red);border-color:var(--red);color:#fff}
-  .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;padding:14px 0 60px}
+  .grid{display:grid;grid-template-columns:repeat(var(--cols,3),1fr);gap:18px;padding:14px 0 60px}
+  .wbar{display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin:0 0 14px}
+  .wctl{display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--sub)}
+  .wctl select{font:600 13px 'Inter',system-ui,sans-serif;text-transform:none;letter-spacing:0;color:var(--ink);padding:7px 10px;border:1px solid var(--line);border-radius:6px;background:#fff;cursor:pointer}
+  .wctl.dens input[type=range]{width:110px;accent-color:var(--red);cursor:pointer}
   .card{background:#fff;border:1px solid var(--line);border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
   .card .im{height:132px;background:#12161a center/cover;display:block}
   .card .bd{padding:13px 15px 15px;display:flex;flex-direction:column;flex:1}
@@ -60,7 +64,7 @@
   .empty{grid-column:1/-1;padding:50px 0;text-align:center;color:var(--sub)}
   .empty h3{font-family:'Playfair Display',serif;font-size:24px;color:var(--ink);margin-bottom:6px}
   @media(max-width:900px){.grid{grid-template-columns:1fr 1fr}}
-  @media(max-width:600px){.grid{grid-template-columns:1fr}}
+  @media(max-width:600px){.grid{grid-template-columns:1fr!important}}
 </style></head><body>
 
 <header class="top">
@@ -109,12 +113,38 @@
     </div>
   </div>
 
+  <div class="wbar">
+    <label class="wctl"><span>Sort</span>
+      <select id="wsort">
+        <option value="newest">Newest</option>
+        <option value="price-desc">Price ↓</option>
+        <option value="price-asc">Price ↑</option>
+        <option value="type">Property Type A→Z</option>
+        <option value="loc">Location A→Z</option>
+        <option value="title">Title A→Z</option>
+      </select>
+    </label>
+    <label class="wctl dens"><span>Density</span>
+      <input id="wdens" type="range" min="2" max="5" step="1" value="3" aria-label="Grid density (columns)">
+    </label>
+  </div>
+
   <section class="grid" id="grid"></section>
 </main>
 
 <script>
 const $=id=>document.getElementById(id);
 const esc=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
+// sort + density (persisted) — HARD RULE: every data grid gets both
+let wsort=localStorage.getItem('rentv.watchlist.sort')||'newest';
+function amtNum(d){const s=String(d.amount_label||d.amount||'').replace(/[,\s$]/g,'');const m=s.match(/([\d.]+)\s*([KMB])?/i);if(!m)return -1;let n=parseFloat(m[1]);if(isNaN(n))return -1;const u=(m[2]||'').toUpperCase();if(u==='K')n*=1e3;else if(u==='M')n*=1e6;else if(u==='B')n*=1e9;return n;}
+function sortRows(rows){const r=rows.slice();
+  if(wsort==='price-desc')r.sort((a,b)=>amtNum(b)-amtNum(a));
+  else if(wsort==='price-asc')r.sort((a,b)=>{const x=amtNum(a),y=amtNum(b);if(x<0)return 1;if(y<0)return -1;return x-y;});
+  else if(wsort==='type')r.sort((a,b)=>(a.property_type||'').localeCompare(b.property_type||''));
+  else if(wsort==='loc')r.sort((a,b)=>[a.city,a.state].filter(Boolean).join(', ').localeCompare([b.city,b.state].filter(Boolean).join(', ')));
+  else if(wsort==='title')r.sort((a,b)=>(a.title||'').localeCompare(b.title||''));
+  return r;} // 'newest' = natural feed order
 const drawer=$('drawer'),scrim=$('scrim');
 $('burger').onclick=()=>{drawer.classList.add('open');scrim.classList.add('open')};
 const close=()=>{drawer.classList.remove('open');scrim.classList.remove('open')};
@@ -154,7 +184,7 @@ function render(){
   chips('fTxn','txns',['Sale','Financing','Lease','Development'].filter(t=>uniq('txn_type').includes(t)));
   chips('fState','states',uniq('state').sort());
   const any=sel.types.length||sel.txns.length||sel.states.length;
-  const list=ALL.filter(match);
+  const list=sortRows(ALL.filter(match));
   $('cnt').innerHTML=any?`<b>${list.length}</b> deal${list.length===1?'':'s'} match your watchlist`:`Showing all <b>${list.length}</b> tracked deals — pin filters above to narrow`;
   if(!list.length){ $('grid').innerHTML=`<div class="empty"><h3>No deals match yet</h3><div>Loosen a filter, or <a href="/subscribe" style="color:var(--red);font-weight:700">get alerted</a> when one lands.</div></div>`; return; }
   $('grid').innerHTML=list.map(d=>`<div class="card">
@@ -169,6 +199,11 @@ $('clear').onclick=()=>{sel={types:[],txns:[],states:[]};save();history.replaceS
 
 (async function(){
   try{ const r=await fetch('/api/deals'); ALL=(await r.json()).deals||[]; }catch(e){ $('grid').innerHTML='<div class="empty"><h3>Deals unavailable</h3></div>'; return; }
+  const ws=$('wsort'), wd=$('wdens');
+  ws.value=wsort; ws.onchange=()=>{wsort=ws.value;localStorage.setItem('rentv.watchlist.sort',wsort);render();};
+  const cols0=localStorage.getItem('rentv.watchlist.cols')||'3';
+  wd.value=cols0; document.documentElement.style.setProperty('--cols',cols0);
+  wd.oninput=()=>{document.documentElement.style.setProperty('--cols',wd.value);localStorage.setItem('rentv.watchlist.cols',wd.value);};
   render();
 })();
 </script>

← fccef3ba feat(deals): add sort + density controls to the CRE deal gri  ·  back to Rentv 2026  ·  feat(watchlist): add sort + density to the deal watchlist gr afcf869a →