diff --git a/package.json b/package.json
index c5b2139..f5c7205 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"react-hook-form": "^7.52.1",
"react-leaflet": "^4.2.1",
"react-leaflet-draw": "^0.20.4",
+ "react-leaflet-markercluster": "^4.2.1",
"react-toastify": "^10.0.5",
"sass": "^1.75.0",
"sharp": "^0.33.4",
diff --git a/src/app/(withAuth)/dashboard/road-items/reports/map/page.js b/src/app/(withAuth)/dashboard/road-items/reports/map/page.js
new file mode 100644
index 0000000..00f000d
--- /dev/null
+++ b/src/app/(withAuth)/dashboard/road-items/reports/map/page.js
@@ -0,0 +1,6 @@
+import RoadItemsReportMap from "@/components/dashboard/roadItems/reports/Map";
+
+const Page = () => {
+ return ;
+};
+export default Page;
diff --git a/src/assets/scss/global.scss b/src/assets/scss/global.scss
index 7b73e7e..1fc9f82 100644
--- a/src/assets/scss/global.scss
+++ b/src/assets/scss/global.scss
@@ -1,6 +1,7 @@
@import "fontiran";
@import "leaflet/dist/leaflet.css";
@import "react-toastify/dist/ReactToastify.css";
+@import "./map-styles.scss";
.filter-toast {
box-shadow:
diff --git a/src/assets/scss/map-styles.scss b/src/assets/scss/map-styles.scss
index 44e6651..595f664 100644
--- a/src/assets/scss/map-styles.scss
+++ b/src/assets/scss/map-styles.scss
@@ -1,27 +1,9 @@
-.leaflet-draw-tooltip {
- background-color: #4f4f4f;
- font-family: IRANSans;
+.custom-marker-cluster {
+ font-family: IRANSansFaNum;
+ background-color: rgba(51, 181, 229, 0.6);
color: #fff;
- font-size: 10px;
- padding: 2px 8px;
- margin-right: 15px;
- border-radius: 4px;
- box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
-}
-
-.leaflet-draw-tooltip:before {
- content: "";
- position: absolute;
- bottom: 100%;
- left: 103%;
- transform: rotate(90deg);
- margin-left: -6px;
- border-width: 6px;
- border-style: solid;
- border-color: transparent transparent #333 transparent;
-}
-
-.leaflet-draw-tooltip-subtext {
- font-size: 12px;
- color: #ffeb3b;
+ border-radius: 50%;
+ text-align: center;
+ font-size: 14px;
+ line-height: 40px;
}
diff --git a/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx b/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx
index cd75f8d..4e1bb43 100644
--- a/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx
+++ b/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx
@@ -1,7 +1,5 @@
"use client";
-import "leaflet-draw/dist/leaflet.draw.css";
-import "@/assets/scss/map-styles.scss";
import { FeatureGroup, useMap } from "react-leaflet";
import { EditControl } from "react-leaflet-draw";
import { useEffect, useRef, useState } from "react";
diff --git a/src/components/dashboard/roadItems/operator/OperatorList.jsx b/src/components/dashboard/roadItems/operator/OperatorList.jsx
index 52913cc..33b37c6 100644
--- a/src/components/dashboard/roadItems/operator/OperatorList.jsx
+++ b/src/components/dashboard/roadItems/operator/OperatorList.jsx
@@ -300,7 +300,7 @@ const OperatorList = () => {
},
{
accessorKey: "status",
- header: "وضعیت", // Status
+ header: "وضعیت نظارت",
id: "status",
enableColumnFilter: true,
datatype: "numeric",
@@ -318,7 +318,7 @@ const OperatorList = () => {
},
{
accessorKey: "supervisor_description",
- header: "توضیحات کارشناس", // Description
+ header: "توضیحات ناظر",
id: "supervisor_description",
enableColumnFilter: false,
enableSorting: false,
diff --git a/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx
index 481e2d6..91b135a 100644
--- a/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx
+++ b/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx
@@ -8,7 +8,7 @@ const DescriptionForm = ({ description }) => {
return (
<>
-
+
setOpenOfficerDescriptionDialog(true)}>
@@ -30,7 +30,7 @@ const DescriptionForm = ({ description }) => {
>
- توضیحات کارشناس
+ توضیحات ناظر
setOpenOfficerDescriptionDialog(false)} size="small">
diff --git a/src/components/dashboard/roadItems/reports/Map/ClusterSwitch/index.jsx b/src/components/dashboard/roadItems/reports/Map/ClusterSwitch/index.jsx
new file mode 100644
index 0000000..e8fac87
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/ClusterSwitch/index.jsx
@@ -0,0 +1,25 @@
+import DoneIcon from "@mui/icons-material/Done";
+import { Chip, Tooltip, Zoom } from "@mui/material";
+
+const ClusterSwitch = ({ isCluster, setCluster, disabled, max }) => {
+ return (
+
+
+ setCluster((c) => !c)}
+ sx={{ borderRadius: 1 }}
+ icon={
+
+
+
+ }
+ />
+
+
+ );
+};
+export default ClusterSwitch;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ActivityDateTimeFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ActivityDateTimeFilter.jsx
new file mode 100644
index 0000000..cab7ddc
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ActivityDateTimeFilter.jsx
@@ -0,0 +1,32 @@
+import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
+import { Box } from "@mui/material";
+
+const ActivityDateTimeFilter = ({ value, onChange, error }) => {
+ return (
+
+ {
+ onChange([formattedDate, value[1]]);
+ }}
+ maxDate={value[1]}
+ placeholder={`از تاریخ`}
+ />
+ {
+ onChange([value[0], formattedDate]);
+ }}
+ minDate={value[0]}
+ placeholder={`تا تاریخ`}
+ helperText={error ? error.message : null}
+ error={Boolean(error)}
+ />
+
+ );
+};
+export default ActivityDateTimeFilter;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/EdarehShahriFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/EdarehShahriFilter.jsx
new file mode 100644
index 0000000..eb08e27
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/EdarehShahriFilter.jsx
@@ -0,0 +1,65 @@
+"use client";
+import useEdaratLists from "@/lib/hooks/useEdaratLists";
+import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
+import { useEffect, useMemo, useState } from "react";
+import { useWatch } from "react-hook-form";
+
+const EdarehShahriFilter = ({ control, value, onChange }) => {
+ const dependencyField = useWatch({ control, name: "province_id" });
+ return ;
+};
+
+const EdarehShahriController = ({ value, onChange, dependencyField }) => {
+ const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(dependencyField);
+ const [prevDependency, setPrevDependency] = useState(dependencyField);
+
+ const columnSelectOption = useMemo(() => {
+ if (dependencyField === "") {
+ return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
+ }
+ if (loadingEdaratList) {
+ return [{ value: "loading", label: "در حال بارگذاری..." }];
+ }
+ if (errorEdaratList) {
+ return [{ value: "error", label: "خطا در بارگذاری" }];
+ }
+ return [
+ { value: "", label: "کل ادارات" },
+ ...edaratList.map((edare) => ({
+ value: edare.id,
+ label: edare.name_fa,
+ })),
+ ];
+ }, [edaratList, loadingEdaratList, errorEdaratList]);
+
+ useEffect(() => {
+ if (prevDependency === dependencyField) return;
+ onChange("");
+ setPrevDependency(dependencyField);
+ }, [dependencyField]);
+
+ return (
+
+
+ اداره
+
+ }
+ size="small"
+ onChange={(e) => onChange(e.target.value)}
+ displayEmpty
+ >
+ {columnSelectOption.map((option) => (
+
+ ))}
+
+
+ );
+};
+export default EdarehShahriFilter;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ItemFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ItemFilter.jsx
new file mode 100644
index 0000000..de27d11
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ItemFilter.jsx
@@ -0,0 +1,47 @@
+import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems";
+import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
+import { useMemo } from "react";
+
+const ItemFilter = ({ value, onChange }) => {
+ const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems();
+ const columnSelectOption = useMemo(() => {
+ if (loadingItemsList) {
+ return [{ value: "loading", label: "در حال بارگذاری..." }];
+ }
+ if (errorItemsList) {
+ return [{ value: "error", label: "خطا در بارگذاری" }];
+ }
+ return [
+ { value: "", label: "همه آیتم ها" },
+ ...itemsList.map((item) => ({
+ value: item.id,
+ label: item.name,
+ })),
+ ];
+ }, [itemsList, loadingItemsList, errorItemsList]);
+
+ return (
+
+
+ آیتم فعالیت
+
+ }
+ size="small"
+ onChange={(e) => onChange(e.target.value)}
+ displayEmpty
+ >
+ {columnSelectOption.map((option) => (
+
+ ))}
+
+
+ );
+};
+export default ItemFilter;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ProvinceFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ProvinceFilter.jsx
new file mode 100644
index 0000000..4de3f9e
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ProvinceFilter.jsx
@@ -0,0 +1,47 @@
+import useProvinces from "@/lib/hooks/useProvince";
+import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
+import { useMemo } from "react";
+
+const ProvinceFilter = ({ value, onChange }) => {
+ const { provinces, errorProvinces, loadingProvinces } = useProvinces();
+ const columnSelectOption = useMemo(() => {
+ if (loadingProvinces) {
+ return [{ value: "loading", label: "در حال بارگذاری..." }];
+ }
+ if (errorProvinces) {
+ return [{ value: "error", label: "خطا در بارگذاری" }];
+ }
+ return [
+ { value: "", label: "کل کشور" },
+ ...provinces.map((province) => ({
+ value: province.id,
+ label: province.name_fa,
+ })),
+ ];
+ }, [provinces, errorProvinces, loadingProvinces]);
+
+ return (
+
+
+ استان
+
+ }
+ size="small"
+ onChange={(e) => onChange(e.target.value)}
+ displayEmpty
+ >
+ {columnSelectOption.map((option) => (
+
+ ))}
+
+
+ );
+};
+export default ProvinceFilter;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/StatusFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/StatusFilter.jsx
new file mode 100644
index 0000000..dd52ea5
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/StatusFilter.jsx
@@ -0,0 +1,35 @@
+import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
+
+const StatusFilter = ({ value, onChange }) => {
+ const statusOptions = [
+ { value: "", label: "همه وضعیت ها" },
+ { value: 0, label: "درحال بررسی" },
+ { value: 1, label: "تایید" },
+ { value: 2, label: "عدم تایید" },
+ ];
+
+ return (
+
+
+ وضعیت نظارت
+
+ }
+ size="small"
+ onChange={(e) => onChange(e.target.value)}
+ displayEmpty
+ >
+ {statusOptions.map((option) => (
+
+ ))}
+
+
+ );
+};
+export default StatusFilter;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/SubItemFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/SubItemFilter.jsx
new file mode 100644
index 0000000..e9f973a
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/SubItemFilter.jsx
@@ -0,0 +1,65 @@
+"use client";
+import useRoadItemGetSubItems from "@/lib/hooks/useRoadItemGetISubtems";
+import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
+import { useEffect, useMemo, useState } from "react";
+import { useWatch } from "react-hook-form";
+
+const SubItemFilter = ({ control, value, onChange }) => {
+ const dependencyField = useWatch({ control, name: "item_id" });
+ return ;
+};
+
+const SubItemController = ({ value, onChange, dependencyField }) => {
+ const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetSubItems(dependencyField);
+ const [prevDependency, setPrevDependency] = useState(dependencyField);
+
+ const columnSelectOption = useMemo(() => {
+ if (dependencyField === "") {
+ return [{ value: "empty", label: "ابتدا آیتم فعالیت را انتخاب کنید" }];
+ }
+ if (loadingSubItemsList) {
+ return [{ value: "loading", label: "در حال بارگذاری..." }];
+ }
+ if (errorSubItemsList) {
+ return [{ value: "error", label: "خطا در بارگذاری" }];
+ }
+ return [
+ { value: "", label: "همه اقدام ها" },
+ ...subItemsList.map((item) => ({
+ value: item.sub_item,
+ label: item.name,
+ })),
+ ];
+ }, [subItemsList, loadingSubItemsList, errorSubItemsList]);
+
+ useEffect(() => {
+ if (prevDependency === dependencyField) return;
+ onChange("");
+ setPrevDependency(dependencyField);
+ }, [dependencyField]);
+
+ return (
+
+
+ اقدام انجام شده
+
+ }
+ size="small"
+ onChange={(e) => onChange(e.target.value)}
+ displayEmpty
+ >
+ {columnSelectOption.map((option) => (
+
+ ))}
+
+
+ );
+};
+export default SubItemFilter;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/index.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/index.jsx
new file mode 100644
index 0000000..9700220
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/index.jsx
@@ -0,0 +1,141 @@
+import ScrollBox from "@/core/components/ScrollBox";
+import { Box, Button, IconButton, Typography } from "@mui/material";
+import CancelIcon from "@mui/icons-material/Cancel";
+import FilterAltIcon from "@mui/icons-material/FilterAlt";
+import { Controller, useForm } from "react-hook-form";
+import ProvinceFilter from "./ProvinceFilter";
+import EdarehShahriFilter from "./EdarehShahriFilter";
+import ItemFilter from "./ItemFilter";
+import SubItemFilter from "./SubItemFilter";
+import StatusFilter from "./StatusFilter";
+import ActivityDateTimeFilter from "./ActivityDateTimeFilter";
+import { yupResolver } from "@hookform/resolvers/yup";
+import * as Yup from "yup";
+
+const validationSchema = Yup.object({
+ activity_date_time: Yup.array()
+ .of(Yup.string().nullable())
+ .test({
+ test(value, ctx) {
+ const [start, end] = value || ["", ""];
+ if ((start && !end) || (!start && end)) {
+ return ctx.createError({ message: "این بخش را تکمیل نمایید" });
+ }
+ return true;
+ },
+ }),
+});
+
+const headerSx = {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ px: 2,
+ py: 1,
+ backgroundColor: "#155175",
+ boxShadow: "rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px",
+ maxWidth: "450px",
+};
+
+const headerTitleSx = { display: "flex", alignItems: "center" };
+const headerIconSx = { color: "#fff", mr: 1 };
+const iconButtonSx = { color: "#fff" };
+
+const formContainerSx = { px: 2, py: 3 };
+const footerSx = { display: "flex", justifyContent: "center", alignItems: "center", pb: 2 };
+
+const submitButtonSx = {
+ px: 8,
+ boxShadow: "rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
+ backgroundColor: "primary2",
+ ":hover": { backgroundColor: "primary2" },
+};
+
+const FilterDrawer = ({ defaultValues, setFilterData, closeDrawer }) => {
+ const {
+ control,
+ handleSubmit,
+ formState: { isDirty },
+ } = useForm({
+ defaultValues,
+ resolver: yupResolver(validationSchema),
+ mode: "onBlur",
+ });
+
+ const onSubmit = (data) => {
+ setFilterData(data);
+ closeDrawer();
+ };
+
+ return (
+ <>
+
+
+
+
+ فیلتر
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+export default FilterDrawer;
diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/index.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/index.jsx
new file mode 100644
index 0000000..c12a511
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Filter/index.jsx
@@ -0,0 +1,44 @@
+"use client";
+import { Box, Button, Drawer } from "@mui/material";
+import FilterListIcon from "@mui/icons-material/FilterList";
+import { useCallback, useState } from "react";
+import FilterDrawer from "./Drawer";
+
+const drawerSx = {
+ overflowY: "hidden",
+ display: "flex",
+ flexDirection: "column",
+ height: "100%",
+ zIndex: "1300",
+};
+
+const boxSx = {
+ width: { xs: 300, sm: 450 },
+};
+
+const Filter = ({ filterData, setFilterData }) => {
+ const [open, setOpen] = useState(false);
+
+ const openDrawer = useCallback(() => setOpen(true), []);
+ const closeDrawer = useCallback(() => setOpen(false), []);
+
+ return (
+ <>
+ }>
+ فیلتر
+
+
+
+ {open && (
+
+ )}
+
+
+ >
+ );
+};
+export default Filter;
diff --git a/src/components/dashboard/roadItems/reports/Map/Legend/index.jsx b/src/components/dashboard/roadItems/reports/Map/Legend/index.jsx
new file mode 100644
index 0000000..20707aa
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/Legend/index.jsx
@@ -0,0 +1,48 @@
+import { Box, Stack, Typography } from "@mui/material";
+
+const Legend = () => {
+ return (
+
+
+ theme.palette.primary.main,
+ background: (theme) => theme.palette.primary.light,
+ }}
+ />
+ در حال بررسی
+
+
+ theme.palette.success.main,
+ background: (theme) => theme.palette.success.light,
+ }}
+ />
+ تایید شده
+
+
+ theme.palette.warning.main,
+ background: (theme) => theme.palette.warning.light,
+ }}
+ />
+ عدم تایید
+
+
+ );
+};
+export default Legend;
diff --git a/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/ContentInfoItem.jsx b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/ContentInfoItem.jsx
new file mode 100644
index 0000000..a10a1aa
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/ContentInfoItem.jsx
@@ -0,0 +1,104 @@
+import { Box, Chip, Divider, Stack, Typography } from "@mui/material";
+import moment from "jalali-moment";
+import Image from "next/image";
+
+const ContentInfoItem = ({ data }) => {
+ return (
+
+
+
+
+ {data.id !== "" && data.id}
+
+
+
+
+
+ {data.province_fa !== "" && data.province_fa}
+
+
+
+
+
+
+ {data.edarat_name !== "" && data.edarat_name}
+
+
+
+
+
+
+ {data.item_fa !== "" && data.item_fa}
+
+
+
+
+
+
+ {data.sub_item_fa !== "" && data.sub_item_fa}
+
+
+
+
+
+
+
+ {(data.sub_item_data / 1).toLocaleString()}
+
+
+ {`(${data.unit_fa})`}
+
+
+
+
+
+
+
+ {data.activity_date_time &&
+ moment(data.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD")}
+
+
+
+
+
+
+ {data.created_at && moment(data.created_at).locale("fa").format("HH:mm | yyyy/MM/DD")}
+
+
+
+
+
+
+ {data.status_fa !== "" && data.status_fa}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+export default ContentInfoItem;
diff --git a/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/index.jsx b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/index.jsx
new file mode 100644
index 0000000..a3c9b23
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/index.jsx
@@ -0,0 +1,51 @@
+import DialogLoading from "@/core/components/DialogLoading";
+import { GET_ROAD_ITEM } from "@/core/utils/routes";
+import useRequest from "@/lib/hooks/useRequest";
+import { Button, DialogActions, DialogContent, DialogTitle } from "@mui/material";
+import { useEffect, useState } from "react";
+import ContentInfoItem from "./ContentInfoItem";
+
+const DialogInfoItem = ({ selectedId, closeDialog }) => {
+ const request = useRequest();
+ const [loading, setLoading] = useState(true);
+ const [data, setData] = useState(null);
+
+ useEffect(() => {
+ const controller = new AbortController();
+
+ const fetchItem = async () => {
+ setLoading(true);
+ setData(null);
+ try {
+ const response = await request(`${GET_ROAD_ITEM}/${selectedId}`, "get", {
+ signal: controller.signal,
+ });
+ setData(response.data.data);
+ } catch (error) {
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchItem();
+
+ return () => {
+ controller.abort();
+ };
+ }, [selectedId]);
+
+ return (
+ <>
+ جزئیات فعالیت
+
+ {loading ? : data && }
+
+
+
+
+ >
+ );
+};
+export default DialogInfoItem;
diff --git a/src/components/dashboard/roadItems/reports/Map/PointsOnMap/index.jsx b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/index.jsx
new file mode 100644
index 0000000..8599f3f
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/index.jsx
@@ -0,0 +1,71 @@
+import { Dialog, useTheme } from "@mui/material";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { CircleMarker, useMap } from "react-leaflet";
+import MarkerClusterGroup from "react-leaflet-markercluster";
+import DialogInfoItem from "./DialogInfoItem";
+
+const PointsOnMap = ({ data, isCluster }) => {
+ const map = useMap();
+ const theme = useTheme();
+ const [selectedId, setSelectedId] = useState(null);
+ const [open, setOpen] = useState(false);
+
+ const openDialog = useCallback(() => setOpen(true), []);
+ const closeDialog = useCallback(() => setOpen(false), []);
+
+ const getMarkerColor = (status) => {
+ if (status === 0) return theme.palette.info.main;
+ if (status === 1) return theme.palette.success.main;
+ return theme.palette.warning.main;
+ };
+
+ const markers = useMemo(() => {
+ return data.map(({ i, l, g, status }) => (
+ {
+ openDialog();
+ setSelectedId(i);
+ },
+ }}
+ key={i}
+ center={L.latLng(l, g)}
+ radius={8}
+ color={getMarkerColor(status)}
+ />
+ ));
+ }, [data, theme]);
+
+ useEffect(() => {
+ if (data.length === 0) return;
+ const bounds = L.latLngBounds(data.map(({ l, g }) => L.latLng(l, g)));
+ map.flyToBounds(bounds, { duration: 0.7 });
+ }, [data]);
+
+ const createClusterCustomIcon = (cluster) => {
+ const count = cluster.getChildCount();
+ return L.divIcon({
+ html: `${count}
`,
+ className: "custom-marker-cluster",
+ iconSize: L.point(40, 40, true),
+ });
+ };
+
+ if (data.length === 0) return null;
+
+ return (
+ <>
+ {isCluster ? (
+
+ {markers}
+
+ ) : (
+ markers
+ )}
+
+ >
+ );
+};
+export default PointsOnMap;
diff --git a/src/components/dashboard/roadItems/reports/Map/index.jsx b/src/components/dashboard/roadItems/reports/Map/index.jsx
new file mode 100644
index 0000000..1daacfb
--- /dev/null
+++ b/src/components/dashboard/roadItems/reports/Map/index.jsx
@@ -0,0 +1,127 @@
+"use client";
+import LoadingHardPage from "@/core/components/LoadingHardPage";
+import MapLoading from "@/core/components/MapLayer/Loading";
+import { GET_ROAD_ITEMS_REPORT_MAP } from "@/core/utils/routes";
+import useRequest from "@/lib/hooks/useRequest";
+import { Box, Stack } from "@mui/material";
+import dynamic from "next/dynamic";
+import { useCallback, useEffect, useState } from "react";
+import Filter from "./Filter";
+import PointsOnMap from "./PointsOnMap";
+import moment from "jalali-moment";
+import ClusterSwitch from "./ClusterSwitch";
+import Legend from "./Legend";
+const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
+ loading: () => ,
+ ssr: false,
+});
+
+const defaultValues = {
+ province_id: "",
+ edarat_id: "",
+ item_id: "",
+ sub_item_id: "",
+ status: "",
+ activity_date_time: [moment().locale("en").format("YYYY-MM-DD"), moment().locale("en").format("YYYY-MM-DD")],
+};
+
+const MAX_POINTS = 1000;
+
+const containerStyles = {
+ width: "100%",
+ height: "100%",
+ p: 1,
+ border: "1px dashed",
+ borderColor: "divider",
+ borderRadius: 1,
+};
+
+const controlBarStyles = {
+ p: 1,
+ background: "#fff",
+};
+
+const RoadItemsReportMap = () => {
+ const requestServer = useRequest();
+ const [filterData, setFilterData] = useState(defaultValues);
+ const [isCluster, setCluster] = useState(false);
+ const [data, setData] = useState([]);
+ const [isLoading, setLoading] = useState(true);
+
+ const buildQueryParams = useCallback(() => {
+ const params = new URLSearchParams();
+ const { province_id, edarat_id, item_id, sub_item_id, activity_date_time, status } = filterData;
+
+ if (province_id) params.set("province_id", province_id);
+ if (edarat_id) params.set("edarat_id", edarat_id);
+ if (item_id) params.set("item_id", item_id);
+ if (sub_item_id) params.set("sub_item_id", sub_item_id);
+ if (activity_date_time[0]) {
+ params.set("date_from", activity_date_time[0]);
+ params.set("date_to", activity_date_time[1]);
+ }
+ if (status) params.set("status", status);
+
+ return params.toString();
+ }, [filterData]);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ setLoading(true);
+ const query = buildQueryParams();
+ const response = await requestServer(`${GET_ROAD_ITEMS_REPORT_MAP}?${query}`);
+ const fetchedData = response?.data?.data || [];
+
+ if (fetchedData.length > MAX_POINTS) {
+ setCluster(true);
+ }
+ setData(fetchedData);
+ } catch (error) {
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+ }, [filterData, buildQueryParams]);
+
+ return (
+
+
+
+
+ MAX_POINTS}
+ max={MAX_POINTS}
+ />
+
+
+
+
+
+
+
+
+
+
+ {isLoading && (
+
+
+
+ )}
+
+
+
+
+ );
+};
+export default RoadItemsReportMap;
diff --git a/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx b/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx
index 84a48c9..f9a7d78 100644
--- a/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx
+++ b/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx
@@ -404,7 +404,7 @@ const SupervisorList = () => {
},
{
accessorKey: "status",
- header: "وضعیت", // Status
+ header: "وضعیت نظارت",
id: "status",
enableColumnFilter: true,
datatype: "numeric",
diff --git a/src/core/components/DataTable/filter/FilterBody.jsx b/src/core/components/DataTable/filter/FilterBody.jsx
index ab12820..461eb01 100644
--- a/src/core/components/DataTable/filter/FilterBody.jsx
+++ b/src/core/components/DataTable/filter/FilterBody.jsx
@@ -1,32 +1,15 @@
"use client";
-import FilterBodyField from "@/core/components/DataTable/filter/FilterBodyField";
+import FilterBodyControllerWithDependency from "@/core/components/DataTable/filter/FilterBodyControllerWithDependency";
import FilterHeader from "@/core/components/DataTable/filter/FilterHeader";
import useDataTable from "@/lib/hooks/useDataTable";
import { yupResolver } from "@hookform/resolvers/yup";
-import { Box, Button, Drawer, styled } from "@mui/material";
+import { Box, Button } from "@mui/material";
import { useEffect, useState } from "react";
-import { Controller, useForm, useWatch } from "react-hook-form";
+import { useForm } from "react-hook-form";
import * as Yup from "yup";
+import ScrollBox from "../../ScrollBox";
import FilterBodyController from "./FilterBodyController";
-import FilterBodyControllerWithDependency from "@/core/components/DataTable/filter/FilterBodyControllerWithDependency";
-
-const ScrollBox = styled(Box)({
- flexGrow: 1,
- overflowY: "scroll",
- maxWidth: "450px",
- "::-webkit-scrollbar": {
- width: "5px",
- },
- "::-webkit-scrollbar-track": {
- boxShadow: "inset 0 0 5px #fff",
- borderRadius: "5px",
- },
- "::-webkit-scrollbar-thumb": {
- background: "#155175",
- borderRadius: "0px",
- },
-});
function FilterBody({ columns, drawerState, setDrawerState }) {
const { filterData, setFilterData } = useDataTable();
@@ -89,7 +72,7 @@ function FilterBody({ columns, drawerState, setDrawerState }) {
{Object.keys(filterData).length > 0 && (