[object Object]

← back to Commercialrealestate

condos viewer: city + firm datums drill to filtered view (no dead-end text rule) — reuse F.cities/F.firms facet state, stopPropagation so it doesn't open modal

aa830f95f5d5f1ad3be7746e2ca883c4e68c485f · 2026-08-03 09:48:38 -0700 · Steve Abrams

Files touched

Diff

commit aa830f95f5d5f1ad3be7746e2ca883c4e68c485f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 3 09:48:38 2026 -0700

    condos viewer: city + firm datums drill to filtered view (no dead-end text rule) — reuse F.cities/F.firms facet state, stopPropagation so it doesn't open modal
---
 public/condos.html | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/public/condos.html b/public/condos.html
index 8f79531..f7d1179 100644
--- a/public/condos.html
+++ b/public/condos.html
@@ -40,6 +40,8 @@
   .viewseg button:last-child{border-right:0;} .viewseg button.active{background:#1f6feb;color:#fff;}
   .gridwrap{padding:24px;}
   .grid{display:grid;gap:22px;grid-template-columns:repeat(var(--cols,3),minmax(0,1fr));}
+  .drill{color:var(--blue,#4aa3ff);cursor:pointer;text-decoration:underline dotted;text-underline-offset:2px;}
+  .drill:hover{text-decoration:underline;}
   .grid.listhost{display:block;}
   .condo{background:var(--card);border:2px solid #fff;border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:6px;}
   .condo .name{font-weight:600;font-size:14px;}
@@ -222,6 +224,14 @@ function reinitResize(){
 function fmtDateTime(v){ if(v==null||v==='') return ''; const d=new Date(v); if(isNaN(d)) return ''; return d.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}); }
 function isoOf(v){ const d=new Date(v); return isNaN(d)?'':d.toISOString(); }
 function dateTimeSpan(v){ const s=fmtDateTime(v); return s?`<span title="${esc(isoOf(v))}">${esc(s)}</span>`:''; }
+// ── drill-down: no datum is dead text (Steve HARD rule). A city/firm value on a card or
+// list cell is a clickable link that filters the grid to that value — reusing the existing
+// F.cities / F.firms facet state so the rail chip lights up too. ──
+function drillSpan(kind,val){ if(val==null||val==='') return ''; return `<span class="drill" data-drill="${esc(kind)}" data-val="${esc(val)}" title="Filter to ${esc(val)}">${esc(val)}</span>`; }
+function applyDrill(kind,val){ if(val==null||val==='') return;
+  if(kind==='city'){ F.cities.add(val); } else if(kind==='firm'){ F.firms.add(val); } else return;
+  buildRail(); apply();
+}
 function fval(c,f){ return f.calc?f.calc(c):c[f.k]; }
 function fcell(c,f){ const v=fval(c,f);
   if(f.badge) return statusBadge(c.warrantable_status||c.warrant_signal||'—');
@@ -229,6 +239,8 @@ function fcell(c,f){ const v=fval(c,f);
   if(f.money) return '$'+(+v).toLocaleString()+(f.suf||'');
   if(f.num) return (+v).toLocaleString();
   if(f.date){ return dateTimeSpan(v)||'—'; }
+  if(f.k==='firm_name') return drillSpan('firm',v);
+  if(f.k==='city') return drillSpan('city',v);
   return esc(v)+(f.suf||'');
 }
 
@@ -320,7 +332,7 @@ function dbCards(rows){
   const vis=orderedFields().filter(f=>fvis(f.k)&&!CARD_SKIP.has(f.k));
   return rows.map(c=>`<div class="condo">
     <div class="chead"><div class="name">${esc(c.project_name||c.address||'Condo')}</div><button class="starbtn ${SHORT.has(c.id)?'on':''}" data-star="${c.id}" aria-label="${SHORT.has(c.id)?'Remove from':'Add to'} shortlist" aria-pressed="${SHORT.has(c.id)}" title="Shortlist">★</button></div>
-    <div class="addr">${esc(c.address||'')}${c.city?', '+esc(c.city):''} ${esc(c.zip||'')}</div>
+    <div class="addr">${esc(c.address||'')}${c.city?', '+drillSpan('city',c.city):''} ${esc(c.zip||'')}</div>
     ${whenChip(c)}
     ${fvis('warrantable_status')?statusBadge(c.warrantable_status||c.warrant_signal):''}
     ${vis.map(f=>`<div class="row"><span class="k">${esc(f.l)}</span><span>${fcell(c,f)}</span></div>`).join('')}
@@ -493,6 +505,8 @@ $('#ov').addEventListener('click',e=>{ if(e.target.id==='ov') $('#ov').classList
 document.addEventListener('keydown',e=>{ if(e.key==='Escape') $('#ov').classList.remove('on'); });
 // row/card click opens detail (but not when the click is a star, header, or link)
 $('#grid').addEventListener('click',e=>{
+  const dr=e.target.closest('[data-drill]');
+  if(dr){ e.stopPropagation(); applyDrill(dr.dataset.drill,dr.dataset.val); return; }   // datum drill → filter, not modal
   if(e.target.closest('[data-star]')||e.target.closest('th')||e.target.tagName==='A'||e.target.tagName==='BUTTON'||e.target.tagName==='TEXTAREA'||e.target.closest('.notewrap')) return;
   const row=e.target.closest('tr[data-id]')||e.target.closest('.condo');
   if(row){ let id=row.dataset.id; if(id==null){ const st=row.querySelector('[data-star]'); id=st&&st.dataset.star; } if(id!=null) openDetail(id); }

← e0227f6 condos viewer: DRY date rendering into fmtDateTime/dateTimeS  ·  back to Commercialrealestate  ·  condos viewer: make list-view sticky-column city drillable t 64d69b9 →