111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import L from "leaflet";
|
|
|
|
// Import leaflet CSS
|
|
import "leaflet/dist/leaflet.css";
|
|
|
|
interface CountryData {
|
|
name: string;
|
|
count: number;
|
|
position: [number, number]; // latitude, longitude
|
|
}
|
|
|
|
interface MapComponentProps {
|
|
countries: CountryData[];
|
|
onCountrySelect: (country: string) => void;
|
|
selectedCountry: string | null;
|
|
}
|
|
|
|
export function MapComponent({ countries, onCountrySelect, selectedCountry }: MapComponentProps) {
|
|
// Use a ref to track if map is initialized
|
|
const mapRef = useRef<L.Map | null>(null);
|
|
const mapContainerRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
// Only initialize map if it doesn't exist and we have a container
|
|
if (!mapRef.current && mapContainerRef.current) {
|
|
// Create the map instance
|
|
mapRef.current = L.map(mapContainerRef.current).setView([20, 0], 2);
|
|
|
|
// Add tile layer
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
}).addTo(mapRef.current);
|
|
|
|
// Add zoom control
|
|
L.control.zoom({
|
|
position: 'bottomright'
|
|
}).addTo(mapRef.current);
|
|
}
|
|
|
|
// Cleanup function to remove the map when component unmounts
|
|
return () => {
|
|
if (mapRef.current) {
|
|
mapRef.current.remove();
|
|
mapRef.current = null;
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
// Update markers when countries or selection changes
|
|
useEffect(() => {
|
|
if (!mapRef.current) return;
|
|
|
|
// Clear existing markers
|
|
mapRef.current.eachLayer((layer) => {
|
|
if (layer instanceof L.Marker) {
|
|
mapRef.current?.removeLayer(layer);
|
|
}
|
|
});
|
|
|
|
// Add markers for each country
|
|
countries.forEach((country) => {
|
|
// Create custom icon
|
|
const size = Math.min(Math.max(20, country.count * 5), 40);
|
|
const icon = L.divIcon({
|
|
html: `<div style="
|
|
background-color: ${country.name === selectedCountry ? '#3b82f6' : '#ef4444'};
|
|
color: white;
|
|
border-radius: 50%;
|
|
width: ${size}px;
|
|
height: ${size}px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
font-weight: bold;
|
|
font-size: ${size > 30 ? 14 : 12}px;
|
|
box-shadow: 0 0 0 2px white;
|
|
">${country.count}</div>`,
|
|
className: '',
|
|
iconSize: [size, size],
|
|
iconAnchor: [size/2, size/2]
|
|
});
|
|
|
|
// Create marker
|
|
const marker = L.marker(country.position, { icon })
|
|
.addTo(mapRef.current!);
|
|
|
|
// Add popup
|
|
marker.bindPopup(`
|
|
<div style="text-align: center;">
|
|
<strong>${country.name}</strong>
|
|
<div>${country.count} news articles</div>
|
|
</div>
|
|
`);
|
|
|
|
// Add click handler
|
|
marker.on('click', () => {
|
|
onCountrySelect(country.name);
|
|
});
|
|
});
|
|
}, [countries, selectedCountry, onCountrySelect]);
|
|
|
|
return (
|
|
<div
|
|
ref={mapContainerRef}
|
|
style={{ width: '100%', height: '100%' }}
|
|
/>
|
|
);
|
|
}
|