/** * Export Intel map β€” Leaflet + clustering + rich popups */ window.ExportIntelMap = (function () { var map = null; var clusterLayer = null; var onEntityClick = null; var halalMode = false; var markersById = {}; var HALAL_COLORS = { hot: '#22c55e', warm: '#eab308', mild: '#f97316', low: '#64748b', }; var TYPE_CFG = { distributor: { color: '#f59e0b', icon: 'πŸ“¦', label: 'Distributeur' }, wholesaler: { color: '#eab308', icon: 'πŸͺ', label: 'Groothandel' }, importer: { color: '#f97316', icon: '🚒', label: 'Importeur' }, logistics: { color: '#38bdf8', icon: '🚚', label: 'Logistiek' }, contract_caterer:{ color: '#a78bfa', icon: '🏒', label: 'Cateraar' }, restaurant: { color: '#34d399', icon: '🍽️', label: 'Restaurant' }, doner_shoarma: { color: '#4ade80', icon: 'πŸ₯™', label: 'DΓΆner / shoarma' }, butcher: { color: '#f87171', icon: 'πŸ₯©', label: 'Slager' }, foodservice: { color: '#2dd4bf', icon: '🍴', label: 'Foodservice' }, }; function cfg(type) { return TYPE_CFG[type] || { color: '#0ea5e9', icon: 'πŸ“', label: String(type || 'entity').replace(/_/g, ' ') }; } function escapeHtml(s) { return String(s || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function row(label, val, link) { if (!val) return ''; var inner = link ? '' + escapeHtml(val) + '' : escapeHtml(val); return ( '
' + '' + escapeHtml(label) + '' + '' + inner + '' + '
' ); } function halalColor(p) { var tier = p.halal_tier || 'low'; return HALAL_COLORS[tier] || HALAL_COLORS.low; } function popupHtml(p) { var c = cfg(p.entity_type); var id = p.id; var web = p.website; if (web && !/^https?:\/\//i.test(web)) web = 'https://' + web; var halalRow = ''; if (p.halal_score != null) { halalRow = row('Halal kans', p.halal_score + '/100' + (halalMode ? ' Β· ' + (p.halal_tier || '') : '')); } var contactBits = []; if (p.email) contactBits.push('πŸ“§ ' + escapeHtml(p.email)); if (p.phone) contactBits.push('πŸ“ž ' + escapeHtml(p.phone)); var contactRow = contactBits.length ? '
Contact' + contactBits.join('
') + '
' : ''; return ( '
' + '
' + '' + c.icon + ' ' + escapeHtml(c.label) + '' + '' + escapeHtml(p.country_iso2 || '') + '' + '
' + '

' + escapeHtml(p.name) + '

' + '
' + halalRow + contactRow + row('Stad', p.city) + row('Adres', p.address_line) + row('E-mail', p.email, p.email ? 'mailto:' + p.email : null) + row('Telefoon', p.phone, p.phone ? 'tel:' + p.phone : null) + row('Website', p.website, web) + row('Volume', p.volume_band) + row('Contacten', p.contact_count > 0 ? p.contact_count + ' in registry' : null) + row('Confidence', p.confidence != null ? p.confidence + '%' : null) + row('Pipeline', p.pipeline_stage) + '
' + '
' + '' + '
' + '
' ); } function pinIcon(type, p) { var c = cfg(type); var color = halalMode && p && p.halal_score != null ? halalColor(p) : c.color; var ring = halalMode && p && p.halal_tier === 'hot' ? ' ei-pin-hot' : ''; return L.divIcon({ className: 'ei-pin-wrap', html: '
' + '' + '' + c.icon + '' + '
', iconSize: [30, 30], iconAnchor: [15, 15], popupAnchor: [0, -16], }); } function openEntityId(rawId) { var id = Number(rawId); if (!Number.isFinite(id)) return; if (onEntityClick) { onEntityClick(id); return; } try { document.dispatchEvent(new CustomEvent('export-intel:open-entity', { detail: { id: id } })); } catch (e) { /* empty */ } } function wirePopup(container) { if (!container) return; var popupRoot = container.closest('.leaflet-popup') || container; if (window.L && L.DomEvent) { L.DomEvent.disableClickPropagation(popupRoot); L.DomEvent.disableScrollPropagation(popupRoot); } var btn = container.querySelector('.ei-popup-btn'); var id = btn && btn.getAttribute('data-entity-id'); if (btn && id) { var onBtn = function (e) { if (e) { e.preventDefault(); e.stopPropagation(); } openEntityId(id); }; if (window.L && L.DomEvent) { L.DomEvent.on(btn, 'click', onBtn); L.DomEvent.on(btn, 'mousedown', L.DomEvent.stopPropagation); } else { btn.addEventListener('click', onBtn); btn.addEventListener('mousedown', function (e) { e.stopPropagation(); }); } } container.addEventListener('click', function (e) { if (e.target.closest('.ei-popup-btn') || e.target.closest('a')) return; var card = e.target.closest('[data-entity-id]'); if (card) openEntityId(card.getAttribute('data-entity-id')); }); } function invalidateMapSize() { if (!map) return; map.invalidateSize(true); setTimeout(function () { if (map) map.invalidateSize(true); }, 120); setTimeout(function () { if (map) map.invalidateSize(true); }, 400); } function init(containerId) { var el = document.getElementById(containerId); if (!el || !window.L) return null; if (map && map.getContainer() === el) { invalidateMapSize(); return map; } if (map) { map.remove(); map = null; clusterLayer = null; } map = L.map(containerId, { zoomControl: true, attributionControl: true }).setView([20, 10], 2); L.tileLayer('/api/map/tiles/{z}/{x}/{y}.png', { attribution: '© OSM © CARTO', maxZoom: 19, }).addTo(map); if (window.L.markerClusterGroup) { clusterLayer = L.markerClusterGroup({ maxClusterRadius: 42, spiderfyOnMaxZoom: true, showCoverageOnHover: false, zoomToBoundsOnClick: true, iconCreateFunction: function (cluster) { var n = cluster.getChildCount(); var size = n < 10 ? 'sm' : n < 50 ? 'md' : 'lg'; return L.divIcon({ html: '
' + n + '
', className: 'ei-cluster-wrap', iconSize: L.point(44, 44), }); }, }); map.addLayer(clusterLayer); } else { clusterLayer = L.layerGroup(); map.addLayer(clusterLayer); } map.on('popupopen', function (e) { var root = e.popup && e.popup.getElement(); if (!root) return; if (window.L && L.DomEvent) { L.DomEvent.disableClickPropagation(root); L.DomEvent.disableScrollPropagation(root); } wirePopup(root.querySelector('.leaflet-popup-content') || root); }); invalidateMapSize(); return map; } function setFeatures(geojson, opts) { opts = opts || {}; if (!map || !clusterLayer) return; clusterLayer.clearLayers(); markersById = {}; var features = (geojson && geojson.features) || []; var markers = []; features.forEach(function (f) { var coords = f.geometry && f.geometry.coordinates; if (!coords) return; var p = f.properties || {}; var marker = L.marker([coords[1], coords[0]], { icon: pinIcon(p.entity_type, p) }); marker._eiId = p.id; if (p.id != null) markersById[p.id] = marker; marker.bindPopup(popupHtml(p), { maxWidth: 320, minWidth: 260, className: 'ei-leaflet-popup' }); marker.on('dblclick', function () { if (onEntityClick && p.id) onEntityClick(p.id); }); markers.push(marker); }); if (clusterLayer.addLayers) clusterLayer.addLayers(markers); else markers.forEach(function (m) { clusterLayer.addLayer(m); }); invalidateMapSize(); if (opts.autoFit === false) { map.setView([20, 10], 2); } else if (features.length === 1) { var c = features[0].geometry.coordinates; map.setView([c[1], c[0]], 10); } else if (opts.autoFit && markers.length > 1 && clusterLayer.getBounds) { try { map.fitBounds(clusterLayer.getBounds(), { padding: [40, 40], maxZoom: 12 }); } catch (e) { /* empty */ } } } function setHalalMode(on) { halalMode = !!on; } function setOnEntityClick(fn) { onEntityClick = fn; } function flyTo(lat, lon, zoom) { if (map && lat && lon) map.setView([lat, lon], zoom || 10); } function highlightEntity(id) { var marker = markersById[id]; if (!marker || !map) return; var latlng = marker.getLatLng(); map.setView(latlng, Math.max(map.getZoom(), 14)); marker.openPopup(); } function closePopup() { if (map) map.closePopup(); } function destroy() { if (map) { map.remove(); map = null; clusterLayer = null; } } if (typeof window !== 'undefined') { window.addEventListener('resize', function () { invalidateMapSize(); }); } return { init, setFeatures, setOnEntityClick, setHalalMode, flyTo, closePopup, highlightEntity, destroy, TYPE_CFG: TYPE_CFG, cfg: cfg }; })();