From 5cd56593eb51eb4970b5ad8d1f46818d27f81e36 Mon Sep 17 00:00:00 2001 From: Chop <28534054+RChopin@users.noreply.github.com> Date: Thu, 10 Jul 2025 22:32:13 +0200 Subject: [PATCH] Fix projects map page - restore original design - Restore original projects map interface with layer controls - Add minimal Suspense wrapper for useSearchParams (SSR fix) - Preserve all original functionality: tools, filters, markers - Fix build issue without changing core interface design --- src/app/projects/map/page.js | 1082 ++++++++++++++++++++++++++-------- 1 file changed, 852 insertions(+), 230 deletions(-) diff --git a/src/app/projects/map/page.js b/src/app/projects/map/page.js index 2ee90f3..23026dd 100644 --- a/src/app/projects/map/page.js +++ b/src/app/projects/map/page.js @@ -28,279 +28,901 @@ function ProjectsMapPageContent() { registered: true, in_progress_design: true, in_progress_construction: true, - in_progress_inspection: true, - completed: false, - cancelled: false, + fulfilled: true, }); - const [selectedLayerName, setSelectedLayerName] = useState("OpenStreetMap"); + const [activeBaseLayer, setActiveBaseLayer] = useState("OpenStreetMap"); + const [activeOverlays, setActiveOverlays] = useState([]); + const [showLayerPanel, setShowLayerPanel] = useState(true); + const [currentTool, setCurrentTool] = useState("move"); // Current map tool - // Check if we have a specific project ID from search params - const projectId = searchParams.get("project"); + // Status configuration with colors and labels + const statusConfig = { + registered: { + color: "#6B7280", + label: "Registered", + shortLabel: "Zarejestr.", + }, + in_progress_design: { + color: "#3B82F6", + label: "In Progress (Design)", + shortLabel: "W real. (P)", + }, + in_progress_construction: { + color: "#F59E0B", + label: "In Progress (Construction)", + shortLabel: "W real. (R)", + }, + fulfilled: { + color: "#10B981", + label: "Completed", + shortLabel: "Zakończony", + }, + }; - useEffect(() => { - const fetchProjects = async () => { - setLoading(true); - try { - const response = await fetch("/api/projects"); - if (response.ok) { - const data = await response.json(); - setProjects(data); + // Toggle all status filters + const toggleAllFilters = () => { + const allActive = Object.values(statusFilters).every((value) => value); + const newState = allActive + ? Object.keys(statusFilters).reduce( + (acc, key) => ({ ...acc, [key]: false }), + {} + ) + : Object.keys(statusFilters).reduce( + (acc, key) => ({ ...acc, [key]: true }), + {} + ); + setStatusFilters(newState); + }; - // If we have a specific project ID, find it and center the map on it - if (projectId) { - const project = data.find(p => p.id === parseInt(projectId)); - if (project?.coordinates) { - const coords = project.coordinates.split(","); - if (coords.length === 2) { - setMapCenter([parseFloat(coords[0]), parseFloat(coords[1])]); - setMapZoom(15); - } - } - } - } - } catch (error) { - console.error("Error fetching projects:", error); - } finally { - setLoading(false); - } - }; - - fetchProjects(); - }, [projectId]); - - const handleStatusFilterChange = (status) => { - setStatusFilters(prev => ({ + // Toggle status filter + const toggleStatusFilter = (status) => { + setStatusFilters((prev) => ({ ...prev, - [status]: !prev[status] + [status]: !prev[status], })); }; - const handleLayerChange = (layerName) => { - setSelectedLayerName(layerName); + // Layer control functions + const handleBaseLayerChange = (layerName) => { + setActiveBaseLayer(layerName); }; - // Filter projects based on status filters - const filteredProjects = projects.filter(project => { - if (!project?.coordinates) return false; - const coords = project.coordinates.split(","); - if (coords.length !== 2) return false; - - const lat = parseFloat(coords[0]); - const lng = parseFloat(coords[1]); - if (isNaN(lat) || isNaN(lng)) return false; - - return statusFilters[project.status] || false; - }); + const toggleOverlay = (layerName) => { + setActiveOverlays((prev) => { + if (prev.includes(layerName)) { + return prev.filter((name) => name !== layerName); + } else { + return [...prev, layerName]; + } + }); + }; - // Convert filtered projects to markers - const markers = filteredProjects.map(project => { - const coords = project.coordinates.split(","); - const lat = parseFloat(coords[0]); - const lng = parseFloat(coords[1]); - - return { - position: [lat, lng], - popup: ` -
-

