Merge pull request #9 from witelgroup/feature/add_map_tollhouse

Feature/add map tollhouse
This commit is contained in:
Amin Ghasempoor
2025-12-01 13:12:32 +03:30
committed by GitHub
18 changed files with 707 additions and 24 deletions

View File

@@ -1,4 +1,5 @@
HOST="rms.witel.ir"
HOST_RMTO="rms.rmto.ir"
NEXT_PUBLIC_VERSION="1.8.3"
NEXT_PUBLIC_API_URL="https://rms.witel.ir"
NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map"

View File

@@ -7,6 +7,10 @@ const nextConfig = {
protocol: 'https',
hostname: process.env.HOST,
},
{
protocol: 'https',
hostname: process.env.HOST_RMTO,
},
],
},
};

View File

@@ -0,0 +1,7 @@
"use client";
import PageLoading from "@/core/components/PageLoading";
const Loading = () => {
return <PageLoading />;
};
export default Loading;

View File

@@ -0,0 +1,16 @@
import WithPermission from "@/core/middlewares/withPermission";
import TollHouseMapPage from "@/components/infrastructure/tollHouseMap";
export const metadata = {
title: "پراکندگی بر روی نقشه راهدارخانه ها",
};
const Page = () => {
return (
<WithPermission permission_name={["show-tollhouse", "show-tollhouse-province"]}>
<TollHouseMapPage />
</WithPermission>
);
};
export default Page;

View File

