← back to Commercialrealestate
condos.html: add broker/agent contact field toggles (listing agent, phone, office, email, DRE#, responsible broker+address, buyer's side) — TK-10243
115cbb90eaa628734b3af4a8a092791bd8edc188 · 2026-08-05 12:00:41 -0700 · Steve Abrams
Master-parity contact toggles backed by real /api/condos overlay data. Cards show
both sides via brokerCell+buyerCell (always-on); List view gets sortable columns;
detail modal shows phones/email/responsible-broker/buyer-side. No dead toggles —
email fills from find-email enrichment, buyer's agent from the boughtWith scrape.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 115cbb90eaa628734b3af4a8a092791bd8edc188
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 5 12:00:41 2026 -0700
condos.html: add broker/agent contact field toggles (listing agent, phone, office, email, DRE#, responsible broker+address, buyer's side) — TK-10243
Master-parity contact toggles backed by real /api/condos overlay data. Cards show
both sides via brokerCell+buyerCell (always-on); List view gets sortable columns;
detail modal shows phones/email/responsible-broker/buyer-side. No dead toggles —
email fills from find-email enrichment, buyer's agent from the boughtWith scrape.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/condos.html | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 68 insertions(+), 4 deletions(-)
diff --git a/public/condos.html b/public/condos.html
index ff85e28..ac1b0de 100644
--- a/public/condos.html
+++ b/public/condos.html
@@ -184,9 +184,18 @@ const FIELDS=[
{k:'year_built', l:'Year built', col:1},
{k:'age', l:'Age (yrs)', col:1, calc:c=>c.year_built?(NOW_YEAR-c.year_built):null},
{k:'zip', l:'ZIP', col:1},
- {k:'broker_name', l:'Broker', col:1},
+ // ── Broker / agent CONTACT block (Steve 2026-08-05, TK-10243): every listing carries the full
+ // listing-side contact set + the sold-listing buyer side. All backed by /api/condos overlay data
+ // (data/condo-brokers.json) — no dead toggles. Email fills from the find-email enrichment; the
+ // buyer's agent fills only on SOLD listings from the boughtWith scrape. ──
+ {k:'broker_name', l:'Listing agent', col:1},
{k:'firm_name', l:'Firm', col:1},
+ {k:'agent_phone', l:'Agent phone', col:1},
+ {k:'office_phone',l:'Office phone', col:1},
+ {k:'agent_email', l:'Agent email', col:1},
{k:'broker_dre', l:'DRE#', col:1},
+ {k:'responsible_broker', l:'Responsible broker', col:1},
+ {k:'buyer_agent', l:'Buyer’s agent (sold)', col:1},
{k:'warrant_source',l:'Warrant note', col:0},
{k:'created_at', l:'Added', date:1, col:1},
];
@@ -244,6 +253,13 @@ function fcell(c,f){ const v=fval(c,f);
if(!/^\d{6,8}$/.test(String(primary))) return esc(String(primary))+extra; // defensive: only link a real DRE#
const u=c.dre_url||('https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id='+primary);
return `<a href="${esc(u)}" target="_blank" rel="noopener noreferrer" title="Full CA DRE license record">${esc(primary)} ↗</a>${extra}`; }
+ // ── contact-block cells (TK-10243) — resolve nested/flat, phone→tel, email→mailto, sortable text ──
+ if(f.k==='agent_phone'){ const p=v||(c.agents&&c.agents[0]&&c.agents[0].phone); return p?`<a href="${telHref(p)}">${esc(p)}</a>`:'—'; }
+ if(f.k==='office_phone'){ return v?`<a href="${telHref(v)}">${esc(v)}</a>`:'—'; }
+ if(f.k==='agent_email'){ const e=agentEmail(c); return e?`<a href="${mailHref(e)}" title="Email the listing agent">${esc(e)} ✉</a>`:'—'; }
+ if(f.k==='responsible_broker'){ const rb=respBroker(c); return rb?esc(rb):'—'; }
+ if(f.k==='buyer_agent'){ const ba=buyerAgents(c); if(!ba.length) return '—';
+ return ba.map(a=>{ const dre=a.dre?` · DRE# ${esc(a.dre)}`:''; return esc(a.name||'')+(a.firm?' · '+esc(a.firm):'')+dre; }).join('<br>'); }
if(v==null||v==='') return '—';
if(f.money) return '$'+(+v).toLocaleString()+(f.suf||'');
if(f.num) return (+v).toLocaleString();
@@ -311,6 +327,16 @@ function buildFieldToggles(){ $('#fFields').innerHTML=orderedFields().map(f=>`<l
// ---- per-listing broker (with DRE# hooked to the full CA DRE record) + notes ----
let NOTES={}; // {id:{note,updated_at}} loaded once at boot from /api/condo-notes
const telHref=p=>'tel:'+String(p||'').replace(/[^0-9]/g,'');
+const mailHref=e=>'mailto:'+String(e||'').trim();
+// ── contact resolvers (TK-10243): each falls back through the flat field → primary agent record so
+// the List column matches whatever the card shows, and returns '' when genuinely absent (no fake '—'). ──
+function agentEmail(c){ return c.agent_email || (c.agents&&c.agents[0]&&c.agents[0].email) || ''; }
+// responsible broker carries the brokerage's office mailing ADDRESS (from the CA DRE record)
+function respBroker(c){ return c.responsible_broker || (c.agents&&c.agents[0]&&c.agents[0].dre_record&&c.agents[0].dre_record.responsible_broker) || ''; }
+// buyer-SIDE agents — SOLD listings only; mirrors agents[]. Filled by the boughtWith scrape
+// (buyer_agents[]) with a flat-field fallback. Empty array for active listings (no buyer side yet).
+function buyerAgents(c){ return (Array.isArray(c.buyer_agents)&&c.buyer_agents.length)?c.buyer_agents
+ : (c.buyer_agent_name?[{name:c.buyer_agent_name,firm:c.buyer_firm_name,dre:c.buyer_agent_dre,phone:c.buyer_agent_phone}]:[]); }
function brokerCell(c){
// Always render (Steve 2026-08-03: every listing must carry the broker/firm/contact) — not gated
// on the field-visibility toggles the way the generic rows are.
@@ -325,7 +351,23 @@ function brokerCell(c){
const agents=(Array.isArray(c.agents)&&c.agents.length)?c.agents:[{name:c.broker_name,firm:c.firm_name,dre:c.broker_dre,dre_record:(c.dre_url?{dre_url:c.dre_url}:null)}];
const dreLink=(a)=>{ if(!a.dre) return ''; const u=(a.dre_record&&a.dre_record.dre_url)||('https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id='+a.dre); return ` · <a href="${esc(u)}" target="_blank" rel="noopener noreferrer" title="Full CA DRE license record">DRE# ${esc(a.dre)}</a>`; };
const lines=agents.map((a,i)=>`🧑💼 ${esc(a.name||'')}${a.firm?(a.name?' · ':'')+esc(a.firm):''}${dreLink(a)}${i===0?aph+oph:'<span style="color:var(--mut);font-size:10px"> (co-agent)</span>'}`).join('<br>');
- return `<div class="broker">${lines}</div>${exp}`;
+ // email line — always shown when present (Steve: every listing carries full contact); mailto-linked.
+ const em=agentEmail(c); const emLine=em?`<div class="broker" style="font-size:11px">✉ <a href="${mailHref(em)}">${esc(em)}</a></div>`:'';
+ return `<div class="broker">${lines}</div>${emLine}${exp}`;
+}
+// ── BUYER'S SIDE — the "other side" of a SOLD deal (TK-10243). Always shown on the card when the
+// boughtWith scrape captured it; empty (renders nothing) for active listings, which have no buyer
+// side yet. Kept visually distinct from the listing side with a 🤝 marker + a dashed divider. ──
+function buyerCell(c){
+ const ba=buyerAgents(c); if(!ba.length) return '';
+ const line=ba.map(a=>{
+ const u=a.dre?('https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id='+a.dre):'';
+ const dre=a.dre?(u?` · <a href="${esc(u)}" target="_blank" rel="noopener noreferrer" title="Full CA DRE license record">DRE# ${esc(a.dre)}</a>`:` · DRE# ${esc(a.dre)}`):'';
+ const ph=a.phone?` · <a href="${telHref(a.phone)}">📱 ${esc(a.phone)}</a>`:'';
+ const em=a.email?` · <a href="${mailHref(a.email)}">✉ ${esc(a.email)}</a>`:'';
+ return `🤝 ${esc(a.name||'')}${a.firm?' · '+esc(a.firm):''}${dre}${ph}${em}`;
+ }).join('<br>');
+ return `<div class="broker" style="border-top:1px dashed var(--line);margin-top:4px;padding-top:5px"><span style="color:var(--mut);font-size:10px;letter-spacing:.4px">BUYER'S SIDE · sold by</span><br>${line}</div>`;
}
function noteBox(c){
const v=(NOTES[c.id]&&NOTES[c.id].note)||'';
@@ -340,7 +382,10 @@ function whenChip(c){
}
// ---- renderers ----
-const CARD_SKIP=new Set(['warrantable_status','city','zip','project_name','warrant_source','created_at']);
+// Contact fields render via brokerCell/buyerCell (always-on) — skip them from the generic card rows
+// so a toggled-on column doesn't double-print on the card. They stay live as List columns + toggles.
+const CARD_SKIP=new Set(['warrantable_status','city','zip','project_name','warrant_source','created_at',
+ 'broker_name','firm_name','agent_phone','office_phone','agent_email','broker_dre','responsible_broker','buyer_agent']);
function dbCards(rows){
const vis=orderedFields().filter(f=>fvis(f.k)&&!CARD_SKIP.has(f.k));
return rows.map(c=>`<div class="condo">
@@ -350,6 +395,7 @@ function dbCards(rows){
${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('')}
${brokerCell(c)}
+ ${buyerCell(c)}
${fvis('warrant_source')&&c.warrant_source?`<div class="src">${esc(c.warrant_source)}</div>`:''}
${noteBox(c)}
</div>`).join('');
@@ -517,7 +563,25 @@ function openDetail(id){
const fm=a.firm?`${a.name?' · ':''}<a class="dl" href="/crcp.html?firm=${encodeURIComponent(a.firm)}">${esc(a.firm)}</a>`:'';
const u=a.dre?((a.dre_record&&a.dre_record.dre_url)||('https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id='+a.dre)):'';
const dre=a.dre?` · <a class="dl" href="${esc(u)}" target="_blank" rel="noopener noreferrer" title="Full CA DRE license record">DRE# ${esc(a.dre)} ↗</a>`:'';
- return line(i===0?'Listed by':'Co-agent', nm+fm+dre);
+ // primary agent carries phone(s) + email inline; co-agents show name/firm/DRE
+ const ph=(i===0&&c.agent_phone)?` · <a class="dl" href="${telHref(c.agent_phone)}">📱 ${esc(c.agent_phone)}</a>`:'';
+ const op=(i===0&&c.office_phone&&c.office_phone!==c.agent_phone)?` · ☎ ${esc(c.office_phone)}`:'';
+ const emv=(i===0)?agentEmail(c):(a.email||''); const em=emv?` · <a class="dl" href="${mailHref(emv)}">✉ ${esc(emv)}</a>`:'';
+ return line(i===0?'Listed by':'Co-agent', nm+fm+dre+ph+op+em);
+ }).join('');
+ })()
+ +(function(){ const rb=respBroker(c); return rb?line('Responsible broker',esc(rb)):''; })()
+ +(function(){
+ // BUYER'S SIDE — the other party on a SOLD deal. Name→CRCP directory, DRE→full record.
+ const ba=buyerAgents(c); if(!ba.length) return '';
+ return ba.map((a,i)=>{
+ const nm=a.name?`<a class="dl" href="/crcp.html?brokerName=${encodeURIComponent(a.name)}">${esc(a.name)}</a>`:'';
+ const fm=a.firm?`${a.name?' · ':''}<a class="dl" href="/crcp.html?firm=${encodeURIComponent(a.firm)}">${esc(a.firm)}</a>`:'';
+ const u=a.dre?('https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id='+a.dre):'';
+ const dre=a.dre?` · <a class="dl" href="${esc(u)}" target="_blank" rel="noopener noreferrer" title="Full CA DRE license record">DRE# ${esc(a.dre)} ↗</a>`:'';
+ const ph=a.phone?` · <a class="dl" href="${telHref(a.phone)}">📱 ${esc(a.phone)}</a>`:'';
+ const em=a.email?` · <a class="dl" href="${mailHref(a.email)}">✉ ${esc(a.email)}</a>`:'';
+ return line(i===0?'Sold by (buyer’s side)':'Co-agent (buyer)', nm+fm+dre+ph+em);
}).join('');
})()
+line('Added',dateTimeSpan(c.created_at))
← 74c643a auto-save: 2026-08-05T07:09:54 (1 files) — data/alerts-state
·
back to Commercialrealestate
·
condos both-sides: buyer-side scraper (Redfin buyingAgents) 2bee932 →