← back to Commercialrealestate
CRCP gov-agents.html: add grid/list/table view system (was table-only) + all-sortable + mobile ⚙ Filters modal
4eac4b6928d9bb4884d9fe15f89f5c1efdeb0c9a · 2026-08-18 20:48:04 -0700 · Steve Abrams
Files touched
Diff
commit 4eac4b6928d9bb4884d9fe15f89f5c1efdeb0c9a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 18 20:48:04 2026 -0700
CRCP gov-agents.html: add grid/list/table view system (was table-only) + all-sortable + mobile ⚙ Filters modal
---
public/gov-agents.html | 78 +++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 71 insertions(+), 7 deletions(-)
diff --git a/public/gov-agents.html b/public/gov-agents.html
index 44779b0..e2098ee 100644
--- a/public/gov-agents.html
+++ b/public/gov-agents.html
@@ -67,6 +67,28 @@
#modal .x{position:absolute;top:12px;right:14px;background:none;border:0;color:var(--mut);font-size:22px;cursor:pointer;}
#modal .litem{display:flex;justify-content:space-between;gap:12px;padding:5px 0;border-bottom:1px solid #20262f;font-size:13px;} #modal .litem .m{color:var(--mut);}
</style>
+ <style>
+ /* grid container + compact List view (added grid/list/table) */
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--card-min,280px),1fr));gap:16px;padding:16px 20px 0;}
+ .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>
@@ -76,6 +98,8 @@
<div class="nav"><a href="/broker-grid.html">← Brokers</a><a href="/mls.html">📋 Listings</a><a href="/crcp.html">🛰️ CRCP</a></div>
<span class="spacer"></span>
<input class="search" id="q" type="text" placeholder="search all fields — name, firm, license #, city, county, zip, expiry… (space = AND)">
+ <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"></select>
<label class="densl" title="Rows per fetch">⤢<input type="range" id="density" min="200" max="2000" step="100" value="500"></label>
<span class="count" id="count">…</span>
@@ -91,7 +115,9 @@
</div>
</aside>
<div class="rwrap">
- <div class="tblwrap"><table class="g" data-no-sort><thead id="thead"></thead><tbody id="tbody"></tbody></table></div>
+ <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>
</div>
<div id="ov"><div id="modal"><button class="x" aria-label="Close" onclick="document.getElementById('ov').classList.remove('on')">✕</button><div id="mbody"></div></div></div>
@@ -112,7 +138,7 @@ const COLS=[
{k:'zip',l:'Zip',g:'Location'},
{k:'state',l:'State',g:'Location'},
];
-let METRO='', AGENTS=[], META={}, q='', sortKey='name', sortDir=1, limit=500, timer=null, lastColSig='';
+let METRO='', AGENTS=[], META={}, q='', sortKey='name', sortDir=1, limit=500, timer=null, lastColSig='', view=(localStorage.getItem('govView')||'table');
// START TIGHT (Steve rule): only the essentials show on first load; the user toggles
// the rest on from the rail. A saved pref in VISCOL always wins.
const DEFAULT_ON=new Set(['name','firm','license_type','license_number','expiration','status']);
@@ -196,14 +222,24 @@ function render(){
const cols=visCols();
const rows=filtered();
$('#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>';
- $('#tbody').innerHTML=rows.map((a,i)=>`<tr data-i="${i}">`+cols.map(c=>`<td>${cell(a,c)}</td>`).join('')+'</tr>').join('')||`<tr><td colspan="${cols.length||1}" class="miss" style="padding:20px">no matches</td></tr>`;
+ const tv=$('#tableView'),gv=$('#gridView'),lv=$('#listView');
+ if(tv) tv.classList.toggle('hide',view!=='table');
+ if(gv) gv.classList.toggle('hide',view!=='grid');
+ if(lv) lv.classList.toggle('hide',view!=='list');
+ if(view==='table'){
+ $('#tbody').innerHTML=rows.map((a,i)=>`<tr data-i="${i}">`+cols.map(c=>`<td>${cell(a,c)}</td>`).join('')+'</tr>').join('')||`<tr><td colspan="${cols.length||1}" class="miss" style="padding:20px">no matches</td></tr>`;
+ 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('.tblwrap table.g')); } // resize handles survive sort/search re-render (TK-10089)
+ } else if(view==='list' && lv){
+ lv.innerHTML=rows.map((a,i)=>`<div class="lrow" data-i="${i}" style="cursor:pointer"><div class="lnm">${esc(a.name||'—')}</div><div class="lmeta">`+cols.filter(c=>c.k!=='name').map(c=>`<span class="lm"><span class="lk">${esc(c.l)}</span>${cell(a,c)}</span>`).join('')+`</div></div>`).join('')||'<div class="miss" style="padding:20px">no matches</div>';
+ } else if(gv){
+ gv.innerHTML=rows.map((a,i)=>`<div class="gc" data-i="${i}" style="cursor:pointer"><div class="nm">${esc(a.name||'—')}</div>${a.firm?`<div class="fm">${esc(a.firm)}</div>`:''}`+cols.filter(c=>c.k!=='name').map(c=>`<div class="row"><span class="k">${esc(c.l)}</span><span>${cell(a,c)}</span></div>`).join('')+`</div>`).join('')||'<div class="miss" style="padding:20px">no matches</div>';
+ }
ROWS=rows;
const capped=META.total>AGENTS.length;
$('#count').textContent=`${rows.length}${(q||capped)?' of '+(capped?META.total.toLocaleString():AGENTS.length):''} agents`;
$('#note').innerHTML=`Source: <b>${esc(META.source||'?')}</b> · ${esc(META.label||'')}`+(capped?` <span class="cap">— server returned ${AGENTS.length.toLocaleString()} of ${META.total.toLocaleString()} matches; refine your search to narrow.</span>`:'');
- 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('.tblwrap table.g')); } // resize handles survive sort/search re-render (TK-10089)
}
let ROWS=[];
// ── expandable row: click a row for full detail (all fields) ──
@@ -218,7 +254,7 @@ function openDetail(a){
mb.innerHTML=`<h3>${esc(a.name||'—')}</h3><div class="mfirm">${esc(a.firm||'—')}${a.license_type?' · '+esc(a.license_type):''}${a.license_number?' · '+esc(a.license_number):''}</div><div style="margin-top:12px">${rows}${extra}</div>`;
}
document.addEventListener('click',e=>{ if(e.target.id==='ov'){ $('#ov').classList.remove('on'); return; }
- const tr=e.target.closest('#tbody tr'); if(tr && tr.dataset.i!=null && e.target.tagName!=='A'){ const a=ROWS[+tr.dataset.i]; if(a) openDetail(a); } });
+ const el=e.target.closest('#tbody tr,#gridView .gc,#listView .lrow'); if(el && el.dataset.i!=null && e.target.tagName!=='A'){ const a=ROWS[+el.dataset.i]; if(a) openDetail(a); } });
document.addEventListener('keydown',e=>{ if(e.key==='Escape') $('#ov').classList.remove('on'); });
async function loadMetro(){
$('#count').textContent='loading…';
@@ -244,6 +280,9 @@ $('#metros').addEventListener('click',e=>{const c=e.target.closest('.mchip');if(
// Search: keep the server-side query (narrows the fetch) AND filter client-side across ALL fields.
$('#q').addEventListener('input',()=>{q=$('#q').value.trim();render();clearTimeout(timer);timer=setTimeout(loadMetro,280);});
$('#sortsel').addEventListener('change',()=>{sortKey=$('#sortsel').value;render();});
+// grid/list/table view toggle
+document.querySelectorAll('#view button').forEach(x=>x.classList.toggle('on',x.dataset.v===view));
+$('#view')&&$('#view').addEventListener('click',e=>{const b=e.target.closest('button');if(!b)return;document.querySelectorAll('#view button').forEach(x=>x.classList.toggle('on',x===b));view=b.dataset.v;localStorage.setItem('govView',view);render();});
$('#thead').addEventListener('click',e=>{const th=e.target.closest('th');if(!th)return;const k=th.dataset.k;if(sortKey===k)sortDir*=-1;else{sortKey=k;sortDir=1;}$('#sortsel').value=sortKey;render();});
$('#density').addEventListener('change',()=>{limit=+$('#density').value;loadMetro();});
// ── drag-to-reorder columns (Steve rule: move columns around) ──
@@ -310,5 +349,30 @@ loadIndex();
<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 */
+ (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('gov-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>
← f634570 CRCP sales.html: add compact List view (grid/list/table) + m
·
back to Commercialrealestate
·
CRCP: broker-direct SUBSTITUTES the aggregator row (sub out 6074d06 →