← 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 →