[object Object]

โ† 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,'&amp;').replace(/</g,'&lt;');
+  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 โ†’