feat: enhance map functionality by adding URL state management and event handling for view changes
This commit is contained in:
@@ -400,12 +400,12 @@ export default async function ProjectViewPage({ params }) {
|
||||
<div className="mb-8">
|
||||
{" "}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardHeader> <div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
Project Location
|
||||
</h2>
|
||||
<Link href="/projects/map">
|
||||
{project.coordinates && (
|
||||
<Link href={`/projects/map?lat=${project.coordinates.split(',')[0].trim()}&lng=${project.coordinates.split(',')[1].trim()}&zoom=16`}>
|
||||
<Button variant="outline" size="sm">
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
@@ -423,6 +423,7 @@ export default async function ProjectViewPage({ params }) {
|
||||
View on Full Map
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import Button from "@/components/ui/Button";
|
||||
|
||||
// Dynamically import the map component to avoid SSR issues
|
||||
@@ -16,9 +17,12 @@ const DynamicMap = dynamic(() => import("@/components/ui/LeafletMap"), {
|
||||
});
|
||||
|
||||
export default function ProjectsMapPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const [projects, setProjects] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [mapCenter, setMapCenter] = useState([50.0614, 19.9366]); // Default to Krakow, Poland
|
||||
const [mapZoom, setMapZoom] = useState(10); // Default zoom level
|
||||
const [statusFilters, setStatusFilters] = useState({
|
||||
registered: true,
|
||||
in_progress_design: true,
|
||||
@@ -64,7 +68,6 @@ export default function ProjectsMapPage() {
|
||||
);
|
||||
setStatusFilters(newState);
|
||||
};
|
||||
|
||||
// Toggle status filter
|
||||
const toggleStatusFilter = (status) => {
|
||||
setStatusFilters((prev) => ({
|
||||
@@ -72,8 +75,49 @@ export default function ProjectsMapPage() {
|
||||
[status]: !prev[status],
|
||||
}));
|
||||
};
|
||||
// Hide navigation and ensure full-screen layout
|
||||
// Update URL with current map state (debounced to avoid too many updates)
|
||||
const updateURL = (center, zoom) => {
|
||||
const params = new URLSearchParams();
|
||||
params.set('lat', center[0].toFixed(6));
|
||||
params.set('lng', center[1].toFixed(6));
|
||||
params.set('zoom', zoom.toString());
|
||||
|
||||
// Use replace to avoid cluttering browser history
|
||||
router.replace(`/projects/map?${params.toString()}`, { scroll: false });
|
||||
};
|
||||
|
||||
// Handle map view changes with debouncing
|
||||
const handleMapViewChange = (center, zoom) => {
|
||||
setMapCenter(center);
|
||||
setMapZoom(zoom);
|
||||
|
||||
// Debounce URL updates to avoid too many history entries
|
||||
clearTimeout(window.mapUpdateTimeout);
|
||||
window.mapUpdateTimeout = setTimeout(() => {
|
||||
updateURL(center, zoom);
|
||||
}, 500); // Wait 500ms after the last move to update URL
|
||||
};// Hide navigation and ensure full-screen layout
|
||||
useEffect(() => {
|
||||
// Check for URL parameters for coordinates and zoom
|
||||
const lat = searchParams.get('lat');
|
||||
const lng = searchParams.get('lng');
|
||||
const zoom = searchParams.get('zoom');
|
||||
|
||||
if (lat && lng) {
|
||||
const latitude = parseFloat(lat);
|
||||
const longitude = parseFloat(lng);
|
||||
if (!isNaN(latitude) && !isNaN(longitude)) {
|
||||
setMapCenter([latitude, longitude]);
|
||||
}
|
||||
}
|
||||
|
||||
if (zoom) {
|
||||
const zoomLevel = parseInt(zoom);
|
||||
if (!isNaN(zoomLevel) && zoomLevel >= 1 && zoomLevel <= 20) {
|
||||
setMapZoom(zoomLevel);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide navigation bar for full-screen experience
|
||||
const nav = document.querySelector("nav");
|
||||
if (nav) {
|
||||
@@ -83,7 +127,6 @@ export default function ProjectsMapPage() {
|
||||
// Prevent scrolling on body
|
||||
document.body.style.overflow = "hidden";
|
||||
document.documentElement.style.overflow = "hidden";
|
||||
|
||||
// Cleanup when leaving page
|
||||
return () => {
|
||||
if (nav) {
|
||||
@@ -91,15 +134,24 @@ export default function ProjectsMapPage() {
|
||||
}
|
||||
document.body.style.overflow = "";
|
||||
document.documentElement.style.overflow = "";
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Clear any pending URL updates
|
||||
if (window.mapUpdateTimeout) {
|
||||
clearTimeout(window.mapUpdateTimeout);
|
||||
}
|
||||
};
|
||||
}, [searchParams]);
|
||||
useEffect(() => {
|
||||
fetch("/api/projects")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setProjects(data);
|
||||
|
||||
// Only calculate center based on projects if no URL parameters are provided
|
||||
const lat = searchParams.get('lat');
|
||||
const lng = searchParams.get('lng');
|
||||
|
||||
if (!lat || !lng) {
|
||||
// Calculate center based on projects with coordinates
|
||||
const projectsWithCoords = data.filter((p) => p.coordinates);
|
||||
if (projectsWithCoords.length > 0) {
|
||||
@@ -121,6 +173,7 @@ export default function ProjectsMapPage() {
|
||||
|
||||
setMapCenter([avgLat, avgLng]);
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
})
|
||||
@@ -128,7 +181,7 @@ export default function ProjectsMapPage() {
|
||||
console.error("Error fetching projects:", error);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
}, [searchParams]);
|
||||
// Convert projects to map markers with filtering
|
||||
const markers = projects
|
||||
.filter((project) => project.coordinates)
|
||||
@@ -506,14 +559,14 @@ export default function ProjectsMapPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute inset-0">
|
||||
) : ( <div className="absolute inset-0">
|
||||
<DynamicMap
|
||||
center={mapCenter}
|
||||
zoom={10}
|
||||
zoom={mapZoom}
|
||||
markers={markers}
|
||||
showLayerControl={true}
|
||||
defaultLayer="Polish Geoportal Orthophoto"
|
||||
onViewChange={handleMapViewChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Marker,
|
||||
Popup,
|
||||
LayersControl,
|
||||
useMapEvents,
|
||||
} from "react-leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { useEffect } from "react";
|
||||
@@ -43,23 +44,44 @@ const createColoredMarkerIcon = (color) => {
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41],
|
||||
});
|
||||
}
|
||||
return null;
|
||||
} return null;
|
||||
};
|
||||
|
||||
// Component to handle map events
|
||||
function MapEventHandler({ onViewChange }) {
|
||||
const map = useMapEvents({
|
||||
moveend: () => {
|
||||
if (onViewChange) {
|
||||
const center = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
onViewChange([center.lat, center.lng], zoom);
|
||||
}
|
||||
},
|
||||
zoomend: () => {
|
||||
if (onViewChange) {
|
||||
const center = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
onViewChange([center.lat, center.lng], zoom);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function EnhancedLeafletMap({
|
||||
center,
|
||||
zoom = 13,
|
||||
markers = [],
|
||||
showLayerControl = true,
|
||||
defaultLayer = "OpenStreetMap",
|
||||
onViewChange,
|
||||
}) {
|
||||
useEffect(() => {
|
||||
fixLeafletIcons();
|
||||
}, []);
|
||||
|
||||
const { BaseLayer } = LayersControl;
|
||||
|
||||
return (
|
||||
<MapContainer
|
||||
center={center}
|
||||
@@ -67,6 +89,9 @@ export default function EnhancedLeafletMap({
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
scrollWheelZoom={true}
|
||||
>
|
||||
{/* Handle map view changes */}
|
||||
{onViewChange && <MapEventHandler onViewChange={onViewChange} />}
|
||||
|
||||
{showLayerControl ? (
|
||||
<LayersControl position="topright">
|
||||
{mapLayers.base.map((layer, index) => (
|
||||
|
||||
Reference in New Issue
Block a user