diff --git a/next.config.mjs b/next.config.mjs
index 29212c9..7b0b855 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,6 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath: '/v3',
+ images: {
+ remotePatterns: [
+ {
+ protocol: 'https',
+ hostname: 'rms.witel.ir',
+ },
+ ],
+ },
};
export default nextConfig;
diff --git a/package.json b/package.json
index 0d256e9..c5b2139 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"yup": "^1.4.0"
},
"devDependencies": {
+ "@types/node": "22.10.1",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"eslint-config-prettier": "^9.1.0",
diff --git a/src/app/(withAuth)/dashboard/road-items/operator/page.js b/src/app/(withAuth)/dashboard/road-items/operator/page.js
new file mode 100644
index 0000000..9c3d8ca
--- /dev/null
+++ b/src/app/(withAuth)/dashboard/road-items/operator/page.js
@@ -0,0 +1,11 @@
+import WithPermission from "@/core/middlewares/withPermission";
+import OperatorPage from "@/components/dashboard/roadItems/operator";
+const Page = () => {
+ return (
+
+
+
+ );
+};
+
+export default Page;
diff --git a/src/app/(withAuth)/dashboard/road-items/supervisor/page.js b/src/app/(withAuth)/dashboard/road-items/supervisor/page.js
new file mode 100644
index 0000000..58c47d8
--- /dev/null
+++ b/src/app/(withAuth)/dashboard/road-items/supervisor/page.js
@@ -0,0 +1,13 @@
+import WithPermission from "@/core/middlewares/withPermission";
+import SupervisorPage from "@/components/dashboard/roadItems/supervisor";
+const Page = () => {
+ return (
+
+
+
+ );
+};
+
+export default Page;
diff --git a/src/app/(withAuth)/layout.js b/src/app/(withAuth)/layout.js
index 35e60c5..62cbf6d 100644
--- a/src/app/(withAuth)/layout.js
+++ b/src/app/(withAuth)/layout.js
@@ -1,8 +1,8 @@
"use client";
-import WithAuthMiddleware from "@/core/middlewares/withAuth";
-import LoadingHardPage from "@/core/components/LoadingHardPage";
import { useAuth } from "@/lib/contexts/auth";
import { Typography, Stack } from "@mui/material";
+import WithAuthMiddleware from "@/core/middlewares/withAuth";
+import LoadingHardPage from "@/core/components/LoadingHardPage";
const Layout = ({ children }) => {
const { isAuth, initAuthState, errorState } = useAuth();
diff --git a/src/app/api/fake-road-items/operator/route.js b/src/app/api/fake-road-items/operator/route.js
new file mode 100644
index 0000000..49424bc
--- /dev/null
+++ b/src/app/api/fake-road-items/operator/route.js
@@ -0,0 +1,84 @@
+const data = [
+ {
+ id: 1,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ },
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 0, // Replace with actual status
+ car_id: 1,
+ person_id: [0, 1, 2],
+ description: "بررسی شد", // Replace with actual description
+ },
+ {
+ id: 2,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ },
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 2, // Replace with actual status
+ car_id: 10,
+ person_id: [10, 11, 12],
+ description: "بررسی شد", // Replace with actual description
+ },
+ {
+ id: 3,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ }, // Add URLs to images if needed
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 0, // Replace with actual status
+ car_id: 11,
+ person_id: [20, 15, 29],
+ description: "بررسی شد", // Replace with actual description
+ },
+ {
+ id: 4,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ }, // Add URLs to images if needed
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 1, // Replace with actual status
+ car_id: 26,
+ person_id: [78, 18, 56],
+ description: "بررسی شد", // Replace with actual description
+ },
+];
+
+export async function GET() {
+ return new Response(JSON.stringify({ data }), {
+ headers: { "Content-Type": "application/json" },
+ });
+}
diff --git a/src/app/api/fake-road-items/supervisor/route.js b/src/app/api/fake-road-items/supervisor/route.js
new file mode 100644
index 0000000..f2ab2bc
--- /dev/null
+++ b/src/app/api/fake-road-items/supervisor/route.js
@@ -0,0 +1,84 @@
+const data = [
+ {
+ id: 1,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ }, // Add URLs to images if needed
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 0, // Replace with actual status
+ car_id: 1,
+ person_id: [0, 1, 2],
+ description: "بررسی شد", // Replace with actual description
+ },
+ {
+ id: 2,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ }, // Add URLs to images if needed
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 2, // Replace with actual status
+ car_id: 10,
+ person_id: [10, 11, 12],
+ description: "بررسی شد", // Replace with actual description
+ },
+ {
+ id: 3,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ }, // Add URLs to images if needed
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 0, // Replace with actual status
+ car_id: 11,
+ person_id: [20, 15, 29],
+ description: "بررسی شد", // Replace with actual description
+ },
+ {
+ id: 4,
+ province: "تهران",
+ office: "اداره کل",
+ action_item: "بررسی خودرو", // Replace with the correct label
+ observed_subject: "تنظیم سطح بستر حریم", // Replace with the correct label
+ value: "1 کیلومتر", // Replace with actual value
+ images: {
+ before: "https://cdn-icons-png.flaticon.com/512/783/783154.png",
+ after: "https://cdn-icons-png.flaticon.com/512/1651/1651833.png",
+ }, // Add URLs to images if needed
+ location: [{ lat: 35.6892, lng: 51.389 }], // Add URLs to images if needed
+ register_date: "2024-11-03T11:01:14.000000Z",
+ start_date: "2024-11-02T11:48:22.000000Z",
+ status: 1, // Replace with actual status
+ car_id: 26,
+ person_id: [78, 18, 56],
+ description: "بررسی شد", // Replace with actual description
+ },
+];
+
+export async function GET() {
+ return new Response(JSON.stringify({ data }), {
+ headers: { "Content-Type": "application/json" },
+ });
+}
diff --git a/src/app/api/fake-operator-list/route.js b/src/app/api/fake-road-patrol/operator/route.js
similarity index 100%
rename from src/app/api/fake-operator-list/route.js
rename to src/app/api/fake-road-patrol/operator/route.js
diff --git a/src/app/api/fake-supervisor-list/route.js b/src/app/api/fake-road-patrol/supervisor/route.js
similarity index 100%
rename from src/app/api/fake-supervisor-list/route.js
rename to src/app/api/fake-road-patrol/supervisor/route.js
diff --git a/src/components/dashboard/azmayesh/ShowLocation/ShowLocationMarker.jsx b/src/components/dashboard/azmayesh/ShowLocation/ShowLocationMarker.jsx
deleted file mode 100644
index 20b44cb..0000000
--- a/src/components/dashboard/azmayesh/ShowLocation/ShowLocationMarker.jsx
+++ /dev/null
@@ -1,104 +0,0 @@
-"use client";
-
-import { Marker, useMap } from "react-leaflet";
-import { useEffect } from "react";
-import L from "leaflet";
-import AzmayeshActiveIcon from "@/assets/images/examine_marker_active.png";
-import { Box, FormControl, InputAdornment, InputLabel, OutlinedInput, Stack, useMediaQuery } from "@mui/material";
-import VerifiedIcon from "@mui/icons-material/Verified";
-import { useTheme } from "@emotion/react";
-
-const ShowLocationMarker = ({ lat, lng }) => {
- const map = useMap();
- const theme = useTheme();
- const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
-
- 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(() => {
- map.flyTo({ lat: lat, lng: lng }, 14);
- }, []);
-
- return (
- <>
-
-
-
-
-
- طول جغرافیایی
-
-
-
-
- }
- label="طول جغرافیایی"
- />
-
-
-
- عرض جغرافیایی
-
-
-
-
- }
- label="عرض جغرافیایی"
- />
-
-
-
- >
- );
-};
-
-export default ShowLocationMarker;
diff --git a/src/components/dashboard/azmayesh/ShowLocation/index.jsx b/src/components/dashboard/azmayesh/ShowLocation/index.jsx
index c260da7..3289871 100644
--- a/src/components/dashboard/azmayesh/ShowLocation/index.jsx
+++ b/src/components/dashboard/azmayesh/ShowLocation/index.jsx
@@ -5,7 +5,7 @@ import { Box, Button, Dialog, DialogActions, DialogContent, IconButton } from "@
import { useState } from "react";
import dynamic from "next/dynamic";
import MapLoading from "@/core/components/MapLayer/Loading";
-import ShowLocationMarker from "./ShowLocationMarker";
+import ShowLocationMarker from "@/core/components/ShowLocationMarker";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => ,
@@ -36,7 +36,7 @@ const ShowLocation = ({ lat, lng }) => {
-
+
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx
new file mode 100644
index 0000000..c05d6d8
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx
@@ -0,0 +1,225 @@
+import React, { useState } from "react";
+import { useForm } from "react-hook-form";
+import { yupResolver } from "@hookform/resolvers/yup";
+import { array, mixed, object, string } from "yup";
+import { useTheme } from "@emotion/react";
+import useRequest from "@/lib/hooks/useRequest";
+import { Box, Button, DialogActions, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material";
+import StyledForm from "@/core/components/StyledForm";
+import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
+import FileCopyIcon from "@mui/icons-material/FileCopy";
+import InfoIcon from "@mui/icons-material/Info";
+import ExitToAppIcon from "@mui/icons-material/ExitToApp";
+import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
+import BeenhereIcon from "@mui/icons-material/Beenhere";
+import GetItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm";
+import GetSubItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm";
+import GetItemInfo from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo";
+import { CREATE_ROAD_ITEMS } from "@/core/utils/routes";
+
+function TabPanel(props) {
+ const { children, value, index } = props;
+ return (
+
+ {value === index && {children}}
+
+ );
+}
+
+const defaultValues = {
+ itemId: null,
+ itemSubId: null,
+ amount: "",
+ action_date: "",
+ start_date: "",
+ before_image: null,
+ after_image: null,
+ cmms_machine_id: null,
+ rahdaran_id: null,
+ start_point: "",
+ end_point: "",
+};
+const validationSchema = object({
+ itemId: string().required("نوع آیتم را مشخص کنید!"),
+ itemSubId: string().required("موضوع مشاهده شده را مشخص کنید!"),
+ amount: string().required("وارد کردن مقدار الزامیست!"),
+ cmms_machine_id: object().required("وارد کردن کد خودرو الزامیست!"),
+ rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"), // بررسی حداقل یک آیتم,
+ action_date: string().required("لطفا زمان فعالیت را انتخاب کنید!"),
+ start_date: string().required("لطفاً تاریخ شروع فعالیت را انتخاب کنید!"),
+ before_image: mixed()
+ .nullable()
+ .test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
+ const { subItemsList } = this.options.context;
+ const needsImage = subItemsList?.needs_image === 1;
+ if (needsImage) {
+ return !!value;
+ }
+ return true;
+ }),
+ after_image: mixed()
+ .nullable()
+ .test("after-image-required", "لطفا عکس بعد از اقدام را بارگذاری کنید!", function (value) {
+ const { subItemsList } = this.options.context;
+ const needsImage = subItemsList?.needs_image === 1;
+ if (needsImage) {
+ return !!value;
+ }
+ return true;
+ }),
+ start_point: mixed()
+ .test("start-point-required", "لطفاً نقطه شروع را مشخص کنید!", function (value) {
+ return !!value; // چک میکند که مقدار موجود است
+ })
+ .required("لطفاً نقطه شروع را مشخص کنید!"),
+ end_point: mixed().test("end-point-required", "لطفاً نقطه پایان را مشخص کنید!", function (value) {
+ const { subItemsList } = this.options.context;
+ const needsEndPoint = subItemsList?.needs_end_point === 1;
+ if (needsEndPoint) {
+ return !!value; // چک میکند که مقدار موجود است
+ }
+ return true;
+ }),
+});
+
+const CreateFormContent = ({ setOpen, mutate, rowId }) => {
+ const [tabState, setTabState] = useState(0);
+ const [itemsList, setItemsList] = useState();
+ const [subItemsList, setSubItemsList] = useState([]);
+ const theme = useTheme();
+ const requestServer = useRequest({ notification: { success: true } });
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const handleChangeTab = (event, newValue) => {
+ setTabState(newValue);
+ };
+
+ const handlePrev = () => {
+ if (tabState === 0) {
+ handleClose();
+ } else {
+ setTabState(tabState - 1);
+ }
+ };
+ const {
+ control,
+ watch,
+ getValues,
+ register,
+ handleSubmit,
+ setValue,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ defaultValues,
+ resolver: yupResolver(validationSchema),
+ mode: "onBlur",
+ context: { subItemsList },
+ });
+
+ const onSubmit = async (data) => {
+ console.log(data);
+ let endPoint;
+ let startPoint = `${data.start_point.lat},${data.start_point.lng}`;
+ data.end_point !== "" && (endPoint = `${data.end_point.lat},${data.end_point.lng}`);
+ const formData = new FormData();
+ data.rahdaran_id.map((rahdar, index) => formData.append(`rahdaran_id[${index}]`, rahdar.id));
+ formData.append("item_id", data.itemId);
+ formData.append("sub_item_id", data.itemSubId);
+ formData.append("amount", data.amount);
+ formData.append("activity_time", data.action_date);
+ formData.append("activity_date", data.start_date);
+ formData.append("cmms_machine_id", data.cmms_machine_id.id);
+ data.before_image !== null && formData.append("before_image", data.before_image);
+ data.after_image !== null && formData.append("after_image", data.after_image);
+ data.start_point !== "" && formData.append("start_point", startPoint);
+ data.end_point !== "" && formData.append("end_point", endPoint);
+ requestServer(CREATE_ROAD_ITEMS, "post", {
+ data: formData,
+ })
+ .then((response) => {
+ mutate();
+ setOpen(false);
+ })
+ .catch((error) => {});
+ };
+ return (
+
+
+ } label="انتخاب آیتم" />
+ } label="انتخاب موضوع مشاهده شده" />
+ } label="اطلاعات فعالیت" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ : }
+ >
+ {tabState === 0 ? "بستن" : "مرحله قبل"}
+
+ {tabState === 2 && (
+ }
+ >
+ {isSubmitting ? "در حال ثبت فعالیت" : "ثبت فعالیت"}
+
+ )}
+
+
+ );
+};
+export default CreateFormContent;
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx
new file mode 100644
index 0000000..54885c1
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx
@@ -0,0 +1,111 @@
+import { Stack, TextField, Grid } from "@mui/material";
+import ImageUpload from "./ImageUpload";
+import MuiDatePicker from "@/core/components/MuiDatePicker";
+import MuiTimePicker from "@/core/components/MuiTimePicker";
+import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
+import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
+import { Controller } from "react-hook-form";
+import CarCode from "@/core/components/CarCode";
+import RahdarCode from "@/core/components/RahdarCode";
+import PreviousStatesInfo from "./PreviousStatesInfo";
+
+const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues }) => {
+ return (
+
+
+
+
+
+ {
+ if (!isNaN(event.target.value)) {
+ setValue("amount", event.target.value);
+ } else {
+ setValue("amount", watch("amount"));
+ }
+ }}
+ helperText={errors.amount ? errors.amount.message : null}
+ variant="outlined"
+ fullWidth
+ />
+
+
+ {
+ return (
+ field.onChange(value || null)}
+ error={error}
+ />
+ );
+ }}
+ name={"cmms_machine_id"}
+ />
+
+
+ {
+ return (
+ field.onChange(value || [])}
+ error={error}
+ />
+ );
+ }}
+ name={"rahdaran_id"}
+ />
+
+
+ {subItemsList.needs_image ? (
+
+ ) : null}
+
+
+
+
+
+
+
+
+
+
+
+
+ {subItemsList?.needs_end_point === 1 ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
+export default GetItemInfo;
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm.jsx
new file mode 100644
index 0000000..0396b71
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm.jsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { Card, CardActionArea, Grid, LinearProgress, Typography } from "@mui/material";
+import Image from "next/image";
+import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems";
+
+const GetItemsForm = ({ setValue, setItemsList, tabState, setTabState }) => {
+ const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems();
+
+ return (
+ <>
+ {errorItemsList ? (
+
+ خطا در دریافت لیست آیتم ها
+
+ ) : loadingItemsList ? (
+
+ ) : (
+
+ {itemsList.map((item) => (
+
+ {
+ setValue("itemId", item.id);
+ setItemsList(item.name);
+ setTabState(tabState + 1);
+ }}
+ >
+
+
+ {item.name}
+
+
+
+ ))}
+
+ )}
+ >
+ );
+};
+export default GetItemsForm;
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm.jsx
new file mode 100644
index 0000000..8b36594
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm.jsx
@@ -0,0 +1,53 @@
+import React from "react";
+import { Card, CardActionArea, Grid, LinearProgress, Typography } from "@mui/material";
+import useRoadItemGetSubItems from "@/lib/hooks/useRoadItemGetISubtems";
+
+const GetSubItemsForm = ({ setValue, watch, setSubItemsList, tabState, setTabState }) => {
+ const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetSubItems(watch("itemId"));
+
+ return (
+ <>
+ {errorSubItemsList ? (
+
+ خطا در دریافت موضوع مشاهده شده
+
+ ) : loadingSubItemsList ? (
+
+ ) : (
+
+ {subItemsList.map((item) => (
+
+ {
+ setValue("itemSubId", item.sub_item);
+ setSubItemsList(item);
+ setTabState(tabState + 1);
+ }}
+ >
+
+ {item.name}
+
+
+
+ ))}
+
+ )}
+ >
+ );
+};
+export default GetSubItemsForm;
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx
new file mode 100644
index 0000000..3efd1d9
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx
@@ -0,0 +1,131 @@
+import { FormControl, FormHelperText, Grid } from "@mui/material";
+import UploadSystem from "@/core/components/UploadSystem";
+import { Controller } from "react-hook-form";
+import React, { useEffect, useState } from "react";
+
+const ImageUpload = ({ control, setValue, errors, getValues, beforeImage = null, afterImage = null }) => {
+ const [beforeImg, setBeforeImg] = useState(beforeImage ? beforeImage : null);
+ const [beforeFileType, setBeforeFileType] = useState(beforeImage ? "image/" : null);
+ const [beforeFileName, setBeforeFileName] = useState(null);
+ const [showBeforeImage, setShowBeforeImage] = useState(!beforeImage);
+
+ const [afterImg, setAfterImg] = useState(afterImage ? afterImage : null);
+ const [afterFileType, setAfterFileType] = useState(afterImage ? "image/" : null);
+ const [afterFileName, setAfterFileName] = useState(null);
+ const [showAfterImage, setShowAfterImage] = useState(!afterImage);
+
+ useEffect(() => {
+ if (getValues("before_image")) {
+ setShowBeforeImage(false);
+ setBeforeImg(getValues("before_image"));
+ setBeforeFileType("image/");
+ }
+ if (getValues("after_image")) {
+ setShowAfterImage(false);
+ setAfterImg(getValues("after_image"));
+ setAfterFileType("image/");
+ }
+ }, [getValues]);
+
+ const handleBeforeFileChange = (event) => {
+ const uploadedFile = event.target?.files?.[0];
+ if (uploadedFile) {
+ const fileType = event.target?.files?.[0].type;
+ const fileName = event.target?.files?.[0].name;
+ setBeforeImg(URL.createObjectURL(uploadedFile));
+ setBeforeFileType(fileType);
+ setBeforeFileName(fileName);
+ setValue("before_image", uploadedFile);
+ setShowBeforeImage(false);
+ }
+ };
+ const handleAfterFileChange = (event) => {
+ const uploadedFile = event.target?.files?.[0];
+ if (uploadedFile) {
+ const fileType = event.target?.files?.[0].type;
+ const fileName = event.target?.files?.[0].name;
+ setAfterImg(URL.createObjectURL(uploadedFile));
+ setAfterFileType(fileType);
+ setAfterFileName(fileName);
+ setValue("after_image", uploadedFile);
+ setShowAfterImage(false);
+ }
+ };
+ return (
+
+
+ {
+ return (
+
+
+ عکس قبل از اقدام
+
+
+
+ {errors.before_image ? errors.before_image.message : null}
+
+
+ );
+ }}
+ />
+
+
+ {
+ return (
+
+
+ عکس بعد از اقدام
+
+
+
+ {errors.after_image ? errors.after_image.message : null}
+
+
+ );
+ }}
+ />
+
+
+ );
+};
+export default ImageUpload;
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/PreviousStatesInfo.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/PreviousStatesInfo.jsx
new file mode 100644
index 0000000..6f3cc86
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/PreviousStatesInfo.jsx
@@ -0,0 +1,43 @@
+import { Box, Chip, Divider, Grid, Typography } from "@mui/material";
+import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
+import FileCopyIcon from "@mui/icons-material/FileCopy";
+
+const PreviousStatesInfo = ({ itemsList, subItemsList }) => {
+ return (
+
+
+
+ }
+ label={
+
+ آیتم انتخاب شده
+
+ }
+ />
+
+
+
+ {itemsList || "هیچ آیتمی انتخاب نشده است"}
+
+
+
+
+ }
+ label={
+
+ موضوع مشاهدهشده
+
+ }
+ />
+
+
+
+ {subItemsList?.name || "هیچ موضوعی انتخاب نشده است"}
+
+
+
+ );
+};
+export default PreviousStatesInfo;
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/index.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/index.jsx
new file mode 100644
index 0000000..52c2d42
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/index.jsx
@@ -0,0 +1,12 @@
+"use client";
+import { Dialog } from "@mui/material";
+import CreateFormContent from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent";
+
+const OperatorCreateForm = ({ open, setOpen, mutate, rowId }) => {
+ return (
+
+ );
+};
+export default OperatorCreateForm;
diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/index.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/index.jsx
new file mode 100644
index 0000000..ce764e7
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Actions/Create/index.jsx
@@ -0,0 +1,38 @@
+"use client";
+import { Button, IconButton, useMediaQuery } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useState } from "react";
+import { AddCircle } from "@mui/icons-material";
+import OperatorCreateForm from "./Forms";
+
+const OperatorCreate = ({ mutate }) => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
+ const [open, setOpen] = useState(false);
+
+ const handleOpen = () => {
+ setOpen(true);
+ };
+
+ return (
+ <>
+ {isMobile ? (
+
+
+
+ ) : (
+ }
+ onClick={handleOpen}
+ >
+ ثبت فعالیت
+
+ )}
+ {open && }
+ >
+ );
+};
+export default OperatorCreate;
diff --git a/src/components/dashboard/roadItems/operator/ExcelPrint/index.jsx b/src/components/dashboard/roadItems/operator/ExcelPrint/index.jsx
new file mode 100644
index 0000000..ad5d8b3
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/ExcelPrint/index.jsx
@@ -0,0 +1,71 @@
+"use client";
+import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material";
+import { useMemo, useState } from "react";
+import moment from "jalali-moment";
+import FileSaver from "file-saver";
+import DescriptionIcon from "@mui/icons-material/Description";
+import useRequest from "@/lib/hooks/useRequest";
+import { EXPORT_ROAD_ITEMS_OPERATOR_LIST } from "@/core/utils/routes";
+import { useTheme } from "@emotion/react";
+
+const PrintExcel = ({ table, filterData }) => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
+ const [loading, setLoading] = useState(false);
+ const requestServer = useRequest({ notification: { success: true } });
+ const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => {
+ if (filter.value) {
+ // Check if there's an active filter
+ acc.push({ id: key, value: filter.value });
+ }
+ return acc;
+ }, []);
+
+ const filterParams = useMemo(() => {
+ const params = new URLSearchParams();
+ activeFilters.length > 0 &&
+ activeFilters.map((filter, index) => {
+ params.set(`${filter.id}`, filter.value);
+ });
+ return params;
+ }, [activeFilters]);
+
+ const clickHandler = () => {
+ setLoading(true);
+ requestServer(`${EXPORT_ROAD_ITEMS_OPERATOR_LIST}?${filterParams}`, "get", {
+ requestOptions: { responseType: "blob" },
+ })
+ .then((response) => {
+ const filename = `خروجی کارتابل فعالیت روزانه تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`;
+ FileSaver.saveAs(response.data, filename);
+ })
+ .catch(() => {})
+ .finally(() => {
+ setLoading(false);
+ });
+ };
+
+ return (
+ <>
+ {isMobile ? (
+
+
+
+ ) : (
+ : }
+ onClick={clickHandler}
+ >
+ خروجی اکسل
+
+ )}
+ >
+ );
+};
+
+export default PrintExcel;
diff --git a/src/components/dashboard/roadItems/operator/OperatorList.jsx b/src/components/dashboard/roadItems/operator/OperatorList.jsx
new file mode 100644
index 0000000..aed25de
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/OperatorList.jsx
@@ -0,0 +1,220 @@
+"use client";
+import { useMemo } from "react";
+import { Box, Stack, Typography } from "@mui/material";
+import DataTableWithAuth from "@/core/components/DataTableWithAuth";
+import Toolbar from "./Toolbar";
+import { GET_ROAD_ITEMS_OPERATOR_LIST } from "@/core/utils/routes";
+import moment from "jalali-moment";
+import RowActions from "./RowActions";
+import LocationForm from "./RowActions/LocationForm";
+import ImageDialog from "./RowActions/ImageForm";
+import RahdaranDialog from "./RowActions/RahdaranForm";
+
+const OperatorList = () => {
+ const columns = useMemo(
+ () => [
+ {
+ accessorKey: "id",
+ header: "کد یکتا",
+ id: "id",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "item_fa",
+ header: "آیتم فعالیت",
+ id: "item_fa",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "sub_item_fa",
+ header: "موضوع مشاهده شده", // Observed Subject
+ id: "sub_item_fa",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "value",
+ header: "مقدار", // Value
+ id: "value",
+ enableColumnFilter: false,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ Cell: ({ renderedCellValue, row }) => {
+ return (
+
+ {row.original.sub_item_data}
+ {`(${row.original.unit_fa})`}
+
+ );
+ },
+ },
+ {
+ accessorKey: "files",
+ header: "تصاویر", // Images
+ id: "files",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "array",
+ muiTableBodyCellProps: {
+ sx: {
+ borderLeft: "1px solid #e1e1e1",
+ py: 0,
+ "&:first-of-type": {
+ borderLeft: "unset",
+ },
+ },
+ },
+ Cell: ({ renderedCellValue }) => {
+ if (renderedCellValue.length > 0) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ بدون تصویر
+
+ );
+ },
+ },
+ {
+ accessorKey: "location",
+ header: "موقعیت", // Location
+ id: "location",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "array",
+ muiTableBodyCellProps: {
+ sx: {
+ borderLeft: "1px solid #e1e1e1",
+ py: 0,
+ "&:first-of-type": {
+ borderLeft: "unset",
+ },
+ },
+ },
+ Cell: ({ renderedCellValue, row }) => {
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "cmms_machine_code",
+ header: "کد خودرو", // Car ID
+ id: "cmms_machine_code",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ enableSorting: false,
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "rahdaran",
+ header: "راهداران",
+ id: "rahdaran",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "array",
+ muiTableBodyCellProps: {
+ sx: {
+ borderLeft: "1px solid #e1e1e1",
+ py: 0,
+ "&:first-of-type": {
+ borderLeft: "unset",
+ },
+ },
+ },
+ Cell: ({ renderedCellValue }) => {
+ if (renderedCellValue) {
+ return (
+
+
+
+ );
+ }
+ return بدون شخص;
+ },
+ },
+ {
+ accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
+ header: "تاریخ ثبت", // Register Date
+ id: "created_at",
+ enableColumnFilter: true,
+ datatype: "date",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
+ header: "تاریخ فعالیت", // Start Date
+ id: "activity_date_time",
+ enableColumnFilter: true,
+ datatype: "date",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "status_fa",
+ header: "وضعیت", // Status
+ id: "status_fa",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ sortDescFirst: true,
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "supervisor_description",
+ header: "توضیحات", // Description
+ id: "supervisor_description",
+ enableColumnFilter: false,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ ],
+ []
+ );
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+export default OperatorList;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx
new file mode 100644
index 0000000..9b3a12c
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx
@@ -0,0 +1,211 @@
+import { Button, DialogActions, DialogContent, Stack, TextField } from "@mui/material";
+import useRequest from "@/lib/hooks/useRequest";
+import { Controller, useForm } from "react-hook-form";
+import { yupResolver } from "@hookform/resolvers/yup";
+import { array, mixed, object, string } from "yup";
+import StyledForm from "@/core/components/StyledForm";
+import React from "react";
+import ImageUpload from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload";
+import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
+import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
+import { UPDATE_ROAD_ITEMS } from "@/core/utils/routes";
+import CarCode from "@/core/components/CarCode";
+import RahdarCode from "@/core/components/RahdarCode";
+import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems";
+
+const validationSchema = object({
+ amount: string().required("وارد کردن مقدار الزامیست!"),
+ cmms_machine_id: object().required("وارد کردن کد خودرو الزامیست!"),
+ rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"),
+ before_image: mixed()
+ .nullable()
+ .test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
+ const { subItem } = this.options.context;
+ const needsImage = subItem?.needs_image === 1;
+ if (needsImage) {
+ return !!value;
+ }
+ return true;
+ }),
+ after_image: mixed()
+ .nullable()
+ .test("after-image-required", "لطفا عکس بعد از اقدام را بارگذاری کنید!", function (value) {
+ const { subItem } = this.options.context;
+ const needsImage = subItem?.needs_image === 1;
+ if (needsImage) {
+ return !!value;
+ }
+ return true;
+ }),
+ start_point: mixed()
+ .test("start-point-required", "لطفاً نقطه شروع را مشخص کنید!", function (value) {
+ return !!value; // چک میکند که مقدار موجود است
+ })
+ .required("لطفاً نقطه شروع را مشخص کنید!"),
+ end_point: mixed().test("end-point-required", "لطفاً نقطه پایان را مشخص کنید!", function (value) {
+ const { subItem } = this.options.context;
+ const needsEndPoint = subItem?.needs_end_point === 1;
+ if (needsEndPoint) {
+ return !!value; // چک میکند که مقدار موجود است
+ }
+ return true;
+ }),
+});
+
+const EditFormContent = ({ row, mutate, setOpenEditDialog, rowId, subItem }) => {
+ const defaultValues = {
+ before_image: row.original?.files[0]?.full_path_for_fast_react || null,
+ after_image: row.original?.files[1]?.full_path_for_fast_react || null,
+ amount: row.original.sub_item_data || null,
+ start_point: { lat: row.original.start_lat || "", lng: row.original.start_lng || "" },
+ end_point: { lat: row.original.end_lat || "", lng: row.original.end_lng || "" },
+ cmms_machine_id: row.original.cmms_machine,
+ rahdaran_id: row.original.rahdaran || null,
+ };
+ const requestServer = useRequest();
+
+ const {
+ control,
+ getValues,
+ watch,
+ register,
+ handleSubmit,
+ setValue,
+ formState: { errors, isSubmitting },
+ } = useForm({
+ defaultValues,
+ resolver: yupResolver(validationSchema),
+ mode: "onBlur",
+ context: { subItem },
+ });
+
+ const onSubmit = async (data) => {
+ let endPoint;
+ let startPoint = `${data.start_point.lat},${data.start_point.lng}`;
+ subItem.needs_end_point === 1 && (endPoint = `${data.end_point.lat},${data.end_point.lng}`);
+ const formData = new FormData();
+ subItem.needs_image === 1 &&
+ data.before_image !== defaultValues.before_image &&
+ formData.append("before_image", data.before_image);
+ subItem.needs_image === 1 &&
+ data.after_image !== defaultValues.after_image &&
+ formData.append("after_image", data.after_image);
+ formData.append("amount", data.amount);
+ formData.append("cmms_machine_id", data.cmms_machine_id.id);
+ data.rahdaran_id.map((rahdar, index) => formData.append(`rahdaran_id[${index}]`, rahdar.id));
+ formData.append("start_point", startPoint);
+ subItem.needs_end_point === 1 && formData.append("end_point", endPoint);
+
+ requestServer(`${UPDATE_ROAD_ITEMS}/${rowId}`, "post", {
+ notification: { success: true },
+ data: formData,
+ })
+ .then((res) => {
+ mutate();
+ setOpenEditDialog(false);
+ })
+ .catch((err) => {});
+ };
+ return (
+ <>
+
+
+
+
+ {subItem.needs_image === 1 ? (
+
+ ) : null}
+
+
+ {
+ if (!isNaN(event.target.value)) {
+ setValue("amount", event.target.value);
+ } else {
+ setValue("amount", watch("amount"));
+ }
+ }}
+ error={!!errors.amount}
+ helperText={errors.amount ? errors.amount.message : null}
+ variant="outlined"
+ fullWidth
+ />
+
+
+ {
+ return (
+ field.onChange(value || "")}
+ inputValueDefault={row.original.cmms_machine_code}
+ error={error} // اگر خطا وجود داشته باشد
+ />
+ );
+ }}
+ name={"cmms_machine_id"}
+ />
+
+
+ {
+ return (
+ field.onChange(value || [])}
+ error={error}
+ />
+ );
+ }}
+ name={"rahdaran_id"}
+ />
+
+
+ {subItem.needs_end_point === 1 ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+ >
+ );
+};
+export default EditFormContent;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/EditForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/EditForm/index.jsx
new file mode 100644
index 0000000..92652e9
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/EditForm/index.jsx
@@ -0,0 +1,54 @@
+import { Dialog, DialogTitle, IconButton, LinearProgress, Tooltip, Typography } from "@mui/material";
+import { useState } from "react";
+import BorderColorIcon from "@mui/icons-material/BorderColor";
+import EditFormContent from "@/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent";
+import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems";
+
+const EditForm = ({ row, mutate, rowId }) => {
+ const [openEditDialog, setOpenEditDialog] = useState(false);
+ const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetISubtems(row.original?.item);
+ const subItem = subItemsList.find((subItem) => subItem.sub_item === row.original?.sub_item);
+ return (
+ <>
+
+ {
+ setOpenEditDialog(true);
+ }}
+ >
+
+
+
+
+ >
+ );
+};
+export default EditForm;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImageFormContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImageFormContent.jsx
new file mode 100644
index 0000000..c9f4e2e
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImageFormContent.jsx
@@ -0,0 +1,41 @@
+import { DialogContent, Paper, Stack, Typography, useTheme } from "@mui/material";
+
+const ImageFormContent = ({ image, title }) => {
+ const theme = useTheme();
+ return (
+
+
+
+
+ {title}
+
+
+
+
+
+ );
+};
+export default ImageFormContent;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/ImageForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/index.jsx
new file mode 100644
index 0000000..8bdb7e9
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/index.jsx
@@ -0,0 +1,41 @@
+import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import { useState } from "react";
+import PhotoLibraryIcon from "@mui/icons-material/PhotoLibrary";
+import ImageFormContent from "./ImageFormContent";
+
+const ImageDialog = ({ images }) => {
+ const [openImageDialog, setOpenImageDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenImageDialog(true)}>
+
+
+
+
+ >
+ );
+};
+
+export default ImageDialog;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/LocationForm/LocationFormContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/LocationForm/LocationFormContent.jsx
new file mode 100644
index 0000000..6c17f3a
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/LocationForm/LocationFormContent.jsx
@@ -0,0 +1,36 @@
+"use client";
+import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
+import React from "react";
+import dynamic from "next/dynamic";
+import MapLoading from "@/core/components/MapLayer/Loading";
+import ShowLocationMarker from "@/core/components/ShowLocationMarker";
+const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
+ loading: () => ,
+ ssr: false,
+});
+
+const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_lat, end_lng }) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default LocationFormContent;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/LocationForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/LocationForm/index.jsx
new file mode 100644
index 0000000..f578ca8
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/LocationForm/index.jsx
@@ -0,0 +1,38 @@
+import React, { useState } from "react";
+import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
+import ExploreIcon from "@mui/icons-material/Explore";
+import LocationFormContent from "./LocationFormContent";
+const LocationForm = ({ start_lat, start_lng, end_lat, end_lng }) => {
+ const [openLocationDialog, setOpenLocationDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenLocationDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default LocationForm;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/RahdaranForm/RahdaranContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/RahdaranForm/RahdaranContent.jsx
new file mode 100644
index 0000000..d1e9b15
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/RahdaranForm/RahdaranContent.jsx
@@ -0,0 +1,48 @@
+import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
+
+const RahdaranContent = ({ rahdarLists }) => {
+ return (
+ <>
+
+
+
+
+
+
+ کد راهدار
+ نام
+
+
+
+ {rahdarLists.map((rahdar) => {
+ return (
+
+ {rahdar.code}
+ {rahdar.name}
+
+ );
+ })}
+
+
+
+
+
+ >
+ );
+};
+export default RahdaranContent;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/RahdaranForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/RahdaranForm/index.jsx
new file mode 100644
index 0000000..2c73d80
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/RahdaranForm/index.jsx
@@ -0,0 +1,38 @@
+import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import GroupsIcon from "@mui/icons-material/Groups";
+import { useState } from "react";
+import RahdaranContent from "./RahdaranContent";
+
+const RahdaranDialog = ({ rahdarLists }) => {
+ const [openRahdaranDialog, setOpenRahdaranDialog] = useState(false);
+ return (
+ <>
+
+ setOpenRahdaranDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default RahdaranDialog;
diff --git a/src/components/dashboard/roadItems/operator/RowActions/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/index.jsx
new file mode 100644
index 0000000..c5051d8
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/RowActions/index.jsx
@@ -0,0 +1,11 @@
+import { Box } from "@mui/material";
+import EditForm from "./EditForm";
+
+const RowActions = ({ row, mutate }) => {
+ return (
+
+ {row.original.status === 2 && }
+
+ );
+};
+export default RowActions;
diff --git a/src/components/dashboard/roadItems/operator/Toolbar.jsx b/src/components/dashboard/roadItems/operator/Toolbar.jsx
new file mode 100644
index 0000000..c98ae05
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/Toolbar.jsx
@@ -0,0 +1,13 @@
+import PrintExcel from "./ExcelPrint";
+import OperatorCreate from "./Actions/Create";
+import { Stack } from "@mui/material";
+
+const Toolbar = ({ table, filterData, mutate }) => {
+ return (
+
+
+
+
+ );
+};
+export default Toolbar;
diff --git a/src/components/dashboard/roadItems/operator/index.jsx b/src/components/dashboard/roadItems/operator/index.jsx
new file mode 100644
index 0000000..a2b328c
--- /dev/null
+++ b/src/components/dashboard/roadItems/operator/index.jsx
@@ -0,0 +1,14 @@
+"use client";
+import PageTitle from "@/core/components/PageTitle";
+import { Stack } from "@mui/material";
+import OperatorList from "./OperatorList";
+
+const OperatorPage = () => {
+ return (
+
+
+
+
+ );
+};
+export default OperatorPage;
diff --git a/src/components/dashboard/roadItems/supervisor/ExcelPrint/index.jsx b/src/components/dashboard/roadItems/supervisor/ExcelPrint/index.jsx
new file mode 100644
index 0000000..1cc0b2c
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/ExcelPrint/index.jsx
@@ -0,0 +1,70 @@
+import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material";
+import { useMemo, useState } from "react";
+import moment from "jalali-moment";
+import FileSaver from "file-saver";
+import DescriptionIcon from "@mui/icons-material/Description";
+import useRequest from "@/lib/hooks/useRequest";
+import { EXPORT_ROAD_ITEMS_SUPERVISOR_LIST } from "@/core/utils/routes";
+import { useTheme } from "@emotion/react";
+
+const PrintExcel = ({ table, filterData }) => {
+ const [loading, setLoading] = useState(false);
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
+ const requestServer = useRequest();
+ const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => {
+ if (filter.value) {
+ // Check if there's an active filter
+ acc.push({ id: key, value: filter.value });
+ }
+ return acc;
+ }, []);
+
+ const filterParams = useMemo(() => {
+ const params = new URLSearchParams();
+ activeFilters.length > 0 &&
+ activeFilters.map((filter, index) => {
+ params.set(`${filter.id}`, filter.value);
+ });
+ return params;
+ }, [activeFilters]);
+
+ const clickHandler = () => {
+ setLoading(true);
+ requestServer(`${EXPORT_ROAD_ITEMS_SUPERVISOR_LIST}?${filterParams}`, "get", {
+ requestOptions: { responseType: "blob" },
+ })
+ .then((response) => {
+ const filename = `خروجی کارتابل فعالیت روزانه تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`;
+ FileSaver.saveAs(response.data, filename);
+ })
+ .catch(() => {})
+ .finally(() => {
+ setLoading(false);
+ });
+ };
+
+ return (
+ <>
+ {isMobile ? (
+
+
+
+ ) : (
+ : }
+ onClick={clickHandler}
+ >
+ خروجی اکسل
+
+ )}
+ >
+ );
+};
+
+export default PrintExcel;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/ConfirmForm/ConfirmContent.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/ConfirmForm/ConfirmContent.jsx
new file mode 100644
index 0000000..2f7af71
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/ConfirmForm/ConfirmContent.jsx
@@ -0,0 +1,69 @@
+import { Button, DialogActions, DialogContent, Stack, TextField } from "@mui/material";
+import { useForm } from "react-hook-form";
+import useRequest from "@/lib/hooks/useRequest";
+import { VERIFY_BY_SUPERVISOR } from "@/core/utils/routes";
+
+const ConfirmContent = ({ rowId, mutate, setOpenConfirmDialog }) => {
+ const requestServer = useRequest({ notification: { success: true } });
+
+ const {
+ register,
+ handleSubmit,
+ formState: { isSubmitting },
+ reset,
+ } = useForm({
+ defaultValues: {
+ description: "",
+ },
+ });
+
+ const onSubmit = async (data) => {
+ const formData = new FormData();
+ data.description !== "" && formData.append("description", data.description);
+ formData.append("verify", 1);
+ requestServer(`${VERIFY_BY_SUPERVISOR}/${rowId}`, "post", {
+ data: formData,
+ })
+ .then(() => {
+ mutate();
+ setOpenConfirmDialog(false);
+ })
+ .catch(() => {});
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default ConfirmContent;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/ConfirmForm/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/ConfirmForm/index.jsx
new file mode 100644
index 0000000..a542a30
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/ConfirmForm/index.jsx
@@ -0,0 +1,29 @@
+import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import { useState } from "react";
+import ConfirmContent from "./ConfirmContent";
+import DoneIcon from "@mui/icons-material/Done";
+const ConfirmForm = ({ rowId, mutate }) => {
+ const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
+ return (
+ <>
+
+ setOpenConfirmDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default ConfirmForm;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/DeleteForm/DeleteContent.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/DeleteForm/DeleteContent.jsx
new file mode 100644
index 0000000..9e61c1b
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/DeleteForm/DeleteContent.jsx
@@ -0,0 +1,42 @@
+import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
+import React, { useState } from "react";
+import useRequest from "@/lib/hooks/useRequest";
+import { DELETE_BY_SUPERVISOR } from "@/core/utils/routes";
+
+const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
+ const [submitting, setSubmitting] = useState(false);
+ const requestServer = useRequest({ notification: { success: true } });
+ const handleClick = () => {
+ setSubmitting(true);
+ requestServer(`${DELETE_BY_SUPERVISOR}/${rowId}`, "post")
+ .then(() => {
+ mutate();
+ setOpenDeleteDialog(false);
+ setSubmitting(false);
+ })
+ .catch(() => {
+ setSubmitting(false);
+ });
+ };
+ return (
+ <>
+
+
+
+ آیا از حذف فعالیت اطمینان دارید؟
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default DeleteContent;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/DeleteForm/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/DeleteForm/index.jsx
new file mode 100644
index 0000000..c8c8375
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/DeleteForm/index.jsx
@@ -0,0 +1,33 @@
+import React, { useState } from "react";
+import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
+import DeleteIcon from "@mui/icons-material/Delete";
+import DeleteContent from "./DeleteContent";
+import useRequest from "@/lib/hooks/useRequest";
+const DeleteForm = ({ rowId, mutate }) => {
+ const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenDeleteDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default DeleteForm;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/ImageForm/ImageFormContent.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/ImageForm/ImageFormContent.jsx
new file mode 100644
index 0000000..c9f4e2e
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/ImageForm/ImageFormContent.jsx
@@ -0,0 +1,41 @@
+import { DialogContent, Paper, Stack, Typography, useTheme } from "@mui/material";
+
+const ImageFormContent = ({ image, title }) => {
+ const theme = useTheme();
+ return (
+
+
+
+
+ {title}
+
+
+
+
+
+ );
+};
+export default ImageFormContent;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/ImageForm/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/ImageForm/index.jsx
new file mode 100644
index 0000000..0e70c1d
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/ImageForm/index.jsx
@@ -0,0 +1,41 @@
+import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import { useState } from "react";
+import PhotoLibraryIcon from "@mui/icons-material/PhotoLibrary";
+import ImageFormContent from "./ImageFormContent";
+
+const ImageDialog = ({ images }) => {
+ const [openImageDialog, setOpenImageDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenImageDialog(true)}>
+
+
+
+
+ >
+ );
+};
+
+export default ImageDialog;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/LocationForm/LocationFormContent.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/LocationForm/LocationFormContent.jsx
new file mode 100644
index 0000000..6c17f3a
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/LocationForm/LocationFormContent.jsx
@@ -0,0 +1,36 @@
+"use client";
+import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
+import React from "react";
+import dynamic from "next/dynamic";
+import MapLoading from "@/core/components/MapLayer/Loading";
+import ShowLocationMarker from "@/core/components/ShowLocationMarker";
+const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
+ loading: () => ,
+ ssr: false,
+});
+
+const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_lat, end_lng }) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default LocationFormContent;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/LocationForm/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/LocationForm/index.jsx
new file mode 100644
index 0000000..f578ca8
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/LocationForm/index.jsx
@@ -0,0 +1,38 @@
+import React, { useState } from "react";
+import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
+import ExploreIcon from "@mui/icons-material/Explore";
+import LocationFormContent from "./LocationFormContent";
+const LocationForm = ({ start_lat, start_lng, end_lat, end_lng }) => {
+ const [openLocationDialog, setOpenLocationDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenLocationDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default LocationForm;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/RahdaranForm/RahdaranContent.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/RahdaranForm/RahdaranContent.jsx
new file mode 100644
index 0000000..d1e9b15
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/RahdaranForm/RahdaranContent.jsx
@@ -0,0 +1,48 @@
+import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
+
+const RahdaranContent = ({ rahdarLists }) => {
+ return (
+ <>
+
+
+
+
+
+
+ کد راهدار
+ نام
+
+
+
+ {rahdarLists.map((rahdar) => {
+ return (
+
+ {rahdar.code}
+ {rahdar.name}
+
+ );
+ })}
+
+
+
+
+
+ >
+ );
+};
+export default RahdaranContent;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/RahdaranForm/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/RahdaranForm/index.jsx
new file mode 100644
index 0000000..2c73d80
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/RahdaranForm/index.jsx
@@ -0,0 +1,38 @@
+import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import GroupsIcon from "@mui/icons-material/Groups";
+import { useState } from "react";
+import RahdaranContent from "./RahdaranContent";
+
+const RahdaranDialog = ({ rahdarLists }) => {
+ const [openRahdaranDialog, setOpenRahdaranDialog] = useState(false);
+ return (
+ <>
+
+ setOpenRahdaranDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default RahdaranDialog;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/RejectForm/RejectContent.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/RejectForm/RejectContent.jsx
new file mode 100644
index 0000000..087e020
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/RejectForm/RejectContent.jsx
@@ -0,0 +1,81 @@
+import { Button, DialogActions, DialogContent, Stack, TextField } from "@mui/material";
+import { useForm } from "react-hook-form";
+import { yupResolver } from "@hookform/resolvers/yup";
+import * as Yup from "yup";
+import useRequest from "@/lib/hooks/useRequest";
+import { REJECT_BY_SUPERVISOR } from "@/core/utils/routes";
+
+const RejectContent = ({ rowId, mutate, setOpenRejectDialog }) => {
+ const requestServer = useRequest({ notification: { success: true } });
+
+ const validationSchema = Yup.object().shape({
+ description: Yup.string().required("توضیحات الزامیست!!!"),
+ });
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isSubmitting },
+ reset,
+ } = useForm({
+ defaultValues: {
+ description: "",
+ },
+ resolver: yupResolver(validationSchema),
+ mode: "onBlur",
+ });
+
+ const onSubmit = async (data) => {
+ const formData = new FormData();
+ formData.append("description", data.description);
+ formData.append("verify", 2);
+ requestServer(`${REJECT_BY_SUPERVISOR}/${rowId}`, "post", {
+ data: formData,
+ })
+ .then(() => {
+ mutate();
+ setOpenRejectDialog(false);
+ })
+ .catch(() => {})
+ .finally(() => {
+ reset();
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+export default RejectContent;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/RejectForm/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/RejectForm/index.jsx
new file mode 100644
index 0000000..e05288c
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/RejectForm/index.jsx
@@ -0,0 +1,29 @@
+import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import { useState } from "react";
+import RejectContent from "./RejectContent";
+import ClearIcon from "@mui/icons-material/Clear";
+const RejectForm = ({ rowId, mutate }) => {
+ const [openRejectDialog, setOpenRejectDialog] = useState(false);
+ return (
+ <>
+
+ setOpenRejectDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default RejectForm;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/RestoreForm/RestoreContent.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/RestoreForm/RestoreContent.jsx
new file mode 100644
index 0000000..b412aa0
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/RestoreForm/RestoreContent.jsx
@@ -0,0 +1,42 @@
+import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
+import React, { useState } from "react";
+import useRequest from "@/lib/hooks/useRequest";
+import { RESTORE_BY_SUPERVISOR } from "@/core/utils/routes";
+
+const RestoreContent = ({ rowId, mutate, setOpenRestoreDialog }) => {
+ const [submitting, setSubmitting] = useState(false);
+ const requestServer = useRequest({ notification: { success: true } });
+ const handleClick = () => {
+ setSubmitting(true);
+ requestServer(`${RESTORE_BY_SUPERVISOR}/${rowId}`, "post")
+ .then(() => {
+ mutate();
+ setOpenRestoreDialog(false);
+ setSubmitting(false);
+ })
+ .catch(() => {
+ setSubmitting(false);
+ });
+ };
+ return (
+ <>
+
+
+
+ آیا از بازگردانی فعالیت اطمینان دارید؟
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default RestoreContent;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/RestoreForm/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/RestoreForm/index.jsx
new file mode 100644
index 0000000..2632c20
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/RestoreForm/index.jsx
@@ -0,0 +1,32 @@
+import React, { useState } from "react";
+import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
+import ReplyIcon from "@mui/icons-material/Reply";
+import RestoreContent from "./RestoreContent";
+const RestoreForm = ({ rowId, mutate }) => {
+ const [openRestoreDialog, setOpenRestoreDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenRestoreDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default RestoreForm;
diff --git a/src/components/dashboard/roadItems/supervisor/RowActions/index.jsx b/src/components/dashboard/roadItems/supervisor/RowActions/index.jsx
new file mode 100644
index 0000000..b18a1da
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/RowActions/index.jsx
@@ -0,0 +1,27 @@
+import { Box } from "@mui/material";
+import DeleteForm from "./DeleteForm";
+import RejectForm from "./RejectForm";
+import ConfirmForm from "./ConfirmForm";
+import RestoreForm from "./RestoreForm";
+import { usePermissions } from "@/lib/hooks/usePermissions";
+
+const RowActions = ({ row, mutate }) => {
+ const { data: userPermissions } = usePermissions();
+ const hasDeletePermission = userPermissions.includes("delete-road-item");
+ const hasRestorePermission = userPermissions.includes("restore-road-item");
+ return (
+
+ {row.original.status === 0 && row.original.can_supervise === 1 && (
+
+ )}
+ {row.original.status === 0 && row.original.can_supervise === 1 && (
+
+ )}
+ {hasRestorePermission && row.original.status !== 0 && (
+
+ )}
+ {hasDeletePermission && }
+
+ );
+};
+export default RowActions;
diff --git a/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx b/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx
new file mode 100644
index 0000000..1166ed6
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx
@@ -0,0 +1,239 @@
+import { useMemo } from "react";
+import { Box, Stack, Typography } from "@mui/material";
+import DataTableWithAuth from "@/core/components/DataTableWithAuth";
+import Toolbar from "./Toolbar";
+import { GET_ROAD_ITEMS_SUPERVISOR_LIST } from "@/core/utils/routes";
+import moment from "jalali-moment";
+import RowActions from "./RowActions";
+import LocationForm from "./RowActions/LocationForm";
+import ImageDialog from "./RowActions/ImageForm";
+import RahdaranDialog from "./RowActions/RahdaranForm";
+
+const SupervisorList = () => {
+ const columns = useMemo(
+ () => [
+ {
+ accessorKey: "id",
+ header: "کد یکتا", // Unique Code
+ id: "id",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ sortDescFirst: false,
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "province_fa",
+ header: "استان", // Province
+ id: "province_fa",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "edarat_name",
+ header: "اداره", // Office
+ id: "edarat_name",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "item_fa",
+ header: "آیتم فعالیت",
+ id: "item_fa",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "sub_item_fa",
+ header: "موضوع مشاهده شده", // Observed Subject
+ id: "sub_item_fa",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "value",
+ header: "مقدار", // Value
+ id: "value",
+ enableColumnFilter: false,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ Cell: ({ renderedCellValue, row }) => {
+ return (
+
+ {row.original.sub_item_data}
+ {`(${row.original.unit_fa})`}
+
+ );
+ },
+ },
+ {
+ accessorKey: "files",
+ header: "تصاویر", // Images
+ id: "files",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "array",
+ muiTableBodyCellProps: {
+ sx: {
+ borderLeft: "1px solid #e1e1e1",
+ py: 0,
+ "&:first-of-type": {
+ borderLeft: "unset",
+ },
+ },
+ },
+ Cell: ({ renderedCellValue }) => {
+ if (renderedCellValue.length > 0) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ بدون تصویر
+
+ );
+ },
+ },
+ {
+ accessorKey: "location",
+ header: "موقعیت", // Location
+ id: "location",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "array",
+ muiTableBodyCellProps: {
+ sx: {
+ borderLeft: "1px solid #e1e1e1",
+ py: 0,
+ "&:first-of-type": {
+ borderLeft: "unset",
+ },
+ },
+ },
+ Cell: ({ renderedCellValue, row }) => {
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "cmms_machine_code",
+ header: "کد خودرو", // Car ID
+ id: "cmms_machine_code",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ enableSorting: false,
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "rahdaran",
+ header: "راهداران",
+ id: "rahdaran",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "array",
+ muiTableBodyCellProps: {
+ sx: {
+ borderLeft: "1px solid #e1e1e1",
+ py: 0,
+ "&:first-of-type": {
+ borderLeft: "unset",
+ },
+ },
+ },
+ Cell: ({ renderedCellValue }) => {
+ if (renderedCellValue) {
+ return (
+
+
+
+ );
+ }
+ return بدون شخص;
+ },
+ },
+ {
+ accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
+ header: "تاریخ ثبت", // Register Date
+ id: "created_at",
+ enableColumnFilter: true,
+ datatype: "date",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
+ header: "تاریخ فعالیت", // Start Date
+ id: "activity_date_time",
+ enableColumnFilter: true,
+ datatype: "date",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "status_fa",
+ header: "وضعیت", // Status
+ id: "status_fa",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ sortDescFirst: true,
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ {
+ accessorKey: "supervisor_description",
+ header: "توضیحات", // Description
+ id: "supervisor_description",
+ enableColumnFilter: false,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ },
+ ],
+ []
+ );
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default SupervisorList;
diff --git a/src/components/dashboard/roadItems/supervisor/Toolbar.jsx b/src/components/dashboard/roadItems/supervisor/Toolbar.jsx
new file mode 100644
index 0000000..32c4395
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/Toolbar.jsx
@@ -0,0 +1,10 @@
+import PrintExcel from "./ExcelPrint";
+
+const Toolbar = ({ table, filterData }) => {
+ return (
+ <>
+
+ >
+ );
+};
+export default Toolbar;
diff --git a/src/components/dashboard/roadItems/supervisor/index.jsx b/src/components/dashboard/roadItems/supervisor/index.jsx
new file mode 100644
index 0000000..3fa490b
--- /dev/null
+++ b/src/components/dashboard/roadItems/supervisor/index.jsx
@@ -0,0 +1,14 @@
+"use client";
+import PageTitle from "@/core/components/PageTitle";
+import { Stack } from "@mui/material";
+import SupervisorList from "./SupervisorList";
+
+const SupervisorPage = () => {
+ return (
+
+
+
+
+ );
+};
+export default SupervisorPage;
diff --git a/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx b/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx
index 8fdc753..54df4da 100644
--- a/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx
+++ b/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx
@@ -4,7 +4,7 @@ import moment from "jalali-moment";
import FileSaver from "file-saver";
import DescriptionIcon from "@mui/icons-material/Description";
import useRequest from "@/lib/hooks/useRequest";
-import { EXPORT_OPERATOR_LIST } from "@/core/utils/routes";
+import { EXPORT_ROAD_PATROL_OPERATOR_LIST } from "@/core/utils/routes";
const PrintExcel = ({ table, filterData }) => {
const [loading, setLoading] = useState(false);
@@ -28,7 +28,7 @@ const PrintExcel = ({ table, filterData }) => {
const clickHandler = () => {
setLoading(true);
- requestServer(`${EXPORT_OPERATOR_LIST}?${filterParams}`, "get", {
+ requestServer(`${EXPORT_ROAD_PATROL_OPERATOR_LIST}?${filterParams}`, "get", {
requestOptions: { responseType: "blob" },
})
.then((response) => {
diff --git a/src/components/dashboard/roadPatrols/operator/OperatorList.jsx b/src/components/dashboard/roadPatrols/operator/OperatorList.jsx
index 1d7118a..05f083b 100644
--- a/src/components/dashboard/roadPatrols/operator/OperatorList.jsx
+++ b/src/components/dashboard/roadPatrols/operator/OperatorList.jsx
@@ -3,7 +3,7 @@ import { useMemo } from "react";
import { Box, Typography } from "@mui/material";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import Toolbar from "./Toolbar";
-import { GET_OPERATOR_LIST } from "@/core/utils/routes";
+import { GET_ROAD_PATROL_OPERATOR_LIST } from "@/core/utils/routes";
import moment from "jalali-moment";
import RowActions from "./RowActions";
@@ -99,7 +99,7 @@ const OperatorList = () => {
table_title={"عملیات"}
need_filter={true}
columns={columns}
- table_url={GET_OPERATOR_LIST}
+ table_url={GET_ROAD_PATROL_OPERATOR_LIST}
page_name={"operator"}
table_name={"operatorList"}
TableToolbar={Toolbar}
diff --git a/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx b/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx
index 49c05eb..e5ba95a 100644
--- a/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx
+++ b/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx
@@ -4,7 +4,7 @@ import moment from "jalali-moment";
import FileSaver from "file-saver";
import DescriptionIcon from "@mui/icons-material/Description";
import useRequest from "@/lib/hooks/useRequest";
-import { EXPORT_SUPERVISOR_LIST } from "@/core/utils/routes";
+import { EXPORT_ROAD_PATROL_SUPERVISOR_LIST } from "@/core/utils/routes";
const PrintExcel = ({ table, filterData }) => {
const [loading, setLoading] = useState(false);
@@ -28,7 +28,7 @@ const PrintExcel = ({ table, filterData }) => {
const clickHandler = () => {
setLoading(true);
- requestServer(`${EXPORT_SUPERVISOR_LIST}?${filterParams}`, "get", {
+ requestServer(`${EXPORT_ROAD_PATROL_SUPERVISOR_LIST}?${filterParams}`, "get", {
requestOptions: { responseType: "blob" },
})
.then((response) => {
diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx
index 46cad99..a7218df 100644
--- a/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx
+++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx
@@ -1,19 +1,7 @@
import React, { useState } from "react";
-import {
- Tooltip,
- IconButton,
- Dialog,
- DialogTitle,
- DialogContent,
- DialogActions,
- Button,
- Box,
- Typography,
-} from "@mui/material";
+import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
-import CloseIcon from "@mui/icons-material/Close";
-import DeleteContent from "@/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/DeleteContent";
-
+import DeleteContent from "./DeleteContent";
const DeleteForm = ({ rowId, mutate }) => {
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
@@ -41,5 +29,4 @@ const DeleteForm = ({ rowId, mutate }) => {
>
);
};
-
export default DeleteForm;
diff --git a/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx b/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx
index 6768a91..a5e15f4 100644
--- a/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx
+++ b/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx
@@ -3,7 +3,7 @@ import { useMemo } from "react";
import { Box, Typography } from "@mui/material";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import Toolbar from "./Toolbar";
-import { GET_SUPERVISOR_LIST } from "@/core/utils/routes";
+import { GET_ROAD_PATROL_SUPERVISOR_LIST } from "@/core/utils/routes";
import moment from "jalali-moment";
import RowActions from "./RowActions";
import useProvinces from "@/lib/hooks/useProvince";
@@ -140,7 +140,7 @@ const OperatorList = () => {
table_title={"ارزیابی"}
need_filter={true}
columns={columns}
- table_url={GET_SUPERVISOR_LIST}
+ table_url={GET_ROAD_PATROL_SUPERVISOR_LIST}
page_name={"supervisor"}
table_name={"supervisorList"}
TableToolbar={Toolbar}
diff --git a/src/core/components/CarCode.jsx b/src/core/components/CarCode.jsx
index 503a826..32a4c20 100644
--- a/src/core/components/CarCode.jsx
+++ b/src/core/components/CarCode.jsx
@@ -1,28 +1,26 @@
-"use client";
-
-import {Autocomplete, CircularProgress, TextField} from "@mui/material";
-import {useEffect, useState} from "react";
+import { Autocomplete, CircularProgress, TextField } from "@mui/material";
+import { useEffect, useState } from "react";
import useRequest from "@/lib/hooks/useRequest";
-import {debounce} from "@mui/material/utils";
+import { debounce } from "@mui/material/utils";
+import { GET_CAR_LIST_SEARCH } from "@/core/utils/routes";
-const CarCode = ({carCode, setCarCode}) => {
- const [inputValue, setInputValue] = useState('');
+const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error }) => {
+ const [inputValue, setInputValue] = useState(inputValueDefault);
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const requestServer = useRequest();
-
const fetchCarCodes = (inputValue, controller) => {
const debouncer = debounce((query) => {
- if (query.length < 2) {
+ if (!query || query?.length < 3) {
setOptions([]);
return;
}
setLoading(true);
- requestServer(`car-code/api?${query}`, "get", {
- requestOptions: {signal: controller.signal}
+ requestServer(`${GET_CAR_LIST_SEARCH}?machine_code=${query}`, "get", {
+ requestOptions: { signal: controller.signal },
})
.then((response) => {
- setOptions(response || []);
+ setOptions(response.data.data || []);
})
.catch(() => {
setOptions([]);
@@ -30,9 +28,9 @@ const CarCode = ({carCode, setCarCode}) => {
.finally(() => {
setLoading(false);
});
- }, 500)
- debouncer(inputValue)
- }
+ }, 500);
+ debouncer(inputValue);
+ };
useEffect(() => {
const controller = new AbortController();
@@ -45,34 +43,35 @@ const CarCode = ({carCode, setCarCode}) => {
value={carCode}
size="small"
onChange={(event, newValue) => {
- setCarCode(newValue);
+ setCarCode(newValue || null); // Set an empty string if no value is selected
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
- setInputValue(newInputValue);
+ setInputValue(newInputValue || ""); // Prevent inputValue from being null or undefined
}}
options={options}
- getOptionLabel={(option) =>
- typeof option === 'string' ? option : option.name || option.code
- }
+ getOptionLabel={(option) => (typeof option === "string" ? option : option.machine_code)}
+ isOptionEqualToValue={(option, value) => {
+ if (!value) return false; // Handle null/undefined values
+ if (value === "") return option.machine_code === ""; // Handle empty string case
+ return option.machine_code === (value?.machine_code || value);
+ }}
loading={loading}
loadingText="درحال جستجوی کد خودرو"
- noOptionsText={
- inputValue.length < 2
- ? "حداقل دو رقم از کد خودرو را وارد کنید"
- : "کد خودرویی یافت نشد"
- }
- sx={{width: 300}}
+ noOptionsText={inputValue?.length < 3 ? "حداقل سه رقم از کد خودرو را وارد کنید" : "کد خودرویی یافت نشد"}
+ fullWidth
renderInput={(params) => (
- {loading ? : null}
+ {loading ? : null}
{params.InputProps.endAdornment}
>
),
@@ -82,5 +81,4 @@ const CarCode = ({carCode, setCarCode}) => {
/>
);
};
-
-export default CarCode;
\ No newline at end of file
+export default CarCode;
diff --git a/src/core/components/DataTable/filter/FilterBody.jsx b/src/core/components/DataTable/filter/FilterBody.jsx
index 2bc6d9c..d0eb7cf 100644
--- a/src/core/components/DataTable/filter/FilterBody.jsx
+++ b/src/core/components/DataTable/filter/FilterBody.jsx
@@ -8,6 +8,7 @@ import { Box, Button, Drawer, styled } from "@mui/material";
import { useEffect, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import * as Yup from "yup";
+import FilterBodyController from "./FilterBodyController";
const ScrollBox = styled(Box)({
flexGrow: 1,
@@ -95,27 +96,12 @@ function FilterBody({ columns, drawerState, setDrawerState }) {
{columns.map(
(column) =>
column.enableColumnFilter && (
- {
- let dependencyField;
- if (column.dependencyId) {
- dependencyField = useWatch({ control, name: column.dependencyId });
- }
- return (
- reset()}
- errors={errors}
- />
- );
- }}
+ errors={errors}
+ reset={reset}
/>
)
)}
diff --git a/src/core/components/DataTable/filter/FilterBodyController.jsx b/src/core/components/DataTable/filter/FilterBodyController.jsx
new file mode 100644
index 0000000..df3ba62
--- /dev/null
+++ b/src/core/components/DataTable/filter/FilterBodyController.jsx
@@ -0,0 +1,25 @@
+import { Controller, useWatch } from "react-hook-form";
+import FilterBodyField from "./FilterBodyField";
+
+const FilterBodyController = ({ column, control, reset, errors }) => {
+ const dependencyField = useWatch({ control, name: column?.dependencyId });
+
+ return (
+ (
+ reset()}
+ errors={errors}
+ />
+ )}
+ />
+ );
+};
+export default FilterBodyController;
diff --git a/src/core/components/DataTable/reset/ResetStorage.jsx b/src/core/components/DataTable/reset/ResetStorage.jsx
index 9f6abd5..8e83d3f 100644
--- a/src/core/components/DataTable/reset/ResetStorage.jsx
+++ b/src/core/components/DataTable/reset/ResetStorage.jsx
@@ -1,11 +1,12 @@
"use client";
-
-import { IconButton, Tooltip } from "@mui/material";
+import { IconButton, Tooltip, Badge } from "@mui/material";
import AutorenewIcon from "@mui/icons-material/Autorenew";
import useTableSetting from "@/lib/hooks/useTableSetting";
+import useDataTable from "@/lib/hooks/useDataTable";
function ResetStorage({ user_id, page_name, table_name }) {
const { resetAction } = useTableSetting();
+ const { isDirty } = useDataTable();
const reset = () => {
resetAction(user_id, page_name, table_name);
};
@@ -13,7 +14,20 @@ function ResetStorage({ user_id, page_name, table_name }) {
return (
-
+ {isDirty ? (
+
+
+
+ ) : (
+
+ )}
);
diff --git a/src/core/components/MapInfoOneMarker.jsx b/src/core/components/MapInfoOneMarker.jsx
new file mode 100644
index 0000000..7132d5d
--- /dev/null
+++ b/src/core/components/MapInfoOneMarker.jsx
@@ -0,0 +1,137 @@
+import React, { useEffect, useRef, useState } from "react";
+import { Marker, useMapEvents } from "react-leaflet";
+import "leaflet/dist/leaflet.css";
+import L from "leaflet";
+import { Box, Button, Typography } from "@mui/material";
+import dynamic from "next/dynamic";
+import MapLoading from "@/core/components/MapLayer/Loading";
+import HereIcon from "@/assets/images/examine_marker_active.png";
+
+const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
+ loading: () => ,
+ ssr: false,
+});
+
+const createCustomIcon = (size, iconUrl, labelText) => {
+ if (labelText) {
+ return L.divIcon({
+ html: `
+
+

+
+ ${labelText}
+
+
+ `,
+ iconSize: size,
+ iconAnchor: [size[0] / 2, size[1]],
+ popupAnchor: [0, -size[1]],
+ });
+ }
+
+ return L.icon({
+ iconUrl: iconUrl,
+ iconSize: size,
+ iconAnchor: [size[0] / 2, size[1]],
+ popupAnchor: [0, -size[1]],
+ });
+};
+
+const MapInteraction = ({ setValue, startLat, startLng }) => {
+ const [isMarkerLocked, setIsMarkerLocked] = useState(!!(startLat && startLng)); // وضعیت قفل مارکر
+ const [markerPosition, setMarkerPosition] = useState(
+ startLat && startLng ? { lat: startLat, lng: startLng } : null
+ );
+ const markerRef = useRef();
+
+ const map = useMapEvents({
+ move(e) {
+ if (!isMarkerLocked && markerRef.current) {
+ markerRef.current.setLatLng(e.target.getCenter());
+ }
+ },
+ zoom(e) {
+ if (!isMarkerLocked && markerRef.current) {
+ markerRef.current.setLatLng(e.target.getCenter());
+ }
+ },
+ });
+
+ useEffect(() => {
+ if (startLat && startLng) {
+ const position = { lat: startLat, lng: startLng };
+ map.setView(position, 15);
+ }
+ }, [startLat, startLng, map]);
+
+ const handleMarkerClick = () => {
+ if (!isMarkerLocked) {
+ const center = map.getCenter();
+ setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
+ setMarkerPosition({ lat: center.lat, lng: center.lng }); // بهروزرسانی موقعیت مارکر
+ setIsMarkerLocked(true);
+ }
+ };
+
+ const handleUnlockMarker = () => {
+ setValue("start_point", null); // حذف مقدار قبلی
+ setIsMarkerLocked(false); // باز کردن قفل مارکر
+ setMarkerPosition(null); // تنظیم مارکر به مرکز نقشه
+ };
+
+ return (
+ <>
+
+ {isMarkerLocked && (
+
+
+
+ )}
+ >
+ );
+};
+
+const MapInfoOneMarker = ({ setValue, errors, StartPoint = null }) => {
+ return (
+
+
+
+
+
+
+
+ {errors.start_point && (
+
+ {errors.start_point.message}
+
+ )}
+
+
+ );
+};
+
+export default MapInfoOneMarker;
diff --git a/src/core/components/MapInfoTwoMarker.jsx b/src/core/components/MapInfoTwoMarker.jsx
new file mode 100644
index 0000000..f4bd24b
--- /dev/null
+++ b/src/core/components/MapInfoTwoMarker.jsx
@@ -0,0 +1,191 @@
+import React, { useEffect, useRef, useState } from "react";
+import { Marker, useMapEvents } from "react-leaflet";
+import "leaflet/dist/leaflet.css";
+import L from "leaflet";
+import { Box, Button, Typography } from "@mui/material";
+import dynamic from "next/dynamic";
+import MapLoading from "@/core/components/MapLayer/Loading";
+import EndIcon from "@/assets/images/examine_marker_active.png";
+import StartIcon from "@/assets/images/examine_marker.png";
+
+const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
+ loading: () => ,
+ ssr: false,
+});
+const createCustomIcon = (size, iconUrl, labelText) => {
+ if (labelText) {
+ return L.divIcon({
+ html: `
+
+

+
+ ${labelText}
+
+
+ `,
+ iconSize: size,
+ iconAnchor: [size[0] / 2, size[1]],
+ popupAnchor: [0, -size[1]],
+ });
+ }
+
+ return L.icon({
+ iconUrl: iconUrl,
+ iconSize: size,
+ iconAnchor: [size[0] / 2, size[1]],
+ popupAnchor: [0, -size[1]],
+ });
+};
+
+const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
+ const [isStartLocked, setIsStartLocked] = useState(!!(startLat && startLng)); // وضعیت قفل نقطه آغاز
+ const [isEndLocked, setIsEndLocked] = useState(!!(endLat && endLng)); // وضعیت قفل نقطه پایان
+ const [startPosition, setStartPosition] = useState(startLat && startLng ? { lat: startLat, lng: startLng } : null);
+ const [endPosition, setEndPosition] = useState(endLat && endLng ? { lat: endLat, lng: endLng } : null);
+ const startRef = useRef();
+ const endRef = useRef();
+
+ const map = useMapEvents({
+ move(e) {
+ if (!isStartLocked && startRef.current) {
+ startRef.current.setLatLng(e.target.getCenter());
+ } else if (isStartLocked && !isEndLocked && endRef.current) {
+ endRef.current.setLatLng(e.target.getCenter());
+ }
+ },
+ zoom(e) {
+ if (!isStartLocked && startRef.current) {
+ startRef.current.setLatLng(e.target.getCenter());
+ } else if (isStartLocked && !isEndLocked && endRef.current) {
+ endRef.current.setLatLng(e.target.getCenter());
+ }
+ },
+ });
+
+ useEffect(() => {
+ if (startLat && endLng) {
+ map.fitBounds(
+ [
+ { lat: startLat, lng: startLng },
+ { lat: endLat, lng: endLng },
+ ],
+ { paddingTopLeft: [16, 16], paddingBottomRight: [16, 130] }
+ );
+ }
+ }, [startLat, map, endLng]);
+
+ const handleUnlockStart = () => {
+ setIsStartLocked(false);
+ setStartPosition(null);
+ setValue("start_point", null);
+ };
+
+ const handleUnlockEnd = () => {
+ setIsEndLocked(false);
+ setIsStartLocked(false);
+ setEndPosition(null);
+ setStartPosition(null);
+ setValue("end_point", "");
+ setValue("start_point", null);
+ };
+
+ return (
+ <>
+ {
+ if (!isStartLocked) {
+ const center = map.getCenter();
+ setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
+ setStartPosition({ lat: center.lat, lng: center.lng });
+ setIsStartLocked(true);
+ }
+ },
+ }}
+ />
+ {isStartLocked && !isEndLocked && (
+
+
+
+ )}
+ {isStartLocked && (
+ {
+ if (!isEndLocked) {
+ const center = map.getCenter();
+ setValue("end_point", { lat: center.lat.toString(), lng: center.lng.toString() });
+ setEndPosition({ lat: center.lat, lng: center.lng });
+ setIsEndLocked(true);
+ }
+ },
+ }}
+ />
+ )}
+ {isEndLocked && (
+
+
+
+ )}
+ >
+ );
+};
+
+const MapInfoTwoMarker = ({ setValue, errors, StartPoint = null, EndPoint = null }) => {
+ return (
+
+
+
+
+
+
+
+ {errors.start_point && (
+
+ {errors.start_point.message}
+
+ )}
+ {errors.end_point && (
+
+ {errors.end_point.message}
+
+ )}
+
+
+ );
+};
+export default MapInfoTwoMarker;
diff --git a/src/core/components/MuiDatePicker.jsx b/src/core/components/MuiDatePicker.jsx
index 1be8fdb..78ffe58 100644
--- a/src/core/components/MuiDatePicker.jsx
+++ b/src/core/components/MuiDatePicker.jsx
@@ -1,53 +1,70 @@
-import { LocalizationProvider, MobileDateTimePicker } from "@mui/x-date-pickers";
+import React from "react";
+import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
-import moment from "jalali-moment";
import { faIR } from "@mui/x-date-pickers/locales";
-import { Box, IconButton, FormControl, FormHelperText, InputAdornment, Stack } from "@mui/material";
+import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
+import moment from "jalali-moment";
-export default function PickerWithButtonField({ formik, name, value, error, touched, disabled }) {
+function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
return (
-
-
-
- {
- const formattedDate = moment(newValue).locale("en").format("YYYY-MM-DD HH:mm");
- formik.setFieldValue(name, formattedDate);
- }}
- slotProps={{
- textField: {
- placeholder:
- name === "start_date" ? "تاریخ شروع را وارد کنید" : "تاریخ اتمام را وارد کنید",
- variant: "outlined",
- disabled,
- fullWidth: true,
- error: !!error,
- helperText: touched && error,
- size: "small", // Ensure that the TextField is small
- InputProps: {
- endAdornment: (
-
- formik.setFieldValue(name, null)}
- >
-
-
-
- ),
- },
+
+
+ {
+ const date = new Date(value);
+ const formattedDate = moment(date).locale("en").format("YYYY-MM-DD");
+ setFieldValue(name, formattedDate);
+ }}
+ minDate={minDate ? new Date(minDate) : null}
+ maxDate={maxDate ? new Date(maxDate) : null}
+ slotProps={{
+ textField: {
+ error: error,
+ size: "small",
+ placeholder: placeholder,
+ InputProps: {
+ endAdornment: (
+
+ {
+ event.stopPropagation();
+ setFieldValue(name, "");
+ }}
+ sx={{
+ color: "#bfbfbf",
+ "&:hover": {
+ backgroundColor: "rgba(189, 189, 189, 0.1)",
+ color: "#363434",
+ },
+ }}
+ >
+
+
+
+ ),
},
- }}
- />
-
-
-
+ },
+ }}
+ />
+
+ {helperText ? helperText : ""}
+
+
+
);
}
+
+export default MuiDatePicker;
diff --git a/src/core/components/MuiTimePicker.jsx b/src/core/components/MuiTimePicker.jsx
new file mode 100644
index 0000000..35532fd
--- /dev/null
+++ b/src/core/components/MuiTimePicker.jsx
@@ -0,0 +1,75 @@
+import React from "react";
+import { LocalizationProvider, MobileTimePicker } from "@mui/x-date-pickers";
+import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
+import { faIR } from "@mui/x-date-pickers/locales";
+import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
+import ClearIcon from "@mui/icons-material/Clear";
+import { parseISO, format } from "date-fns";
+
+function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperText, placeholder, error }) {
+ // Ensure value, minTime, maxTime are Date objects
+ const parsedValue = value ? (typeof value === "string" ? parseISO(value) : value) : null;
+ const parsedMinTime = minTime ? (typeof minTime === "string" ? parseISO(minTime) : minTime) : null;
+ const parsedMaxTime = maxTime && (typeof maxTime === "string" ? parseISO(maxTime) : null);
+
+ return (
+
+
+ {
+ const formattedTime = newValue ? format(new Date(newValue), "HH:mm") : "";
+ setFieldValue(name, formattedTime);
+ }}
+ minTime={parsedMinTime}
+ maxTime={parsedMaxTime}
+ slotProps={{
+ textField: {
+ placeholder: placeholder,
+ size: "small",
+ error: error,
+ InputProps: {
+ endAdornment: (
+
+ {
+ event.stopPropagation();
+ setFieldValue(name, "");
+ }}
+ sx={{
+ color: "#bfbfbf",
+ "&:hover": {
+ backgroundColor: "rgba(189, 189, 189, 0.1)",
+ color: "#363434",
+ },
+ }}
+ >
+
+
+
+ ),
+ },
+ },
+ }}
+ />
+
+ {helperText ? helperText : ""}
+
+
+
+ );
+}
+
+export default MuiTimePicker;
diff --git a/src/core/components/RahdarCode.jsx b/src/core/components/RahdarCode.jsx
new file mode 100644
index 0000000..9dfeeb7
--- /dev/null
+++ b/src/core/components/RahdarCode.jsx
@@ -0,0 +1,90 @@
+"use client";
+import { Autocomplete, CircularProgress, TextField } from "@mui/material";
+import { useEffect, useState } from "react";
+import useRequest from "@/lib/hooks/useRequest";
+import { debounce } from "@mui/material/utils";
+import { GET_RAHDARANS_LIST_SEARCH } from "@/core/utils/routes";
+
+const RahdarCode = ({ rahdarsCode, setRahdarsCode, error }) => {
+ const [inputValue, setInputValue] = useState(""); // مدیریت مقدار تایپشده
+ const [options, setOptions] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const requestServer = useRequest();
+
+ const fetchRahdarCodes = (inputValue, controller) => {
+ const debouncer = debounce((query) => {
+ if ((query || "").length < 3) {
+ setOptions([]);
+ return;
+ }
+ setLoading(true);
+ requestServer(`${GET_RAHDARANS_LIST_SEARCH}?code=${query}`, "get", {
+ requestOptions: { signal: controller.signal },
+ })
+ .then((response) => {
+ setOptions(response.data.data || []);
+ })
+ .catch(() => {
+ setOptions([]);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }, 500);
+ debouncer(inputValue);
+ };
+
+ useEffect(() => {
+ const controller = new AbortController();
+ fetchRahdarCodes(inputValue, controller);
+ return () => controller.abort();
+ }, [inputValue]);
+
+ return (
+ {
+ setRahdarsCode(newValue || []); // بهروزرسانی موارد انتخابشده
+ }}
+ inputValue={inputValue} // مقدار تایپشده
+ onInputChange={(event, newInputValue) => {
+ setInputValue(newInputValue || ""); // بهروزرسانی مقدار تایپشده
+ }}
+ options={options} // گزینههای موجود
+ getOptionLabel={(option) => (typeof option === "string" ? option : `${option.code} - ${option.name}`)}
+ isOptionEqualToValue={(option, value) => {
+ if (!value) return false;
+ if (value === "") return option.code === "";
+ return option.code === (value?.code || value);
+ }}
+ loading={loading}
+ loadingText="درحال جستجوی کد راهدار"
+ noOptionsText={
+ (inputValue || "").length < 3 ? "حداقل سه رقم از کد راهدار را وارد کنید" : "کد راهداری یافت نشد"
+ }
+ fullWidth
+ renderInput={(params) => (
+
+ {loading ? : null}
+ {params.InputProps.endAdornment}
+ >
+ ),
+ }}
+ />
+ )}
+ />
+ );
+};
+
+export default RahdarCode;
diff --git a/src/core/components/ShowLocationMarker.jsx b/src/core/components/ShowLocationMarker.jsx
new file mode 100644
index 0000000..e30a4e4
--- /dev/null
+++ b/src/core/components/ShowLocationMarker.jsx
@@ -0,0 +1,181 @@
+"use client";
+
+import { Marker, useMap } from "react-leaflet";
+import { useEffect } from "react";
+import L from "leaflet";
+import AzmayeshActiveIcon from "@/assets/images/examine_marker_active.png";
+import { Box, FormControl, InputAdornment, InputLabel, OutlinedInput, Stack, useMediaQuery } from "@mui/material";
+import VerifiedIcon from "@mui/icons-material/Verified";
+import { useTheme } from "@emotion/react";
+
+const ShowLocationMarker = ({ start_lat, start_lng, end_lat, end_lng }) => {
+ const map = useMap();
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
+
+ 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 (start_lat && end_lng) {
+ map.fitBounds(
+ [
+ { lat: start_lat, lng: start_lng },
+ { lat: end_lat, lng: end_lng },
+ ],
+ { paddingTopLeft: [16, 16], paddingBottomRight: [16, 130] }
+ );
+ } else if (start_lat) {
+ map.setView({ lat: start_lat, lng: start_lng }, 15);
+ }
+ }, [start_lat, start_lng, map, end_lng, end_lat]);
+
+ return (
+ <>
+ {start_lat && start_lng && (
+
+ )}
+ {end_lat && end_lng && (
+
+ )}
+
+
+ {start_lat && start_lng && (
+
+
+
+ {end_lat && end_lng ? "طول جغرافیایی شروع" : "طول جغرافیایی"}
+
+
+
+
+ }
+ label={end_lat && end_lng ? "طول جغرافیایی شروع" : "طول جغرافیایی"}
+ />
+
+
+
+ {end_lat && end_lng ? "عرض جغرافیایی شروع" : "عرض جغرافیایی"}
+
+
+
+
+ }
+ label={end_lat && end_lng ? "عرض جغرافیایی شروع" : "عرض جغرافیایی"}
+ />
+
+
+ )}
+ {end_lat && end_lng && (
+
+
+
+ طول جغرافیایی پایان
+
+
+
+
+ }
+ label="طول جغرافیایی پایان"
+ />
+
+
+
+ عرض جغرافیایی پایان
+
+
+
+
+ }
+ label="عرض جغرافیایی پایان"
+ />
+
+
+ )}
+
+
+ >
+ );
+};
+
+export default ShowLocationMarker;
diff --git a/src/core/components/Toasts/error.jsx b/src/core/components/Toasts/error.jsx
index e93138b..dbc25e8 100644
--- a/src/core/components/Toasts/error.jsx
+++ b/src/core/components/Toasts/error.jsx
@@ -61,6 +61,35 @@ export const errorUnauthorizedToast = (toastContainer) =>
draggable: true,
}
);
+export const errorAccessDeniedToast = (message, toastContainer) =>
+ toast.error(
+ () => (
+
+
+
+
+ {message || "Access Denied"}
+
+
+
+ ),
+ {
+ icon: false,
+ containerId: toastContainer,
+ autoClose: 3000,
+ hideProgressBar: true,
+ pauseOnHover: true,
+ closeOnClick: false,
+ draggable: true,
+ }
+ );
export const errorLogicToast = (message, toastContainer) =>
toast.error(
diff --git a/src/core/components/UploadSystem.jsx b/src/core/components/UploadSystem.jsx
new file mode 100644
index 0000000..98ac8c3
--- /dev/null
+++ b/src/core/components/UploadSystem.jsx
@@ -0,0 +1,94 @@
+import { Box, Button, Paper, Typography } from "@mui/material";
+import AddIcon from "@mui/icons-material/Add";
+import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
+import { useRef } from "react";
+
+const UploadSystem = ({ selectedImage, handleUploadChange, imageSize, fileType, showAddIcon }) => {
+ const fileInputRef = useRef(null);
+
+ const handleClick = () => {
+ fileInputRef.current.click();
+ };
+
+ return (
+
+ {showAddIcon ? (
+
+
+
+ فرمت قابل قبول : png, jpg
+
+ حداکثر 3Mb
+
+
+ ) : (
+ <>
+ {fileType && fileType.startsWith("image/") && (
+
+
+
+ )}
+ >
+ )}
+
+
+ );
+};
+export default UploadSystem;
diff --git a/src/core/utils/errorResponse.js b/src/core/utils/errorResponse.js
index c71bfed..5de1e77 100644
--- a/src/core/utils/errorResponse.js
+++ b/src/core/utils/errorResponse.js
@@ -1,6 +1,7 @@
"use client";
import { toast } from "react-toastify";
import {
+ errorAccessDeniedToast,
errorClientToast,
errorLogicToast,
errorServerToast,
@@ -22,6 +23,9 @@ const errorClient = (response, notification, toastContainer, logout) => {
logout();
if (notification) errorUnauthorizedToast(toastContainer);
break;
+ case 403:
+ if (notification) errorAccessDeniedToast(response.data.message, toastContainer);
+ break;
case 422:
if ("type" in response.data) {
if (Array.isArray(response.data.message)) {
diff --git a/src/core/utils/pageMenu.js b/src/core/utils/pageMenu.js
index 6000f64..798d28b 100644
--- a/src/core/utils/pageMenu.js
+++ b/src/core/utils/pageMenu.js
@@ -108,7 +108,7 @@ export const pageMenu = [
id: "roadItemManagmentSupervisorCartable",
label: "کارتابل",
type: "page",
- route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/supervisor/cartable",
+ route: "/dashboard/road-items/supervisor",
permissions: [
"show-road-item-supervise-cartable",
"show-road-item-supervise-cartable-province",
@@ -135,7 +135,7 @@ export const pageMenu = [
id: "roadItemManagmentOparationCartable",
label: "کارتابل",
type: "page",
- route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/operator/cartable",
+ route: "/dashboard/road-items/operator",
permissions: ["create-road-item"],
},
],
diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js
index db24fd6..9548f91 100644
--- a/src/core/utils/routes.js
+++ b/src/core/utils/routes.js
@@ -27,7 +27,25 @@ export const DELETE_SAMPLE_LIST = api + "/api/v3/azmayesh_samples/delete";
export const DELETE_AZMAYESH_TYPE_LIST = api + "/api/v3/azmayesh_types/delete";
export const ADD_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/store";
export const UPDATE_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/update";
-export const GET_OPERATOR_LIST = "/v3/api/fake-operator-list";
-export const EXPORT_OPERATOR_LIST = "https://rms.rmto.ir/v2/road_patrols/operator/cartable/report";
-export const GET_SUPERVISOR_LIST = "/v3/api/fake-supervisor-list";
-export const EXPORT_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
+
+//road patrol
+export const GET_ROAD_PATROL_OPERATOR_LIST = "/v3/api/fake-road-patrol/operator";
+export const EXPORT_ROAD_PATROL_OPERATOR_LIST = "https://rms.rmto.ir/v2/road_patrols/operator/cartable/report";
+export const GET_ROAD_PATROL_SUPERVISOR_LIST = "/v3/api/fake-road-patrol/supervisor";
+export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
+
+// road items
+export const GET_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_index";
+export const EXPORT_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_report";
+export const GET_ROAD_ITEMS_OPERATOR_LIST = api + "/api/v3/road_items/operator_index";
+export const EXPORT_ROAD_ITEMS_OPERATOR_LIST = api + "/api/v3/road_items/operator_report";
+export const GET_ROAD_ITEMS_ITEM = "https://rms.witel.ir/v2/items";
+export const GET_ROAD_ITEMS_SUB_ITEM = "https://rms.witel.ir/v2/sub_items";
+export const CREATE_ROAD_ITEMS = api + "/api/v3/road_items/store";
+export const UPDATE_ROAD_ITEMS = api + "/api/v3/road_items/update";
+export const VERIFY_BY_SUPERVISOR = api + "/api/v3/road_items/verify_by_supervisor";
+export const REJECT_BY_SUPERVISOR = api + "/api/v3/road_items/verify_by_supervisor";
+export const DELETE_BY_SUPERVISOR = api + "/api/v3/road_items/delete";
+export const RESTORE_BY_SUPERVISOR = api + "/api/v3/road_items/restore";
+export const GET_CAR_LIST_SEARCH = api + "/api/v3/cmms_machines/search";
+export const GET_RAHDARANS_LIST_SEARCH = api + "/api/v3/rahdaran/search";
diff --git a/src/lib/contexts/DataTable.js b/src/lib/contexts/DataTable.js
index dcadd83..ec22020 100644
--- a/src/lib/contexts/DataTable.js
+++ b/src/lib/contexts/DataTable.js
@@ -17,6 +17,16 @@ const DataTableProvider = ({ children, user_id, page_name, table_name, columns,
const [sortData, setSortData] = useState(initialSort || []);
const [initHide, setInitHide] = useState({});
const [hideData, setHideData] = useState({});
+ const [isDirty, setIsDirty] = useState(false);
+
+ useEffect(() => {
+ const hasConflict =
+ JSON.stringify(hideData) !== JSON.stringify(initHide) ||
+ JSON.stringify(sortData) !== JSON.stringify(initSort) ||
+ JSON.stringify(filterData) !== JSON.stringify(initFilter);
+
+ setIsDirty(hasConflict);
+ }, [hideData, sortData, filterData, initHide, initSort, initFilter]);
useEffect(() => {
if (!settingStore) return;
@@ -125,7 +135,9 @@ const DataTableProvider = ({ children, user_id, page_name, table_name, columns,
if (!isInitStates) return ;
return (
-
+
{children}
);
diff --git a/src/lib/hooks/useDataTable.js b/src/lib/hooks/useDataTable.js
index 9bef124..9026caa 100644
--- a/src/lib/hooks/useDataTable.js
+++ b/src/lib/hooks/useDataTable.js
@@ -2,8 +2,9 @@ import { useContext } from "react";
import { DataTableContext } from "@/lib/contexts/DataTable";
const useTableSetting = () => {
- const { filterData, setFilterData, sortData, setSortData, hideData, setHideData } = useContext(DataTableContext);
- return { filterData, setFilterData, sortData, setSortData, hideData, setHideData };
+ const { filterData, setFilterData, sortData, setSortData, hideData, setHideData, isDirty } =
+ useContext(DataTableContext);
+ return { filterData, setFilterData, sortData, setSortData, hideData, setHideData, isDirty };
};
export default useTableSetting;
diff --git a/src/lib/hooks/useRoadItemGetISubtems.js b/src/lib/hooks/useRoadItemGetISubtems.js
new file mode 100644
index 0000000..f315fd3
--- /dev/null
+++ b/src/lib/hooks/useRoadItemGetISubtems.js
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react";
+import { GET_ROAD_ITEMS_SUB_ITEM } from "@/core/utils/routes";
+import useRequest from "@/lib/hooks/useRequest";
+
+const useRoadItemGetItems = (id) => {
+ const requestServer = useRequest();
+ const [subItemsList, setSubItemsList] = useState([]);
+ const [loadingSubItemsList, setLoadingSubItemsList] = useState(true);
+ const [errorSubItemsList, setErrorSubItemsList] = useState(null);
+
+ useEffect(() => {
+ const fetchSubItems = async () => {
+ try {
+ const response = await requestServer(`${GET_ROAD_ITEMS_SUB_ITEM}/${id}`);
+ setSubItemsList(response.data.data);
+ setLoadingSubItemsList(false);
+ } catch (e) {
+ setErrorSubItemsList(e);
+ setLoadingSubItemsList(false);
+ }
+ };
+
+ fetchSubItems();
+ }, []);
+
+ return { subItemsList, loadingSubItemsList, errorSubItemsList };
+};
+
+export default useRoadItemGetItems;
diff --git a/src/lib/hooks/useRoadItemGetItems.js b/src/lib/hooks/useRoadItemGetItems.js
new file mode 100644
index 0000000..523f7c9
--- /dev/null
+++ b/src/lib/hooks/useRoadItemGetItems.js
@@ -0,0 +1,29 @@
+import { useEffect, useState } from "react";
+import { GET_ROAD_ITEMS_ITEM } from "@/core/utils/routes";
+import useRequest from "@/lib/hooks/useRequest";
+
+const useRoadItemGetItems = () => {
+ const requestServer = useRequest();
+ const [itemsList, setItemsList] = useState([]);
+ const [loadingItemsList, setLoadingItemsList] = useState(true);
+ const [errorItemsList, setErrorItemsList] = useState(null);
+
+ useEffect(() => {
+ const fetchItems = async () => {
+ try {
+ const response = await requestServer(`${GET_ROAD_ITEMS_ITEM}`);
+ setItemsList(response.data.data);
+ setLoadingItemsList(false);
+ } catch (e) {
+ setErrorItemsList(e);
+ setLoadingItemsList(false);
+ }
+ };
+
+ fetchItems();
+ }, []);
+
+ return { itemsList, loadingItemsList, errorItemsList };
+};
+
+export default useRoadItemGetItems;