96 lines
3.3 KiB
JavaScript
96 lines
3.3 KiB
JavaScript
import React, { useEffect } from 'react';
|
||
import { MapContainer, TileLayer, Marker, Popup, Circle, useMap } from 'react-leaflet';
|
||
import L from 'leaflet';
|
||
import 'leaflet/dist/leaflet.css';
|
||
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
|
||
import markerIcon from 'leaflet/dist/images/marker-icon.png';
|
||
import markerShadow from 'leaflet/dist/images/marker-shadow.png';
|
||
import './MapView.css';
|
||
|
||
// Standard-Marker-Icons aus dem installierten Leaflet-Paket buendeln.
|
||
// Vorher kamen sie von cdnjs — in einer Offline-PWA fuer den Wald waren die
|
||
// Marker damit ohne Empfang kaputt, und die IP jedes Besuchers ging an ein
|
||
// fremdes CDN. Die Dateien liegen ohnehin in leaflet/dist/images.
|
||
delete L.Icon.Default.prototype._getIconUrl;
|
||
L.Icon.Default.mergeOptions({
|
||
iconRetinaUrl: markerIcon2x,
|
||
iconUrl: markerIcon,
|
||
shadowUrl: markerShadow,
|
||
});
|
||
|
||
// Komponente zum Aktualisieren der Kartenansicht
|
||
function MapUpdater({ center, zoom }) {
|
||
const map = useMap();
|
||
|
||
useEffect(() => {
|
||
if (center && zoom) {
|
||
map.setView(center, zoom, { animate: true, duration: 0.5 });
|
||
}
|
||
}, [center, zoom, map]);
|
||
|
||
return null;
|
||
}
|
||
|
||
const MapView = ({ users, center = [52.5, 9.5], zoom = 8, userLocation = null, radiusKm = 25 }) => {
|
||
const usersWithGPS = users ? users.filter(user => user.gps && user.gps.lat && user.gps.lng) : [];
|
||
|
||
// Berechne Kartenmittelpunkt und Zoom basierend auf Benutzerposition und Radius
|
||
const mapCenter = userLocation ? [userLocation.lat, userLocation.lng] : center;
|
||
const mapZoom = userLocation ? Math.max(8, Math.min(13, Math.round(14 - Math.log2(radiusKm)))) : zoom;
|
||
|
||
return (
|
||
<div className="map-container">
|
||
<MapContainer
|
||
center={mapCenter}
|
||
zoom={mapZoom}
|
||
style={{ height: '500px', width: '100%' }}
|
||
scrollWheelZoom={true}
|
||
>
|
||
<MapUpdater center={mapCenter} zoom={mapZoom} />
|
||
<TileLayer
|
||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||
/>
|
||
{userLocation && (
|
||
<>
|
||
<Marker position={[userLocation.lat, userLocation.lng]}>
|
||
<Popup>
|
||
<div className="map-popup">
|
||
<h3>Ihr Standort</h3>
|
||
</div>
|
||
</Popup>
|
||
</Marker>
|
||
<Circle
|
||
center={[userLocation.lat, userLocation.lng]}
|
||
radius={radiusKm * 1000}
|
||
pathOptions={{ color: 'blue', fillColor: 'blue', fillOpacity: 0.1 }}
|
||
/>
|
||
</>
|
||
)}
|
||
{usersWithGPS.map(user => (
|
||
<Marker
|
||
key={user._id}
|
||
position={[user.gps.lat, user.gps.lng]}
|
||
>
|
||
<Popup>
|
||
<div className="map-popup">
|
||
<h3>{user.name}</h3>
|
||
<p>{user.address}</p>
|
||
<p>
|
||
<a href={`tel:${user.phone}`}>{user.phone}</a>
|
||
</p>
|
||
<p className="map-popup-type">{user.type}</p>
|
||
</div>
|
||
</Popup>
|
||
</Marker>
|
||
))}
|
||
</MapContainer>
|
||
{usersWithGPS.length === 0 && !userLocation && (
|
||
<div className="map-no-data-overlay">Keine GPS-Daten verfügbar – Karte wird trotzdem angezeigt.</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default MapView;
|