${project.name}

-

- Status: ${project.status?.replace(/_/g, ' ')} -

- ${project.contractor ? `

- Contractor: ${project.contractor} -

` : ''} - ${project.deadline ? `

- Deadline: ${new Date(project.deadline).toLocaleDateString()} -

` : ''} - - View Project → - -
- `, - data: project + const toggleLayerPanel = () => { + setShowLayerPanel(!showLayerPanel); + }; + + // 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) { + nav.style.display = "none"; + } + + // Prevent scrolling on body + document.body.style.overflow = "hidden"; + document.documentElement.style.overflow = "hidden"; + + // Cleanup when leaving page + return () => { + if (nav) { + nav.style.display = ""; + } + document.body.style.overflow = ""; + document.documentElement.style.overflow = ""; + + // Clear any pending URL updates + if (window.mapUpdateTimeout) { + clearTimeout(window.mapUpdateTimeout); + } }; - }); + }, [searchParams]); - const getStatusColor = (status) => { - switch (status) { - case 'registered': return 'bg-blue-100 text-blue-800'; - case 'in_progress_design': return 'bg-yellow-100 text-yellow-800'; - case 'in_progress_construction': return 'bg-orange-100 text-orange-800'; - case 'in_progress_inspection': return 'bg-purple-100 text-purple-800'; - case 'completed': return 'bg-green-100 text-green-800'; - case 'cancelled': return 'bg-red-100 text-red-800'; - default: return 'bg-gray-100 text-gray-800'; - } - }; + useEffect(() => { + fetch("/api/projects") + .then((res) => res.json()) + .then((data) => { + setProjects(data); - const getStatusDisplay = (status) => { - switch (status) { - case 'registered': return 'Registered'; - case 'in_progress_design': return 'In Progress - Design'; - case 'in_progress_construction': return 'In Progress - Construction'; - case 'in_progress_inspection': return 'In Progress - Inspection'; - case 'completed': return 'Completed'; - case 'cancelled': return 'Cancelled'; - default: return status; - } - }; + // Only calculate center based on projects if no URL parameters are provided + const lat = searchParams.get("lat"); + const lng = searchParams.get("lng"); - const exportGeoJSON = () => { - const geojson = { - type: "FeatureCollection", - features: filteredProjects.map(project => { - const coords = project.coordinates.split(","); - const lat = parseFloat(coords[0]); - const lng = parseFloat(coords[1]); - - return { - type: "Feature", - geometry: { - type: "Point", - coordinates: [lng, lat] // GeoJSON uses [lng, lat] - }, - properties: { - id: project.id, - name: project.name, - status: project.status, - contractor: project.contractor || null, - deadline: project.deadline || null, - description: project.description || null, - contract_id: project.contract_id || null + 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; + + setMapCenter([avgLat, avgLng]); } - }; + } + + setLoading(false); }) - }; + .catch((error) => { + console.error("Error fetching projects:", error); + setLoading(false); + }); + }, [searchParams]); - const blob = new Blob([JSON.stringify(geojson, null, 2)], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `projects_${new Date().toISOString().split('T')[0]}.geojson`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - }; + // Convert projects to map markers with filtering + const markers = projects + .filter((project) => project.coordinates) + .filter((project) => statusFilters[project.project_status] !== false) + .map((project) => { + const [lat, lng] = project.coordinates + .split(",") + .map((coord) => parseFloat(coord.trim())); + if (isNaN(lat) || isNaN(lng)) { + return null; + } - if (loading) { - return ( -
-
-
-

Loading projects...

-
-
- ); - } + const statusInfo = + statusConfig[project.project_status] || statusConfig.registered; - return ( -
-
-
-
-
-

Projects Map

-

- Showing {filteredProjects.length} of {projects.length} projects with location data -

+ return { + position: [lat, lng], + color: statusInfo.color, + popup: ( +
+
+

+ {project.project_name} +

+ {project.project_number && ( +
+ {project.project_number} +
+ )}
-
- - -
+ ), + }; + }) + .filter((marker) => marker !== null); + + if (loading) { + return ( +
+
+
+

Loading projects map...

+

+ Preparing your full-screen map experience +

+ ); + } + return ( +
+ {/* Floating Header - Left Side */} +
+ {/* Title Box */} +
+
+

+ Projects Map +

+
+ {markers.length} of {projects.length} projects with coordinates +
+
{" "} +
+
+ {/* Zoom Controls - Below Title */} +
+
+ + {" "} +
+
{" "} + {/* Tool Panel - Below Zoom Controls */} +
+ {" "} +
+ {" "} + {/* Move Tool */} + + {/* Select Tool */} + + {/* Measure Tool */} + + {/* Draw Tool */} + + {/* Pin/Marker Tool */} + + {/* Area Tool */} + +
+
+ {/* Layer Control Panel - Right Side */} +
+ {/* Action Buttons */} +
+ + + + + + +
-
-
- {/* Filters Sidebar */} -
-
-

Filters

- -
+ {/* Layer Control Panel */} +
+ {/* Layer Control Header */} +
+ +
{" "} + {/* Layer Control Content */} +
+
+ {/* Base Layers Section */} +
+

+ + + + Base Maps +

+
+ {mapLayers.base.map((layer, index) => ( + + ))} +
+
+ + {/* Overlay Layers Section */} + {mapLayers.overlays && mapLayers.overlays.length > 0 && (
-

Status

+

+ + + + Overlay Layers +

{" "}
- {Object.entries(statusFilters).map(([status, checked]) => ( -
- -
-

Map Layer

- -
-
+ )}
- - {/* Legend */} -
-

Legend

-
- {Object.entries(statusFilters).filter(([_, checked]) => checked).map(([status, _]) => ( -
-
- {getStatusDisplay(status)} -
- ))} -
-
-
- - {/* Map */} -
-
-
- {" "} +
+
+ {/* Status Filter Panel - Bottom Left */} +
+
+
+ + Filters: + + {/* Toggle All Button */} +
+ + + {Object.values(statusFilters).every((v) => v) + ? "Hide All" + : "Show All"} + + + {/* Individual Status Filters */} + {Object.entries(statusConfig).map(([status, config]) => { + const isActive = statusFilters[status]; + const projectCount = projects.filter( + (p) => p.project_status === status && p.coordinates + ).length; + + return ( + + ); + })}{" "} +
+
+
{" "} + {/* Status Panel - Bottom Left */} + {markers.length > 0 && ( +
+
+ + Filters: + + + {/* Toggle All Button */} + + + {/* Individual Status Filters */} + {Object.entries(statusConfig).map(([status, config]) => { + const isActive = statusFilters[status]; + const projectCount = projects.filter( + (p) => p.project_status === status && p.coordinates + ).length; + + return ( + + ); + })} +
+
+ )}{" "} + {/* Full Screen Map */} + {markers.length === 0 ? ( +
+
+
+ + + +
+

+ No projects with coordinates +

+

+ Projects need coordinates to appear on the map. Add coordinates + when creating or editing projects. +

+
+ + + + + +
-
+ ) : ( +
+ +
+ )}{" "}
); }