[object Object]

← back to Commercialrealestate

feat(index): ⬇ CSV export of the current filtered+sorted view (visible columns, proper escaping) + column-sort persisted in the shareable URL (?cs=key:a|d restores click-to-sort). Contrarian-gated, 0 errors.

c919ae1670c38a0aa0807f7ce073283116c8f46d · 2026-07-02 11:10:22 -0700 · Steve Abrams

Officer: vp-engineering
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit c919ae1670c38a0aa0807f7ce073283116c8f46d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 2 11:10:22 2026 -0700

    feat(index): ⬇ CSV export of the current filtered+sorted view (visible columns, proper escaping) + column-sort persisted in the shareable URL (?cs=key:a|d restores click-to-sort). Contrarian-gated, 0 errors.
    
    Officer: vp-engineering
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 public/index.html | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/public/index.html b/public/index.html
index e9108a5..49e4427 100644
--- a/public/index.html
+++ b/public/index.html
@@ -422,6 +422,7 @@
       <button data-view="list">☰ List</button>
     </span>
     <div class="right">
+      <button id="csvbtn" class="ex" title="Download the current filtered + sorted view (visible columns) as CSV">⬇ CSV</button>
       <button id="linkbtn" class="ex" title="Copy a shareable link to this exact filtered view">🔗 Copy link</button>
       <label>Sort</label>
       <select id="sortTop" style="width:auto;min-width:180px"></select>
@@ -933,6 +934,7 @@ function stateToURL(){
   if(F.nonqm) p.set('nq','1');
   if(scenario!=='25') p.set('sc',scenario);
   const so=$('#sort')&&$('#sort').value; if(so && so!=='deal') p.set('sort',so);
+  if(colSort) p.set('cs',colSort.key+':'+(colSort.dir>0?'a':'d'));
   if(view!=='grid') p.set('v',view);
   const qs=p.toString();
   // NOTE: the app declares a local `history` (chat log) that shadows window.history — must qualify.
@@ -955,8 +957,18 @@ function urlToState(){
   if(p.get('sc')){ scenario=p.get('sc'); }
   if(p.get('sort')&&$('#sort')){ $('#sort').value=p.get('sort'); if($('#sortTop'))$('#sortTop').value=p.get('sort'); }
   if(p.get('v')){ view=p.get('v'); }
+  if(p.get('cs')){ const [k,d]=p.get('cs').split(':'); colSort={key:k,dir:d==='a'?1:-1}; }
   return any;
 }
+function csvEsc(v){ v=(v==null?'':String(v)); return '"'+v.replace(/"/g,'""')+'"'; }
+function exportCSV(){
+  const cols=listCols();
+  const rows=sortRows(applyFilters(DATA.ranked.slice()));
+  const head=['Address','City','ZIP'].concat(cols.map(c=>c.label));
+  const lines=[head.map(csvEsc).join(',')];
+  rows.forEach(p=>{ const row=[p.address,p.city,p.zip].concat(cols.map(c=>fieldVal(p,c.k))); lines.push(row.map(csvEsc).join(',')); });
+  const a=document.createElement('a'); a.href=URL.createObjectURL(new Blob([lines.join('\n')],{type:'text/csv'})); a.download='cre-explorer-'+rows.length+'.csv'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1000);
+}
 function copyShareLink(){ const t=$('#linkbtn'); try{ navigator.clipboard.writeText(location.href); if(t){ const o=t.textContent; t.textContent='✓ Copied'; setTimeout(()=>t.textContent=o,1400); } }catch(e){ if(t) t.textContent='⌘C to copy'; } }
 function render(){
   const g=$('#grid'); const fc=activeFilterCount();
@@ -1177,6 +1189,7 @@ fetch('data/ranked.json').then(r=>r.json()).then(async d=>{
   // active-filter summary bar: remove one filter or reset all
   $('#activebar').addEventListener('click',e=>{ if(e.target.closest('#afreset')){ resetAll(); return; } const c=e.target.closest('.afchip'); if(c) clearFilter(c.dataset.clear); });
   $('#linkbtn').onclick=copyShareLink;
+  $('#csvbtn').onclick=exportCSV;
   // facet chip handlers (multi-select toggles)
   $('#fType').onclick=e=>{ const c=e.target.closest('.chip'); if(!c) return;
     if(c.dataset.warr==='nonqm'){ F.nonqm=!F.nonqm; syncChips(); render(); return; }

← acf3768 feat(mls+broker-grid): fleet consistency (DTD Option D) — co  ·  back to Commercialrealestate  ·  feat(index): shortlist — ⭐ star any listing (grid + list), d 8b66d89 →