// DetailModal + MapView (Leaflet)
const { useState: _useState, useEffect: _useEffect, useRef: _useRef, useMemo: _useMemo } = React;
// --------------------------------------------------------------
// DETAIL MODAL
// --------------------------------------------------------------
function DetailModal({ listing, onClose }) {
const [idx, setIdx] = _useState(0);
const history = _useMemo(() => parsePriceHistory(listing.price_history), [listing.price_history]);
_useEffect(() => {
const onKey = (e) => {
if (e.key === "Escape") onClose();
if (e.key === "ArrowLeft") setIdx((i) => (i - 1 + listing.photos.length) % listing.photos.length);
if (e.key === "ArrowRight") setIdx((i) => (i + 1) % listing.photos.length);
};
window.addEventListener("keydown", onKey);
document.body.style.overflow = "hidden";
return () => {
window.removeEventListener("keydown", onKey);
document.body.style.overflow = "";
};
}, [listing.photos.length, onClose]);
// mini-map render on listing change
const hasGeo = listing.latitude != null && listing.longitude != null;
const mapRef = _useRef(null);
_useEffect(() => {
if (!mapRef.current || !window.L || !hasGeo) return;
const map = window.L.map(mapRef.current, {
zoomControl: false, attributionControl: false,
scrollWheelZoom: false, dragging: false
}).setView([listing.latitude, listing.longitude], 13);
window.L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19, crossOrigin: true,
}).addTo(map);
// divIcon dot (no marker-image asset dependency)
window.L.marker([listing.latitude, listing.longitude], {
icon: window.L.divIcon({ className: "geo-dot", html: "", iconSize: [18, 18] })
}).addTo(map);
return () => map.remove();
}, [listing.id, hasGeo]);
return (
e.stopPropagation()}>
REF{listing.ref_id}
·
ID {listing.id}

{listing.photos.length > 1 && (
<>
{listing.photos.map((_, i) => (
setIdx(i)}/>
))}
>
)}
{listing.photos.map((p, i) => (
))}
{listing.title}
{listing.location}
{listing.description}
Amenities
{listing.amenities.map((a) => (
{a}
))}
{fmtPrice(listing.price)}/yr
{(listing.price / 12).toLocaleString("en-US", { maximumFractionDigits: 0 })} AED/month ·
{" "}{(listing.price / listing.size_sqft).toFixed(1)} AED/sqft
Bedrooms
{listing.bedrooms}
Bathrooms
{listing.bathrooms}
Size
{listing.size_sqft.toLocaleString()} sqft
Type
{listing.property_type}
Furnishing
{listing.furnished}
Balcony
{listing.balcony}
Water view
{listing.water_view}
Status
{listing.status}
{history.length > 0 ? (
Price history
{history.map((h, i) => {
const up = h.to > h.from;
return (
{fmtDate(h.date)}
{fmtPriceShort(h.from)}
→
{fmtPriceShort(h.to)}
{up ? : }
{Math.round(Math.abs((h.to - h.from) / h.from) * 100)}%
);
})}
) : (
Price history
No price changes since {fmtDate(listing.first_seen)}.
)}
Location
{hasGeo ? (
<>
{listing.latitude.toFixed(4)}, {listing.longitude.toFixed(4)}
>
) : (
No map coordinates for this listing.
)}
);
}
// --------------------------------------------------------------
// MAP VIEW
// --------------------------------------------------------------
function MapView({ listings, onOpen, activeId, setActiveId }) {
const ref = _useRef(null);
const mapRef = _useRef(null);
const markersRef = _useRef({});
// init map once
_useEffect(() => {
if (!ref.current || !window.L) return;
const map = window.L.map(ref.current, {
zoomControl: true, attributionControl: true
}).setView([24.466, 54.49], 11);
window.L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '© OpenStreetMap'
}).addTo(map);
mapRef.current = map;
return () => { map.remove(); mapRef.current = null; };
}, []);
// rebuild markers when listings change
_useEffect(() => {
const map = mapRef.current;
if (!map || !window.L) return;
Object.values(markersRef.current).forEach((m) => map.removeLayer(m));
markersRef.current = {};
const geoListings = listings.filter((l) => l.latitude != null && l.longitude != null);
if (!geoListings.length) return;
const bounds = window.L.latLngBounds([]);
geoListings.forEach((l) => {
const isDelisted = l.status === "delisted";
const isActive = l.id === activeId;
const icon = window.L.divIcon({
className: "price-marker-wrap",
html: `
${fmtPriceShort(l.price)}
`,
iconSize: [0, 0],
iconAnchor: [0, 0]
});
const m = window.L.marker([l.latitude, l.longitude], { icon, riseOnHover: true });
m.on("click", () => setActiveId(l.id));
m.bindPopup(buildPopupHtml(l), { offset: [0, -8], closeButton: false, autoClose: true });
m.on("popupopen", () => {
const node = m.getPopup().getElement();
node?.querySelector(".minicard__cta")?.addEventListener("click", () => onOpen(l));
node?.querySelector(".minicard")?.addEventListener("click", () => onOpen(l));
});
m.addTo(map);
markersRef.current[l.id] = m;
bounds.extend([l.latitude, l.longitude]);
});
if (bounds.isValid()) {
map.fitBounds(bounds, { padding: [60, 60], maxZoom: 13 });
}
}, [listings]);
// open popup of active marker
_useEffect(() => {
if (!activeId) return;
const m = markersRef.current[activeId];
if (m) m.openPopup();
}, [activeId, listings]);
function buildPopupHtml(l) {
return `
${fmtPrice(l.price)} /yr
${l.title}
${l.bedrooms} bd · ${l.bathrooms} ba · ${l.size_sqft.toLocaleString()} sqft
Open details →
`;
}
return
;
}
Object.assign(window, { DetailModal, MapView });