← back to Commercialrealestate
CRE leads: Lead Desk viewer (sortable grid + density + filters + drill-down, auth)
7c4d9f35c54a50dbc501471c4e4b8971772ed1ca · 2026-08-07 16:28:56 -0700 · Steve Abrams
Files touched
A data/marketing-exec-leads/index.html
Diff
commit 7c4d9f35c54a50dbc501471c4e4b8971772ed1ca
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 7 16:28:56 2026 -0700
CRE leads: Lead Desk viewer (sortable grid + density + filters + drill-down, auth)
---
data/marketing-exec-leads/index.html | 152 +++++++++++++++++++++++++++++++++++
1 file changed, 152 insertions(+)
diff --git a/data/marketing-exec-leads/index.html b/data/marketing-exec-leads/index.html
new file mode 100644
index 0000000..bbc9644
--- /dev/null
+++ b/data/marketing-exec-leads/index.html
@@ -0,0 +1,152 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>CRE Marketing Execs — Lead Desk</title>
+<style>
+:root{--cols:3;--bg:#0f1115;--card:#171a21;--line:#252a34;--ink:#e7eaf0;--dim:#9aa3b2;--teal:#39c2b8;--gold:#c9a24a;--red:#e0605e;--grn:#4bbf73}
+*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
+header{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;flex-wrap:wrap;gap:12px;align-items:center;position:sticky;top:0;background:var(--bg);z-index:5}
+h1{font-size:16px;margin:0;font-weight:600;letter-spacing:.02em}
+.spacer{flex:1}
+input,select{background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:7px 10px;font:inherit}
+input[type=search]{min-width:220px}
+label.ctl{display:flex;align-items:center;gap:6px;color:var(--dim);font-size:12px}
+.chips{display:flex;flex-wrap:wrap;gap:6px;padding:10px 18px;border-bottom:1px solid var(--line)}
+.chip{cursor:pointer;border:1px solid var(--line);border-radius:999px;padding:4px 11px;font-size:12px;color:var(--dim);user-select:none}
+.chip.on{background:var(--teal);border-color:var(--teal);color:#04110f;font-weight:600}
+.count{color:var(--dim);font-size:12px;padding:8px 18px}
+.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:12px;padding:0 18px 40px}
+.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:13px 14px;cursor:pointer;transition:border-color .15s}
+.card:hover{border-color:var(--teal)}
+.card .nm{font-weight:600;font-size:15px}
+.card .ti{color:var(--dim);font-size:12.5px;margin:2px 0 8px}
+.card .fm{font-size:13px;color:var(--ink)}
+.row{display:flex;gap:7px;align-items:center;margin-top:6px;font-size:12.5px}
+.row a{color:var(--teal);text-decoration:none}.row a:hover{text-decoration:underline}
+.k{color:var(--dim);min-width:52px}
+.badge{font-size:10.5px;padding:2px 7px;border-radius:999px;border:1px solid var(--line);color:var(--dim)}
+.b-conf{color:var(--grn);border-color:var(--grn)}.b-inf{color:var(--gold);border-color:var(--gold)}.b-none{color:var(--dim)}
+.b-t1{color:var(--grn);border-color:var(--grn)}.b-t2{color:var(--teal);border-color:var(--teal)}.b-t3{color:var(--gold);border-color:var(--gold)}
+.when{color:var(--dim);font-size:11px;margin-top:8px;display:flex;align-items:center;gap:5px}
+#detail{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;align-items:center;justify-content:center;z-index:20}
+#detail .panel{background:var(--card);border:1px solid var(--line);border-radius:14px;max-width:560px;width:92%;max-height:86vh;overflow:auto;padding:22px}
+#detail h2{margin:0 0 2px}#detail .x{float:right;cursor:pointer;color:var(--dim);font-size:20px}
+#detail table{width:100%;border-collapse:collapse;margin-top:12px}
+#detail td{padding:7px 6px;border-bottom:1px solid var(--line);vertical-align:top}
+#detail td.k{color:var(--dim);width:120px}
+#detail a{color:var(--teal)}
+</style></head>
+<body>
+<header>
+ <h1>CRE Marketing Execs · Lead Desk</h1>
+ <span class="badge" id="hdrcount"></span>
+ <div class="spacer"></div>
+ <input type="search" id="q" placeholder="Search all fields (space = AND)">
+ <label class="ctl">Sort
+ <select id="sort">
+ <option value="firm">Firm A→Z</option>
+ <option value="name">Contact A→Z</option>
+ <option value="tier">Tier</option>
+ <option value="email">Email status</option>
+ <option value="conf">Confidence</option>
+ </select>
+ </label>
+ <label class="ctl">Density <input type="range" id="dens" min="1" max="6" value="3"></label>
+</header>
+<div class="chips" id="chips"></div>
+<div class="count" id="count"></div>
+<div class="grid" id="grid"></div>
+
+<div id="detail"><div class="panel"><span class="x" onclick="closeD()">✕</span><div id="dbody"></div></div></div>
+
+<script>
+const IMPORTED = "2026-08-07"; // list import date (admin card rule)
+let ROWS=[], HEADERS=[], FILT={tier:new Set(),channel:new Set(),email:new Set()};
+
+function parseCSV(t){
+ const rows=[];let i=0,f='',row=[],q=false;
+ while(i<t.length){const c=t[i];
+ if(q){if(c=='"'){if(t[i+1]=='"'){f+='"';i++}else q=false}else f+=c}
+ else{if(c=='"')q=true;else if(c==','){row.push(f);f=''}else if(c=='\n'||c=='\r'){if(c=='\r'&&t[i+1]=='\n')i++;if(f!==''||row.length){row.push(f);rows.push(row);row=[];f=''}}else f+=c}
+ i++}
+ if(f!==''||row.length){row.push(f);rows.push(row)}
+ return rows;
+}
+function tierClass(t){return t=='1'?'b-t1':t=='2'?'b-t2':'b-t3'}
+function emClass(e){return /CONFIRMED/.test(e)?'b-conf':/INFERRED/.test(e)?'b-inf':'b-none'}
+
+function esc(s){return (s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]))}
+
+function render(){
+ const q=document.getElementById('q').value.toLowerCase().split(/\s+/).filter(Boolean);
+ let out=ROWS.filter(r=>{
+ if(FILT.tier.size && !FILT.tier.has(r.Tier)) return false;
+ if(FILT.email.size){const es=/CONFIRMED/.test(r['Email Status'])?'CONFIRMED':/INFERRED/.test(r['Email Status'])?'INFERRED':'NONE';if(!FILT.email.has(es))return false}
+ const hay=Object.values(r).join(' ').toLowerCase();
+ return q.every(t=>hay.includes(t));
+ });
+ const s=document.getElementById('sort').value;
+ out.sort((a,b)=>{
+ if(s=='firm')return (a.Firm||'').localeCompare(b.Firm||'');
+ if(s=='name')return (a['Contact Name']||'').localeCompare(b['Contact Name']||'');
+ if(s=='tier')return (a.Tier||'9').localeCompare(b.Tier||'9');
+ if(s=='email')return (a['Email Status']||'z').localeCompare(b['Email Status']||'z');
+ if(s=='conf'){const o={high:0,medium:1,low:2};return (o[a.Confidence]??3)-(o[b.Confidence]??3)}
+ return 0;
+ });
+ const g=document.getElementById('grid');g.innerHTML='';
+ out.forEach((r,idx)=>{
+ const named=r['Contact Name']&&!r['Contact Name'].startsWith('(');
+ const tier=r.Tier||'—';
+ const div=document.createElement('div');div.className='card';div.onclick=()=>openD(r);
+ div.innerHTML=`
+ <div class="nm">${esc(r['Contact Name'])||'<span style="color:var(--dim)">— unresolved —</span>'}</div>
+ <div class="ti">${esc(r.Title)||''}</div>
+ <div class="fm">${esc(r.Firm)}</div>
+ ${r.Phone?`<div class="row"><span class="k">Call</span><a href="tel:${esc(r.Phone.replace(/[^0-9x]/g,''))}">${esc(r.Phone)}</a>${r.Mobile?` · <a href="tel:${esc(r.Mobile.replace(/[^0-9]/g,''))}">m ${esc(r.Mobile)}</a>`:''}</div>`:''}
+ ${r.Email?`<div class="row"><span class="k">Email</span><a href="mailto:${esc(r.Email)}">${esc(r.Email)}</a></div>`:''}
+ <div class="row" style="margin-top:9px">
+ ${tier!=='—'?`<span class="badge ${tierClass(tier)}">Tier ${tier}</span>`:''}
+ ${r['Email Status']?`<span class="badge ${emClass(r['Email Status'])}">${/CONFIRMED/.test(r['Email Status'])?'Email ✓':/INFERRED/.test(r['Email Status'])?'Email ~inferred':''}</span>`:''}
+ ${r.Confidence?`<span class="badge">${esc(r.Confidence)}</span>`:''}
+ </div>
+ <div class="when" title="List imported ${IMPORTED}">🕓 imported ${IMPORTED}</div>`;
+ g.appendChild(div);
+ });
+ document.getElementById('count').textContent=`${out.length} of ${ROWS.length} contacts`;
+ document.getElementById('hdrcount').textContent=`${ROWS.filter(r=>r['Contact Name']&&!r['Contact Name'].startsWith('(')).length} named leads`;
+}
+function openD(r){
+ const b=document.getElementById('dbody');
+ b.innerHTML=`<h2>${esc(r['Contact Name'])}</h2><div style="color:var(--dim)">${esc(r.Title)} · ${esc(r.Firm)}</div><table>${
+ HEADERS.map(h=>r[h]?`<tr><td class="k">${esc(h)}</td><td>${
+ h=='Phone'||h=='Mobile'?`<a href="tel:${esc(r[h].replace(/[^0-9x]/g,''))}">${esc(r[h])}</a>`:
+ h=='Email'?`<a href="mailto:${esc(r[h])}">${esc(r[h])}</a>`:
+ h=='LinkedIn'?`<a href="${esc(r[h])}" target="_blank" rel="noopener noreferrer">${esc(r[h])}</a>`:
+ h=='Firm Website'?`<a href="https://${esc(r[h])}" target="_blank" rel="noopener noreferrer">${esc(r[h])}</a>`:
+ esc(r[h])}</td></tr>`:'').join('')}</table>`;
+ document.getElementById('detail').style.display='flex';
+}
+function closeD(){document.getElementById('detail').style.display='none'}
+document.getElementById('detail').onclick=e=>{if(e.target.id=='detail')closeD()};
+
+function buildChips(){
+ const c=document.getElementById('chips');
+ const add=(label,group,val)=>{const s=document.createElement('span');s.className='chip';s.textContent=label;
+ s.onclick=()=>{FILT[group].has(val)?FILT[group].delete(val):FILT[group].add(val);s.classList.toggle('on');render()};c.appendChild(s)};
+ c.insertAdjacentHTML('beforeend','<span class="k" style="align-self:center">Tier:</span>');
+ ['1','2','3'].forEach(t=>add('Tier '+t,'tier',t));
+ c.insertAdjacentHTML('beforeend','<span class="k" style="align-self:center;margin-left:10px">Email:</span>');
+ add('Confirmed','email','CONFIRMED');add('Inferred','email','INFERRED');add('None','email','NONE');
+}
+document.getElementById('dens').oninput=e=>document.documentElement.style.setProperty('--cols',e.target.value);
+['q','sort'].forEach(id=>document.getElementById(id).addEventListener('input',render));
+
+fetch('cre-marketing-execs-MASTER.csv').then(r=>r.text()).then(t=>{
+ const rows=parseCSV(t);HEADERS=rows[0];
+ ROWS=rows.slice(1).filter(r=>r.length>1).map(r=>Object.fromEntries(HEADERS.map((h,i)=>[h,r[i]||''])));
+ buildChips();render();
+}).catch(e=>{document.getElementById('grid').innerHTML='<p style="color:var(--red);padding:18px">Could not load CSV: '+e+'</p>'});
+</script>
+</body></html>
← abab627 CRE leads: personalized outreach queue (drafts only, sends g
·
back to Commercialrealestate
·
mls + properties grid: Day theme default (fresh key), Fields 1229a73 →