Merge branch 'develop'
This commit is contained in:
111
package.json
111
package.json
@@ -1,55 +1,56 @@
|
|||||||
{
|
{
|
||||||
"name": "rms",
|
"name": "rms",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\"",
|
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\"",
|
||||||
"build:publish": "npm i && npm run build && pm2 restart ecosystem.config.js"
|
"build:publish": "npm i && npm run build && pm2 restart ecosystem.config.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/cache": "^11.11.0",
|
"@emotion/cache": "^11.11.0",
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@hookform/resolvers": "^3.9.0",
|
"@hookform/resolvers": "^3.9.0",
|
||||||
"@mui/icons-material": "^5.16.7",
|
"@mapbox/polyline": "^1.2.1",
|
||||||
"@mui/lab": "^5.0.0-alpha.173",
|
"@mui/icons-material": "^5.16.7",
|
||||||
"@mui/material": "^5.16.7",
|
"@mui/lab": "^5.0.0-alpha.173",
|
||||||
"@mui/material-nextjs": "^5.16.6",
|
"@mui/material": "^5.16.7",
|
||||||
"@mui/x-date-pickers": "^6.19.5",
|
"@mui/material-nextjs": "^5.16.6",
|
||||||
"@mui/x-tree-view": "^7.11.0",
|
"@mui/x-date-pickers": "^6.19.5",
|
||||||
"@turf/turf": "^7.2.0",
|
"@mui/x-tree-view": "^7.11.0",
|
||||||
"axios": "^1.7.2",
|
"@turf/turf": "^7.2.0",
|
||||||
"date-fns": "^3.3.1",
|
"axios": "^1.7.2",
|
||||||
"date-fns-jalali": "^2.13.0-0",
|
"date-fns": "^3.3.1",
|
||||||
"dayjs": "^1.11.10",
|
"date-fns-jalali": "^2.13.0-0",
|
||||||
"file-saver": "^2.0.5",
|
"dayjs": "^1.11.10",
|
||||||
"jalali-moment": "^3.3.11",
|
"file-saver": "^2.0.5",
|
||||||
"lz-string": "^1.5.0",
|
"jalali-moment": "^3.3.11",
|
||||||
"material-react-table": "^2.11.2",
|
"lz-string": "^1.5.0",
|
||||||
"next": "^14.2.3",
|
"material-react-table": "^2.11.2",
|
||||||
"react": "^18",
|
"next": "^14.2.3",
|
||||||
"react-dom": "^18",
|
"react": "^18",
|
||||||
"react-hook-form": "^7.52.1",
|
"react-dom": "^18",
|
||||||
"react-leaflet": "^4.2.1",
|
"react-hook-form": "^7.52.1",
|
||||||
"react-leaflet-draw": "^0.20.4",
|
"react-leaflet": "^4.2.1",
|
||||||
"react-leaflet-markercluster": "^4.2.1",
|
"react-leaflet-draw": "^0.20.4",
|
||||||
"react-qr-code": "^2.0.18",
|
"react-leaflet-markercluster": "^4.2.1",
|
||||||
"react-toastify": "^10.0.5",
|
"react-qr-code": "^2.0.18",
|
||||||
"sass": "^1.75.0",
|
"react-toastify": "^10.0.5",
|
||||||
"sharp": "^0.33.4",
|
"sass": "^1.75.0",
|
||||||
"stylis": "^4.3.1",
|
"sharp": "^0.33.4",
|
||||||
"stylis-plugin-rtl": "^2.1.1",
|
"stylis": "^4.3.1",
|
||||||
"swr": "^2.2.5",
|
"stylis-plugin-rtl": "^2.1.1",
|
||||||
"yup": "^1.4.0"
|
"swr": "^2.2.5",
|
||||||
},
|
"yup": "^1.4.0"
|
||||||
"devDependencies": {
|
},
|
||||||
"eslint": "^8",
|
"devDependencies": {
|
||||||
"eslint-config-next": "14.1.0",
|
"eslint": "^8",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-next": "14.1.0",
|
||||||
"prettier": "^3.3.2"
|
"eslint-config-prettier": "^9.1.0",
|
||||||
}
|
"prettier": "^3.3.2"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
18
public/icons/destination-icon.svg
Normal file
18
public/icons/destination-icon.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
18
public/icons/origin-icon.svg
Normal file
18
public/icons/origin-icon.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import PageLoading from "@/core/components/PageLoading";
|
import PageLoading from "@/core/components/PageLoading";
|
||||||
|
|
||||||
const Loading = () => {
|
const Loading = () => {
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
};
|
};
|
||||||
export default Loading;
|
export default Loading;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import WithPermission from "@/core/middlewares/withPermission";
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
import MachinesPage from "@/components/dashboard/machines";
|
import MachinesPage from "@/components/dashboard/machines";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "کارتابل ماشینها",
|
title: "کارتابل ماشینها",
|
||||||
};
|
};
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WithPermission permission_name={["all"]}>
|
<WithPermission permission_name={["all"]}>
|
||||||
<MachinesPage />
|
<MachinesPage />
|
||||||
</WithPermission>
|
</WithPermission>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
18
src/assets/images/destination-icon.svg
Normal file
18
src/assets/images/destination-icon.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
21
src/assets/images/prev-destination-icon.svg
Normal file
21
src/assets/images/prev-destination-icon.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
21
src/assets/images/prev-source-icon.svg
Normal file
21
src/assets/images/prev-source-icon.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
18
src/assets/images/source-icon.svg
Normal file
18
src/assets/images/source-icon.svg
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,134 +1,134 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
import { GET_MACHINES_LIST } from "@/core/utils/routes";
|
import { GET_MACHINES_LIST } from "@/core/utils/routes";
|
||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import RowActions from "./RowActions";
|
import RowActions from "./RowActions";
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
{ value: "", label: "همه وضعیت ها" },
|
{ value: "", label: "همه وضعیت ها" },
|
||||||
{ value: 1, label: "آماده" },
|
{ value: 1, label: "آماده" },
|
||||||
{ value: 2, label: "رزرو" },
|
{ value: 2, label: "رزرو" },
|
||||||
{ value: 3, label: "در ماموریت" },
|
{ value: 3, label: "در ماموریت" },
|
||||||
{ value: 4, label: "خارج از رده" },
|
{ value: 4, label: "خارج از رده" },
|
||||||
{ value: 5, label: "در تعمیر" },
|
{ value: 5, label: "در تعمیر" },
|
||||||
{ value: 6, label: "فروخته شده" },
|
{ value: 6, label: "فروخته شده" },
|
||||||
{ value: 7, label: "نیاز به تعمیر" },
|
{ value: 7, label: "نیاز به تعمیر" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const MachinesList = () => {
|
const MachinesList = () => {
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
accessorKey: "id",
|
accessorKey: "id",
|
||||||
header: "کد یکتا", // Unique Code
|
header: "کد یکتا", // Unique Code
|
||||||
id: "id",
|
id: "id",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 50,
|
size: 50,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
header: "اطلاعات ماشینها",
|
header: "اطلاعات ماشینها",
|
||||||
id: "machines_info",
|
id: "machines_info",
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
grow: false,
|
grow: false,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
accessorKey: "car_name",
|
accessorKey: "car_name",
|
||||||
header: "نام ماشین",
|
header: "نام ماشین",
|
||||||
id: "car_name",
|
id: "car_name",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "car_type",
|
accessorKey: "car_type",
|
||||||
header: "نوع ماشین",
|
header: "نوع ماشین",
|
||||||
id: "car_type",
|
id: "car_type",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "machine_code",
|
accessorKey: "machine_code",
|
||||||
header: "کد ماشین",
|
header: "کد ماشین",
|
||||||
id: "machine_code",
|
id: "machine_code",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "plak_number",
|
accessorKey: "plak_number",
|
||||||
header: "پلاک",
|
header: "پلاک",
|
||||||
id: "plak_number",
|
id: "plak_number",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "status",
|
accessorKey: "status",
|
||||||
header: "وضعیت",
|
header: "وضعیت",
|
||||||
id: "status",
|
id: "status",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "numeric",
|
datatype: "numeric",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
columnSelectOption: () => {
|
columnSelectOption: () => {
|
||||||
return statusOptions.map((status) => ({
|
return statusOptions.map((status) => ({
|
||||||
value: status.value,
|
value: status.value,
|
||||||
label: status.label,
|
label: status.label,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
Cell: ({ row }) => {
|
Cell: ({ row }) => {
|
||||||
const status = statusOptions.find((option) => option.value == row.original.status);
|
const status = statusOptions.find((option) => option.value == row.original.status);
|
||||||
return <Typography variant="body2">{status?.label || ""}</Typography>;
|
return <Typography variant="body2">{status?.label || ""}</Typography>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
<DataTableWithAuth
|
<DataTableWithAuth
|
||||||
need_filter={true}
|
need_filter={true}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
table_url={GET_MACHINES_LIST}
|
table_url={GET_MACHINES_LIST}
|
||||||
page_name={"machines"}
|
page_name={"machines"}
|
||||||
table_name={"machinesList"}
|
table_name={"machinesList"}
|
||||||
sorting={[{ id: "id", desc: true }]}
|
sorting={[{ id: "id", desc: true }]}
|
||||||
enableRowActions
|
enableRowActions
|
||||||
positionActionsColumn={"first"}
|
positionActionsColumn={"first"}
|
||||||
RowActions={RowActions}
|
RowActions={RowActions}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default MachinesList;
|
export default MachinesList;
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
|
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import useRequest from "@/lib/hooks/useRequest";
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
import { DELETE_TOLL_HOUSE_ITEM } from "@/core/utils/routes";
|
import { DELETE_TOLL_HOUSE_ITEM } from "@/core/utils/routes";
|
||||||
|
|
||||||
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const requestServer = useRequest({ notificationSuccess: true });
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
requestServer(`${DELETE_TOLL_HOUSE_ITEM}/${rowId}`, "delete")
|
requestServer(`${DELETE_TOLL_HOUSE_ITEM}/${rowId}`, "delete")
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate();
|
mutate();
|
||||||
setOpenDeleteDialog(false);
|
setOpenDeleteDialog(false);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Stack alignItems="center" spacing={2}>
|
<Stack alignItems="center" spacing={2}>
|
||||||
<Typography mt={2}>آیا از حذف راهدارخانه اطمینان دارید؟</Typography>
|
<Typography mt={2}>آیا از حذف راهدارخانه اطمینان دارید؟</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||||
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
|
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
|
||||||
خیر !
|
خیر !
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
||||||
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default DeleteContent;
|
export default DeleteContent;
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import DeleteContent from "./DeleteContent";
|
import DeleteContent from "./DeleteContent";
|
||||||
const DeleteDialog = ({ rowId, mutate }) => {
|
const DeleteDialog = ({ rowId, mutate }) => {
|
||||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="حذف">
|
<Tooltip title="حذف">
|
||||||
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Dialog
|
<Dialog
|
||||||
open={openDeleteDialog}
|
open={openDeleteDialog}
|
||||||
onClose={() => setOpenDeleteDialog(false)}
|
onClose={() => setOpenDeleteDialog(false)}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
maxWidth={"md"}
|
maxWidth={"md"}
|
||||||
>
|
>
|
||||||
<DialogTitle>حذف راهدارخانه</DialogTitle>
|
<DialogTitle>حذف راهدارخانه</DialogTitle>
|
||||||
{openDeleteDialog && (
|
{openDeleteDialog && (
|
||||||
<DeleteContent rowId={rowId} mutate={mutate} setOpenDeleteDialog={setOpenDeleteDialog} />
|
<DeleteContent rowId={rowId} mutate={mutate} setOpenDeleteDialog={setOpenDeleteDialog} />
|
||||||
)}
|
)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default DeleteDialog;
|
export default DeleteDialog;
|
||||||
|
|||||||
@@ -1,281 +1,281 @@
|
|||||||
import StyledForm from "@/core/components/StyledForm";
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
import {
|
import {
|
||||||
GET_CITIES_LIST,
|
GET_CITIES_LIST,
|
||||||
GET_PROVINCES_LIST,
|
GET_PROVINCES_LIST,
|
||||||
GET_STATIONS_LIST,
|
GET_STATIONS_LIST,
|
||||||
UPDATE_MACHINE_STATUS_AND_LOCATION,
|
UPDATE_MACHINE_STATUS_AND_LOCATION,
|
||||||
} from "@/core/utils/routes";
|
} from "@/core/utils/routes";
|
||||||
import useRequest from "@/lib/hooks/useRequest";
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { Beenhere, ExitToApp } from "@mui/icons-material";
|
import { Beenhere, ExitToApp } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog";
|
import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||||
import { number, object } from "yup";
|
import { number, object } from "yup";
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
{ value: 1, label: "آماده" },
|
{ value: 1, label: "آماده" },
|
||||||
{ value: 2, label: "رزرو" },
|
{ value: 2, label: "رزرو" },
|
||||||
{ value: 3, label: "در ماموریت" },
|
{ value: 3, label: "در ماموریت" },
|
||||||
{ value: 4, label: "خارج از رده" },
|
{ value: 4, label: "خارج از رده" },
|
||||||
{ value: 5, label: "در تعمیر" },
|
{ value: 5, label: "در تعمیر" },
|
||||||
{ value: 6, label: "فروخته شده" },
|
{ value: 6, label: "فروخته شده" },
|
||||||
{ value: 7, label: "نیاز به تعمیر" },
|
{ value: 7, label: "نیاز به تعمیر" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
status: number().required("انتخاب وضعیت الزامی است"),
|
status: number().required("انتخاب وضعیت الزامی است"),
|
||||||
province_id: number().required("انتخاب استان الزامی است"),
|
province_id: number().required("انتخاب استان الزامی است"),
|
||||||
city_id: number().required("انتخاب شهر الزامی است"),
|
city_id: number().required("انتخاب شهر الزامی است"),
|
||||||
station_id: number().required("انتخاب راهداری الزامی است"),
|
station_id: number().required("انتخاب راهداری الزامی است"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const EditFormContent = ({ row, rowId, mutate, setOpen }) => {
|
const EditFormContent = ({ row, rowId, mutate, setOpen }) => {
|
||||||
const requestServer = useRequest();
|
const requestServer = useRequest();
|
||||||
|
|
||||||
const [provinces, setProvinces] = useState([]);
|
const [provinces, setProvinces] = useState([]);
|
||||||
const [cities, setCities] = useState([]);
|
const [cities, setCities] = useState([]);
|
||||||
const [stations, setStations] = useState([]);
|
const [stations, setStations] = useState([]);
|
||||||
|
|
||||||
const [loadingProvinces, setLoadingProvinces] = useState(false);
|
const [loadingProvinces, setLoadingProvinces] = useState(false);
|
||||||
const [loadingCities, setLoadingCities] = useState(false);
|
const [loadingCities, setLoadingCities] = useState(false);
|
||||||
const [loadingStations, setLoadingStations] = useState(false);
|
const [loadingStations, setLoadingStations] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
status: row.original.status,
|
status: row.original.status,
|
||||||
province_id: row.original.province_id,
|
province_id: row.original.province_id,
|
||||||
city_id: row.original.city_id,
|
city_id: row.original.city_id,
|
||||||
station_id: row.original.station_id,
|
station_id: row.original.station_id,
|
||||||
},
|
},
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
mode: "all",
|
mode: "all",
|
||||||
});
|
});
|
||||||
|
|
||||||
const provinceId = useWatch({
|
const provinceId = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "province_id",
|
name: "province_id",
|
||||||
});
|
});
|
||||||
|
|
||||||
const cityId = useWatch({
|
const cityId = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "city_id",
|
name: "city_id",
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadProvinces = async () => {
|
const loadProvinces = async () => {
|
||||||
try {
|
try {
|
||||||
setLoadingProvinces(true);
|
setLoadingProvinces(true);
|
||||||
const response = await requestServer(GET_PROVINCES_LIST, "get");
|
const response = await requestServer(GET_PROVINCES_LIST, "get");
|
||||||
setProvinces(response.data?.data || []);
|
setProvinces(response.data?.data || []);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingProvinces(false);
|
setLoadingProvinces(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadCities = async (provinceId) => {
|
const loadCities = async (provinceId) => {
|
||||||
if (!provinceId) return;
|
if (!provinceId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoadingCities(true);
|
setLoadingCities(true);
|
||||||
const response = await requestServer(`${GET_CITIES_LIST}?province_id=${provinceId}`, "get");
|
const response = await requestServer(`${GET_CITIES_LIST}?province_id=${provinceId}`, "get");
|
||||||
setCities(response.data?.data || []);
|
setCities(response.data?.data || []);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingCities(false);
|
setLoadingCities(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadStations = async (cityId) => {
|
const loadStations = async (cityId) => {
|
||||||
if (!cityId) return;
|
if (!cityId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoadingStations(true);
|
setLoadingStations(true);
|
||||||
const response = await requestServer(`${GET_STATIONS_LIST}/search_by_city?city_id=${cityId}`, "get");
|
const response = await requestServer(`${GET_STATIONS_LIST}/search_by_city?city_id=${cityId}`, "get");
|
||||||
setStations(response.data?.data || []);
|
setStations(response.data?.data || []);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingStations(false);
|
setLoadingStations(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadProvinces();
|
loadProvinces();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial edit mode loading
|
* Initial edit mode loading
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (row.original.province_id) {
|
if (row.original.province_id) {
|
||||||
loadCities(row.original.province_id);
|
loadCities(row.original.province_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.original.city_id) {
|
if (row.original.city_id) {
|
||||||
loadStations(row.original.city_id);
|
loadStations(row.original.city_id);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!provinceId) return;
|
if (!provinceId) return;
|
||||||
|
|
||||||
loadCities(provinceId);
|
loadCities(provinceId);
|
||||||
|
|
||||||
if (provinceId !== row.original.province_id) {
|
if (provinceId !== row.original.province_id) {
|
||||||
setValue("city_id", "");
|
setValue("city_id", "");
|
||||||
setValue("station_id", "");
|
setValue("station_id", "");
|
||||||
setStations([]);
|
setStations([]);
|
||||||
}
|
}
|
||||||
}, [provinceId]);
|
}, [provinceId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!cityId) return;
|
if (!cityId) return;
|
||||||
|
|
||||||
loadStations(cityId);
|
loadStations(cityId);
|
||||||
|
|
||||||
if (cityId !== row.original.city_id) {
|
if (cityId !== row.original.city_id) {
|
||||||
setValue("station_id", "");
|
setValue("station_id", "");
|
||||||
}
|
}
|
||||||
}, [cityId]);
|
}, [cityId]);
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
const onSubmit = async (data) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
formData.append("status", String(data.status));
|
formData.append("status", String(data.status));
|
||||||
formData.append("province_id", String(data.province_id));
|
formData.append("province_id", String(data.province_id));
|
||||||
formData.append("city_id", String(data.city_id));
|
formData.append("city_id", String(data.city_id));
|
||||||
formData.append("station_id", String(data.station_id));
|
formData.append("station_id", String(data.station_id));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await requestServer(`${UPDATE_MACHINE_STATUS_AND_LOCATION}/${rowId}`, "post", {
|
await requestServer(`${UPDATE_MACHINE_STATUS_AND_LOCATION}/${rowId}`, "post", {
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
mutate();
|
mutate();
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>ویرایش ماشین</DialogTitle>
|
<DialogTitle>ویرایش ماشین</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<StyledForm id="updateMachineForm" onSubmit={handleSubmit(onSubmit)}>
|
<StyledForm id="updateMachineForm" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Controller
|
<Controller
|
||||||
name="province_id"
|
name="province_id"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl fullWidth size="small" error={!!error}>
|
<FormControl fullWidth size="small" error={!!error}>
|
||||||
<InputLabel>استان</InputLabel>
|
<InputLabel>استان</InputLabel>
|
||||||
|
|
||||||
<Select {...field} label="استان" disabled={loadingProvinces}>
|
<Select {...field} label="استان" disabled={loadingProvinces}>
|
||||||
{provinces.map((province) => (
|
{provinces.map((province) => (
|
||||||
<MenuItem key={province.id} value={province.id}>
|
<MenuItem key={province.id} value={province.id}>
|
||||||
{province.name_fa}
|
{province.name_fa}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="city_id"
|
name="city_id"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl fullWidth size="small" error={!!error}>
|
<FormControl fullWidth size="small" error={!!error}>
|
||||||
<InputLabel>شهر</InputLabel>
|
<InputLabel>شهر</InputLabel>
|
||||||
|
|
||||||
<Select {...field} label="شهر" disabled={!provinceId || loadingCities}>
|
<Select {...field} label="شهر" disabled={!provinceId || loadingCities}>
|
||||||
{cities.map((city) => (
|
{cities.map((city) => (
|
||||||
<MenuItem key={city.id} value={city.id}>
|
<MenuItem key={city.id} value={city.id}>
|
||||||
{city.name_fa}
|
{city.name_fa}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="station_id"
|
name="station_id"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl fullWidth size="small" error={!!error}>
|
<FormControl fullWidth size="small" error={!!error}>
|
||||||
<InputLabel>راهداری</InputLabel>
|
<InputLabel>راهداری</InputLabel>
|
||||||
|
|
||||||
<Select {...field} label="راهداری" disabled={!cityId || loadingStations}>
|
<Select {...field} label="راهداری" disabled={!cityId || loadingStations}>
|
||||||
{stations.map((station) => (
|
{stations.map((station) => (
|
||||||
<MenuItem key={station.id} value={station.id}>
|
<MenuItem key={station.id} value={station.id}>
|
||||||
{station.name}
|
{station.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="status"
|
name="status"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl fullWidth size="small" error={!!error}>
|
<FormControl fullWidth size="small" error={!!error}>
|
||||||
<InputLabel>وضعیت</InputLabel>
|
<InputLabel>وضعیت</InputLabel>
|
||||||
|
|
||||||
<Select {...field} label="وضعیت">
|
<Select {...field} label="وضعیت">
|
||||||
{statusOptions.map((item) => (
|
{statusOptions.map((item) => (
|
||||||
<MenuItem key={item.value} value={item.value}>
|
<MenuItem key={item.value} value={item.value}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(loadingCities || loadingStations) && (
|
{(loadingCities || loadingStations) && (
|
||||||
<Stack direction="row" justifyContent="center">
|
<Stack direction="row" justifyContent="center">
|
||||||
<CircularProgress size={24} />
|
<CircularProgress size={24} />
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setOpen(false)} variant="outlined" color="secondary" startIcon={<ExitToApp />}>
|
<Button onClick={() => setOpen(false)} variant="outlined" color="secondary" startIcon={<ExitToApp />}>
|
||||||
بستن
|
بستن
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
type="submit"
|
type="submit"
|
||||||
form="updateMachineForm"
|
form="updateMachineForm"
|
||||||
endIcon={<Beenhere />}
|
endIcon={<Beenhere />}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "در حال ویرایش" : "ویرایش ماشین"}
|
{isSubmitting ? "در حال ویرایش" : "ویرایش ماشین"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EditFormContent;
|
export default EditFormContent;
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
import { SwapHoriz } from "@mui/icons-material";
|
import { SwapHoriz } from "@mui/icons-material";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { Dialog, IconButton, Tooltip } from "@mui/material";
|
import { Dialog, IconButton, Tooltip } from "@mui/material";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import EditFormContent from "./Form";
|
import EditFormContent from "./Form";
|
||||||
|
|
||||||
const Edit = ({ row, mutate, rowId }) => {
|
const Edit = ({ row, mutate, rowId }) => {
|
||||||
const [openEditDialog, setOpenEditDialog] = useState(false);
|
const [openEditDialog, setOpenEditDialog] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="ویرایش" arrow placement="right">
|
<Tooltip title="ویرایش" arrow placement="right">
|
||||||
<IconButton
|
<IconButton
|
||||||
color="primary"
|
color="primary"
|
||||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenEditDialog(true);
|
setOpenEditDialog(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SwapHoriz />
|
<SwapHoriz />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Dialog open={openEditDialog} fullWidth maxWidth="xs">
|
<Dialog open={openEditDialog} fullWidth maxWidth="xs">
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
onClick={() => setOpenEditDialog(false)}
|
onClick={() => setOpenEditDialog(false)}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: 8,
|
right: 8,
|
||||||
top: 8,
|
top: 8,
|
||||||
zIndex: 50,
|
zIndex: 50,
|
||||||
color: theme.palette.grey[500],
|
color: theme.palette.grey[500],
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{openEditDialog && (
|
{openEditDialog && (
|
||||||
<EditFormContent rowId={rowId} row={row} setOpen={setOpenEditDialog} mutate={mutate} />
|
<EditFormContent rowId={rowId} row={row} setOpen={setOpenEditDialog} mutate={mutate} />
|
||||||
)}
|
)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Edit;
|
export default Edit;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { usePermissions } from "@/lib/hooks/usePermissions";
|
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import Edit from "./Edit";
|
import Edit from "./Edit";
|
||||||
|
|
||||||
const RowActions = ({ row, mutate }) => {
|
const RowActions = ({ row, mutate }) => {
|
||||||
const { data: userPermissions } = usePermissions();
|
const { data: userPermissions } = usePermissions();
|
||||||
const hasEditPermission = userPermissions.some((item) => ["update-machine"].includes(item));
|
const hasEditPermission = userPermissions.some((item) => ["update-machine"].includes(item));
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", gap: 1 }}>
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
{hasEditPermission && <Edit row={row} mutate={mutate} rowId={row.getValue("id")} />}
|
{hasEditPermission && <Edit row={row} mutate={mutate} rowId={row.getValue("id")} />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default RowActions;
|
export default RowActions;
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { FeatureGroup, useMap } from "react-leaflet";
|
import { FeatureGroup, useMap } from "react-leaflet";
|
||||||
|
|
||||||
const ShowBound = ({ bound }) => {
|
const ShowBound = ({ bound }) => {
|
||||||
const map = useMap();
|
const map = useMap();
|
||||||
const featureRef = useRef(null);
|
const featureRef = useRef(null);
|
||||||
|
|
||||||
const safeFitBounds = (layer) => {
|
const safeFitBounds = (layer) => {
|
||||||
if (!layer || !map) return;
|
if (!layer || !map) return;
|
||||||
try {
|
try {
|
||||||
const latLngs = layer.getLatLngs();
|
const latLngs = layer.getLatLngs();
|
||||||
map.fitBounds(latLngs, {
|
map.fitBounds(latLngs, {
|
||||||
paddingTopLeft: [20, 20],
|
paddingTopLeft: [20, 20],
|
||||||
paddingBottomRight: [20, 20],
|
paddingBottomRight: [20, 20],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("fitBounds failed:", e);
|
console.warn("fitBounds failed:", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bound?.editing?.disable();
|
bound?.editing?.disable();
|
||||||
featureRef.current.addLayer(bound);
|
featureRef.current.addLayer(bound);
|
||||||
safeFitBounds(bound);
|
safeFitBounds(bound);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <FeatureGroup ref={featureRef} />;
|
return <FeatureGroup ref={featureRef} />;
|
||||||
};
|
};
|
||||||
export default ShowBound;
|
export default ShowBound;
|
||||||
|
|||||||
@@ -1,65 +1,65 @@
|
|||||||
const { IconButton, Tooltip, Box, Dialog, DialogTitle, Typography, DialogContent } = require("@mui/material");
|
const { IconButton, Tooltip, Box, Dialog, DialogTitle, Typography, DialogContent } = require("@mui/material");
|
||||||
import MapLayer from "@/core/components/MapLayer";
|
import MapLayer from "@/core/components/MapLayer";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import LayersIcon from "@mui/icons-material/Layers";
|
import LayersIcon from "@mui/icons-material/Layers";
|
||||||
import L from "leaflet";
|
import L from "leaflet";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import ShowBound from "./ShowBound";
|
import ShowBound from "./ShowBound";
|
||||||
|
|
||||||
const ShowProjectArea = ({ primaryArea }) => {
|
const ShowProjectArea = ({ primaryArea }) => {
|
||||||
const [openShowAreaDialog, setOpenShowAreaDialog] = useState(false);
|
const [openShowAreaDialog, setOpenShowAreaDialog] = useState(false);
|
||||||
const bound = useMemo(() => {
|
const bound = useMemo(() => {
|
||||||
const coordinates = Object.values(primaryArea.coordinates);
|
const coordinates = Object.values(primaryArea.coordinates);
|
||||||
|
|
||||||
const latLngCoords = coordinates.map(([lat, lng]) => [parseFloat(lng), parseFloat(lat)]);
|
const latLngCoords = coordinates.map(([lat, lng]) => [parseFloat(lng), parseFloat(lat)]);
|
||||||
|
|
||||||
return primaryArea.type === "polygon"
|
return primaryArea.type === "polygon"
|
||||||
? L.polygon(latLngCoords, { color: "#ff5555" })
|
? L.polygon(latLngCoords, { color: "#ff5555" })
|
||||||
: L.polyline(latLngCoords);
|
: L.polyline(latLngCoords);
|
||||||
}, [primaryArea]);
|
}, [primaryArea]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Tooltip title="نمایش پلیگان" placement="right" arrow>
|
<Tooltip title="نمایش پلیگان" placement="right" arrow>
|
||||||
<IconButton size="small" color="primary" onClick={() => setOpenShowAreaDialog(true)}>
|
<IconButton size="small" color="primary" onClick={() => setOpenShowAreaDialog(true)}>
|
||||||
<LayersIcon />
|
<LayersIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Dialog
|
<Dialog
|
||||||
open={openShowAreaDialog}
|
open={openShowAreaDialog}
|
||||||
onClose={() => setOpenShowAreaDialog(false)}
|
onClose={() => setOpenShowAreaDialog(false)}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
transition: "all .3s",
|
transition: "all .3s",
|
||||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
padding: 1,
|
padding: 1,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
maxWidth="sm"
|
maxWidth="sm"
|
||||||
fullWidth
|
fullWidth
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
>
|
>
|
||||||
<DialogTitle sx={{ display: "flex", justifyContent: "space-between", padding: "16px 24px" }}>
|
<DialogTitle sx={{ display: "flex", justifyContent: "space-between", padding: "16px 24px" }}>
|
||||||
<Typography variant="body1" sx={{ fontWeight: "bold", fontSize: "large" }}>
|
<Typography variant="body1" sx={{ fontWeight: "bold", fontSize: "large" }}>
|
||||||
محدوده طرح
|
محدوده طرح
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton onClick={() => setOpenShowAreaDialog(false)} size="small">
|
<IconButton onClick={() => setOpenShowAreaDialog(false)} size="small">
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Box sx={{ width: "100%", height: "400px" }}>
|
<Box sx={{ width: "100%", height: "400px" }}>
|
||||||
<MapLayer style={{ borderRadius: "4px" }}>
|
<MapLayer style={{ borderRadius: "4px" }}>
|
||||||
<ShowBound bound={bound} />
|
<ShowBound bound={bound} />
|
||||||
</MapLayer>
|
</MapLayer>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ShowProjectArea;
|
export default ShowProjectArea;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import PageTitle from "@/core/components/PageTitle";
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
import { Stack } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
import MachinesList from "./MachinesList";
|
import MachinesList from "./MachinesList";
|
||||||
|
|
||||||
const MachinesPage = () => {
|
const MachinesPage = () => {
|
||||||
return (
|
return (
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<PageTitle title={"لیست ماشین ها"} />
|
<PageTitle title={"لیست ماشین ها"} />
|
||||||
<MachinesList />
|
<MachinesList />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default MachinesPage;
|
export default MachinesPage;
|
||||||
|
|||||||
@@ -1,116 +1,113 @@
|
|||||||
import { Delete, Route } from "@mui/icons-material";
|
"use client";
|
||||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
|
||||||
import { useReducer } from "react";
|
|
||||||
import DrawBound from "./DrawBound";
|
|
||||||
|
|
||||||
const statusType = [
|
import { useEffect, useState } from "react";
|
||||||
{
|
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||||
id: 0,
|
import L from "leaflet";
|
||||||
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 });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const createInitialState = (bound) => {
|
// ===== Icons =====
|
||||||
if (bound) {
|
const originIcon = new L.Icon({
|
||||||
return { status: 2 };
|
iconUrl: "/icons/origin-icon.svg",
|
||||||
}
|
iconSize: [32, 32],
|
||||||
return { status: 0 };
|
iconAnchor: [16, 32],
|
||||||
};
|
});
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
const destinationIcon = new L.Icon({
|
||||||
switch (action.type) {
|
iconUrl: "/icons/destination-icon.svg",
|
||||||
case "SET_STATUS":
|
iconSize: [32, 32],
|
||||||
return { ...state, status: action.status };
|
iconAnchor: [16, 32],
|
||||||
default:
|
});
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const MapControlPolyline = ({ bound, setBound }) => {
|
// ===== Track Center =====
|
||||||
const [control, controlDispach] = useReducer(reducer, bound, createInitialState);
|
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");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<DrawBound bound={bound} setBound={setBound} control={control} controlDispach={controlDispach} />
|
<div
|
||||||
<Stack
|
style={{
|
||||||
direction={"row"}
|
position: "relative",
|
||||||
justifyContent={"center"}
|
height: "400px",
|
||||||
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))",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack direction={"row"} justifyContent={"center"} spacing={2}>
|
<MapCenterTracker setCenter={setCenter} />
|
||||||
{statusType
|
|
||||||
.find((st) => st.id == control.status)
|
{/* saved markers */}
|
||||||
.buttons.map((button) => (
|
{origin && <Marker position={origin} icon={originIcon} />}
|
||||||
<Button
|
|
||||||
size="small"
|
{destination && <Marker position={destination} icon={destinationIcon} />}
|
||||||
key={button.key}
|
|
||||||
variant="contained"
|
{/* route line */}
|
||||||
onClick={() => button.onclick(controlDispach)}
|
{origin && destination && <Polyline positions={[origin, destination]} />}
|
||||||
color={button.color}
|
|
||||||
startIcon={button.icon}
|
{/* fixed center pin */}
|
||||||
>
|
{step !== "done" && <FixedCenterMarker />}
|
||||||
{button.label}
|
</div>
|
||||||
</Button>
|
|
||||||
))}
|
<div style={{ marginTop: 16, position: "absolute", z: 50, top: 5 }}>
|
||||||
</Stack>
|
<button onClick={handleOk}>
|
||||||
</Box>
|
{step === "origin" && "Set Origin"}
|
||||||
</>
|
{step === "destination" && "Set Destination"}
|
||||||
|
{step === "done" && "Completed"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
export default MapControlPolyline;
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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;
|
||||||
@@ -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]],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||||
|
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||||
|
<Marker position={destPosition} icon={destIcon} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowRoute;
|
||||||
@@ -0,0 +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 (
|
||||||
|
<>
|
||||||
|
<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,19 +2,21 @@ import MapLoading from "@/core/components/MapLayer/Loading";
|
|||||||
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
|
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import MapControlPolyline from "./MapControlPolyline";
|
import Routing from "./Routing";
|
||||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||||
loading: () => <MapLoading />,
|
loading: () => <MapLoading />,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
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(() => {
|
const handleNext = useCallback(() => {
|
||||||
setAllData({ bound: bound });
|
setAllData({ area: area });
|
||||||
|
setAllData({ locations: locations });
|
||||||
setTabState((s) => s + 1);
|
setTabState((s) => s + 1);
|
||||||
}, [bound]);
|
}, [area]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -22,7 +24,7 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
|||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Box sx={{ width: "100%", height: "400px" }}>
|
<Box sx={{ width: "100%", height: "400px" }}>
|
||||||
<MapLayer style={{ borderRadius: "4px" }}>
|
<MapLayer style={{ borderRadius: "4px" }}>
|
||||||
<MapControlPolyline bound={bound} setBound={setBound} boundType={"polyline"} />
|
<Routing area={area} setArea={setArea} locations={locations} setLocations={setLocations} />
|
||||||
</MapLayer>
|
</MapLayer>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -31,8 +33,8 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
|||||||
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
|
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
|
||||||
{"مرحله قبلی"}
|
{"مرحله قبلی"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" size="large" disabled={!bound} onClick={handleNext}>
|
<Button variant="contained" size="large" disabled={!area} onClick={handleNext}>
|
||||||
{!bound ? "در انتظار ترسیم" : "مرحله بعد"}
|
{!area ? "در انتظار ترسیم" : "مرحله بعد"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typogr
|
|||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import ShowBound from "../../../showBound";
|
import ShowRoute from "../Area/Routing/ShowRoute";
|
||||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||||
loading: () => <MapLoading />,
|
loading: () => <MapLoading />,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -23,11 +23,16 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
|
|||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Box sx={{ width: "100%", height: "200px" }}>
|
<Stack spacing={1}>
|
||||||
<MapLayer style={{ borderRadius: "4px" }}>
|
<Divider>
|
||||||
<ShowBound bound={allData.bound} />
|
<Chip color="primary" variant="outlined" label="مسیر ماموریت" />
|
||||||
</MapLayer>
|
</Divider>
|
||||||
</Box>
|
<Box sx={{ width: "100%", height: "400px" }}>
|
||||||
|
<MapLayer style={{ borderRadius: "4px" }}>
|
||||||
|
<ShowRoute area={allData.area} />
|
||||||
|
</MapLayer>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Divider>
|
<Divider>
|
||||||
<Chip color="primary" variant="outlined" label="مشخصات ماموریت" />
|
<Chip color="primary" variant="outlined" label="مشخصات ماموریت" />
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ const Create = ({ mutate }) => {
|
|||||||
|
|
||||||
const submitForm = async (result) => {
|
const submitForm = async (result) => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
const bound = result.bound.getLatLngs();
|
|
||||||
let area = bound.map((latlng) => [latlng.lng, latlng.lat]);
|
|
||||||
|
|
||||||
await requestServer(REQUEST_MISSION, "post", {
|
await requestServer(REQUEST_MISSION, "post", {
|
||||||
data: {
|
data: {
|
||||||
@@ -31,10 +29,7 @@ const Create = ({ mutate }) => {
|
|||||||
road_observed_id: result.road_observed_id,
|
road_observed_id: result.road_observed_id,
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
area: {
|
encoded_route: result.area,
|
||||||
type: result.bound_type,
|
|
||||||
coordinates: area,
|
|
||||||
},
|
|
||||||
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}),
|
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}),
|
||||||
requested_machines: result.requested_machines,
|
requested_machines: result.requested_machines,
|
||||||
type: result.type,
|
type: result.type,
|
||||||
@@ -101,8 +96,8 @@ const Create = ({ mutate }) => {
|
|||||||
road_observed_id: "",
|
road_observed_id: "",
|
||||||
rahdaran: [],
|
rahdaran: [],
|
||||||
requested_machines: [],
|
requested_machines: [],
|
||||||
bound: null,
|
area: null,
|
||||||
bound_type: "polyline",
|
locations: [],
|
||||||
type: "",
|
type: "",
|
||||||
start_date: "",
|
start_date: "",
|
||||||
start_time: null,
|
start_time: null,
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
"use client";
|
"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 DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes";
|
import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes";
|
||||||
import { missionRegions } from "@/core/utils/missionRegions";
|
import { missionRegions } from "@/core/utils/missionRegions";
|
||||||
import { missionTypes } from "@/core/utils/missionTypes";
|
import { missionTypes } from "@/core/utils/missionTypes";
|
||||||
import { GET_ROAD_MISSIONS_OPERATOR_LIST } from "@/core/utils/routes";
|
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 { Box, Stack } from "@mui/material";
|
||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import ShowArea from "./Actions/showArea";
|
|
||||||
import RowActions from "./RowActions";
|
import RowActions from "./RowActions";
|
||||||
import MachinesDialog from "./RowActions/Machines";
|
import MachinesDialog from "./RowActions/Machines";
|
||||||
import Toolbar from "./Toolbar";
|
|
||||||
import RahdaranDialog from "./RowActions/Rahdaran";
|
import RahdaranDialog from "./RowActions/Rahdaran";
|
||||||
import DescriptionDialog from "@/components/dashboard/roadMissions/operator/RowActions/DescriptionDialog";
|
import RouteDetail from "./RowActions/RouteDetail";
|
||||||
import useProvinces from "@/lib/hooks/useProvince";
|
import Toolbar from "./Toolbar";
|
||||||
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
|
||||||
import useCities from "@/lib/hooks/useCities";
|
|
||||||
|
|
||||||
const OperatorList = () => {
|
const OperatorList = () => {
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
@@ -247,7 +247,7 @@ const OperatorList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "منطقه عملیاتی",
|
header: "منطقه عملیاتی",
|
||||||
id: "area",
|
id: "encoded_route",
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
datatype: "numeric",
|
datatype: "numeric",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
@@ -263,9 +263,9 @@ const OperatorList = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Cell: ({ row }) =>
|
Cell: ({ row }) =>
|
||||||
row.original.area ? (
|
row.original.encoded_route ? (
|
||||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||||
<ShowArea area={row.original.area} />
|
<RouteDetail encoded_route={row.original.encoded_route} />
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
"-"
|
"-"
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<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;
|
||||||
@@ -14,16 +14,10 @@ import MachinesDialog from "./MachinesDialog";
|
|||||||
import Reject from "./Reject";
|
import Reject from "./Reject";
|
||||||
import SelectBox from "@/core/components/SelectBox";
|
import SelectBox from "@/core/components/SelectBox";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import RouteDetail from "../../RouteDetail";
|
||||||
|
import ShowRoute from "../../RouteDetail/ShowRoute";
|
||||||
|
|
||||||
const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => {
|
const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => {
|
||||||
const latLngs = useMemo(() => {
|
|
||||||
return row.area.coordinates.map(([lng, lat]) => [lat, lng]);
|
|
||||||
}, [row.area.coordinates]);
|
|
||||||
|
|
||||||
const bound = useMemo(
|
|
||||||
() => (row.area.type === "polygon" ? L.polygon(latLngs) : L.polyline(latLngs)),
|
|
||||||
[row.area.type, latLngs]
|
|
||||||
);
|
|
||||||
const requestServer = useRequest({ notificationSuccess: true });
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
const [machine, setMachine] = useState();
|
const [machine, setMachine] = useState();
|
||||||
const [driver, setDriver] = useState();
|
const [driver, setDriver] = useState();
|
||||||
@@ -180,10 +174,32 @@ const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => {
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Box sx={{ width: "100%", height: "200px" }}>
|
<Box
|
||||||
<MapLayer style={{ borderRadius: "4px" }}>
|
sx={{
|
||||||
<ShowBound bound={bound} />
|
width: "100%",
|
||||||
</MapLayer>
|
height: "200px",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row.encoded_route ? (
|
||||||
|
<MapLayer style={{ borderRadius: "4px" }}>
|
||||||
|
<ShowRoute area={row.encoded_route} />
|
||||||
|
</MapLayer>
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#ff4a4c",
|
||||||
|
fontSize: "16px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
letterSpacing: "1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
مختصات برای این ماموریت در دسترس نمیباشد
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -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]],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||||
|
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||||
|
<Marker position={destPosition} icon={destIcon} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowRoute;
|
||||||
@@ -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 "./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 { Box, Stack } from "@mui/material";
|
||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import ShowArea from "./Actions/showArea";
|
|
||||||
import RowActions from "./RowActions";
|
import RowActions from "./RowActions";
|
||||||
import { missionRegions } from "@/core/utils/missionRegions";
|
import { missionRegions } from "@/core/utils/missionRegions";
|
||||||
import { missionTypes } from "@/core/utils/missionTypes";
|
import { missionTypes } from "@/core/utils/missionTypes";
|
||||||
import useProvinces from "@/lib/hooks/useProvince";
|
import useProvinces from "@/lib/hooks/useProvince";
|
||||||
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
||||||
import useCities from "@/lib/hooks/useCities";
|
import useCities from "@/lib/hooks/useCities";
|
||||||
|
import RouteDetail from "./RowActions/RouteDetail";
|
||||||
|
|
||||||
const TransportationList = () => {
|
const TransportationList = () => {
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
@@ -230,9 +230,9 @@ const TransportationList = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Cell: ({ row }) =>
|
Cell: ({ row }) =>
|
||||||
row.original.area ? (
|
row.original.encoded_route ? (
|
||||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||||
<ShowArea area={row.original.area} />
|
<RouteDetail encoded_route={row.original.encoded_route} />
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
"-"
|
"-"
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
import L from "leaflet";
|
|
||||||
import { FeatureGroup, useMap } from "react-leaflet";
|
|
||||||
import "leaflet-draw";
|
|
||||||
|
|
||||||
const DrawBound = ({ control, controlDispach, bound, setBound }) => {
|
|
||||||
const map = useMap();
|
|
||||||
const featureRef = useRef(null);
|
|
||||||
const [area, setArea] = useState();
|
|
||||||
const drawControlRef = useRef(null);
|
|
||||||
|
|
||||||
const safeFlyToBounds = (layer) => {
|
|
||||||
if (!layer || !map) return;
|
|
||||||
try {
|
|
||||||
const latLngs = layer.getLatLngs();
|
|
||||||
map.flyToBounds(latLngs, {
|
|
||||||
paddingTopLeft: [20, 35],
|
|
||||||
paddingBottomRight: [20, 55],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("flyToBounds failed:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const safeFitBounds = (layer) => {
|
|
||||||
if (!layer || !map) return;
|
|
||||||
try {
|
|
||||||
const latLngs = layer.getLatLngs();
|
|
||||||
map.fitBounds(latLngs, {
|
|
||||||
paddingTopLeft: [20, 35],
|
|
||||||
paddingBottomRight: [20, 55],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("fitBounds failed:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const bindEditEvent = (layer) => {
|
|
||||||
if (!layer) return;
|
|
||||||
layer.on("edit", function (e) {
|
|
||||||
const editedLayer = e.target;
|
|
||||||
setArea(editedLayer);
|
|
||||||
setBound(editedLayer);
|
|
||||||
safeFlyToBounds(editedLayer);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlerCreatedBound = useCallback((event) => {
|
|
||||||
const { layer } = event;
|
|
||||||
layer.editing.enable();
|
|
||||||
featureRef.current.addLayer(layer); // 🟢 اول اضافه به نقشه
|
|
||||||
setArea(layer);
|
|
||||||
setBound(layer);
|
|
||||||
safeFlyToBounds(layer);
|
|
||||||
bindEditEvent(layer);
|
|
||||||
controlDispach({ type: "SET_STATUS", status: 2 });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
control.status === 1 &&
|
|
||||||
drawControlRef.current &&
|
|
||||||
drawControlRef.current._toolbars?.draw?._modes?.polygon?.handler
|
|
||||||
) {
|
|
||||||
drawControlRef.current._toolbars.draw._modes.polygon.handler.enable();
|
|
||||||
}
|
|
||||||
}, [control.status]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (control.status === 0 && area && featureRef.current) {
|
|
||||||
featureRef.current.removeLayer(area);
|
|
||||||
setArea(null);
|
|
||||||
setBound(null);
|
|
||||||
}
|
|
||||||
}, [control.status, area]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (control.status === 2 && bound && featureRef.current) {
|
|
||||||
setArea(bound);
|
|
||||||
featureRef.current.addLayer(bound);
|
|
||||||
bound.editing.enable();
|
|
||||||
safeFitBounds(bound);
|
|
||||||
bindEditEvent(bound);
|
|
||||||
}
|
|
||||||
}, [control.status, bound]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!featureRef.current) return;
|
|
||||||
|
|
||||||
L.drawLocal.draw.handlers.polygon.tooltip.start = "برای شروع ترسیم محدوده، روی نقشه کلیک کنید";
|
|
||||||
L.drawLocal.draw.handlers.polygon.tooltip.cont = "برای ادامه ترسیم، نقاط بعدی را کلیک کنید";
|
|
||||||
L.drawLocal.draw.handlers.polygon.tooltip.end = "برای بستن محدوده، روی نقطهی شروع کلیک کنید";
|
|
||||||
|
|
||||||
const drawControl = new L.Control.Draw({
|
|
||||||
draw: {
|
|
||||||
polygon: {
|
|
||||||
shapeOptions: {
|
|
||||||
color: "#3388ff",
|
|
||||||
weight: 3,
|
|
||||||
clickable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
polyline: false,
|
|
||||||
rectangle: false,
|
|
||||||
circle: false,
|
|
||||||
marker: false,
|
|
||||||
circlemarker: false,
|
|
||||||
},
|
|
||||||
edit: {
|
|
||||||
featureGroup: featureRef.current,
|
|
||||||
edit: true,
|
|
||||||
remove: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
drawControlRef.current = drawControl;
|
|
||||||
map.addControl(drawControl);
|
|
||||||
map.on(L.Draw.Event.CREATED, handlerCreatedBound);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
map.off(L.Draw.Event.CREATED, handlerCreatedBound);
|
|
||||||
map.removeControl(drawControl);
|
|
||||||
};
|
|
||||||
}, [map, handlerCreatedBound]);
|
|
||||||
|
|
||||||
return <FeatureGroup ref={featureRef} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DrawBound;
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { Delete, Route } from "@mui/icons-material";
|
|
||||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
|
||||||
import { useReducer } from "react";
|
|
||||||
import DrawBound from "./DrawBound";
|
|
||||||
|
|
||||||
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 });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const createInitialState = (bound) => {
|
|
||||||
if (bound) {
|
|
||||||
return { status: 2 };
|
|
||||||
}
|
|
||||||
return { status: 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case "SET_STATUS":
|
|
||||||
return { ...state, status: action.status };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const MapControlPolygon = ({ bound, setBound }) => {
|
|
||||||
const [control, controlDispach] = useReducer(reducer, bound, createInitialState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<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))",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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 MapControlPolygon;
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
||||||
import L from "leaflet";
|
|
||||||
import { FeatureGroup, useMap } from "react-leaflet";
|
|
||||||
import "leaflet-draw";
|
|
||||||
|
|
||||||
const DrawBound = ({ control, controlDispach, bound, setBound }) => {
|
|
||||||
const map = useMap();
|
|
||||||
const featureRef = useRef(null);
|
|
||||||
const [area, setArea] = useState();
|
|
||||||
const drawControlRef = useRef(null);
|
|
||||||
|
|
||||||
const safeFlyToBounds = (layer) => {
|
|
||||||
if (!layer || !map) return;
|
|
||||||
try {
|
|
||||||
const latLngs = layer.getLatLngs();
|
|
||||||
map.flyToBounds(latLngs, {
|
|
||||||
paddingTopLeft: [20, 35],
|
|
||||||
paddingBottomRight: [20, 55],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("flyToBounds failed:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const safeFitBounds = (layer) => {
|
|
||||||
if (!layer || !map) return;
|
|
||||||
try {
|
|
||||||
const latLngs = layer.getLatLngs();
|
|
||||||
map.fitBounds(latLngs, {
|
|
||||||
paddingTopLeft: [20, 35],
|
|
||||||
paddingBottomRight: [20, 55],
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("fitBounds failed:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const bindEditEvent = (layer) => {
|
|
||||||
if (!layer) return;
|
|
||||||
layer.on("edit", function (e) {
|
|
||||||
const editedLayer = e.target;
|
|
||||||
setArea(editedLayer);
|
|
||||||
setBound(editedLayer);
|
|
||||||
safeFlyToBounds(editedLayer);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlerCreatedBound = useCallback((event) => {
|
|
||||||
const { layer } = event;
|
|
||||||
layer.editing.enable();
|
|
||||||
featureRef.current.addLayer(layer); // 🟢 اول اضافه به نقشه
|
|
||||||
setArea(layer);
|
|
||||||
setBound(layer);
|
|
||||||
safeFlyToBounds(layer);
|
|
||||||
bindEditEvent(layer);
|
|
||||||
controlDispach({ type: "SET_STATUS", status: 2 });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
control.status === 1 &&
|
|
||||||
drawControlRef.current &&
|
|
||||||
drawControlRef.current._toolbars?.draw?._modes?.polyline?.handler
|
|
||||||
) {
|
|
||||||
drawControlRef.current._toolbars.draw._modes.polyline.handler.enable();
|
|
||||||
}
|
|
||||||
}, [control.status]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (control.status === 0 && area && featureRef.current) {
|
|
||||||
featureRef.current.removeLayer(area);
|
|
||||||
setArea(null);
|
|
||||||
setBound(null);
|
|
||||||
}
|
|
||||||
}, [control.status, area]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (control.status === 2 && bound && featureRef.current) {
|
|
||||||
setArea(bound);
|
|
||||||
featureRef.current.addLayer(bound);
|
|
||||||
bound.editing.enable();
|
|
||||||
safeFitBounds(bound);
|
|
||||||
bindEditEvent(bound);
|
|
||||||
}
|
|
||||||
}, [control.status, bound]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!featureRef.current) return;
|
|
||||||
|
|
||||||
L.drawLocal.draw.handlers.polyline.tooltip.start = "برای شروع ترسیم مسیر، روی نقشه کلیک کنید";
|
|
||||||
L.drawLocal.draw.handlers.polyline.tooltip.cont = "برای ادامه ترسیم، نقاط بعدی را کلیک کنید";
|
|
||||||
L.drawLocal.draw.handlers.polyline.tooltip.end = "برای بستن مسیر، روی نقطه پایانی کلیک کنید";
|
|
||||||
|
|
||||||
const drawControl = new L.Control.Draw({
|
|
||||||
draw: {
|
|
||||||
polyline: {
|
|
||||||
shapeOptions: {
|
|
||||||
color: "#3388ff",
|
|
||||||
weight: 3,
|
|
||||||
clickable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
polygon: false,
|
|
||||||
rectangle: false,
|
|
||||||
circle: false,
|
|
||||||
marker: false,
|
|
||||||
circlemarker: false,
|
|
||||||
},
|
|
||||||
edit: {
|
|
||||||
featureGroup: featureRef.current,
|
|
||||||
edit: true,
|
|
||||||
remove: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
drawControlRef.current = drawControl;
|
|
||||||
map.addControl(drawControl);
|
|
||||||
map.on(L.Draw.Event.CREATED, handlerCreatedBound);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
map.off(L.Draw.Event.CREATED, handlerCreatedBound);
|
|
||||||
map.removeControl(drawControl);
|
|
||||||
};
|
|
||||||
}, [map, handlerCreatedBound]);
|
|
||||||
|
|
||||||
return <FeatureGroup ref={featureRef} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DrawBound;
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
import { Delete, Route } from "@mui/icons-material";
|
|
||||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
|
||||||
import { useReducer } from "react";
|
|
||||||
import DrawBound from "./DrawBound";
|
|
||||||
|
|
||||||
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 });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const createInitialState = (bound) => {
|
|
||||||
if (bound) {
|
|
||||||
return { status: 2 };
|
|
||||||
}
|
|
||||||
return { status: 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case "SET_STATUS":
|
|
||||||
return { ...state, status: action.status };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const MapControlPolyline = ({ bound, setBound }) => {
|
|
||||||
const [control, controlDispach] = useReducer(reducer, bound, createInitialState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<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))",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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;
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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;
|
||||||
@@ -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);
|
||||||
|
useEffect(() => {
|
||||||
|
if (coords.length > 0) {
|
||||||
|
map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 });
|
||||||
|
}
|
||||||
|
}, [area]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||||
|
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||||
|
<Marker position={destPosition} icon={destIcon} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowRoute;
|
||||||
@@ -0,0 +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 (
|
||||||
|
<>
|
||||||
|
<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;
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import SelectBox from "@/core/components/SelectBox";
|
|
||||||
|
|
||||||
export const boundTypes = [
|
|
||||||
{ id: "polygon", name_fa: "محدوده" },
|
|
||||||
{ id: "polyline", name_fa: "مسیر" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const SelectBoundType = ({ boundType, setBoundType, setBound, setAllData }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SelectBox
|
|
||||||
value={boundType}
|
|
||||||
label="نوع منطقه عملیاتی"
|
|
||||||
selectors={boundTypes}
|
|
||||||
schema={{ name: "name_fa", value: "id" }}
|
|
||||||
onChange={(newValue) => {
|
|
||||||
setBound(null);
|
|
||||||
setBoundType(newValue);
|
|
||||||
setAllData({ bound_type: newValue });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default SelectBoundType;
|
|
||||||
@@ -2,40 +2,29 @@ import MapLoading from "@/core/components/MapLayer/Loading";
|
|||||||
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
|
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import MapControlPolygon from "./MapControlPolygon";
|
import Routing from "./Routing";
|
||||||
import MapControlPolyline from "./MapControlPolyline";
|
|
||||||
import SelectBoundType from "./SelectBoundType";
|
|
||||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||||
loading: () => <MapLoading />,
|
loading: () => <MapLoading />,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
||||||
const [bound, setBound] = useState(allData.bound);
|
const [area, setArea] = useState(allData.area);
|
||||||
const [boundType, setBoundType] = useState(allData.bound_type);
|
const [locations, setLocations] = useState(allData.locations);
|
||||||
|
|
||||||
const handleNext = useCallback(() => {
|
const handleNext = useCallback(() => {
|
||||||
setAllData({ bound: bound });
|
setAllData({ area: area });
|
||||||
|
setAllData({ locations: locations });
|
||||||
setTabState((s) => s + 1);
|
setTabState((s) => s + 1);
|
||||||
}, [bound]);
|
}, [area]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<SelectBoundType
|
|
||||||
boundType={boundType}
|
|
||||||
setBoundType={setBoundType}
|
|
||||||
setBound={setBound}
|
|
||||||
setAllData={setAllData}
|
|
||||||
/>
|
|
||||||
<Box sx={{ width: "100%", height: "400px" }}>
|
<Box sx={{ width: "100%", height: "400px" }}>
|
||||||
<MapLayer style={{ borderRadius: "4px" }}>
|
<MapLayer style={{ borderRadius: "4px" }}>
|
||||||
{boundType == "polygon" ? (
|
<Routing area={area} setArea={setArea} locations={locations} setLocations={setLocations} />
|
||||||
<MapControlPolygon bound={bound} setBound={setBound} boundType={boundType} />
|
|
||||||
) : (
|
|
||||||
<MapControlPolyline bound={bound} setBound={setBound} boundType={boundType} />
|
|
||||||
)}
|
|
||||||
</MapLayer>
|
</MapLayer>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -44,8 +33,8 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
|||||||
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
|
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
|
||||||
{"مرحله قبلی"}
|
{"مرحله قبلی"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" size="large" disabled={!bound} onClick={handleNext}>
|
<Button variant="contained" size="large" disabled={!area} onClick={handleNext}>
|
||||||
{!bound ? "در انتظار ترسیم" : "مرحله بعد"}
|
{!area ? "در انتظار ترسیم" : "مرحله بعد"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typogr
|
|||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import ShowBound from "../../../showBound";
|
import ShowRoute from "../Area/Routing/ShowRoute";
|
||||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||||
loading: () => <MapLoading />,
|
loading: () => <MapLoading />,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
@@ -22,6 +22,16 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
|
|||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Divider>
|
||||||
|
<Chip color="primary" variant="outlined" label="مسیر ماموریت" />
|
||||||
|
</Divider>
|
||||||
|
<Box sx={{ width: "100%", height: "400px" }}>
|
||||||
|
<MapLayer style={{ borderRadius: "4px" }}>
|
||||||
|
<ShowRoute area={allData.area} />
|
||||||
|
</MapLayer>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
<Divider>
|
<Divider>
|
||||||
<Chip color="primary" variant="outlined" label="مشخصات ماموریت" />
|
<Chip color="primary" variant="outlined" label="مشخصات ماموریت" />
|
||||||
</Divider>
|
</Divider>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Engineering, InsertDriveFile, Person, Verified } from "@mui/icons-material";
|
import { AccessTime, Engineering, InsertDriveFile, Person, Verified, Map } from "@mui/icons-material";
|
||||||
import { Box, Tab, Tabs } from "@mui/material";
|
import { Box, Tab, Tabs } from "@mui/material";
|
||||||
import { useReducer, useState } from "react";
|
import { useReducer, useState } from "react";
|
||||||
|
import GetDateTime from "./GetDateTime";
|
||||||
import GetItemInfo from "./GetItemInfo";
|
import GetItemInfo from "./GetItemInfo";
|
||||||
import MachineAndDriver from "./MachineAndDriver";
|
import MachineAndDriver from "./MachineAndDriver";
|
||||||
import Rahdaran from "./Rahdaran";
|
import Rahdaran from "./Rahdaran";
|
||||||
import Verify from "./Verify";
|
import Verify from "./Verify";
|
||||||
|
import Area from "./Area";
|
||||||
|
|
||||||
function TabPanel(props) {
|
function TabPanel(props) {
|
||||||
const { children, value, index } = props;
|
const { children, value, index } = props;
|
||||||
@@ -41,6 +43,7 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
|
|||||||
setTabState((t) => t - 1);
|
setTabState((t) => t - 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabs
|
<Tabs
|
||||||
@@ -57,9 +60,11 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tab icon={<InsertDriveFile />} label="مشخصات" />
|
<Tab icon={<InsertDriveFile />} label="مشخصات" />
|
||||||
<Tab disabled={tabState < 1} icon={<Person />} label="راننده" />
|
<Tab disabled={tabState < 1} icon={<AccessTime />} label="زمانبندی" />
|
||||||
<Tab disabled={tabState < 2} icon={<Engineering />} label="همراهان" />
|
<Tab disabled={tabState < 2} icon={<Person />} label="راننده" />
|
||||||
<Tab disabled={tabState < 3} icon={<Verified />} label="بررسی نهایی" />
|
<Tab disabled={tabState < 3} icon={<Map />} label="مختصات" />
|
||||||
|
<Tab disabled={tabState < 4} icon={<Engineering />} label="همراهان" />
|
||||||
|
<Tab disabled={tabState < 5} icon={<Verified />} label="بررسی نهایی" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<TabPanel value={tabState} index={0}>
|
<TabPanel value={tabState} index={0}>
|
||||||
<GetItemInfo
|
<GetItemInfo
|
||||||
@@ -72,7 +77,7 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
|
|||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={tabState} index={1}>
|
<TabPanel value={tabState} index={1}>
|
||||||
<MachineAndDriver
|
<GetDateTime
|
||||||
allData={allData}
|
allData={allData}
|
||||||
setAllData={(data) => {
|
setAllData={(data) => {
|
||||||
dispatch({ type: "changeData", data });
|
dispatch({ type: "changeData", data });
|
||||||
@@ -82,7 +87,7 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
|
|||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={tabState} index={2}>
|
<TabPanel value={tabState} index={2}>
|
||||||
<Rahdaran
|
<MachineAndDriver
|
||||||
allData={allData}
|
allData={allData}
|
||||||
setAllData={(data) => {
|
setAllData={(data) => {
|
||||||
dispatch({ type: "changeData", data });
|
dispatch({ type: "changeData", data });
|
||||||
@@ -92,6 +97,26 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
|
|||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={tabState} index={3}>
|
<TabPanel value={tabState} index={3}>
|
||||||
|
<Area
|
||||||
|
allData={allData}
|
||||||
|
setAllData={(data) => {
|
||||||
|
dispatch({ type: "changeData", data });
|
||||||
|
}}
|
||||||
|
handlePrev={handlePrev}
|
||||||
|
setTabState={setTabState}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabState} index={4}>
|
||||||
|
<Rahdaran
|
||||||
|
allData={allData}
|
||||||
|
setAllData={(data) => {
|
||||||
|
dispatch({ type: "changeData", data });
|
||||||
|
}}
|
||||||
|
handlePrev={handlePrev}
|
||||||
|
setTabState={setTabState}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabState} index={5}>
|
||||||
<Verify allData={allData} handlePrev={handlePrev} submitForm={submitForm} submitting={submitting} />
|
<Verify allData={allData} handlePrev={handlePrev} submitForm={submitForm} submitting={submitting} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const CreateWithoutProcess = ({ row, mutate }) => {
|
|||||||
zone: result.region,
|
zone: result.region,
|
||||||
end_point: result.end_point,
|
end_point: result.end_point,
|
||||||
driver: result.driver.id,
|
driver: result.driver.id,
|
||||||
|
encoded_route: result.area,
|
||||||
},
|
},
|
||||||
hasSidebarUpdate: true,
|
hasSidebarUpdate: true,
|
||||||
})
|
})
|
||||||
@@ -47,7 +48,7 @@ const CreateWithoutProcess = ({ row, mutate }) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Dialog open={open} fullWidth maxWidth="sm">
|
<Dialog open={open} fullWidth maxWidth="md">
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
@@ -71,6 +72,8 @@ const CreateWithoutProcess = ({ row, mutate }) => {
|
|||||||
end_point: "",
|
end_point: "",
|
||||||
region: "",
|
region: "",
|
||||||
driver: null,
|
driver: null,
|
||||||
|
area: null,
|
||||||
|
locations: [],
|
||||||
}}
|
}}
|
||||||
submitForm={submitForm}
|
submitForm={submitForm}
|
||||||
submitting={submitting}
|
submitting={submitting}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import SearchReportList from "./Search";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ReportLists from "./ReportLists";
|
import ReportLists from "./ReportLists";
|
||||||
import useRequest from "@/lib/hooks/useRequest";
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
import { GET_CITY_ACTIVITY_REPORT, GET_PROVINCE_ACTIVITY_REPORT } from "@/core/utils/routes";
|
import { GET_COUNTRY_ACTIVITY_REPORT, GET_PROVINCE_ACTIVITY_REPORT } from "@/core/utils/routes";
|
||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -70,7 +70,7 @@ const ReportPage = () => {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
params.set("province_id", `${data.province_id}`);
|
params.set("province_id", `${data.province_id}`);
|
||||||
const response = await requestServer(`${GET_CITY_ACTIVITY_REPORT}?${params}`);
|
const response = await requestServer(`${GET_COUNTRY_ACTIVITY_REPORT}?${params}`);
|
||||||
const result = response.data.data;
|
const result = response.data.data;
|
||||||
const nationalReportForItem = result.activities.find((report) => report.edare_id === "-1");
|
const nationalReportForItem = result.activities.find((report) => report.edare_id === "-1");
|
||||||
const nationalReport = {
|
const nationalReport = {
|
||||||
|
|||||||
Reference in New Issue
Block a user