[object Object]

← back to Nationalrealestate

usre: contractors.html gets grid/list/table views + all-column server-side sort (clickable table headers + full Sort select, view+sort persisted)

cf1e3b288c604638deff4f21280797cdbfe39657 · 2026-08-13 08:04:15 -0700 · steve

Files touched

Diff

commit cf1e3b288c604638deff4f21280797cdbfe39657
Author: steve <steve@designerwallcoverings.com>
Date:   Thu Aug 13 08:04:15 2026 -0700

    usre: contractors.html gets grid/list/table views + all-column server-side sort (clickable table headers + full Sort select, view+sort persisted)
---
 public/contractors.html | 57 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 52 insertions(+), 5 deletions(-)

diff --git a/public/contractors.html b/public/contractors.html
index af8c2d8..5b77c30 100644
--- a/public/contractors.html
+++ b/public/contractors.html
@@ -284,12 +284,56 @@ function card(r){
   return `<div class="cd">${parts.join('')}</div>`;
 }
 
+// ── table view: dense all-columns spreadsheet, click-to-sort headers (server sort) ──
+const TBL_COLS = [
+  ['business_name','Business'],['license_no','License #'],['license_status','Status'],
+  ['city','City'],['county','County'],['zip','ZIP'],['primary_class','Class'],
+  ['issue_date','Issued'],['expire_date','Expires'],
+];
+function renderTable(){
+  const [sk,sd]=SORT.split(':');
+  const arrow=k=> k===sk ? (sd==='desc'?' ▾':' ▴') : '';
+  let h='<table class="ctrtbl"><thead><tr>'+
+    TBL_COLS.map(([k,l])=>`<th data-k="${k}" class="${k===sk?'sorted':''}">${esc(l)}${arrow(k)}</th>`).join('')+
+    '<th>Phone</th></tr></thead><tbody>';
+  for(const r of PAGE){
+    const cls=(r.classifications||[]).join(', ');
+    h+='<tr>'+
+      `<td><a href="/contractors.html?q=${encodeURIComponent(r.business_name)}">${esc(r.business_name)}</a></td>`+
+      `<td>${esc(r.license_no)}</td>`+
+      `<td>${esc(r.license_status||'—')}</td>`+
+      `<td>${r.city?`<a href="/contractors.html?city=${encodeURIComponent(r.city)}">${esc(r.city)}</a>`:'—'}</td>`+
+      `<td>${r.county?`<a href="/contractors.html?county=${encodeURIComponent(r.county)}">${esc(r.county)}</a>`:'—'}</td>`+
+      `<td>${esc(r.zip||'—')}</td>`+
+      `<td>${r.primary_class?`<a href="/contractors.html?class=${encodeURIComponent(r.primary_class)}">${esc(r.primary_class)}</a>`:esc(cls||'—')}</td>`+
+      `<td>${esc(r.issue_date||'—')}</td>`+
+      `<td>${esc(r.expire_date||'—')}</td>`+
+      `<td>${esc(r.phone||'—')}</td>`+
+      '</tr>';
+  }
+  h+='</tbody></table>';
+  const grid=$('#grid'); grid.className='table'; grid.innerHTML='';
+  // remove any prior table then insert
+  const prev=$('.ctrtbl'); if(prev) prev.remove();
+  grid.insertAdjacentHTML('afterend',h);
+  $('.ctrtbl thead').querySelectorAll('th[data-k]').forEach(th=>th.onclick=()=>{
+    const k=th.dataset.k; const [ck,cd]=SORT.split(':');
+    SORT = (k===ck) ? `${k}:${cd==='asc'?'desc':'asc'}` : `${k}:asc`;
+    localStorage.setItem('ctr:sort',SORT); $('#sort').value=SORT; loadFeed();
+  });
+}
+
 function renderGrid(){
   const grid=$('#grid');
-  if(!PAGE.length){ grid.innerHTML='<div class="empty">No contractors match. (The CSLB registry may still be loading — this page is empty-safe.)</div>'; return; }
-  const rows=[...PAGE].sort(SORTERS[SORT]||SORTERS.name);
-  grid.innerHTML=rows.map(card).join('');
-  // refresh in-page facets from what's loaded
+  // clear any stray table left by a previous table-view render
+  const prev=$('.ctrtbl'); if(prev) prev.remove();
+  if(!PAGE.length){ grid.className=''; grid.innerHTML='<div class="empty">No contractors match. (The CSLB registry may still be loading — this page is empty-safe.)</div>'; refreshFacets(); return; }
+  if(VIEW==='table'){ renderTable(); refreshFacets(); return; }
+  grid.className = (VIEW==='list'?'list':'');
+  grid.innerHTML=PAGE.map(card).join('');
+  refreshFacets();
+}
+function refreshFacets(){
   facetList('#fCounty','county',agg(PAGE,'county'));
   facetList('#fCity','city',agg(PAGE,'city').slice(0,25));
   facetList('#fClass','class',aggClasses(PAGE));
@@ -298,6 +342,7 @@ function renderGrid(){
 async function loadFeed(){
   const p=new URLSearchParams();
   for(const f of FILTERS){ if(state[f]) p.set(f,state[f]); }
+  const sp=sortParts(); p.set('sort',sp.sort); p.set('order',sp.order);
   p.set('limit',limit); p.set('offset',state.offset||0);
   $('#count').textContent='loading…';
   let d; try{ d=await (await fetch('/api/contractors?'+p.toString())).json(); }
@@ -334,7 +379,9 @@ let qTimer;
 $('#q').oninput=e=>{ clearTimeout(qTimer); const v=e.target.value.trim(); qTimer=setTimeout(()=>go({q:v||undefined}),300); };
 $('#class').onchange=e=>go({class:e.target.value||undefined});
 $('#status').onchange=e=>go({status:e.target.value});
-$('#sort').onchange=e=>{ SORT=e.target.value; localStorage.setItem('ctr:sort',SORT); renderGrid(); };
+$('#sort').onchange=e=>{ SORT=e.target.value; localStorage.setItem('ctr:sort',SORT); loadFeed(); };
+$('#viewseg').querySelectorAll('button').forEach(b=>b.onclick=()=>{ VIEW=b.dataset.view; localStorage.setItem('ctr:view',VIEW);
+  $('#viewseg').querySelectorAll('button').forEach(x=>x.classList.toggle('active',x.dataset.view===VIEW)); renderGrid(); });
 $('#density').oninput=e=>{ document.documentElement.style.setProperty('--card',e.target.value+'px'); localStorage.setItem('ctr:card',e.target.value); };
 $('#prev').onclick=()=>go({offset:Math.max(0,(state.offset||0)-limit)});
 $('#next').onclick=()=>go({offset:(state.offset||0)+limit});

← 846689f nav-agent: add universal grid-controls drop-in (field-toggle  ·  back to Nationalrealestate  ·  usre: deals.html gets grid/list/table view toggle + every ta 1227b5c →