diff --git a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/AlarmText.jsx b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/AlarmText.jsx index 0a3be1b..0ddf8a0 100644 --- a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/AlarmText.jsx +++ b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/AlarmText.jsx @@ -1,28 +1,28 @@ -import { Box, Typography } from "@mui/material"; - -const AlarmText = ({ isSource }) => { - if (isSource) return null; - return ( - - - با کلیک بر روی مارکر موقعیت را انتخاب کنید - - - ); -}; - -export default AlarmText; +import { Box, Typography } from "@mui/material"; + +const AlarmText = ({ isSource }) => { + if (isSource) return null; + return ( + + + با کلیک بر روی مارکر موقعیت را انتخاب کنید + + + ); +}; + +export default AlarmText; diff --git a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/DataBox.jsx b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/DataBox.jsx index c19cb6f..5768f75 100644 --- a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/DataBox.jsx +++ b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/DataBox.jsx @@ -1,53 +1,53 @@ -import { Box, Button, TextField } from "@mui/material"; - -const DataBox = ({ isSource, isDest, sourcePosition, destPosition, editingTarget, onEditSource, onEditDest }) => { - if (!isSource) return null; - - return ( - -
- - {isSource && isDest && ( - - )} -
- {isDest && ( -
- - -
- )} -
- ); -}; - -export default DataBox; +import { Box, Button, TextField } from "@mui/material"; + +const DataBox = ({ isSource, isDest, sourcePosition, destPosition, editingTarget, onEditSource, onEditDest }) => { + if (!isSource) return null; + + return ( + +
+ + {isSource && isDest && ( + + )} +
+ {isDest && ( +
+ + +
+ )} +
+ ); +}; + +export default DataBox; diff --git a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/ShowRoute.jsx b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/ShowRoute.jsx index 5e1a4bd..7d47ba8 100644 --- a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/ShowRoute.jsx +++ b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/ShowRoute.jsx @@ -1,46 +1,46 @@ -import { useEffect, useRef } from "react"; -import { Marker, Polyline, useMap } from "react-leaflet"; -import L from "leaflet"; -import polyline from "@mapbox/polyline"; -import SourceIcon from "@/assets/images/source-icon.svg"; -import DestIcon from "@/assets/images/destination-icon.svg"; - -const ShowRoute = ({ area }) => { - const map = useMap(); - const defaultIconSize = [35, 35]; - - const createCustomIcon = (size, iconUrl) => { - return L.icon({ - iconUrl: iconUrl, - iconSize: size, - iconAnchor: [size[0] / 2, size[1]], - popupAnchor: [0, -size[1]], - }); - }; - - useEffect(() => { - if (coords.length > 0) { - map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 }); - } - }, [area]); - const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src); - const destIcon = createCustomIcon(defaultIconSize, DestIcon.src); - - if (!area) return null; - const coords = polyline.decode(area, 6); - - if (coords.length === 0) return null; - - const sourcePosition = coords[0]; - const destPosition = coords[coords.length - 1]; - - return ( - <> - - - - - ); -}; - -export default ShowRoute; +import { useEffect, useRef } from "react"; +import { Marker, Polyline, useMap } from "react-leaflet"; +import L from "leaflet"; +import polyline from "@mapbox/polyline"; +import SourceIcon from "@/assets/images/source-icon.svg"; +import DestIcon from "@/assets/images/destination-icon.svg"; + +const ShowRoute = ({ area }) => { + const map = useMap(); + const defaultIconSize = [35, 35]; + + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + useEffect(() => { + if (coords.length > 0) { + map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 }); + } + }, [area]); + const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src); + const destIcon = createCustomIcon(defaultIconSize, DestIcon.src); + + if (!area) return null; + const coords = polyline.decode(area, 6); + + if (coords.length === 0) return null; + + const sourcePosition = coords[0]; + const destPosition = coords[coords.length - 1]; + + return ( + <> + + + + + ); +}; + +export default ShowRoute; diff --git a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/index.jsx b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/index.jsx index 06e7c9b..49c4651 100644 --- a/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/index.jsx +++ b/src/components/dashboard/roadMissions/operator/RowActions/Add/AddController/Dialog/Form/Area/Routing/index.jsx @@ -1,219 +1,219 @@ -import { Marker, Polyline, useMapEvents } from "react-leaflet"; -import L from "leaflet"; -import polyline from "@mapbox/polyline"; -import SourceIcon from "@/assets/images/source-icon.svg"; -import PrevSourceIcon from "@/assets/images/prev-source-icon.svg"; -import PrevDestIcon from "@/assets/images/prev-destination-icon.svg"; -import DestIcon from "@/assets/images/destination-icon.svg"; -import { useEffect, useRef, useState } from "react"; -import DataBox from "./DataBox"; -import AlarmText from "./AlarmText"; - -const Routing = ({ setArea, setLocations, locations, area }) => { - const mapPrevSourceMarker = useRef(); - const mapPrevDestMarker = useRef(); - const mapRef = useRef(null); - const defaultIconSize = [35, 35]; - const [isSource, setIsSource] = useState(false); - const [sourcePosition, setSourcePosition] = useState(null); - const [isDest, setIsDest] = useState(false); - const [destPosition, setDestPosition] = useState(null); - const [routeCoords, setRouteCoords] = useState([]); - const [alternateCoords, setAlternateCoords] = useState([]); - const [editingTarget, setEditingTarget] = useState(null); - const [selectedRoute, setSelectedRoute] = useState(null); - - const createCustomIcon = (size, iconUrl) => { - return L.icon({ - iconUrl: iconUrl, - iconSize: size, - iconAnchor: [size[0] / 2, size[1]], - popupAnchor: [0, -size[1]], - }); - }; - - const defaultIcon = createCustomIcon( - defaultIconSize, - isSource && editingTarget !== "source" ? SourceIcon.src : PrevSourceIcon.src - ); - - const prevDestIcon = createCustomIcon( - defaultIconSize, - isDest && editingTarget !== "dest" ? DestIcon.src : PrevDestIcon.src - ); - - const isMovingSource = !isSource || editingTarget === "source"; - const isMovingDest = isSource && (!isDest || editingTarget === "dest"); - - const map = useMapEvents({ - move(e) { - if (isMovingSource) mapPrevSourceMarker.current.setLatLng(e.target.getCenter()); - if (isMovingDest && mapPrevDestMarker.current) mapPrevDestMarker.current.setLatLng(e.target.getCenter()); - }, - movestart() { - if (isMovingSource) mapPrevSourceMarker.current.setIcon(createCustomIcon([45, 45], PrevSourceIcon.src)); - if (isMovingDest && mapPrevDestMarker.current) - mapPrevDestMarker.current.setIcon(createCustomIcon([45, 45], PrevDestIcon.src)); - }, - moveend() { - if (isMovingSource) - mapPrevSourceMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevSourceIcon.src)); - if (isMovingDest && mapPrevDestMarker.current) - mapPrevDestMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevDestIcon.src)); - }, - }); - - useEffect(() => { - mapRef.current = map; - }, [map]); - - useEffect(() => { - if (locations.length < 2) return; - const restoredSource = { lat: locations[0].lat, lng: locations[0].lon }; - const restoredDest = { lat: locations[1].lat, lng: locations[1].lon }; - setSourcePosition(restoredSource); - setDestPosition(restoredDest); - setIsSource(true); - setIsDest(true); - if (area) { - const decoded = polyline.decode(area, 6); - setRouteCoords(decoded); - setSelectedRoute({ index: "main", coords: decoded }); - setTimeout(() => { - mapRef.current?.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); - }, 100); - } else { - fetchRoute(restoredSource, restoredDest); - } - }, []); - - const fetchRoute = async (source, dest) => { - const locs = [ - { lat: source.lat, lon: source.lng }, - { lat: dest.lat, lon: dest.lng }, - ]; - try { - const response = await fetch("https://jouya.141.ir/api/router/drive", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ locations: locs }), - }); - const data = await response.json(); - const shape = data.trip.legs[0].shape; - setLocations(locs); - setArea(shape); - const decoded = polyline.decode(shape, 6); - setRouteCoords(decoded); - setSelectedRoute({ index: "main", coords: decoded }); - - if (data.alternates?.length > 0) { - const decodedAlternates = data.alternates.map((alt) => polyline.decode(alt.trip.legs[0].shape, 6)); - setAlternateCoords(decodedAlternates); - } else { - setAlternateCoords([]); - } - map.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); - } catch (error) { - console.error("Routing error:", error); - } - }; - - const handleMarkerClick = async () => { - if (!isSource || editingTarget === "source") { - const clickedPosition = mapPrevSourceMarker.current.getLatLng(); - setSourcePosition(clickedPosition); - setIsSource(true); - setEditingTarget(null); - setRouteCoords([]); - setAlternateCoords([]); - setSelectedRoute(null); - - if (editingTarget === "source" && destPosition) { - await fetchRoute(clickedPosition, destPosition); - } - } - }; - - const handleDestMarkerClick = async () => { - if (!isDest || editingTarget === "dest") { - const clickedPosition = mapPrevDestMarker.current.getLatLng(); - setDestPosition(clickedPosition); - setIsDest(true); - setEditingTarget(null); - - await fetchRoute(sourcePosition, clickedPosition); - } - }; - - const handleEditSource = () => { - setEditingTarget("source"); - setRouteCoords([]); - setAlternateCoords([]); - setSelectedRoute(null); - }; - - const handleEditDest = () => { - setEditingTarget("dest"); - setRouteCoords([]); - setAlternateCoords([]); - setSelectedRoute(null); - }; - - const handleSelectRoute = (index, coords) => { - setSelectedRoute({ index, coords }); - const encoded = polyline.encode(coords, 6); - setArea(encoded); - map.flyToBounds(coords, { padding: [50, 50], duration: 0.3 }); - }; - - return ( - <> - - - - {isSource && ( - - )} - {alternateCoords.map((coords, index) => ( - handleSelectRoute(index, coords) }} - /> - ))} - {routeCoords.length > 0 && ( - handleSelectRoute("main", routeCoords) }} - /> - )} - - ); -}; - -export default Routing; +import { Marker, Polyline, useMapEvents } from "react-leaflet"; +import L from "leaflet"; +import polyline from "@mapbox/polyline"; +import SourceIcon from "@/assets/images/source-icon.svg"; +import PrevSourceIcon from "@/assets/images/prev-source-icon.svg"; +import PrevDestIcon from "@/assets/images/prev-destination-icon.svg"; +import DestIcon from "@/assets/images/destination-icon.svg"; +import { useEffect, useRef, useState } from "react"; +import DataBox from "./DataBox"; +import AlarmText from "./AlarmText"; + +const Routing = ({ setArea, setLocations, locations, area }) => { + const mapPrevSourceMarker = useRef(); + const mapPrevDestMarker = useRef(); + const mapRef = useRef(null); + const defaultIconSize = [35, 35]; + const [isSource, setIsSource] = useState(false); + const [sourcePosition, setSourcePosition] = useState(null); + const [isDest, setIsDest] = useState(false); + const [destPosition, setDestPosition] = useState(null); + const [routeCoords, setRouteCoords] = useState([]); + const [alternateCoords, setAlternateCoords] = useState([]); + const [editingTarget, setEditingTarget] = useState(null); + const [selectedRoute, setSelectedRoute] = useState(null); + + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + const defaultIcon = createCustomIcon( + defaultIconSize, + isSource && editingTarget !== "source" ? SourceIcon.src : PrevSourceIcon.src + ); + + const prevDestIcon = createCustomIcon( + defaultIconSize, + isDest && editingTarget !== "dest" ? DestIcon.src : PrevDestIcon.src + ); + + const isMovingSource = !isSource || editingTarget === "source"; + const isMovingDest = isSource && (!isDest || editingTarget === "dest"); + + const map = useMapEvents({ + move(e) { + if (isMovingSource) mapPrevSourceMarker.current.setLatLng(e.target.getCenter()); + if (isMovingDest && mapPrevDestMarker.current) mapPrevDestMarker.current.setLatLng(e.target.getCenter()); + }, + movestart() { + if (isMovingSource) mapPrevSourceMarker.current.setIcon(createCustomIcon([45, 45], PrevSourceIcon.src)); + if (isMovingDest && mapPrevDestMarker.current) + mapPrevDestMarker.current.setIcon(createCustomIcon([45, 45], PrevDestIcon.src)); + }, + moveend() { + if (isMovingSource) + mapPrevSourceMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevSourceIcon.src)); + if (isMovingDest && mapPrevDestMarker.current) + mapPrevDestMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevDestIcon.src)); + }, + }); + + useEffect(() => { + mapRef.current = map; + }, [map]); + + useEffect(() => { + if (locations.length < 2) return; + const restoredSource = { lat: locations[0].lat, lng: locations[0].lon }; + const restoredDest = { lat: locations[1].lat, lng: locations[1].lon }; + setSourcePosition(restoredSource); + setDestPosition(restoredDest); + setIsSource(true); + setIsDest(true); + if (area) { + const decoded = polyline.decode(area, 6); + setRouteCoords(decoded); + setSelectedRoute({ index: "main", coords: decoded }); + setTimeout(() => { + mapRef.current?.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); + }, 100); + } else { + fetchRoute(restoredSource, restoredDest); + } + }, []); + + const fetchRoute = async (source, dest) => { + const locs = [ + { lat: source.lat, lon: source.lng }, + { lat: dest.lat, lon: dest.lng }, + ]; + try { + const response = await fetch("https://jouya.141.ir/api/router/drive", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ locations: locs }), + }); + const data = await response.json(); + const shape = data.trip.legs[0].shape; + setLocations(locs); + setArea(shape); + const decoded = polyline.decode(shape, 6); + setRouteCoords(decoded); + setSelectedRoute({ index: "main", coords: decoded }); + + if (data.alternates?.length > 0) { + const decodedAlternates = data.alternates.map((alt) => polyline.decode(alt.trip.legs[0].shape, 6)); + setAlternateCoords(decodedAlternates); + } else { + setAlternateCoords([]); + } + map.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); + } catch (error) { + console.error("Routing error:", error); + } + }; + + const handleMarkerClick = async () => { + if (!isSource || editingTarget === "source") { + const clickedPosition = mapPrevSourceMarker.current.getLatLng(); + setSourcePosition(clickedPosition); + setIsSource(true); + setEditingTarget(null); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + + if (editingTarget === "source" && destPosition) { + await fetchRoute(clickedPosition, destPosition); + } + } + }; + + const handleDestMarkerClick = async () => { + if (!isDest || editingTarget === "dest") { + const clickedPosition = mapPrevDestMarker.current.getLatLng(); + setDestPosition(clickedPosition); + setIsDest(true); + setEditingTarget(null); + + await fetchRoute(sourcePosition, clickedPosition); + } + }; + + const handleEditSource = () => { + setEditingTarget("source"); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + }; + + const handleEditDest = () => { + setEditingTarget("dest"); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + }; + + const handleSelectRoute = (index, coords) => { + setSelectedRoute({ index, coords }); + const encoded = polyline.encode(coords, 6); + setArea(encoded); + map.flyToBounds(coords, { padding: [50, 50], duration: 0.3 }); + }; + + return ( + <> + + + + {isSource && ( + + )} + {alternateCoords.map((coords, index) => ( + handleSelectRoute(index, coords) }} + /> + ))} + {routeCoords.length > 0 && ( + handleSelectRoute("main", routeCoords) }} + /> + )} + + ); +}; + +export default Routing;