From 6d8e9ddb5b6baa7d96a3583c22e910a20bf4132f Mon Sep 17 00:00:00 2001 From: mhmjalali Date: Wed, 10 Jun 2026 16:05:25 +0330 Subject: [PATCH] complete routing section and view of route on table actions --- .../Create/Form/Area/Routing/AlarmText.jsx | 28 ++++++++ .../Create/Form/Area/Routing/DataBox.jsx | 8 ++- .../Create/Form/Area/Routing/ShowRoute.jsx | 46 +++++++++++++ .../Create/Form/Area/Routing/index.jsx | 65 ++++++++++++------- .../Actions/Create/Form/Area/index.jsx | 14 ++-- .../Actions/Create/Form/Verify/index.jsx | 18 +++-- .../operator/Actions/Create/Form/index.jsx | 2 +- .../operator/Actions/Create/index.jsx | 11 +--- .../roadMissions/operator/OperatorList.jsx | 18 ++--- .../operator/RowActions/RouteDetail/index.jsx | 57 ++++++++++++++++ 10 files changed, 211 insertions(+), 56 deletions(-) create mode 100644 src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/AlarmText.jsx create mode 100644 src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/ShowRoute.jsx create mode 100644 src/components/dashboard/roadMissions/operator/RowActions/RouteDetail/index.jsx diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/AlarmText.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/AlarmText.jsx new file mode 100644 index 0000000..0a3be1b --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/AlarmText.jsx @@ -0,0 +1,28 @@ +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/Actions/Create/Form/Area/Routing/DataBox.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/DataBox.jsx index 5269a52..c19cb6f 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/DataBox.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/DataBox.jsx @@ -27,9 +27,11 @@ const DataBox = ({ isSource, isDest, sourcePosition, destPosition, editingTarget readOnly value={sourcePosition ? `${sourcePosition.lat.toFixed(6)}, ${sourcePosition.lng.toFixed(6)}` : ""} /> - + {isSource && isDest && ( + + )} {isDest && (
diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/ShowRoute.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/ShowRoute.jsx new file mode 100644 index 0000000..8cc7e90 --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/ShowRoute.jsx @@ -0,0 +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]], + }); + }; + + 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]; + useEffect(() => { + if (coords.length > 0) { + map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 }); + } + }, [area]); + + return ( + <> + + + + + ); +}; + +export default ShowRoute; diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/index.jsx index 4f1e47a..06e7c9b 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/index.jsx @@ -5,12 +5,14 @@ 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 { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import DataBox from "./DataBox"; +import AlarmText from "./AlarmText"; -const Routing = () => { +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); @@ -45,33 +47,48 @@ const Routing = () => { const map = useMapEvents({ move(e) { - if (isMovingSource) { - mapPrevSourceMarker.current.setLatLng(e.target.getCenter()); - } - if (isMovingDest && mapPrevDestMarker.current) { - mapPrevDestMarker.current.setLatLng(e.target.getCenter()); - } + 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) { + 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) { + if (isMovingSource) mapPrevSourceMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevSourceIcon.src)); - } - if (isMovingDest && mapPrevDestMarker.current) { + 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 locations = [ + const locs = [ { lat: source.lat, lon: source.lng }, { lat: dest.lat, lon: dest.lng }, ]; @@ -79,16 +96,17 @@ const Routing = () => { const response = await fetch("https://jouya.141.ir/api/router/drive", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ locations }), + 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 && data.alternates.length > 0) { + if (data.alternates?.length > 0) { const decodedAlternates = data.alternates.map((alt) => polyline.decode(alt.trip.legs[0].shape, 6)); setAlternateCoords(decodedAlternates); } else { @@ -103,7 +121,6 @@ const Routing = () => { const handleMarkerClick = async () => { if (!isSource || editingTarget === "source") { const clickedPosition = mapPrevSourceMarker.current.getLatLng(); - console.log("Source:", clickedPosition.lat, clickedPosition.lng); setSourcePosition(clickedPosition); setIsSource(true); setEditingTarget(null); @@ -144,8 +161,9 @@ const Routing = () => { const handleSelectRoute = (index, coords) => { setSelectedRoute({ index, coords }); + const encoded = polyline.encode(coords, 6); + setArea(encoded); map.flyToBounds(coords, { padding: [50, 50], duration: 0.3 }); - console.log("Selected route:", index, coords); }; return ( @@ -159,6 +177,7 @@ const Routing = () => { onEditSource={handleEditSource} onEditDest={handleEditDest} /> + import("@/core/components/MapLayer"), { }); const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - const [bound, setBound] = useState(allData.bound); + const [area, setArea] = useState(allData.area); + const [locations, setLocations] = useState(allData.locations); const handleNext = useCallback(() => { - setAllData({ bound: bound }); + setAllData({ area: area }); + setAllData({ locations: locations }); setTabState((s) => s + 1); - }, [bound]); + }, [area]); return ( <> @@ -22,7 +24,7 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - + @@ -31,8 +33,8 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx index 57515c4..639fdef 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx @@ -7,7 +7,7 @@ import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typogr import moment from "jalali-moment"; import dynamic from "next/dynamic"; import { useCallback } from "react"; -import ShowBound from "../../../showBound"; +import ShowRoute from "../Area/Routing/ShowRoute"; const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { loading: () => , ssr: false, @@ -23,11 +23,17 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => { - - - - - + + + + + + {console.log("allData.area", allData.area)} + + + + + diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/index.jsx index dd39d68..994ce63 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/index.jsx @@ -27,7 +27,7 @@ const reducer = (state, action) => { const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => { const [allData, dispatch] = useReducer(reducer, defaultValues); - const [tabState, setTabState] = useState(2); + const [tabState, setTabState] = useState(0); const handleClose = () => { setOpen(false); }; diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx index 3726f9e..2595dca 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx @@ -19,8 +19,6 @@ const Create = ({ mutate }) => { const submitForm = async (result) => { setSubmitting(true); - const bound = result.bound.getLatLngs(); - let area = bound.map((latlng) => [latlng.lng, latlng.lat]); await requestServer(REQUEST_MISSION, "post", { data: { @@ -31,10 +29,7 @@ const Create = ({ mutate }) => { road_observed_id: result.road_observed_id, } : {}), - area: { - type: result.bound_type, - coordinates: area, - }, + encoded_route: result.area, ...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}), requested_machines: result.requested_machines, type: result.type, @@ -101,8 +96,8 @@ const Create = ({ mutate }) => { road_observed_id: "", rahdaran: [], requested_machines: [], - bound: null, - bound_type: "polyline", + area: null, + locations: [], type: "", start_date: "", start_time: null, diff --git a/src/components/dashboard/roadMissions/operator/OperatorList.jsx b/src/components/dashboard/roadMissions/operator/OperatorList.jsx index e343866..8c277e3 100644 --- a/src/components/dashboard/roadMissions/operator/OperatorList.jsx +++ b/src/components/dashboard/roadMissions/operator/OperatorList.jsx @@ -1,21 +1,21 @@ "use client"; +import DescriptionDialog from "@/components/dashboard/roadMissions/operator/RowActions/DescriptionDialog"; +import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; import DataTableWithAuth from "@/core/components/DataTableWithAuth"; import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes"; import { missionRegions } from "@/core/utils/missionRegions"; import { missionTypes } from "@/core/utils/missionTypes"; import { GET_ROAD_MISSIONS_OPERATOR_LIST } from "@/core/utils/routes"; +import useCities from "@/lib/hooks/useCities"; +import useProvinces from "@/lib/hooks/useProvince"; import { Box, Stack } from "@mui/material"; import moment from "jalali-moment"; import { useMemo } from "react"; -import ShowArea from "./Actions/showArea"; import RowActions from "./RowActions"; import MachinesDialog from "./RowActions/Machines"; -import Toolbar from "./Toolbar"; import RahdaranDialog from "./RowActions/Rahdaran"; -import DescriptionDialog from "@/components/dashboard/roadMissions/operator/RowActions/DescriptionDialog"; -import useProvinces from "@/lib/hooks/useProvince"; -import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; -import useCities from "@/lib/hooks/useCities"; +import RouteDetail from "./RowActions/RouteDetail"; +import Toolbar from "./Toolbar"; const OperatorList = () => { const columns = useMemo(() => { @@ -247,7 +247,7 @@ const OperatorList = () => { }, { header: "منطقه عملیاتی", - id: "area", + id: "encoded_route", enableColumnFilter: false, datatype: "numeric", filterMode: "equals", @@ -263,9 +263,9 @@ const OperatorList = () => { }, }, Cell: ({ row }) => - row.original.area ? ( + row.original.encoded_route ? ( - + ) : ( "-" diff --git a/src/components/dashboard/roadMissions/operator/RowActions/RouteDetail/index.jsx b/src/components/dashboard/roadMissions/operator/RowActions/RouteDetail/index.jsx new file mode 100644 index 0000000..e345bf9 --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/RowActions/RouteDetail/index.jsx @@ -0,0 +1,57 @@ +import { Box, Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import MapIcon from "@mui/icons-material/Map"; +import MapLayer from "@/core/components/MapLayer"; +import ShowRoute from "../../Actions/Create/Form/Area/Routing/ShowRoute"; + +const RouteDetail = ({ encoded_route }) => { + const [openRouteDetailDialog, setOpenRouteDetailDialog] = useState(false); + return ( + <> + + setOpenRouteDetailDialog(true)}> + + + + setOpenRouteDetailDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"sm"} + > + مسیر انتخابی + + + + + + + + + + + + + ); +}; +export default RouteDetail;