feat: Add comprehensive roadmap for app development and prioritize features

- Created ROADMAP.md outlining current features, critical missing features, implementation phases, and technology recommendations.
- Added immediate next steps for development focus.

fix: Enhance test data creation scripts for diverse project scenarios

- Implemented create-diverse-test-data.js to generate varied test projects with different statuses and locations.
- Updated create-additional-test-data.js for better project creation consistency.

refactor: Improve project view page with additional navigation and map features

- Added link to view all projects on the map from the project view page.
- Enhanced project location display with status indicators.

feat: Implement project map page with status filtering

- Added status filters for project markers on the map.
- Integrated color-coded markers based on project status.

fix: Update LeafletMap and ProjectMap components for better marker handling

- Created colored marker icons for different project statuses.
- Enhanced ProjectMap to display project status and coordinates more effectively.

test: Add mobile view test HTML for responsive map testing

- Created test-mobile.html to test the map's responsive behavior on mobile devices.
This commit is contained in:
Chop
2025-06-19 20:57:50 +02:00
parent a8f52f6d28
commit aaa08a3504
8 changed files with 969 additions and 174 deletions

View File

@@ -1,74 +1,107 @@
"use client";
import { MapContainer, TileLayer, Marker, Popup, LayersControl } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import { useEffect } from 'react';
import { mapLayers } from './mapLayers';
import {
MapContainer,
TileLayer,
Marker,
Popup,
LayersControl,
} from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { useEffect } from "react";
import { mapLayers } from "./mapLayers";
// Fix for default markers in react-leaflet
const fixLeafletIcons = () => {
if (typeof window !== 'undefined') {
const L = require('leaflet');
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: '/leaflet/marker-icon-2x.png',
iconUrl: '/leaflet/marker-icon.png',
shadowUrl: '/leaflet/marker-shadow.png',
});
}
if (typeof window !== "undefined") {
const L = require("leaflet");
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: "/leaflet/marker-icon-2x.png",
iconUrl: "/leaflet/marker-icon.png",
shadowUrl: "/leaflet/marker-shadow.png",
});
}
};
export default function EnhancedLeafletMap({
center,
zoom = 13,
markers = [],
showLayerControl = true,
defaultLayer = 'OpenStreetMap'
// Create colored marker icons
const createColoredMarkerIcon = (color) => {
if (typeof window !== "undefined") {
const L = require("leaflet");
return new L.Icon({
iconUrl: `data:image/svg+xml;base64,${btoa(`
<svg width="25" height="41" viewBox="0 0 25 41" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 0C5.596 0 0 5.596 0 12.5c0 12.5 12.5 28.5 12.5 28.5S25 25 25 12.5C25 5.596 19.404 0 12.5 0z" fill="${color}" stroke="#ffffff" stroke-width="2"/>
<circle cx="12.5" cy="12.5" r="6" fill="#ffffff"/>
</svg>
`)}`,
shadowUrl: "/leaflet/marker-shadow.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41],
});
}
return null;
};
export default function EnhancedLeafletMap({
center,
zoom = 13,
markers = [],
showLayerControl = true,
defaultLayer = "OpenStreetMap",
}) {
useEffect(() => {
fixLeafletIcons();
}, []);
useEffect(() => {
fixLeafletIcons();
}, []);
const { BaseLayer } = LayersControl;
const { BaseLayer } = LayersControl;
return (
<MapContainer
center={center}
zoom={zoom}
style={{ height: '100%', width: '100%' }}
scrollWheelZoom={true}
>
{showLayerControl ? (
<LayersControl position="topright">
{mapLayers.base.map((layer, index) => (
<BaseLayer
key={index}
checked={layer.checked || layer.name === defaultLayer}
name={layer.name}
>
<TileLayer
attribution={layer.attribution}
url={layer.url}
maxZoom={layer.maxZoom}
tileSize={layer.tileSize || 256}
/>
</BaseLayer>
))}
</LayersControl>
) : (
// Default layer when no layer control
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
)}
{markers.map((marker, index) => (
<Marker key={index} position={marker.position}>
{marker.popup && <Popup>{marker.popup}</Popup>}
</Marker>
))}
</MapContainer>
);
return (
<MapContainer
center={center}
zoom={zoom}
style={{ height: "100%", width: "100%" }}
scrollWheelZoom={true}
>
{showLayerControl ? (
<LayersControl position="topright">
{mapLayers.base.map((layer, index) => (
<BaseLayer
key={index}
checked={layer.checked || layer.name === defaultLayer}
name={layer.name}
>
<TileLayer
attribution={layer.attribution}
url={layer.url}
maxZoom={layer.maxZoom}
tileSize={layer.tileSize || 256}
/>
</BaseLayer>
))}
</LayersControl>
) : (
// Default layer when no layer control
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
)}{" "}
{markers.map((marker, index) => (
<Marker
key={index}
position={marker.position}
icon={
marker.color ? createColoredMarkerIcon(marker.color) : undefined
}
>
{marker.popup && <Popup>{marker.popup}</Popup>}
</Marker>
))}
</MapContainer>
);
}