@@ -11,6 +11,7 @@ const StartMissionContent = ({ rowId, mutate, setOpenStartMissionDialog }) => {
const validationSchema = object().shape({
code: string().length(6, "کد خروج باید 6 رقم باشد").required("لطفاً کد خروج را وارد کنید."),
km: string().required("لطفاً کیلومتر را وارد کنید."),
});
const {
@@ -20,6 +21,7 @@ const StartMissionContent = ({ rowId, mutate, setOpenStartMissionDialog }) => {
} = useForm({
defaultValues: {
code: "",
km: "",
},
resolver: yupResolver(validationSchema),
mode: "all",
@@ -29,6 +31,7 @@ const StartMissionContent = ({ rowId, mutate, setOpenStartMissionDialog }) => {
requestServer(`${START_MISSION}/${rowId}`, "post", {
data: {
code: values.code,
km: values.km,
},
hasSidebarUpdate: true,
})
@@ -69,6 +72,27 @@ const StartMissionContent = ({ rowId, mutate, setOpenStartMissionDialog }) => {
)}
/>
</Stack>
<Stack>
<Controller
control={control}
name={"km"}
render={({ field, fieldState: { error } }) => (
<LtrTextField
autoComplete="off"
type="tel"
value={field.value}
onChange={(e) => {
if (isNaN(Number(e.target.value))) return;
field.onChange(e.target.value);
}}
size="small"
fullWidth
label="کیلومتر خودرو / ساعت کار"
InputLabelProps={{ shrink: true }}
/>
)}
/>
</Stack>
</Stack>
</DialogContent>
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>

View File

@@ -22,16 +22,14 @@ const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => {
const requestServer = useRequest({ notificationSuccess: true });
const [machine, setMachine] = useState();
const [driver, setDriver] = useState();
const [kilometer, setKilometer] = useState();
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (_machine, _driver, _kilometer) => {
const handleSubmit = async (_machine, _driver) => {
setIsSubmitting(true);
await requestServer(`${ALLOCATE_REQUEST_MISSION}/${row.id}`, "post", {
data: {
machines: [_machine.id],
machines: _machine.id,
driver: _driver.id,
km: kilometer,
},
hasSidebarUpdate: true,
})
@@ -110,23 +108,6 @@ const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => {
)}
</Stack>
)}
{driver && (
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<LtrTextField
autoComplete="off"
type="tel"
value={kilometer}
onChange={(e) => {
if (isNaN(Number(e.target.value))) return;
setKilometer(e.target.value);
}}
size="small"
fullWidth
label="کیلومتر خودرو"
InputLabelProps={{ shrink: true }}
/>
</Stack>
)}
</Stack>
</Paper>
<Stack spacing={1}>
@@ -208,10 +189,10 @@ const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => {
</Stack>
<Box>
<Button
onClick={() => handleSubmit(machine, driver, kilometer)}
onClick={() => handleSubmit(machine, driver)}
variant="contained"
color="primary"
disabled={!machine || !driver || !kilometer || isSubmitting}
disabled={!machine || !driver || isSubmitting}
autoFocus
>
تایید و تخصیص

View File

@@ -0,0 +1,29 @@
import { useEffect, useRef } from "react";
import { FeatureGroup, useMap } from "react-leaflet";
const ShowBound = ({ bound }) => {
const map = useMap();
const featureRef = useRef(null);
const safeFitBounds = (layer) => {
if (!layer || !map) return;
try {
const latLngs = layer.getLatLngs();
map.fitBounds(latLngs, {
paddingTopLeft: [20, 20],
paddingBottomRight: [20, 20],
});
} catch (e) {
console.warn("fitBounds failed:", e);
}
};
useEffect(() => {
bound?.editing?.disable();
featureRef.current.addLayer(bound);
safeFitBounds(bound);
}, []);
return <FeatureGroup ref={featureRef} />;
};
export default ShowBound;

View File

@@ -0,0 +1,65 @@
const { IconButton, Tooltip, Box, Dialog, DialogTitle, Typography, DialogContent } = require("@mui/material");
import MapLayer from "@/core/components/MapLayer";
import CloseIcon from "@mui/icons-material/Close";
import LayersIcon from "@mui/icons-material/Layers";
import L from "leaflet";
import { useMemo, useState } from "react";
import ShowBound from "./ShowBound";
const ShowProjectArea = ({ primaryArea }) => {
const [openShowAreaDialog, setOpenShowAreaDialog] = useState(false);
const bound = useMemo(() => {
const coordinates = Object.values(primaryArea.coordinates);
const latLngCoords = coordinates.map(([lat, lng]) => [parseFloat(lng), parseFloat(lat)]);
return primaryArea.type === "polygon"
? L.polygon(latLngCoords, { color: "#ff5555" })
: L.polyline(latLngCoords);
}, [primaryArea]);
return (
<Box>
<Tooltip title="نمایش پلیگان" placement="right" arrow>
<IconButton size="small" color="primary" onClick={() => setOpenShowAreaDialog(true)}>
<LayersIcon />
</IconButton>
</Tooltip>
<Dialog
open={openShowAreaDialog}
onClose={() => setOpenShowAreaDialog(false)}
PaperProps={{
sx: {
transition: "all .3s",
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
borderRadius: 2,
padding: 1,
},
}}
maxWidth="sm"
fullWidth
dir="rtl"
>
<DialogTitle sx={{ display: "flex", justifyContent: "space-between", padding: "16px 24px" }}>
<Typography variant="body1" sx={{ fontWeight: "bold", fontSize: "large" }}>
محدوده طرح
</Typography>
<IconButton onClick={() => setOpenShowAreaDialog(false)} size="small">
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<Box sx={{ flex: 1 }}>
<Box sx={{ width: "100%", height: "400px" }}>
<MapLayer style={{ borderRadius: "4px" }}>
<ShowBound bound={bound} />
</MapLayer>
</Box>
</Box>
</DialogContent>
</Dialog>
</Box>
);
};
export default ShowProjectArea;

View File

@@ -13,6 +13,7 @@ import { usePermissions } from "@/lib/hooks/usePermissions";
import LocationForm from "./RowActions/LocationForm";
import ImageDialog from "./RowActions/ImageForm";
import { useAuth } from "@/lib/contexts/auth";
import ShowProjectArea from "./ShowProjectArea";
const statusOptions = [
{ value: "", label: "همه وضعیت ها" },
@@ -225,6 +226,26 @@ const TollHouseList = () => {
);
},
},
{
accessorKey: "area",
header: "نمایش محدوده طرح",
id: "area",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
Cell: ({ row }) =>
row.original.area ? (
<Stack alignItems={"center"} justifyContent={"center"}>
<ShowProjectArea primaryArea={row.original.area} />
</Stack>
) : (
"-"
),
},
{
accessorKey: "phone",
header: "تلفن راهدارخانه",

View File

@@ -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 (
<Tooltip title={disabled ? `بیشتر از ${max} تا فعالیت باید حالت کلاستر باشد` : ""}>
<span>
<Chip
label={"حالت کلاستر"}
variant="outlined"
disabled={disabled}
color={isCluster ? "success" : "default"}
onClick={() => setCluster((c) => !c)}
sx={{ borderRadius: 1 }}
icon={
<Zoom in={isCluster}>
<DoneIcon />
</Zoom>
}
/>
</span>
</Tooltip>
);
};
export default ClusterSwitch;

View File

@@ -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 "@/core/components/FilterDrawer";
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 (
<>
<Button onClick={openDrawer} variant="outlined" size="small" endIcon={<FilterListIcon />}>
فیلتر
</Button>
<Drawer open={open} onClose={closeDrawer} sx={drawerSx}>
<Box sx={boxSx}>
{open && (
<FilterDrawer
defaultValues={filterData}
setFilterData={setFilterData}
closeDrawer={closeDrawer}
/>
)}
</Box>
</Drawer>
</>
);
};
export default Filter;

View File

@@ -0,0 +1,35 @@
import { Box, Stack, Typography } from "@mui/material";
const Legend = () => {
return (
<Stack>
<Stack direction={"row"} spacing={1} alignItems={"center"}>
<Box
sx={{
width: 8,
height: 8,
border: 1,
borderRadius: "50%",
borderColor: (theme) => theme.palette.success.main,
background: (theme) => theme.palette.success.light,
}}
/>
<Typography variant="caption">فعال</Typography>
</Stack>
<Stack direction={"row"} spacing={1} alignItems={"center"}>
<Box
sx={{
width: 8,
height: 8,
border: 1,
borderRadius: "50%",
borderColor: (theme) => theme.palette.error.main,
background: (theme) => theme.palette.error.light,
}}
/>
<Typography variant="caption">غیرفعال</Typography>
</Stack>
</Stack>
);
};
export default Legend;

View File

@@ -0,0 +1,71 @@
import { Box, Chip, Divider, Stack, Typography } from "@mui/material";
import moment from "jalali-moment";
import Image from "next/image";
const ContentInfoItem = ({ data }) => {
return (
<Stack spacing={2}>
<Stack direction={"row"} alignItems={"center"}>
<Chip size="small" label="کد یکتا" />
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>{data.id !== "" && data.id}</Typography>
</Stack>
<Stack direction={"row"} alignItems={"center"}>
<Chip size="small" label="استان" />
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
{data.province_name !== "" && data.province_name}
</Typography>
</Stack>
<Stack direction={"row"} alignItems={"center"}>
<Chip size="small" label="نوع" />
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
{data.type_fa !== "" && data.type_fa}
</Typography>
</Stack>
<Stack direction={"row"} alignItems={"center"}>
<Chip size="small" label="وضعیت" />
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
{data.status === "1" ? "فعال" : "غیرفعال"}
</Typography>
</Stack>
<Stack spacing={2}>
<Stack
sx={{ flex: 1, border: 1, borderColor: "divider", borderRadius: 2, p: 1 }}
spacing={1}
alignItems={"center"}
>
<Chip size="small" label="تصویر بازدید" />
<Box sx={{ position: "relative", height: 300, width: "100%" }}>
<Image
src={data.files[0].path ?? ""}
style={{ objectFit: "contain" }}
alt="recognize_picture"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</Box>
</Stack>
<Stack
sx={{ flex: 1, border: 1, borderColor: "divider", borderRadius: 2, p: 1 }}
spacing={1}
alignItems={"center"}
>
<Chip size="small" label="تصویر بازدید" />
<Box sx={{ position: "relative", height: 300, width: "100%" }}>
<Image
src={data.files[1].path ?? ""}
style={{ objectFit: "contain" }}
alt="recognize_picture_second"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</Box>
</Stack>
</Stack>
</Stack>
);
};
export default ContentInfoItem;

View File

@@ -0,0 +1,51 @@
import DialogLoading from "@/core/components/DialogLoading";
import { GET_TOLL_HOUSE_DETAILS } 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_TOLL_HOUSE_DETAILS}/${selectedId}`, "get", {
signal: controller.signal,
});
setData(response.data.data);
} catch (error) {
} finally {
setLoading(false);
}
};
fetchItem();
return () => {
controller.abort();
};
}, [selectedId]);
return (
<>
<DialogTitle>جزئیات فعالیت</DialogTitle>
<DialogContent dividers>
{loading ? <DialogLoading /> : data && <ContentInfoItem data={data} />}
</DialogContent>
<DialogActions>
<Button onClick={closeDialog} variant="outlined" color="secondary" autoFocus>
بستن
</Button>
</DialogActions>
</>
);
};
export default DialogInfoItem;

View File

@@ -0,0 +1,77 @@
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 === 1) return theme.palette.warning.main;
if (status === 2) return theme.palette.error.main;
return theme.palette.success.main;
};
const markers = useMemo(() => {
return data.map(({ id, lat, lng, step }) => (
<CircleMarker
key={id}
center={L.latLng(Number(lat), Number(lng))}
radius={8}
eventHandlers={{
click: () => {
openDialog();
setSelectedId(id);
},
}}
color={getMarkerColor(Number(status))}
/>
));
}, [data, theme]);
useEffect(() => {
if (data.length === 0) return;
const bounds = L.latLngBounds(
data.map(({ lat, lng }) => {
return L.latLng(Number(lat), Number(lng));
})
);
map.flyToBounds(bounds, { duration: 0.7 });
}, [data]);
const createClusterCustomIcon = (cluster) => {
const count = cluster.getChildCount();
return L.divIcon({
html: `<div class="custom-marker-cluster">${count}</div>`,
className: "custom-marker-cluster",
iconSize: L.point(40, 40, true),
});
};
if (data.length === 0) return null;
return (
<>
{isCluster ? (
<MarkerClusterGroup iconCreateFunction={createClusterCustomIcon} showCoverageOnHover={false}>
{markers}
</MarkerClusterGroup>
) : (
markers
)}
<Dialog open={open} onClose={closeDialog} fullWidth maxWidth="sm">
{open && <DialogInfoItem selectedId={selectedId} closeDialog={closeDialog} />}
</Dialog>
</>
);
};
export default PointsOnMap;

View File

@@ -0,0 +1,231 @@
"use client";
import LoadingHardPage from "@/core/components/LoadingHardPage";
import MapLoading from "@/core/components/MapLayer/Loading";
import isArrayEmpty from "@/core/utils/isArrayEmpty";
import { GET_TOLL_HOUSE_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, useMemo, useState } from "react";
import ClusterSwitch from "./ClusterSwitch";
import Filter from "./Filter";
import Legend from "./Legend";
import PointsOnMap from "./PointsOnMap";
import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure";
import useProvinces from "@/lib/hooks/useProvince";
import CustomSelectByDependency from "@/core/components/FilterDrawer/fieldsType/CustomSelectByDependency";
import useEdaratLists from "@/lib/hooks/useEdaratLists";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => <MapLoading />,
ssr: false,
});
const typeOptions = [
{ value: "", label: "همه نوع ها" },
{ value: 0, label: "فصلی" },
{ value: 1, label: "دائمی" },
{ value: 2, label: "موقت" },
];
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 RoadSafetyReportMap = () => {
const defaultValues = useMemo(
() => [
{
header: "استان",
id: "province_id",
filterMode: "equals",
datatype: "numeric",
value: "",
enable: true,
SelectComponent: (props) => {
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
const getSelectOptions = 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 (
<CustomSelectByDependency
{...props}
value={loadingProvinces ? "loading" : props.filterParameters.value}
selectOption={getSelectOptions}
/>
);
},
},
{
eader: "اداره",
id: "edare_shahri_id",
filterMode: "equals",
datatype: "numeric",
dependencyId: "province_id",
value: "",
enable: true,
SelectComponent: (props) => {
const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(
props.dependencyFieldValue.value
);
const [prevDependency, setPrevDependency] = useState(props.dependencyFieldValue.value);
const getSelectOptions = useMemo(() => {
if (props.dependencyFieldValue.value === "") {
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 === props.dependencyFieldValue?.value) return;
props.handleChange({ ...props.filterParameters, value: "" });
setPrevDependency(props.dependencyFieldValue?.value);
}, [props.dependencyFieldValue?.value]);
return (
<CustomSelectByDependency
{...props}
value={
props.dependencyFieldValue?.value === ""
? "empty"
: loadingEdaratList
? "loading"
: props.filterParameters.value
}
selectOption={getSelectOptions}
/>
);
},
},
{
header: "نوع",
id: "type",
filterMode: "equals",
datatype: "numeric",
value: "",
enable: true,
selectOption: () => {
return typeOptions.map((status) => ({
value: status.value,
label: status.label,
}));
},
},
],
[]
);
const requestServer = useRequest();
const [filterData, setFilterData] = useState(
defaultValues.reduce((acc, item) => {
acc[item.id] = item;
return acc;
}, {})
);
const [isCluster, setCluster] = useState(false);
const [data, setData] = useState([]);
const [isLoading, setLoading] = useState(true);
const buildQueryParams = useCallback(() => {
const params = new URLSearchParams();
const filters = DataTableFilterDataStructure(filterData, isArrayEmpty);
params.set("filters", JSON.stringify(filters || []));
return params.toString();
}, [filterData]);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const params = new URLSearchParams();
const filters = DataTableFilterDataStructure(filterData, isArrayEmpty);
params.set("filters", JSON.stringify(filters || []));
const query = params.toString();
const response = await requestServer(`${GET_TOLL_HOUSE_MAP}?${query}`);
const fetchedData = response?.data?.data || [];
if (fetchedData.length > MAX_POINTS) {
setCluster(true);
}
setData(fetchedData);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};
fetchData();
}, [filterData, buildQueryParams]);
return (
<Box sx={containerStyles}>
<MapLayer style={{ borderRadius: "4px" }}>
<Stack
direction="row-reverse"
sx={{ position: "absolute", top: 0, right: 0, width: "100%", zIndex: 1000 }}
>
<Stack direction="row" spacing={1} sx={{ ...controlBarStyles, borderBottomLeftRadius: "4px" }}>
<ClusterSwitch
isCluster={isCluster}
setCluster={setCluster}
disabled={data.length > MAX_POINTS}
max={MAX_POINTS}
/>
<Filter filterData={filterData} setFilterData={setFilterData} />
</Stack>
</Stack>
<Stack
direction="row-reverse"
sx={{ position: "absolute", bottom: 0, right: 0, width: "100%", zIndex: 1000 }}
>
<Stack direction="row" spacing={1} sx={{ ...controlBarStyles, pb: 0, borderTopLeftRadius: "4px" }}>
<Legend />
</Stack>
</Stack>
{isLoading && (
<Box sx={{ position: "relative", width: "100%", height: "100%", opacity: 0.7, zIndex: 999 }}>
<LoadingHardPage width={80} height={80} loading={isLoading} sx={{ position: "absolute" }} />
</Box>
)}
<PointsOnMap data={data} isCluster={isCluster} />
</MapLayer>
</Box>
);
};
export default RoadSafetyReportMap;

View File

@@ -33,7 +33,7 @@ export const headerMenu = [
[
{
title: "پراکندگی بر روی نقشه راهدارخانه ها",
href: api + "/v2/map?type=rahdar",
href: "/infrastructure/map",
},
{
title: "راهدارخانه ها",

View File

@@ -210,6 +210,7 @@ export const GET_TOLL_HOUSE_DETAILS = api + "/api/v3/road_maintenance_station";
export const DELETE_TOLL_HOUSE_ITEM = api + "/api/v3/road_maintenance_station";
export const UPDATE_TOLL_HOUSE_ITEM = api + "/api/v3/road_maintenance_station";
export const GET_TOLL_HOUSE_IMAGES = api + "/api/v3/road_maintenance_station/images";
export const GET_TOLL_HOUSE_MAP = api + "/api/v3/road_maintenance_station/map";
// road missions
export const GET_ROAD_MISSIONS_OPERATOR_LIST = api + "/api/v3/missions/request_portal";