← back to Commercialrealestate
CRCP sales.html: add compact List view (grid/list/table) + mobile ⚙ Filters modal
f634570dfb7218c5319fa783c1d77abd595d3d40 · 2026-08-18 20:44:52 -0700 · Steve Abrams
Files touched
Diff
commit f634570dfb7218c5319fa783c1d77abd595d3d40
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 18 20:44:52 2026 -0700
CRCP sales.html: add compact List view (grid/list/table) + mobile ⚙ Filters modal
---
public/sales.html | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 57 insertions(+), 4 deletions(-)
diff --git a/public/sales.html b/public/sales.html
index 575cbbd..c5257d5 100644
--- a/public/sales.html
+++ b/public/sales.html
@@ -89,6 +89,27 @@
.rail .rsec.collapsed > h4::after { transform:rotate(-90deg); }
.rail .rsec.collapsed > *:not(h4) { display:none !important; }
</style>
+ <style>
+ /* compact List view (3rd view) */
+ .lst{padding:14px 20px 0;display:flex;flex-direction:column;}
+ .lrow{display:flex;align-items:baseline;gap:16px;flex-wrap:wrap;padding:10px 4px;border-bottom:1px solid var(--line2,#20262f);cursor:pointer;}
+ .lrow:hover{background:rgba(200,162,75,.06);}
+ .lnm{font-weight:700;min-width:190px;flex:0 0 auto;color:var(--ink);}
+ .lmeta{display:flex;flex-wrap:wrap;gap:5px 16px;flex:1 1 320px;min-width:0;}
+ .lm{font-size:12.5px;white-space:nowrap;}
+ .lk{color:var(--mut);font-size:10px;text-transform:uppercase;letter-spacing:.5px;margin-right:5px;}
+ /* mobile: ⚙ Filters button + rail modal (rail is display:none <820px) */
+ .railbtn{display:none;background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:9px;padding:8px 12px;font-size:13px;cursor:pointer;}
+ .railbtn:hover{border-color:var(--gold);}
+ .railov{position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:70;display:none;align-items:flex-start;justify-content:center;padding:14px;}
+ .railov.on{display:flex;}
+ .railmd{width:100%;max-width:520px;max-height:90vh;display:flex;flex-direction:column;background:var(--card);border:1px solid var(--line);border-radius:15px;overflow:hidden;}
+ .railmd-h{display:flex;align-items:center;justify-content:space-between;padding:13px 16px;border-bottom:1px solid var(--line);font-size:15px;color:var(--ink);}
+ .railmd-x{background:none;border:0;color:var(--mut);font-size:20px;cursor:pointer;line-height:1;}
+ .railmd-b{overflow:auto;padding:0;}
+ .rail.in-modal{display:block!important;position:static!important;height:auto!important;width:auto!important;flex:none!important;top:auto!important;border:0!important;}
+ @media(max-width:820px){ .railbtn{display:inline-block;} }
+ </style>
<link rel="stylesheet" href="/nav-agent/nav-agent.css"><!-- nav-agent -->
<link rel="stylesheet" href="/crcp-theme.css"><!-- CRCP day/night tokens: load LAST so html[data-theme] wins -->
</head>
@@ -98,7 +119,8 @@
<div class="nav"><a href="/crcp.html">← CRCP</a><a href="/condos.html">Condos</a><a href="/graphics.html">Graphics</a><a href="/fha-loans.html">FHA Loans</a></div>
<span class="spacer"></span>
<input class="search" id="q" type="text" placeholder="search all fields — address, city, zip, price, beds, sqft… (space = AND)">
- <div class="toggle" id="view"><button data-v="table" class="on">▦ List</button><button data-v="grid">▤ Grid</button></div>
+ <button class="railbtn" id="railBtn" type="button">⚙ Filters</button>
+ <div class="toggle" id="view"><button data-v="grid">▦ Grid</button><button data-v="list">☰ List</button><button data-v="table" class="on">▤ Table</button></div>
<select id="sortsel" title="Sort sold comps"></select>
<label id="denswrap" title="Card density — grid view">⤢<input type="range" id="density" min="1" max="10" step="1"></label>
<button class="csvbtn" id="csv" title="Export current rows to CSV">⬇ CSV</button>
@@ -120,6 +142,7 @@
<div class="wrap">
<div id="tableView" class="tblwrap"><table class="g" data-no-sort><thead id="thead"></thead><tbody id="tbody"></tbody></table></div>
<div id="gridView" class="grid hide"></div>
+ <div id="listView" class="lst hide"></div>
<div class="charts">
<div class="panel"><h3>By city (avg price)</h3><canvas id="cityChart"></canvas></div>
<div class="panel"><h3>Sales by year</h3><canvas id="yearChart"></canvas></div>
@@ -262,14 +285,18 @@ function render(){
const rows=filtered(); const cols=visCols();
$('#count').textContent=`${rows.length} of ${DATA.length} sold comps`;
$('#thead').innerHTML='<tr>'+cols.map(c=>`<th data-k="${c.k}" draggable="true" title="Drag to reorder · click to sort" class="dragcol${sortKey===c.k?(sortDir>0?' asc':' desc'):''}">${esc(c.l)}</th>`).join('')+'</tr>';
+ const lv=$('#listView');
+ $('#tableView').classList.toggle('hide',view!=='table');
+ $('#gridView').classList.toggle('hide',view!=='grid');
+ if(lv) lv.classList.toggle('hide',view!=='list');
if(view==='table'){
- $('#tableView').classList.remove('hide'); $('#gridView').classList.add('hide');
$('#tbody').innerHTML=rows.map((r,i)=>`<tr class="srow" data-i="${i}" style="cursor:pointer">`+cols.map(c=>{const cls=(c.t==='n'||c.t==='money')?' class="n"':'';return `<td${cls}>${cell(r,c)}</td>`;}).join('')+'</tr>').join('');
const csig=cols.map(c=>c.k).join(',');
if(csig!==lastColSig){ lastColSig=csig; reinitResize(); }
else if(window.ColResize && window.ColResize.reattach){ window.ColResize.reattach(document.querySelector('#tableView table.g')); } // resize handles survive sort/search re-render (TK-10089)
+ } else if(view==='list' && lv){
+ lv.innerHTML=rows.map((r,i)=>`<div class="lrow srow" data-i="${i}" style="cursor:pointer"><div class="lnm">${esc(r.address||'—')}</div><div class="lmeta">`+cols.filter(c=>c.k!=='address').map(c=>`<span class="lm"><span class="lk">${esc(c.l)}</span>${cell(r,c)}</span>`).join('')+`</div></div>`).join('');
} else {
- $('#tableView').classList.add('hide'); $('#gridView').classList.remove('hide');
const skipGrid=new Set(['address','city']);
const bodyCols=cols.filter(c=>!skipGrid.has(c.k));
$('#gridView').innerHTML=rows.map((r,i)=>`<div class="gc srow" data-i="${i}">
@@ -354,7 +381,7 @@ $('#view').addEventListener('click',e=>{const b=e.target.closest('button');if(!b
const dv=parseInt(localStorage.getItem('salesDensity'),10),dval=(dv>=1&&dv<=10)?dv:5;
dz.value=dval;applyDensity(dval);
dz.addEventListener('input',()=>{const v=+dz.value;applyDensity(v);localStorage.setItem('salesDensity',v);});
- const pv=localStorage.getItem('salesView'); if(pv==='grid'||pv==='table'){view=pv;document.querySelectorAll('#view button').forEach(x=>x.classList.toggle('on',x.dataset.v===view));}
+ const pv=localStorage.getItem('salesView'); if(pv==='grid'||pv==='table'||pv==='list'){view=pv;document.querySelectorAll('#view button').forEach(x=>x.classList.toggle('on',x.dataset.v===view));}
$('#denswrap').style.display=(view==='grid')?'inline-flex':'none';
window.__syncSortSel=()=>{if(Array.from(sel.options).some(o=>o.value===sortKey)&&sel.value!==sortKey)sel.value=sortKey;};
})();
@@ -421,5 +448,31 @@ fetch('/api/closed-sales?limit=5000').then(r=>r.json()).then(d=>{
<script src="/corner-nav.js" defer></script>
<script src="/nav-agent/nav-agent.js" defer></script><!-- nav-agent -->
<script src="/crcp-theme.js" defer></script><!-- CRCP day/night toggle (auto-injects into header) -->
+ <script>
+ /* mobile: relocate the filter rail into a ⚙ Filters modal (<820px), back to the sidebar on desktop.
+ The rail keeps its own .rsec collapse handler; this only moves the node + toggles the overlay. */
+ (function(){
+ function boot(){
+ var rail=document.querySelector('.rail'), hdr=document.querySelector('header'), btn=document.getElementById('railBtn');
+ if(!rail||!hdr||!btn||document.getElementById('railOv')) return;
+ var home=document.createComment('sales-rail-home'); rail.parentNode.insertBefore(home,rail);
+ var ov=document.createElement('div'); ov.id='railOv'; ov.className='railov';
+ var md=document.createElement('div'); md.className='railmd';
+ var hd=document.createElement('div'); hd.className='railmd-h'; hd.innerHTML='<span>Filters</span><button type="button" class="railmd-x" aria-label="Close">✕</button>';
+ var bd=document.createElement('div'); bd.className='railmd-b';
+ md.appendChild(hd); md.appendChild(bd); ov.appendChild(md); document.body.appendChild(ov);
+ var open=function(){ov.classList.add('on');}, close=function(){ov.classList.remove('on');};
+ btn.addEventListener('click',open);
+ hd.querySelector('.railmd-x').addEventListener('click',close);
+ ov.addEventListener('click',function(e){ if(e.target===ov) close(); });
+ document.addEventListener('keydown',function(e){ if(e.key==='Escape') close(); });
+ var mq=window.matchMedia('(max-width: 820px)');
+ function place(){ if(mq.matches){ if(rail.parentNode!==bd) bd.appendChild(rail); rail.classList.add('in-modal'); } else { if(home.parentNode&&rail.parentNode!==home.parentNode) home.parentNode.insertBefore(rail,home); rail.classList.remove('in-modal'); close(); } }
+ if(mq.addEventListener) mq.addEventListener('change',place); else if(mq.addListener) mq.addListener(place);
+ place();
+ }
+ if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',boot); else boot();
+ })();
+ </script>
</body>
</html>
← 324dab8 CRCP: browser-render template (scrape-stepp.js) via global P
·
back to Commercialrealestate
·
CRCP gov-agents.html: add grid/list/table view system (was t 4eac4b6 →