feat(frontend): Kartenansicht für Bienenstände (Später-Liste)
Neue Seite /apiaries/map zeigt alle Bienenstände mit hinterlegten Koordinaten auf einer Leaflet-Karte (OpenStreetMap-Kacheln), mit Marker-Popup, das zum jeweiligen Bienenstand verlinkt. Karte wird per next/dynamic mit ssr:false geladen, da Leaflet auf window/document zugreift. Breiten-/Längengrad waren zwar seit dem MVP im Backend-DTO vorhanden, aber über kein Formular erreichbar – ohne diese Lücke wäre die Karte dauerhaft leer geblieben. Daher Breitengrad-/Längengrad-Felder auf der Bienenstand-Anlegen- und Bearbeiten-Seite ergänzt. Marker-Icons werden aus public/leaflet/ bedient statt aus node_modules-Bild-Imports, da Turbopack Bilder aus node_modules nicht als StaticImageData auflöst (führte zu "iconUrl not set"-Laufzeitfehler, live im Browser gefunden und behoben) – der von Leaflet empfohlene Workaround für Bundler-Icon-Probleme. Live verifiziert: Koordinaten speichern, Karte zeigt Marker an korrekter Position, Popup verlinkt korrekt, Empty State bei fehlenden Koordinaten, mobile Breite ohne Overflow. Zero Konsolenfehler.
This commit is contained in:
1 parent
4b9f5c7f47
commit
aa3a777381
11 files changed
+238
-5
No files matched your search
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";
|
||||
import { Apiary } from "@/lib/apiaries/types";
|
||||
|
||||
const markerIcon = L.icon({
|
||||
iconUrl: "/leaflet/marker-icon.png",
|
||||
iconRetinaUrl: "/leaflet/marker-icon-2x.png",
|
||||
shadowUrl: "/leaflet/marker-shadow.png",
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41],
|
||||
});
|
||||
|
||||
interface ApiaryMapProps {
|
||||
apiaries: (Apiary & { latitude: number; longitude: number })[];
|
||||
}
|
||||
|
||||
export function ApiaryMap({ apiaries }: ApiaryMapProps) {
|
||||
const center: [number, number] = [apiaries[0].latitude, apiaries[0].longitude];
|
||||
|
||||
return (
|
||||
<MapContainer
|
||||
center={center}
|
||||
zoom={9}
|
||||
scrollWheelZoom
|
||||
className="h-[60vh] w-full rounded-2xl"
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{apiaries.map((apiary) => (
|
||||
<Marker key={apiary.id} position={[apiary.latitude, apiary.longitude]} icon={markerIcon}>
|
||||
<Popup>
|
||||
<Link href={`/apiaries/${apiary.id}`} className="font-medium text-primary underline">
|
||||
{apiary.name}
|
||||
</Link>
|
||||
{apiary.location ? <p className="text-sm text-muted-foreground">{apiary.location}</p> : null}
|
||||
</Popup>
|
||||
</Marker>
|
||||
))}
|
||||
</MapContainer>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user