From e5d681547d8b4d88c57f4ff9ba4042768f5a38a6 Mon Sep 17 00:00:00 2001 From: RKWojs Date: Fri, 20 Jun 2025 23:56:11 +0200 Subject: [PATCH] feat: enhance map functionality by adding URL state management and event handling for view changes --- src/app/projects/[id]/page.js | 41 ++++++------ src/app/projects/map/page.js | 107 ++++++++++++++++++++++++-------- src/components/ui/LeafletMap.js | 31 ++++++++- 3 files changed, 129 insertions(+), 50 deletions(-) diff --git a/src/app/projects/[id]/page.js b/src/app/projects/[id]/page.js index dadcc4b..a58f6fb 100644 --- a/src/app/projects/[id]/page.js +++ b/src/app/projects/[id]/page.js @@ -400,29 +400,30 @@ export default async function ProjectViewPage({ params }) {
{" "} - -
+

Project Location

- - - + {project.coordinates && ( + + + + )}
diff --git a/src/app/projects/map/page.js b/src/app/projects/map/page.js index 83c3cad..6f1b837 100644 --- a/src/app/projects/map/page.js +++ b/src/app/projects/map/page.js @@ -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,35 +134,45 @@ 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); - // Calculate center based on projects with coordinates - const projectsWithCoords = data.filter((p) => p.coordinates); - if (projectsWithCoords.length > 0) { - const avgLat = - projectsWithCoords.reduce((sum, p) => { - const [lat] = p.coordinates - .split(",") - .map((coord) => parseFloat(coord.trim())); - return sum + lat; - }, 0) / projectsWithCoords.length; + // 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) { + const avgLat = + projectsWithCoords.reduce((sum, p) => { + const [lat] = p.coordinates + .split(",") + .map((coord) => parseFloat(coord.trim())); + return sum + lat; + }, 0) / projectsWithCoords.length; - const avgLng = - projectsWithCoords.reduce((sum, p) => { - const [, lng] = p.coordinates - .split(",") - .map((coord) => parseFloat(coord.trim())); - return sum + lng; - }, 0) / projectsWithCoords.length; + const avgLng = + projectsWithCoords.reduce((sum, p) => { + const [, lng] = p.coordinates + .split(",") + .map((coord) => parseFloat(coord.trim())); + return sum + lng; + }, 0) / projectsWithCoords.length; - setMapCenter([avgLat, avgLng]); + 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() {
- ) : ( -
+ ) : (
)} diff --git a/src/components/ui/LeafletMap.js b/src/components/ui/LeafletMap.js index 068b4cb..0720972 100644 --- a/src/components/ui/LeafletMap.js +++ b/src/components/ui/LeafletMap.js @@ -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 ( + {/* Handle map view changes */} + {onViewChange && } + {showLayerControl ? ( {mapLayers.base.map((layer, index) => (