import React, { useEffect, useState } from 'react' import { MapContainer, TileLayer, CircleMarker, Popup, Tooltip } from 'react-leaflet' // import MarkerClusterGroup from 'react-leaflet-cluster' import L from 'leaflet' import 'leaflet/dist/leaflet.css' import './MapStyles.css' import { MapPin, TrendingUp, DollarSign, Building2 } from 'lucide-react' // Fix for default markers in react-leaflet delete L.Icon.Default.prototype._getIconUrl L.Icon.Default.mergeOptions({ iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png', iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png', }) const GeographicHeatMap = ({ data = [], height = '500px', onAreaClick, showClusters = true, colorScheme = 'viridis' }) => { const [mapCenter, setMapCenter] = useState([25.2048, 55.2708]) // Dubai coordinates const [mapZoom, setMapZoom] = useState(11) // Handle case where data might be an object with a results property const actualData = data && typeof data === 'object' && !Array.isArray(data) && data.results ? data.results : Array.isArray(data) ? data : [] // Dubai area coordinates mapping const areaCoordinates = { 'Downtown Dubai': [25.1972, 55.2744], 'Dubai Marina': [25.0772, 55.1308], 'Jumeirah': [25.2100, 55.2600], 'Palm Jumeirah': [25.1124, 55.1390], 'Business Bay': [25.1881, 55.2653], 'DIFC': [25.2138, 55.2792], 'JBR': [25.0772, 55.1308], 'Dubai Hills': [25.1500, 55.3000], 'Arabian Ranches': [25.1000, 55.2000], 'Jumeirah Village': [25.1500, 55.2500], 'Dubai Silicon Oasis': [25.1167, 55.3833], 'Dubai Sports City': [25.0500, 55.2000], 'Dubai Investment Park': [25.0167, 55.2000], 'International City': [25.1333, 55.4000], 'Discovery Gardens': [25.0167, 55.2000], 'Jumeirah Lake Towers': [25.0667, 55.1500], 'Dubai Healthcare City': [25.2333, 55.3000], 'Dubai International Financial Centre': [25.2138, 55.2792], 'Dubai Creek Harbour': [25.2000, 55.3500], 'Dubai Hills Estate': [25.1500, 55.3000], } // Color schemes for different metrics const colorSchemes = { viridis: ['#440154', '#482777', '#3f4a8a', '#31678e', '#26838f', '#1f9d8a', '#6cce5a', '#b6de2b', '#fee825'], plasma: ['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26'], inferno: ['#000004', '#1b0c42', '#4a0c6b', '#781c6d', '#a52c60', '#cf4446', '#ed6925', '#fb9a06', '#fcce25'], magma: ['#000004', '#1d1147', '#51127c', '#822681', '#b63679', '#e65164', '#fb8861', '#fec287', '#fcfdbf'], cool: ['#003f5c', '#2e4a62', '#4d5568', '#6c606e', '#8b6b74', '#aa767a', '#c98180', '#e88c86', '#ff9a8c'], hot: ['#000000', '#330000', '#660000', '#990000', '#cc0000', '#ff0000', '#ff3300', '#ff6600', '#ff9900'] } const getColorForValue = (value, maxValue, minValue = 0) => { const colors = colorSchemes[colorScheme] || colorSchemes.viridis const normalizedValue = (value - minValue) / (maxValue - minValue) const colorIndex = Math.floor(normalizedValue * (colors.length - 1)) return colors[Math.max(0, Math.min(colorIndex, colors.length - 1))] } const getRadiusForValue = (value, maxValue, minValue = 0) => { const minRadius = 5 const maxRadius = 25 const normalizedValue = (value - minValue) / (maxValue - minValue) return minRadius + (normalizedValue * (maxRadius - minRadius)) } // Process data to get min/max values for normalization const processedData = Array.isArray(actualData) ? actualData.map(item => { if (!item) return null const coords = areaCoordinates[item.area_en] || [25.2048 + (Math.random() - 0.5) * 0.1, 55.2708 + (Math.random() - 0.5) * 0.1] return { ...item, coordinates: coords, value: item.transaction_count || item.total_value || item.average_price || 0 } }).filter(Boolean) : [] const maxValue = processedData.length > 0 ? Math.max(...processedData.map(item => item.value)) : 0 const minValue = processedData.length > 0 ? Math.min(...processedData.map(item => item.value)) : 0 const formatValue = (value, type = 'count') => { if (type === 'price') { return `AED ${typeof value === 'number' ? value.toLocaleString() : '0'}` } else if (type === 'count') { return typeof value === 'number' ? value.toLocaleString() : '0' } return value } const getMetricType = () => { if (!actualData || !Array.isArray(actualData) || actualData.length === 0) return 'count' const firstItem = actualData[0] if (firstItem && firstItem.total_value) return 'price' if (firstItem && firstItem.average_price) return 'price' return 'count' } const metricType = getMetricType() // Show loading state if data is not ready if (!actualData || actualData === undefined) { return (

Geographic Heat Map

Property activity distribution across Dubai areas

Loading map data...

) } return (

Geographic Heat Map

Property activity distribution across Dubai areas

Low Activity
Medium
High Activity
{processedData.length > 0 ? ( {/* Temporarily disabled clustering */} {false && showClusters ? ( {processedData.map((item, index) => ( onAreaClick && onAreaClick(item) }} >

{item.area_en}

Transactions: {formatValue(item.transaction_count || 0, 'count')}
{item.total_value && (
Total Value: {formatValue(item.total_value, 'price')}
)} {item.average_price && (
Avg Price: {formatValue(item.average_price, 'price')}
)} {item.price_per_sqft && (
Price/Sqft: AED {typeof item.price_per_sqft === 'number' ? item.price_per_sqft.toFixed(2) : '0'}
)}
{item.area_en}
{formatValue(item.value, metricType)}
))}
) : ( processedData.map((item, index) => ( onAreaClick && onAreaClick(item) }} >

{item.area_en}

Transactions: {formatValue(item.transaction_count || 0, 'count')}
{item.total_value && (
Total Value: {formatValue(item.total_value, 'price')}
)} {item.average_price && (
Avg Price: {formatValue(item.average_price, 'price')}
)}
)) )}
) : (

No Geographic Data

{!Array.isArray(actualData) ? 'Data format error - expected array but received: ' + typeof actualData : 'No location data available for the selected filters' }

{!Array.isArray(actualData) && (

Debug: {JSON.stringify(actualData)}

)}
)}
) } export default GeographicHeatMap