← back to Commercialrealestate
feat(condos): List/Grid toggle (both DB-listing & FHA-directory schemas) + density max to 20
5a3ee55f0b882e5ab156ca8b954907e2c13c5070 · 2026-07-02 09:40:54 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
Diff
commit 5a3ee55f0b882e5ab156ca8b954907e2c13c5070
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 09:40:54 2026 -0700
feat(condos): List/Grid toggle (both DB-listing & FHA-directory schemas) + density max to 20
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
public/condos.html | 55 ++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 49 insertions(+), 6 deletions(-)
diff --git a/public/condos.html b/public/condos.html
index afe0255..26febfb 100644
--- a/public/condos.html
+++ b/public/condos.html
@@ -32,6 +32,18 @@
.src{font-size:11px;color:var(--mut);border-top:1px solid var(--line);padding-top:7px;margin-top:2px;}
.broker{font-size:11px;color:var(--purp);}
.empty{padding:50px;text-align:center;color:var(--mut);}
+ .viewseg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden;}
+ .viewseg button{background:var(--card);color:var(--mut);border:0;border-right:1px solid var(--line);padding:7px 12px;font-size:12px;cursor:pointer;}
+ .viewseg button:last-child{border-right:0;} .viewseg button.active{background:#1f6feb;color:#fff;}
+ .grid.listhost{display:block;}
+ .listwrap{overflow-x:auto;border:2px solid #fff;border-radius:12px;}
+ .listtbl{width:100%;border-collapse:collapse;font-size:12.5px;white-space:nowrap;}
+ .listtbl th,.listtbl td{text-align:right;padding:9px 13px;border-top:1px solid var(--line);}
+ .listtbl th{position:sticky;top:0;background:#0b0e13;color:var(--mut);font-size:10px;text-transform:uppercase;letter-spacing:.5px;border-top:0;}
+ .listtbl td.stick,.listtbl th.stick{text-align:left;position:sticky;left:0;background:var(--card);}
+ .listtbl th.stick{background:#0b0e13;}
+ .listtbl tr:hover td{background:rgba(88,166,255,.06);}
+ .listtbl .la{font-weight:600;color:var(--ink);} .listtbl .lc{color:var(--mut);font-size:11px;}
</style>
</head>
<body>
@@ -50,7 +62,11 @@
<option value="all">All</option>
</select>
</label>
- <label>Density <input type="range" id="cols" min="1" max="6" step="1" value="3"></label>
+ <span class="viewseg" id="viewseg">
+ <button data-view="grid" class="active">▦ Grid</button>
+ <button data-view="list">☰ List</button>
+ </span>
+ <label>Density <input type="range" id="cols" min="1" max="20" step="1" value="3"></label>
<div class="count" id="count"></div>
</div>
@@ -68,9 +84,33 @@ function statusBadge(s){
return `<span class="badge ${s}">${txt}</span>`;
}
+const esc=s=>String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/"/g,'"');
+const money=n=>n?'$'+(+n).toLocaleString():'—';
+let VIEW=localStorage.getItem('condoView')||'grid';
+let LAST={rows:[],mode:'db'};
+function paint(){ const g=$('#grid'); if(!LAST.rows.length){ g.className='grid'; g.innerHTML='<div class="empty">no projects match.</div>'; return; }
+ if(VIEW==='list'){ g.className='grid listhost'; g.innerHTML=(LAST.mode==='db'?dbList:fhaList)(LAST.rows); }
+ else { g.className='grid'; g.innerHTML=(LAST.mode==='db'?dbCards:fhaCards)(LAST.rows); } }
+function dbList(rows){
+ const head=['Project','Status','Price','HOA','City','ZIP','Broker','Firm'];
+ return `<div class="listwrap"><table class="listtbl"><thead><tr><th class="stick">${head[0]}</th>${head.slice(1).map(h=>`<th>${h}</th>`).join('')}</tr></thead><tbody>`
+ +rows.map(c=>`<tr><td class="stick"><div class="la">${esc(c.project_name||c.address||'Condo')}</div><div class="lc">${esc(c.address||'')}</div></td>`
+ +`<td>${esc(c.warrantable_status||'—')}</td><td>${money(c.price)}</td><td>${c.hoa?'$'+c.hoa+'/mo':'—'}</td>`
+ +`<td>${esc(c.city||'—')}</td><td>${esc(c.zip||'—')}</td><td>${esc(c.broker_name||'—')}</td><td>${esc(c.firm_name||'—')}</td></tr>`).join('')
+ +'</tbody></table></div>';
+}
+function fhaList(rows){
+ const head=['Project','Signal','FHA concentration','Expires','City','ZIP','Method','HUD ID'];
+ return `<div class="listwrap"><table class="listtbl"><thead><tr><th class="stick">${head[0]}</th>${head.slice(1).map(h=>`<th>${h}</th>`).join('')}</tr></thead><tbody>`
+ +rows.map(c=>`<tr><td class="stick"><div class="la">${esc(c.project_name||'')}</div><div class="lc">${esc(c.address||'')}</div></td>`
+ +`<td>${esc(c.warrant_signal||'—')}</td><td>${esc(c.fha_concentration||'—')}</td><td>${esc(c.expiration_date||'—')}</td>`
+ +`<td>${esc(c.city||'—')}</td><td>${esc(c.zip||'—')}</td><td>${esc(c.approval_method||'—')}</td><td>${esc(c.condo_id||'—')}</td></tr>`).join('')
+ +'</tbody></table></div>';
+}
// Render either real DB condo listings (preferred) OR the FHA-approved project directory.
-function renderDbCondos(rows){
- $('#grid').innerHTML = rows.map(c=>{
+function renderDbCondos(rows){ LAST={rows,mode:'db'}; paint(); }
+function dbCards(rows){
+ return rows.map(c=>{
const ws=c.warrant_signals; const sigArr=Array.isArray(ws)?ws:((ws&&ws.signals)||[]);
const sig=sigArr.map(x=>`• ${(x&&x.note)||x}`).join('<br>');
return `<div class="condo">
@@ -85,8 +125,9 @@ function renderDbCondos(rows){
}).join('');
}
-function renderFhaDirectory(rows){
- $('#grid').innerHTML = rows.map(c=>`
+function renderFhaDirectory(rows){ LAST={rows:rows||[],mode:'fha'}; paint(); }
+function fhaCards(rows){
+ return rows.map(c=>`
<div class="condo">
<div class="name">${c.project_name}</div>
<div class="addr">${c.address||''}${c.city?', '+c.city:''} ${c.zip||''}</div>
@@ -95,7 +136,7 @@ function renderFhaDirectory(rows){
<div class="row"><span class="k">Composition</span><span style="text-align:right;max-width:60%">${(c.composition||'').slice(0,60)}</span></div>
<div class="row"><span class="k">Expires</span><span>${c.expiration_date||'—'}</span></div>
<div class="src">HUD ${c.condo_id||''} ${c.submission||''} · method ${c.approval_method||'—'} · status ${c.status||c.hud_status||''}</div>
- </div>`).join('') || '<div class="empty">no projects match.</div>';
+ </div>`).join('');
}
async function load(){
@@ -122,6 +163,8 @@ $('#q').oninput=()=>{ clearTimeout(window._t); window._t=setTimeout(load,200); }
$('#status').onchange=load;
$('#cols').oninput=e=>{ document.documentElement.style.setProperty('--cols',e.target.value); localStorage.setItem('condoCols',e.target.value); };
const savedCols=localStorage.getItem('condoCols'); if(savedCols){ $('#cols').value=savedCols; document.documentElement.style.setProperty('--cols',savedCols); }
+document.querySelectorAll('#viewseg button').forEach(b=>b.classList.toggle('active',b.dataset.view===VIEW));
+$('#viewseg').onclick=e=>{ const b=e.target.closest('button[data-view]'); if(!b) return; VIEW=b.dataset.view; localStorage.setItem('condoView',VIEW); document.querySelectorAll('#viewseg button').forEach(x=>x.classList.toggle('active',x.dataset.view===VIEW)); paint(); };
load();
</script>
</body>
← c02f5b2 feat(index): Amazon-style docked left filter rail + List/Gri
·
back to Commercialrealestate
·
auto-save: 2026-07-02T09:46:55 (1 files) — public/mls.html d74ce02 →