โ back to Commercialrealestate
Map markers โ 100-yr property-history popup (๐ History link in every marker popup)
ac6be59221b7fe63f909a217c412350810b84758 ยท 2026-06-30 17:49:46 -0700 ยท Steve Abrams
Adds a '๐ History' link to each Leaflet marker popup opening the address-only property-history modal
(assessor parcel + sold history + permits) via /api/property-history โ the same feature now on the MLS
grid, front-page + broker modals. Self-contained modal on map.html. Verified headless: opens, 0 errors.
Files touched
Diff
commit ac6be59221b7fe63f909a217c412350810b84758
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 30 17:49:46 2026 -0700
Map markers โ 100-yr property-history popup (๐ History link in every marker popup)
Adds a '๐ History' link to each Leaflet marker popup opening the address-only property-history modal
(assessor parcel + sold history + permits) via /api/property-history โ the same feature now on the MLS
grid, front-page + broker modals. Self-contained modal on map.html. Verified headless: opens, 0 errors.
---
public/map.html | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/public/map.html b/public/map.html
index 8100a95..01ef6da 100644
--- a/public/map.html
+++ b/public/map.html
@@ -138,6 +138,7 @@ function popupHtml(p){
`<img loading="lazy" src="${aerialUrl(p.lat,p.lng)}" alt="aerial view" onerror="this.style.display='none';this.nextElementSibling.textContent='Aerial imagery unavailable for this point';">`+
`<div class="aerialcap">Overhead imagery ยฉ Esri World Imagery (free)</div>`+
`<div class="links">`+
+ `<a href="#" onclick="mapPropHistory('${(p.address||'').replace(/'/g,"\\'")}','${(p.city||'').replace(/'/g,"\\'")}');return false;">๐ History</a>`+
(p.source?`<a href="${p.source}" target="_blank" rel="noopener">View listing โ</a>`:'')+
`<a href="${ln}" target="_blank" rel="noopener">LoopNet โ</a>`+
`<a href="https://www.google.com/maps/search/?api=1&query=${p.lat},${p.lng}" target="_blank" rel="noopener">Google Maps โ</a>`+
@@ -222,5 +223,40 @@ function buildLegend(){
}));
}
</script>
+<!-- 100-yr property-history popup (address-only, no names) โ reuses /api/property-history -->
+<div id="phov"><div id="phmodal"><button id="phx" aria-label="Close">โ</button><div id="phbody"></div></div></div>
+<style>
+ #phov{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:9999;align-items:flex-start;justify-content:center;padding:40px 14px;overflow-y:auto;}
+ #phov.on{display:flex;}
+ #phmodal{width:min(640px,96vw);background:#161b22;border:1px solid #2a313c;border-radius:15px;padding:20px 22px;position:relative;color:#e6edf3;font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;}
+ #phmodal h3{margin:0 0 2px;font-size:19px;} #phmodal .s{color:#d29922;font-size:13px;}
+ #phx{position:absolute;top:12px;right:14px;background:none;border:0;color:#8b949e;font-size:22px;cursor:pointer;}
+ #phmodal .ci{display:flex;gap:14px;flex-wrap:wrap;margin:8px 0;font-size:13.5px;}
+ #phmodal .sec{margin-top:14px;} #phmodal .sec h4{margin:0 0 6px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#8b949e;}
+ #phmodal .it{display:flex;justify-content:space-between;gap:10px;padding:4px 0;border-bottom:1px solid #20262f;font-size:13px;} #phmodal .m{color:#8b949e;}
+</style>
+<script>
+(function(){
+ var esc=s=>(s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<');
+ var mny=n=>(n==null||n==='')?'โ':'$'+Number(n).toLocaleString();
+ document.getElementById('phx').onclick=()=>document.getElementById('phov').classList.remove('on');
+ document.getElementById('phov').addEventListener('click',e=>{ if(e.target.id==='phov') e.currentTarget.classList.remove('on'); });
+ window.mapPropHistory=async function(address,city){
+ var ov=document.getElementById('phov'), mb=document.getElementById('phbody'); ov.classList.add('on'); mb.innerHTML='<div class="m">loading history for '+esc(address)+'โฆ</div>';
+ var d; try{ d=await (await fetch('/api/property-history?address='+encodeURIComponent(address)+'&city='+encodeURIComponent(city||''))).json(); }catch(e){ mb.innerHTML='<div class="m">error</div>'; return; }
+ var a=d.assessor||d.assessorArchive;
+ var h='<h3>'+esc(address)+'</h3><div class="s">'+esc(city||'')+' ยท property history (address only โ no names)</div>';
+ if(a){ h+='<div class="sec"><h4>Assessor parcel'+(a.roll_year?' ('+esc(a.roll_year)+' roll)':'')+'</h4><div class="ci">'
+ +(a.year_built?'<span>๐ Built '+a.year_built+'</span>':'')+(a.bedrooms!=null?'<span>๐ '+a.bedrooms+' bd</span>':'')+(a.bathrooms!=null?'<span>๐ '+a.bathrooms+' ba</span>':'')
+ +(a.sqft_main?'<span>๐ '+Number(a.sqft_main).toLocaleString()+' sqft</span>':'')+(a.units?'<span>๐ '+a.units+' units</span>':'')
+ +((a.use_desc2||a.use_desc1||a.use_description)?'<span>๐ท '+esc(a.use_desc2||a.use_desc1||a.use_description)+'</span>':'')
+ +((a.roll_total_value||a.total_value)?'<span>๐ฐ assessed '+mny(a.roll_total_value||a.total_value)+'</span>':'')+'</div></div>'; }
+ h+='<div class="sec"><h4>Sold history ('+d.sales.length+')</h4>'+(d.sales.length?d.sales.map(x=>'<div class="it"><span>'+esc(x.address)+(x.city?', '+esc(x.city):'')+'</span><span class="m">'+mny(x.sold_price)+' ยท '+String(x.sold_date||'').slice(0,10)+(x.mls?' ยท MLS '+esc(x.mls):'')+'</span></div>').join(''):'<div class="m">no sold records on file</div>')+'</div>';
+ if(d.permits&&d.permits.length) h+='<div class="sec"><h4>Permits ('+d.permits.length+')</h4>'+d.permits.map(p=>'<div class="it"><span>'+esc(p.permit_type||p.use_description||'permit')+'</span><span class="m">'+esc(String(p.issue_date||'').slice(0,10))+(p.valuation?' ยท '+mny(p.valuation):'')+'</span></div>').join('')+'</div>';
+ if(!a && !d.sales.length && !(d.permits||[]).length) h+='<div class="m">No history on file for this address yet.</div>';
+ mb.innerHTML=h;
+ };
+})();
+</script>
</body>
</html>
โ 1760f5c assessor keyset: stream line-count (1.3GB file exceeds V8 ma
ยท
back to Commercialrealestate
ยท
chore: lint โ, v0.1.1 (session close โ condos render fix) ddb1e4d โ