Compare commits
5 Commits
70ea71cbfa
...
v1.8.8
| Author | SHA1 | Date | |
|---|---|---|---|
| cab6677bca | |||
| b0a0facea8 | |||
| 58fc9135ac | |||
| 3a28da9b39 | |||
| 80da9a1e40 |
@@ -1,5 +1,5 @@
|
||||
HOST="rms.witel.ir"
|
||||
HOST_RMTO="rms.rmto.ir"
|
||||
NEXT_PUBLIC_VERSION="1.8.7"
|
||||
NEXT_PUBLIC_VERSION="1.8.8"
|
||||
NEXT_PUBLIC_API_URL="https://rms.witel.ir"
|
||||
NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map"
|
||||
@@ -15,7 +15,6 @@
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@mapbox/polyline": "^1.2.1",
|
||||
"@mui/icons-material": "^5.16.7",
|
||||
"@mui/lab": "^5.0.0-alpha.173",
|
||||
"@mui/material": "^5.16.7",
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<svg width="20" height="23" viewBox="0 0 20 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<foreignObject x="-63" y="-64" width="146" height="149"><div xmlns="http://www.w3.org/1999/xhtml" style="backdrop-filter:blur(32.5px);clip-path:url(#bgblur_0_2302_1310_clip_path);height:100%;width:100%"></div></foreignObject><g filter="url(#filter0_d_2302_1310)" data-figma-bg-blur-radius="65">
|
||||
<path d="M9.38305 1.02444C15.543 0.551472 19.9197 7.03396 17.1518 12.6025C15.8547 15.2112 13.6759 15.7958 11.9558 17.881C11.6271 18.2796 10.7507 19.7126 10.4472 19.8829C9.98265 20.1439 9.56634 19.9365 9.25987 19.5758C8.76416 18.992 8.37012 18.2543 7.83732 17.6549C6.56763 16.2249 4.84751 15.4187 3.69508 13.9418C-0.231962 8.90724 3.07767 1.50856 9.38305 1.02444ZM9.55002 5.15175C6.61587 5.45517 5.13915 8.92434 6.86743 11.3212C8.38571 13.4272 11.6293 13.425 13.1468 11.3212C15.1415 8.554 12.925 4.80298 9.55076 5.15175H9.55002Z" fill="#EF4444"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_2302_1310" x="-63" y="-64" width="146" height="149" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.235294 0 0 0 0 0.25098 0 0 0 0 0.262745 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2302_1310"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2302_1310" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="bgblur_0_2302_1310_clip_path" transform="translate(63 64)"><path d="M9.38305 1.02444C15.543 0.551472 19.9197 7.03396 17.1518 12.6025C15.8547 15.2112 13.6759 15.7958 11.9558 17.881C11.6271 18.2796 10.7507 19.7126 10.4472 19.8829C9.98265 20.1439 9.56634 19.9365 9.25987 19.5758C8.76416 18.992 8.37012 18.2543 7.83732 17.6549C6.56763 16.2249 4.84751 15.4187 3.69508 13.9418C-0.231962 8.90724 3.07767 1.50856 9.38305 1.02444ZM9.55002 5.15175C6.61587 5.45517 5.13915 8.92434 6.86743 11.3212C8.38571 13.4272 11.6293 13.425 13.1468 11.3212C15.1415 8.554 12.925 4.80298 9.55076 5.15175H9.55002Z"/>
|
||||
</clipPath></defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,18 +0,0 @@
|
||||
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<foreignObject x="-42.6271" y="-43.6271" width="105.045" height="105.045"><div xmlns="http://www.w3.org/1999/xhtml" style="backdrop-filter:blur(22.31px);clip-path:url(#bgblur_0_2302_1307_clip_path);height:100%;width:100%"></div></foreignObject><g filter="url(#filter0_d_2302_1307)" data-figma-bg-blur-radius="44.6271">
|
||||
<path d="M10.3018 1.00977C14.4733 1.22136 17.7908 4.67141 17.791 8.89551C17.7908 13.2559 14.2559 16.7908 9.89551 16.791C5.67141 16.7908 2.22136 13.4733 2.00977 9.30176L2 8.89551L2.00977 8.48926C2.22119 4.31755 5.6713 1.00017 9.89551 1L10.3018 1.00977ZM9.89551 5.80566C8.18945 5.80584 6.80584 7.18945 6.80566 8.89551L6.80957 9.05371C6.89208 10.6863 8.2425 11.9842 9.89551 11.9844C11.5485 11.9842 12.898 10.6863 12.9805 9.05371L12.9844 8.89551L12.9805 8.73633C12.9003 7.1565 11.6336 5.88942 10.0537 5.80957L9.89551 5.80566Z" fill="#16A795"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_2302_1307" x="-42.6271" y="-43.6271" width="105.045" height="105.045" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.686571"/>
|
||||
<feGaussianBlur stdDeviation="0.686571"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.235294 0 0 0 0 0.25098 0 0 0 0 0.262745 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2302_1307"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2302_1307" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="bgblur_0_2302_1307_clip_path" transform="translate(42.6271 43.6271)"><path d="M10.3018 1.00977C14.4733 1.22136 17.7908 4.67141 17.791 8.89551C17.7908 13.2559 14.2559 16.7908 9.89551 16.791C5.67141 16.7908 2.22136 13.4733 2.00977 9.30176L2 8.89551L2.00977 8.48926C2.22119 4.31755 5.6713 1.00017 9.89551 1L10.3018 1.00977ZM9.89551 5.80566C8.18945 5.80584 6.80584 7.18945 6.80566 8.89551L6.80957 9.05371C6.89208 10.6863 8.2425 11.9842 9.89551 11.9844C11.5485 11.9842 12.898 10.6863 12.9805 9.05371L12.9844 8.89551L12.9805 8.73633C12.9003 7.1565 11.6336 5.88942 10.0537 5.80957L9.89551 5.80566Z"/>
|
||||
</clipPath></defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,18 +0,0 @@
|
||||
<svg width="20" height="23" viewBox="0 0 20 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<foreignObject x="-63" y="-64" width="146" height="149"><div xmlns="http://www.w3.org/1999/xhtml" style="backdrop-filter:blur(32.5px);clip-path:url(#bgblur_0_2302_1310_clip_path);height:100%;width:100%"></div></foreignObject><g filter="url(#filter0_d_2302_1310)" data-figma-bg-blur-radius="65">
|
||||
<path d="M9.38305 1.02444C15.543 0.551472 19.9197 7.03396 17.1518 12.6025C15.8547 15.2112 13.6759 15.7958 11.9558 17.881C11.6271 18.2796 10.7507 19.7126 10.4472 19.8829C9.98265 20.1439 9.56634 19.9365 9.25987 19.5758C8.76416 18.992 8.37012 18.2543 7.83732 17.6549C6.56763 16.2249 4.84751 15.4187 3.69508 13.9418C-0.231962 8.90724 3.07767 1.50856 9.38305 1.02444ZM9.55002 5.15175C6.61587 5.45517 5.13915 8.92434 6.86743 11.3212C8.38571 13.4272 11.6293 13.425 13.1468 11.3212C15.1415 8.554 12.925 4.80298 9.55076 5.15175H9.55002Z" fill="#EF4444"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_2302_1310" x="-63" y="-64" width="146" height="149" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.235294 0 0 0 0 0.25098 0 0 0 0 0.262745 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2302_1310"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2302_1310" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="bgblur_0_2302_1310_clip_path" transform="translate(63 64)"><path d="M9.38305 1.02444C15.543 0.551472 19.9197 7.03396 17.1518 12.6025C15.8547 15.2112 13.6759 15.7958 11.9558 17.881C11.6271 18.2796 10.7507 19.7126 10.4472 19.8829C9.98265 20.1439 9.56634 19.9365 9.25987 19.5758C8.76416 18.992 8.37012 18.2543 7.83732 17.6549C6.56763 16.2249 4.84751 15.4187 3.69508 13.9418C-0.231962 8.90724 3.07767 1.50856 9.38305 1.02444ZM9.55002 5.15175C6.61587 5.45517 5.13915 8.92434 6.86743 11.3212C8.38571 13.4272 11.6293 13.425 13.1468 11.3212C15.1415 8.554 12.925 4.80298 9.55076 5.15175H9.55002Z"/>
|
||||
</clipPath></defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,21 +0,0 @@
|
||||
<svg width="42" height="66" viewBox="0 0 42 66" fill="#F15152" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M21 66C23.7614 66 26 64.8807 26 63.5C26 62.1193 23.7614 61 21 61C18.2386 61 16 62.1193 16 63.5C16 64.8807 18.2386 66 21 66Z" fill="#F15152"/>
|
||||
<path fill-rule="#F15152" clip-rule="evenodd" d="M21 0C32.598 0 42 9.40202 42 21C42 31.9234 33.6599 40.8989 23 41.906V62C23 63.1046 22.1046 64 21 64C19.8954 64 19 63.1046 19 62V41.906C8.34014 40.8989 0 31.9234 0 21C0 9.40202 9.40202 0 21 0Z" fill="#F15152"/>
|
||||
<foreignObject x="-61" y="-61" width="164" height="164"></foreignObject><g filter="url(#filter0_d_173_69)" data-figma-bg-blur-radius="65">
|
||||
<path d="M38 21C38 11.6112 30.3888 4 21 4C11.6112 4 4 11.6112 4 21C4 30.3888 11.6112 38 21 38C30.3888 38 38 30.3888 38 21Z" fill="#F15152"/>
|
||||
</g>
|
||||
<path d="M28 21C28 17.134 24.866 14 21 14C17.134 14 14 17.134 14 21C14 24.866 17.134 28 21 28C24.866 28 28 24.866 28 21Z" fill="white"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_173_69" x="-61" y="-61" width="164" height="164" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.235294 0 0 0 0 0.25098 0 0 0 0 0.262745 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_173_69"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_173_69" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="bgblur_0_173_69_clip_path" transform="translate(61 61)"><path d="M38 21C38 11.6112 30.3888 4 21 4C11.6112 4 4 11.6112 4 21C4 30.3888 11.6112 38 21 38C30.3888 38 38 30.3888 38 21Z"/>
|
||||
</clipPath></defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,21 +0,0 @@
|
||||
<svg width="42" height="66" viewBox="0 0 42 66" fill="#2A8A7E" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M21 66C23.7614 66 26 64.8807 26 63.5C26 62.1193 23.7614 61 21 61C18.2386 61 16 62.1193 16 63.5C16 64.8807 18.2386 66 21 66Z" fill="#2A8A7E"/>
|
||||
<path fill-rule="#2A8A7E" clip-rule="evenodd" d="M21 0C32.598 0 42 9.40202 42 21C42 31.9234 33.6599 40.8989 23 41.906V62C23 63.1046 22.1046 64 21 64C19.8954 64 19 63.1046 19 62V41.906C8.34014 40.8989 0 31.9234 0 21C0 9.40202 9.40202 0 21 0Z" fill="#2A8A7E"/>
|
||||
<foreignObject x="-61" y="-61" width="164" height="164"></foreignObject><g filter="url(#filter0_d_173_69)" data-figma-bg-blur-radius="65">
|
||||
<path d="M38 21C38 11.6112 30.3888 4 21 4C11.6112 4 4 11.6112 4 21C4 30.3888 11.6112 38 21 38C30.3888 38 38 30.3888 38 21Z" fill="#2A8A7E"/>
|
||||
</g>
|
||||
<path d="M28 21C28 17.134 24.866 14 21 14C17.134 14 14 17.134 14 21C14 24.866 17.134 28 21 28C24.866 28 28 24.866 28 21Z" fill="white"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_173_69" x="-61" y="-61" width="164" height="164" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="1"/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.235294 0 0 0 0 0.25098 0 0 0 0 0.262745 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_173_69"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_173_69" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="bgblur_0_173_69_clip_path" transform="translate(61 61)"><path d="M38 21C38 11.6112 30.3888 4 21 4C11.6112 4 4 11.6112 4 21C4 30.3888 11.6112 38 21 38C30.3888 38 38 30.3888 38 21Z"/>
|
||||
</clipPath></defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,18 +0,0 @@
|
||||
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<foreignObject x="-42.6271" y="-43.6271" width="105.045" height="105.045"><div xmlns="http://www.w3.org/1999/xhtml" style="backdrop-filter:blur(22.31px);clip-path:url(#bgblur_0_2302_1307_clip_path);height:100%;width:100%"></div></foreignObject><g filter="url(#filter0_d_2302_1307)" data-figma-bg-blur-radius="44.6271">
|
||||
<path d="M10.3018 1.00977C14.4733 1.22136 17.7908 4.67141 17.791 8.89551C17.7908 13.2559 14.2559 16.7908 9.89551 16.791C5.67141 16.7908 2.22136 13.4733 2.00977 9.30176L2 8.89551L2.00977 8.48926C2.22119 4.31755 5.6713 1.00017 9.89551 1L10.3018 1.00977ZM9.89551 5.80566C8.18945 5.80584 6.80584 7.18945 6.80566 8.89551L6.80957 9.05371C6.89208 10.6863 8.2425 11.9842 9.89551 11.9844C11.5485 11.9842 12.898 10.6863 12.9805 9.05371L12.9844 8.89551L12.9805 8.73633C12.9003 7.1565 11.6336 5.88942 10.0537 5.80957L9.89551 5.80566Z" fill="#16A795"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_2302_1307" x="-42.6271" y="-43.6271" width="105.045" height="105.045" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="0.686571"/>
|
||||
<feGaussianBlur stdDeviation="0.686571"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.235294 0 0 0 0 0.25098 0 0 0 0 0.262745 0 0 0 0.3 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2302_1307"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2302_1307" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="bgblur_0_2302_1307_clip_path" transform="translate(42.6271 43.6271)"><path d="M10.3018 1.00977C14.4733 1.22136 17.7908 4.67141 17.791 8.89551C17.7908 13.2559 14.2559 16.7908 9.89551 16.791C5.67141 16.7908 2.22136 13.4733 2.00977 9.30176L2 8.89551L2.00977 8.48926C2.22119 4.31755 5.6713 1.00017 9.89551 1L10.3018 1.00977ZM9.89551 5.80566C8.18945 5.80584 6.80584 7.18945 6.80566 8.89551L6.80957 9.05371C6.89208 10.6863 8.2425 11.9842 9.89551 11.9844C11.5485 11.9842 12.898 10.6863 12.9805 9.05371L12.9844 8.89551L12.9805 8.73633C12.9003 7.1565 11.6336 5.88942 10.0537 5.80957L9.89551 5.80566Z"/>
|
||||
</clipPath></defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -14,6 +14,7 @@ import useCities from "@/lib/hooks/useCities";
|
||||
const ControlList = () => {
|
||||
const columns = useMemo(() => {
|
||||
const dynamicColumns = {
|
||||
accessorKey: "province_id",
|
||||
header: "استان",
|
||||
id: "province_id",
|
||||
enableColumnFilter: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import LtrTextField from "@/core/components/LtrTextField";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import MuiTimePicker from "@/core/components/MuiTimePicker";
|
||||
import { FINISH_MISSION } from "@/core/utils/routes";
|
||||
@@ -14,6 +15,7 @@ const FinishMissionContent = ({ rowId, mutate, setOpenFinishMissionDialog }) =>
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
|
||||
const validationSchema = object().shape({
|
||||
end_km: string().required("لطفاً کیلومتر را وارد کنید."),
|
||||
start_date: mixed().required("تاریخ خروج خودرو را مشخص کنید."),
|
||||
start_time: mixed().required("ساعت خروج خودرو را مشخص کنید."),
|
||||
});
|
||||
@@ -24,6 +26,7 @@ const FinishMissionContent = ({ rowId, mutate, setOpenFinishMissionDialog }) =>
|
||||
formState: { isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
end_km: "",
|
||||
start_date: null,
|
||||
start_time: null,
|
||||
},
|
||||
@@ -36,6 +39,7 @@ const FinishMissionContent = ({ rowId, mutate, setOpenFinishMissionDialog }) =>
|
||||
const formattedTime = format(values.start_time, "HH:mm");
|
||||
requestServer(`${FINISH_MISSION}/${rowId}`, "post", {
|
||||
data: {
|
||||
end_km: values.end_km,
|
||||
time: `${formattedDate} ${formattedTime}`,
|
||||
},
|
||||
hasSidebarUpdate: true,
|
||||
@@ -55,6 +59,27 @@ const FinishMissionContent = ({ rowId, mutate, setOpenFinishMissionDialog }) =>
|
||||
<DialogContent>
|
||||
<Stack alignItems="center" spacing={2}>
|
||||
<Typography mt={2}>آیا از ورود خودرو و پایان ماموریت اطمینان دارید؟</Typography>
|
||||
<Stack>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"end_km"}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<LtrTextField
|
||||
autoComplete="off"
|
||||
type="tel"
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
if (isNaN(Number(e.target.value))) return;
|
||||
field.onChange(e.target.value);
|
||||
}}
|
||||
size="small"
|
||||
fullWidth
|
||||
label="کیلومتر خودرو / ساعت کار"
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -65,8 +90,8 @@ const FinishMissionContent = ({ rowId, mutate, setOpenFinishMissionDialog }) =>
|
||||
error={error}
|
||||
value={field.value || null}
|
||||
disableFuture={false}
|
||||
placeholder={"تاریخ شروع ماموریت را وارد کنید"}
|
||||
label={"تاریخ شروع ماموریت"}
|
||||
placeholder={"تاریخ پایان ماموریت را وارد کنید"}
|
||||
label={"تاریخ پایان ماموریت"}
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
@@ -83,8 +108,8 @@ const FinishMissionContent = ({ rowId, mutate, setOpenFinishMissionDialog }) =>
|
||||
error={error}
|
||||
value={field.value || null}
|
||||
views={["hours", "minutes"]}
|
||||
placeholder={"زمان شروع ماموریت را وارد کنید"}
|
||||
label={"زمان شروع ماموریت"}
|
||||
placeholder={"زمان پایان ماموریت را وارد کنید"}
|
||||
label={"زمان پایان ماموریت"}
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
|
||||
@@ -1,113 +1,116 @@
|
||||
"use client";
|
||||
import { Delete, Route } from "@mui/icons-material";
|
||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
||||
import { useReducer } from "react";
|
||||
import DrawBound from "./DrawBound";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
const statusType = [
|
||||
{
|
||||
id: 0,
|
||||
message: "برای آغاز ترسیم مسیر، کلیک کنید!",
|
||||
buttons: [
|
||||
{
|
||||
label: "شروع ترسیم مسیر",
|
||||
key: "start",
|
||||
color: "primary",
|
||||
icon: <Route />,
|
||||
onclick: (controlDispach) => {
|
||||
controlDispach({ type: "SET_STATUS", status: 1 });
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
message: "مسیر موردنظر را با کلیک روی نقشه مشخص کنید. برای اتمام، روی نقطهی پایانی کلیک کنید!",
|
||||
buttons: [],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
message: "برای اصلاح مسیر، گوشهها را بکشید. برای ترسیم دوباره، آن را حذف کنید",
|
||||
buttons: [
|
||||
{
|
||||
label: "حذف",
|
||||
key: "end",
|
||||
color: "error",
|
||||
icon: <Delete />,
|
||||
onclick: (controlDispach) => {
|
||||
controlDispach({ type: "SET_STATUS", status: 0 });
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ===== Icons =====
|
||||
const originIcon = new L.Icon({
|
||||
iconUrl: "/icons/origin-icon.svg",
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
});
|
||||
const createInitialState = (bound) => {
|
||||
if (bound) {
|
||||
return { status: 2 };
|
||||
}
|
||||
return { status: 0 };
|
||||
};
|
||||
|
||||
const destinationIcon = new L.Icon({
|
||||
iconUrl: "/icons/destination-icon.svg",
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
});
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "SET_STATUS":
|
||||
return { ...state, status: action.status };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
// ===== Track Center =====
|
||||
function MapCenterTracker({ setCenter }) {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
const updateCenter = () => {
|
||||
setCenter(map.getCenter());
|
||||
};
|
||||
|
||||
updateCenter();
|
||||
|
||||
map.on("move", updateCenter);
|
||||
|
||||
return () => {
|
||||
map.off("move", updateCenter);
|
||||
};
|
||||
}, [map, setCenter]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ===== Fixed Center Marker =====
|
||||
function FixedCenterMarker() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -100%)",
|
||||
zIndex: 1000,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
<img src="/icons/origin-icon.svg" width={40} height={40} alt="center" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Example() {
|
||||
const [center, setCenter] = useState(null);
|
||||
|
||||
const [origin, setOrigin] = useState(null);
|
||||
const [destination, setDestination] = useState(null);
|
||||
|
||||
const [step, setStep] = useState("origin");
|
||||
|
||||
const handleOk = () => {
|
||||
if (!center) return;
|
||||
|
||||
if (step === "origin") {
|
||||
setOrigin(center);
|
||||
setStep("destination");
|
||||
return;
|
||||
}
|
||||
|
||||
if (step === "destination") {
|
||||
setDestination(center);
|
||||
setStep("done");
|
||||
}
|
||||
};
|
||||
const MapControlPolyline = ({ bound, setBound }) => {
|
||||
const [control, controlDispach] = useReducer(reducer, bound, createInitialState);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "400px",
|
||||
<>
|
||||
<DrawBound bound={bound} setBound={setBound} control={control} controlDispach={controlDispach} />
|
||||
<Stack
|
||||
direction={"row"}
|
||||
justifyContent={"center"}
|
||||
sx={{ position: "absolute", left: 0, top: 0, zIndex: 2000, width: "100%" }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
background: (theme) => theme.palette.info.main,
|
||||
borderBottomLeftRadius: 8,
|
||||
borderBottomRightRadius: 8,
|
||||
py: 0.5,
|
||||
px: 2,
|
||||
}}
|
||||
>
|
||||
<Typography align="center" component={"div"} variant="caption" color={"#fff"}>
|
||||
{statusType.find((st) => st.id == control.status).message}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
zIndex: 2000,
|
||||
width: "100%",
|
||||
py: 1,
|
||||
background: "linear-gradient(to top, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0))",
|
||||
}}
|
||||
>
|
||||
<MapCenterTracker setCenter={setCenter} />
|
||||
|
||||
{/* saved markers */}
|
||||
{origin && <Marker position={origin} icon={originIcon} />}
|
||||
|
||||
{destination && <Marker position={destination} icon={destinationIcon} />}
|
||||
|
||||
{/* route line */}
|
||||
{origin && destination && <Polyline positions={[origin, destination]} />}
|
||||
|
||||
{/* fixed center pin */}
|
||||
{step !== "done" && <FixedCenterMarker />}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 16, position: "absolute", z: 50, top: 5 }}>
|
||||
<button onClick={handleOk}>
|
||||
{step === "origin" && "Set Origin"}
|
||||
{step === "destination" && "Set Destination"}
|
||||
{step === "done" && "Completed"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Stack direction={"row"} justifyContent={"center"} spacing={2}>
|
||||
{statusType
|
||||
.find((st) => st.id == control.status)
|
||||
.buttons.map((button) => (
|
||||
<Button
|
||||
size="small"
|
||||
key={button.key}
|
||||
variant="contained"
|
||||
onClick={() => button.onclick(controlDispach)}
|
||||
color={button.color}
|
||||
startIcon={button.icon}
|
||||
>
|
||||
{button.label}
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default MapControlPolyline;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
const AlarmText = ({ isSource }) => {
|
||||
if (isSource) return null;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 5,
|
||||
left: 5,
|
||||
zIndex: 1000,
|
||||
border: "1px solid #ed0f02",
|
||||
backgroundColor: "#f19898",
|
||||
py: 0.5,
|
||||
px: 1,
|
||||
borderRadius: 2,
|
||||
opacity: 0.8,
|
||||
boxShadow: "rgba(0, 0, 0, 0.35) 0px 5px 15px",
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ fontWeight: "bold" }} color="#ed0f02">
|
||||
با کلیک بر روی مارکر موقعیت را انتخاب کنید
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlarmText;
|
||||
@@ -1,53 +0,0 @@
|
||||
import { Box, Button, TextField } from "@mui/material";
|
||||
|
||||
const DataBox = ({ isSource, isDest, sourcePosition, destPosition, editingTarget, onEditSource, onEditDest }) => {
|
||||
if (!isSource) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 5,
|
||||
left: 5,
|
||||
zIndex: 1000,
|
||||
backgroundColor: "white",
|
||||
border: "1px solid #e1e1e1",
|
||||
padding: 1,
|
||||
borderRadius: 2,
|
||||
opacity: 0.8,
|
||||
gap: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "end", gap: 4 }}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
size="small"
|
||||
readOnly
|
||||
value={sourcePosition ? `${sourcePosition.lat.toFixed(6)}, ${sourcePosition.lng.toFixed(6)}` : ""}
|
||||
/>
|
||||
{isSource && isDest && (
|
||||
<Button variant="contained" color="success" size="small" onClick={onEditSource}>
|
||||
ویرایش مبدا
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isDest && (
|
||||
<div style={{ display: "flex", alignItems: "end", gap: 4 }}>
|
||||
<TextField
|
||||
size="small"
|
||||
variant="outlined"
|
||||
readOnly
|
||||
value={destPosition ? `${destPosition.lat.toFixed(6)}, ${destPosition.lng.toFixed(6)}` : ""}
|
||||
/>
|
||||
<Button variant="contained" color="error" size="small" onClick={onEditDest}>
|
||||
ویرایش مقصد
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataBox;
|
||||
@@ -1,46 +0,0 @@
|
||||
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 (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShowRoute;
|
||||
@@ -1,219 +0,0 @@
|
||||
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 (
|
||||
<>
|
||||
<DataBox
|
||||
isSource={isSource}
|
||||
isDest={isDest}
|
||||
sourcePosition={sourcePosition}
|
||||
destPosition={destPosition}
|
||||
editingTarget={editingTarget}
|
||||
onEditSource={handleEditSource}
|
||||
onEditDest={handleEditDest}
|
||||
/>
|
||||
<AlarmText isSource={isSource} />
|
||||
<Marker
|
||||
position={sourcePosition || map.getCenter()}
|
||||
ref={mapPrevSourceMarker}
|
||||
eventHandlers={{ click: handleMarkerClick }}
|
||||
icon={defaultIcon}
|
||||
/>
|
||||
{isSource && (
|
||||
<Marker
|
||||
position={destPosition || map.getCenter()}
|
||||
ref={mapPrevDestMarker}
|
||||
eventHandlers={{ click: handleDestMarkerClick }}
|
||||
icon={prevDestIcon}
|
||||
/>
|
||||
)}
|
||||
{alternateCoords.map((coords, index) => (
|
||||
<Polyline
|
||||
key={`alternate-${index}-${selectedRoute?.index === index}`}
|
||||
positions={coords}
|
||||
color={selectedRoute?.index === index ? "#015688" : "#91B2C6"}
|
||||
weight={selectedRoute?.index === index ? 7 : 6}
|
||||
opacity={selectedRoute?.index === index ? 1 : 0.7}
|
||||
eventHandlers={{ click: () => handleSelectRoute(index, coords) }}
|
||||
/>
|
||||
))}
|
||||
{routeCoords.length > 0 && (
|
||||
<Polyline
|
||||
key={`main-${selectedRoute?.index === "main"}`}
|
||||
positions={routeCoords}
|
||||
color={selectedRoute?.index === "main" ? "#015688" : "#91B2C6"}
|
||||
weight={selectedRoute?.index === "main" ? 7 : 6}
|
||||
opacity={selectedRoute?.index === "main" ? 1 : 0.7}
|
||||
eventHandlers={{ click: () => handleSelectRoute("main", routeCoords) }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Routing;
|
||||
@@ -2,21 +2,19 @@ import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCallback, useState } from "react";
|
||||
import Routing from "./Routing";
|
||||
import MapControlPolyline from "./MapControlPolyline";
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
||||
const [area, setArea] = useState(allData.area);
|
||||
const [locations, setLocations] = useState(allData.locations);
|
||||
const [bound, setBound] = useState(allData.bound);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
setAllData({ area: area });
|
||||
setAllData({ locations: locations });
|
||||
setAllData({ bound: bound });
|
||||
setTabState((s) => s + 1);
|
||||
}, [area]);
|
||||
}, [bound]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -24,7 +22,7 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
||||
<Stack spacing={2}>
|
||||
<Box sx={{ width: "100%", height: "400px" }}>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<Routing area={area} setArea={setArea} locations={locations} setLocations={setLocations} />
|
||||
<MapControlPolyline bound={bound} setBound={setBound} boundType={"polyline"} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -33,8 +31,8 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
||||
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
|
||||
{"مرحله قبلی"}
|
||||
</Button>
|
||||
<Button variant="contained" size="large" disabled={!area} onClick={handleNext}>
|
||||
{!area ? "در انتظار ترسیم" : "مرحله بعد"}
|
||||
<Button variant="contained" size="large" disabled={!bound} onClick={handleNext}>
|
||||
{!bound ? "در انتظار ترسیم" : "مرحله بعد"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
|
||||
@@ -51,7 +51,6 @@ const MissionDates = ({ control }) => {
|
||||
name="start_time"
|
||||
error={error}
|
||||
value={field.value}
|
||||
views={["hours"]}
|
||||
minTime={
|
||||
start_date && new Date(start_date).toDateString() === now.toDateString()
|
||||
? now
|
||||
@@ -100,7 +99,6 @@ const MissionDates = ({ control }) => {
|
||||
name="end_time"
|
||||
error={error}
|
||||
value={field.value}
|
||||
views={["hours"]}
|
||||
minTime={startDateTime ?? null}
|
||||
placeholder={"زمان پایان ماموریت را وارد کنید"}
|
||||
label={"زمان پایان ماموریت"}
|
||||
|
||||
@@ -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 ShowRoute from "../Area/Routing/ShowRoute";
|
||||
import ShowBound from "../../../showBound";
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
@@ -23,17 +23,11 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
|
||||
<DialogContent dividers>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Stack spacing={2}>
|
||||
<Stack spacing={1}>
|
||||
<Divider>
|
||||
<Chip color="primary" variant="outlined" label="مسیر ماموریت" />
|
||||
</Divider>
|
||||
<Box sx={{ width: "100%", height: "400px" }}>
|
||||
{console.log("allData.area", allData.area)}
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowRoute area={allData.area} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box sx={{ width: "100%", height: "200px" }}>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowBound bound={allData.bound} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
<Stack spacing={1}>
|
||||
<Divider>
|
||||
<Chip color="primary" variant="outlined" label="مشخصات ماموریت" />
|
||||
|
||||
@@ -19,6 +19,8 @@ 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: {
|
||||
@@ -29,7 +31,10 @@ const Create = ({ mutate }) => {
|
||||
road_observed_id: result.road_observed_id,
|
||||
}
|
||||
: {}),
|
||||
encoded_route: result.area,
|
||||
area: {
|
||||
type: result.bound_type,
|
||||
coordinates: area,
|
||||
},
|
||||
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}),
|
||||
requested_machines: result.requested_machines,
|
||||
type: result.type,
|
||||
@@ -96,8 +101,8 @@ const Create = ({ mutate }) => {
|
||||
road_observed_id: "",
|
||||
rahdaran: [],
|
||||
requested_machines: [],
|
||||
area: null,
|
||||
locations: [],
|
||||
bound: null,
|
||||
bound_type: "polyline",
|
||||
type: "",
|
||||
start_date: "",
|
||||
start_time: null,
|
||||
|
||||
@@ -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 RahdaranDialog from "./RowActions/Rahdaran";
|
||||
import RouteDetail from "./RowActions/RouteDetail";
|
||||
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";
|
||||
|
||||
const OperatorList = () => {
|
||||
const columns = useMemo(() => {
|
||||
@@ -247,7 +247,7 @@ const OperatorList = () => {
|
||||
},
|
||||
{
|
||||
header: "منطقه عملیاتی",
|
||||
id: "encoded_route",
|
||||
id: "area",
|
||||
enableColumnFilter: false,
|
||||
datatype: "numeric",
|
||||
filterMode: "equals",
|
||||
@@ -263,9 +263,9 @@ const OperatorList = () => {
|
||||
},
|
||||
},
|
||||
Cell: ({ row }) =>
|
||||
row.original.encoded_route ? (
|
||||
row.original.area ? (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<RouteDetail encoded_route={row.original.encoded_route} />
|
||||
<ShowArea area={row.original.area} />
|
||||
</Stack>
|
||||
) : (
|
||||
"-"
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
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 (
|
||||
<>
|
||||
<Tooltip title="مسیر انتخابی">
|
||||
<IconButton color="primary" onClick={() => setOpenRouteDetailDialog(true)}>
|
||||
<MapIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openRouteDetailDialog}
|
||||
onClose={() => 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"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>مسیر انتخابی</DialogTitle>
|
||||
<Box sx={{ mx: 1, borderRadius: "5px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "300px",
|
||||
border: "1px solid #e1e1e1",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowRoute area={encoded_route} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setOpenRouteDetailDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RouteDetail;
|
||||
@@ -15,7 +15,7 @@ const ToDateController = ({ control }) => {
|
||||
maxDate={new Date()}
|
||||
error={!!error}
|
||||
value={value}
|
||||
label={"تاریخ شروع (تا تاریخ)"}
|
||||
label={"تاریخ پایان (تا تاریخ)"}
|
||||
placeholder={"تا تاریخ"}
|
||||
setFieldValue={(name, newValue) => {
|
||||
const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD");
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
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 (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShowRoute;
|
||||
@@ -1,57 +0,0 @@
|
||||
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 "./ShowRoute";
|
||||
|
||||
const RouteDetail = ({ encoded_route }) => {
|
||||
const [openRouteDetailDialog, setOpenRouteDetailDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="مسیر انتخابی">
|
||||
<IconButton color="primary" onClick={() => setOpenRouteDetailDialog(true)}>
|
||||
<MapIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openRouteDetailDialog}
|
||||
onClose={() => 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"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>مسیر انتخابی</DialogTitle>
|
||||
<Box sx={{ mx: 1, borderRadius: "5px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "300px",
|
||||
border: "1px solid #e1e1e1",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowRoute area={encoded_route} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setOpenRouteDetailDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RouteDetail;
|
||||
@@ -4,13 +4,13 @@ import { GET_ROAD_MISSIONS_TRANSPORTATION_LIST } from "@/core/utils/routes";
|
||||
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 { missionRegions } from "@/core/utils/missionRegions";
|
||||
import { missionTypes } from "@/core/utils/missionTypes";
|
||||
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";
|
||||
|
||||
const TransportationList = () => {
|
||||
const columns = useMemo(() => {
|
||||
@@ -230,9 +230,9 @@ const TransportationList = () => {
|
||||
},
|
||||
},
|
||||
Cell: ({ row }) =>
|
||||
row.original.encoded_route ? (
|
||||
row.original.area ? (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<RouteDetail encoded_route={row.original.encoded_route} />
|
||||
<ShowArea area={row.original.area} />
|
||||
</Stack>
|
||||
) : (
|
||||
"-"
|
||||
|
||||
Reference in New Issue
Block a user