feat: Implement route planning feature with project selection and optimization

- Added route planning functionality to the map page, allowing users to select projects for routing.
- Implemented state management for route projects, start/end points, and search functionality.
- Integrated OpenRouteService API for route calculation and optimization.
- Enhanced UI with a route planning panel, including search and drag-and-drop reordering of projects.
- Added visual indicators for route start and end points on the map.
- Included translations for route planning features in both Polish and English.
- Created utility functions for route calculations, optimizations, and formatting of route data.
This commit is contained in:
2025-09-26 00:18:10 +02:00
parent 8a0baa02c3
commit 5aac63dfde
6 changed files with 3750 additions and 100 deletions

View File

@@ -186,6 +186,10 @@ export default function EnhancedLeafletMap({
isMeasuring = false,
measurementPoints = [],
onMeasurementClick,
currentTool = "move",
routeProjects = [],
onProjectClick,
routeData = null,
}) {
useEffect(() => {
fixLeafletIcons();
@@ -292,17 +296,88 @@ export default function EnhancedLeafletMap({
}
</>
)}{" "}
{markers.map((marker, index) => (
<Marker
key={index}
position={marker.position}
icon={
marker.color ? createColoredMarkerIcon(marker.color) : undefined
}
>
{marker.popup && <Popup>{marker.popup}</Popup>}
</Marker>
))}
{markers.map((marker, index) => {
const isInRoute = routeProjects.some(p => p.project_id === marker.project?.project_id);
const isRouteTool = currentTool === "route";
return (
<Marker
key={index}
position={marker.position}
icon={
isRouteTool && isInRoute
? createColoredMarkerIcon("blue")
: marker.color ? createColoredMarkerIcon(marker.color) : undefined
}
eventHandlers={{
click: () => {
if (isRouteTool && marker.project && onProjectClick) {
onProjectClick(marker.project);
}
}
}}
>
{marker.popup && <Popup>{marker.popup}</Popup>}
</Marker>
);
})}
{/* Route visualization */}
{routeData && routeData.geometry && routeData.geometry.length > 0 && (
<>
<Polyline
positions={routeData.geometry}
color="blue"
weight={4}
opacity={0.8}
/>
{/* Start marker */}
{routeData.geometry.length > 0 && (
<Marker
position={routeData.geometry[0]}
icon={L.divIcon({
html: '<div style="background-color: green; width: 12px; height: 12px; border-radius: 50%; border: 2px solid white; box-shadow: 0 0 4px rgba(0,0,0,0.3);"></div>',
className: 'custom-route-marker',
iconSize: [12, 12],
iconAnchor: [6, 6]
})}
>
<Popup>Route Start</Popup>
</Marker>
)}
{/* End marker */}
{routeData.geometry.length > 1 && (
<Marker
position={routeData.geometry[routeData.geometry.length - 1]}
icon={L.divIcon({
html: '<div style="background-color: red; width: 12px; height: 12px; border-radius: 50%; border: 2px solid white; box-shadow: 0 0 4px rgba(0,0,0,0.3);"></div>',
className: 'custom-route-marker',
iconSize: [12, 12],
iconAnchor: [6, 6]
})}
>
<Popup>Route End</Popup>
</Marker>
)}
</>
)}
{/* Fallback: straight line route if no geometry */}
{routeProjects.length > 1 && (!routeData || !routeData.geometry || routeData.geometry.length === 0) && (
<Polyline
positions={routeProjects.map(project => {
if (project.coordinates) {
const [lat, lng] = project.coordinates.split(",").map(coord => parseFloat(coord.trim()));
return [lat, lng];
}
return null;
}).filter(pos => pos !== null)}
color="blue"
weight={4}
opacity={0.8}
dashArray="10, 10"
/>
)}
{/* Measurement elements */}
{isMeasuring && measurementPoints.length > 0 && (