[object Object]

← back to Commercialrealestate

CRE: sortable columns on /sales.html comps table (address/city/price/date, click to rank ▲▼)

4d1c2a9e21e28765ba6207817b147309c42f2909 · 2026-06-28 10:42:11 -0700 · Steve

Files touched

Diff

commit 4d1c2a9e21e28765ba6207817b147309c42f2909
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jun 28 10:42:11 2026 -0700

    CRE: sortable columns on /sales.html comps table (address/city/price/date, click to rank ▲▼)
---
 public/sales.html | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/public/sales.html b/public/sales.html
index d238b9f..42f85a4 100644
--- a/public/sales.html
+++ b/public/sales.html
@@ -20,6 +20,8 @@
   .panel h3{margin:0 0 12px;font-size:12px;text-transform:uppercase;letter-spacing:.6px;color:var(--gold);}
   table{width:100%;border-collapse:collapse;font-size:13px;} td,th{text-align:left;padding:6px;border-top:1px solid var(--line);}
   th{color:var(--muted);font-size:11px;text-transform:uppercase;border-top:0;} td.n{text-align:right;font-variant-numeric:tabular-nums;}
+  th.srt{cursor:pointer;user-select:none;} th.srt:hover{color:var(--fg);}
+  th.srt.asc::after{content:' ▲';color:var(--gold);} th.srt.desc::after{content:' ▼';color:var(--gold);}
   .price{color:var(--active);font-weight:600;} a.row{color:var(--draft);text-decoration:none;}
   canvas{max-height:240px;}
 </style>
@@ -39,7 +41,7 @@
   </div>
   <div class="cols">
     <div class="panel"><h3>Sales (newest first)</h3>
-      <table id="tbl"><thead><tr><th>Address</th><th>City</th><th class="n">Sold</th><th class="n">Date</th></tr></thead><tbody></tbody></table>
+      <table id="tbl"><thead><tr><th class="srt" data-col="address">Address</th><th class="srt" data-col="city">City</th><th class="srt n" data-col="sold_price">Sold</th><th class="srt n" data-col="sold_date">Date</th></tr></thead><tbody></tbody></table>
     </div>
     <div>
       <div class="panel"><h3>By city (avg price)</h3><canvas id="cityChart"></canvas></div>
@@ -50,7 +52,15 @@
 <script>
 const $=s=>document.querySelector(s);
 const money=n=>n?'$'+Number(n).toLocaleString():'—';
-let cityChart, yearChart;
+let cityChart, yearChart, lastRows=[], sort={col:'sold_date',dir:-1};
+function renderRows(){
+  const rows=lastRows.slice().sort((a,b)=>{ let x=a[sort.col],y=b[sort.col];
+    if(sort.col==='sold_price'){x=+x||0;y=+y||0; return sort.dir*(x-y);}
+    return sort.dir*String(x||'').localeCompare(String(y||''),undefined,{numeric:true}); });
+  $('#tbl tbody').innerHTML=rows.slice(0,400).map(r=>`<tr><td>${r.url?`<a class="row" href="${r.url}" target="_blank" rel="noopener noreferrer">${r.address}</a>`:r.address}</td><td>${r.city}</td><td class="n price">${money(r.sold_price)}</td><td class="n" style="color:var(--muted)">${(r.sold_date||'').toString().slice(0,10)}</td></tr>`).join('');
+  document.querySelectorAll('#tbl th.srt').forEach(th=>{ th.classList.remove('asc','desc'); if(th.dataset.col===sort.col) th.classList.add(sort.dir>0?'asc':'desc'); });
+}
+document.querySelectorAll('#tbl th.srt').forEach(th=>th.onclick=()=>{ const c=th.dataset.col; if(sort.col===c) sort.dir*=-1; else {sort.col=c; sort.dir=(c==='sold_price'||c==='sold_date')?-1:1;} renderRows(); });
 async function load(){
   const p=new URLSearchParams();
   if($('#city').value) p.set('city',$('#city').value.trim());
@@ -60,7 +70,7 @@ async function load(){
   const t=d.total||{};
   $('#stat').innerHTML=`<b>${(t.n||0).toLocaleString()}</b> sold condos · avg <b>${money(t.avg_price)}</b> · ${(t.oldest||'').toString().slice(0,10)} → ${(t.newest||'').toString().slice(0,10)} · <span style="color:var(--muted)">${d.source||''}</span>`;
   if(d.depthNote) $('#depthNote').textContent='ℹ '+d.depthNote;
-  $('#tbl tbody').innerHTML=(d.rows||[]).slice(0,300).map(r=>`<tr><td>${r.url?`<a class="row" href="${r.url}" target="_blank" rel="noopener noreferrer">${r.address}</a>`:r.address}</td><td>${r.city}</td><td class="n price">${money(r.sold_price)}</td><td class="n" style="color:var(--muted)">${(r.sold_date||'').toString().slice(0,10)}</td></tr>`).join('');
+  lastRows=d.rows||[]; renderRows();
   // charts
   const bc=d.byCity||[];
   if(cityChart) cityChart.destroy();

← cc6c1a9 closed-sales: full LA County coverage — 228 resolved regions  ·  back to Commercialrealestate  ·  afternoon CRE update 2026-06-28 a880a29 →