+
+
+ {project.project_name}
+
+ {project.project_number && (
+
+ {project.project_number}
+
+ )}
+
+
+
+ {project.address && (
+
+
+
+
+ {project.address}
+
+ {project.city && (
+ , {project.city}
+ )}
+
+
+ )}
+
+ {project.wp && (
+
+ WP:{" "}
+ {project.wp}
+
+ )}
+ {project.plot && (
+
+ Plot:{" "}
+ {project.plot}
+
+ )}
+
+ {project.project_status && (
+
+ Status:
+
+ {statusInfo.shortLabel}
+
+
+ )}
+
+
+
+
+
+
+
+
+ ),
+ };
+ })
+ .filter((marker) => marker !== null);
+
+ if (loading) {
+ 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 */}
+
+
+
+
+
+
+
+
+
+ {/* 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 && (
+
+
+
+ Overlay Layers
+
{" "}
+
+ {mapLayers.overlays.map((layer, index) => (
+
+ ))}
+
+
+ )}
+
+
{" "}
+
+
+ {/* Status Filter Panel - Bottom Left */}
+
+
+
+
+ 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 (
+
+ );
+ })}{" "}
+
+
+
{" "}
+ {/* 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.
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+ )}{" "}
+
+ );
+}
diff --git a/src/app/projects/map/page.js b/src/app/projects/map/page.js
index 33e6751..2ee90f3 100644
--- a/src/app/projects/map/page.js
+++ b/src/app/projects/map/page.js
@@ -1,6 +1,6 @@
"use client";
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useState, Suspense } from "react";
import Link from "next/link";
import dynamic from "next/dynamic";
import { useSearchParams, useRouter } from "next/navigation";
@@ -17,7 +17,7 @@ const DynamicMap = dynamic(() => import("@/components/ui/LeafletMap"), {
),
});
-export default function ProjectsMapPage() {
+function ProjectsMapPageContent() {
const searchParams = useSearchParams();
const router = useRouter();
const [projects, setProjects] = useState([]);
@@ -28,901 +28,294 @@ export default function ProjectsMapPage() {
registered: true,
in_progress_design: true,
in_progress_construction: true,
- fulfilled: true,
+ in_progress_inspection: true,
+ completed: false,
+ cancelled: false,
});
- const [activeBaseLayer, setActiveBaseLayer] = useState("OpenStreetMap");
- const [activeOverlays, setActiveOverlays] = useState([]);
- const [showLayerPanel, setShowLayerPanel] = useState(true);
- const [currentTool, setCurrentTool] = useState("move"); // Current map tool
+ const [selectedLayerName, setSelectedLayerName] = useState("OpenStreetMap");
- // 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",
- },
- };
+ // Check if we have a specific project ID from search params
+ const projectId = searchParams.get("project");
- // 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);
- };
+ useEffect(() => {
+ const fetchProjects = async () => {
+ setLoading(true);
+ try {
+ const response = await fetch("/api/projects");
+ if (response.ok) {
+ const data = await response.json();
+ setProjects(data);
- // Toggle status filter
- const toggleStatusFilter = (status) => {
- setStatusFilters((prev) => ({
+ // 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 => ({
...prev,
- [status]: !prev[status],
+ [status]: !prev[status]
}));
};
- // Layer control functions
- const handleBaseLayerChange = (layerName) => {
- setActiveBaseLayer(layerName);
+ const handleLayerChange = (layerName) => {
+ setSelectedLayerName(layerName);
};
- const toggleOverlay = (layerName) => {
- setActiveOverlays((prev) => {
- if (prev.includes(layerName)) {
- return prev.filter((name) => name !== layerName);
- } else {
- return [...prev, 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 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);
- }
+ // 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
};
- }, [searchParams]);
+ });
- useEffect(() => {
- fetch("/api/projects")
- .then((res) => res.json())
- .then((data) => {
- setProjects(data);
+ 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';
+ }
+ };
- // Only calculate center based on projects if no URL parameters are provided
- const lat = searchParams.get("lat");
- const lng = searchParams.get("lng");
+ 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;
+ }
+ };
- 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]);
+ 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
}
- }
-
- setLoading(false);
+ };
})
- .catch((error) => {
- console.error("Error fetching projects:", error);
- setLoading(false);
- });
- }, [searchParams]);
+ };
- // 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;
- }
-
- const statusInfo =
- statusConfig[project.project_status] || statusConfig.registered;
-
- return {
- position: [lat, lng],
- color: statusInfo.color,
- popup: (
-
-
-
- {project.project_name}
-
- {project.project_number && (
-
- {project.project_number}
-
- )}
-
-
-
- {project.address && (
-
-
-
-
- {project.address}
-
- {project.city && (
- , {project.city}
- )}
-
-
- )}
-
- {project.wp && (
-
- WP:{" "}
- {project.wp}
-
- )}
- {project.plot && (
-
- Plot:{" "}
- {project.plot}
-
- )}
-
- {project.project_status && (
-
- Status:
-
- {statusInfo.shortLabel}
-
-
- )}
-
-
-
-
-
-
-
-
- ),
- };
- })
- .filter((marker) => marker !== null);
+ 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);
+ };
if (loading) {
return (
-