diff --git a/package.json b/package.json index 8a2e759..79b3f30 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@hookform/resolvers": "^3.9.0", + "@mapbox/polyline": "^1.2.1", "@mui/icons-material": "^5.16.7", "@mui/lab": "^5.0.0-alpha.173", "@mui/material": "^5.16.7", diff --git a/public/icons/destination-icon.svg b/public/icons/destination-icon.svg new file mode 100644 index 0000000..ad8a633 --- /dev/null +++ b/public/icons/destination-icon.svg @@ -0,0 +1,18 @@ + +
+ + + + + + + + + + + + + + + +
diff --git a/public/icons/origin-icon.svg b/public/icons/origin-icon.svg new file mode 100644 index 0000000..295c9da --- /dev/null +++ b/public/icons/origin-icon.svg @@ -0,0 +1,18 @@ + +
+ + + + + + + + + + + + + + + +
diff --git a/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/loading.jsx b/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/loading.jsx index 4535a8d..c3935ed 100644 --- a/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/loading.jsx +++ b/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/loading.jsx @@ -1,7 +1,7 @@ -"use client"; -import PageLoading from "@/core/components/PageLoading"; - -const Loading = () => { - return ; -}; -export default Loading; +"use client"; +import PageLoading from "@/core/components/PageLoading"; + +const Loading = () => { + return ; +}; +export default Loading; diff --git a/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/page.jsx b/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/page.jsx index 35c27ad..ad5b6c2 100644 --- a/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/page.jsx +++ b/src/app/(withAuth)/(dashboardLayout)/dashboard/machines/page.jsx @@ -1,17 +1,17 @@ -import WithPermission from "@/core/middlewares/withPermission"; -import MachinesPage from "@/components/dashboard/machines"; - -export const metadata = { - title: "کارتابل ماشین‌ها", -}; -const Page = () => { - return ( - <> - - - - - ); -}; - -export default Page; +import WithPermission from "@/core/middlewares/withPermission"; +import MachinesPage from "@/components/dashboard/machines"; + +export const metadata = { + title: "کارتابل ماشین‌ها", +}; +const Page = () => { + return ( + <> + + + + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/missionsReports/page.js b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/missionsReports/page.js index 0fae9c1..f60b32b 100644 --- a/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/missionsReports/page.js +++ b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/missionsReports/page.js @@ -1,18 +1,18 @@ -import WithPermission from "@/core/middlewares/withPermission"; -import MissionsReportPage from "@/components/dashboard/roadMissions/reports/taradodReports"; -export const metadata = { - title: "کارتابل گزارش ترددها", -}; -const Page = () => { - return ( - - - - ); -}; - -export default Page; +import WithPermission from "@/core/middlewares/withPermission"; +import MissionsReportPage from "@/components/dashboard/roadMissions/reports/taradodReports"; +export const metadata = { + title: "کارتابل گزارش ترددها", +}; +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/violationsReports/page.js b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/violationsReports/page.js index 6aa2483..66bc585 100644 --- a/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/violationsReports/page.js +++ b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/violationsReports/page.js @@ -1,18 +1,18 @@ -import ViolationsReportsPage from "@/components/dashboard/roadMissions/reports/violationsReports"; -import WithPermission from "@/core/middlewares/withPermission"; - -export const metadata = { - title: "کارتابل گزارش تخلفات", -}; - -export default function Page() { - return ( - - - - ); -} +import ViolationsReportsPage from "@/components/dashboard/roadMissions/reports/violationsReports"; +import WithPermission from "@/core/middlewares/withPermission"; + +export const metadata = { + title: "کارتابل گزارش تخلفات", +}; + +export default function Page() { + return ( + + + + ); +} diff --git a/src/assets/images/destination-icon.svg b/src/assets/images/destination-icon.svg new file mode 100644 index 0000000..ad8a633 --- /dev/null +++ b/src/assets/images/destination-icon.svg @@ -0,0 +1,18 @@ + +
+ + + + + + + + + + + + + + + +
diff --git a/src/assets/images/prev-destination-icon.svg b/src/assets/images/prev-destination-icon.svg new file mode 100644 index 0000000..656eca3 --- /dev/null +++ b/src/assets/images/prev-destination-icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/prev-source-icon.svg b/src/assets/images/prev-source-icon.svg new file mode 100644 index 0000000..621e71a --- /dev/null +++ b/src/assets/images/prev-source-icon.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/source-icon.svg b/src/assets/images/source-icon.svg new file mode 100644 index 0000000..295c9da --- /dev/null +++ b/src/assets/images/source-icon.svg @@ -0,0 +1,18 @@ + +
+ + + + + + + + + + + + + + + +
diff --git a/src/components/dashboard/machines/MachinesList.jsx b/src/components/dashboard/machines/MachinesList.jsx index 880f0cb..ce787e0 100644 --- a/src/components/dashboard/machines/MachinesList.jsx +++ b/src/components/dashboard/machines/MachinesList.jsx @@ -1,134 +1,134 @@ -"use client"; -import DataTableWithAuth from "@/core/components/DataTableWithAuth"; -import { GET_MACHINES_LIST } from "@/core/utils/routes"; -import { Box, Typography } from "@mui/material"; -import { useMemo } from "react"; -import RowActions from "./RowActions"; - -const statusOptions = [ - { value: "", label: "همه وضعیت ها" }, - { value: 1, label: "آماده" }, - { value: 2, label: "رزرو" }, - { value: 3, label: "در ماموریت" }, - { value: 4, label: "خارج از رده" }, - { value: 5, label: "در تعمیر" }, - { value: 6, label: "فروخته شده" }, - { value: 7, label: "نیاز به تعمیر" }, -]; - -const MachinesList = () => { - const columns = useMemo(() => { - return [ - { - accessorKey: "id", - header: "کد یکتا", // Unique Code - id: "id", - enableColumnFilter: true, - datatype: "text", - filterMode: "equals", - sortDescFirst: false, - columnFilterModeOptions: ["equals", "contains"], - grow: false, - size: 50, - }, - - { - header: "اطلاعات ماشین‌ها", - id: "machines_info", - enableColumnFilter: false, - enableSorting: false, - datatype: "text", - grow: false, - columns: [ - { - accessorKey: "car_name", - header: "نام ماشین", - id: "car_name", - enableColumnFilter: true, - datatype: "text", - filterMode: "equals", - sortDescFirst: false, - columnFilterModeOptions: ["equals", "contains"], - grow: false, - size: 100, - }, - { - accessorKey: "car_type", - header: "نوع ماشین", - id: "car_type", - enableColumnFilter: true, - datatype: "text", - filterMode: "equals", - sortDescFirst: false, - columnFilterModeOptions: ["equals", "contains"], - grow: false, - size: 100, - }, - { - accessorKey: "machine_code", - header: "کد ماشین", - id: "machine_code", - enableColumnFilter: true, - datatype: "text", - filterMode: "equals", - sortDescFirst: false, - columnFilterModeOptions: ["equals", "contains"], - grow: false, - size: 100, - }, - { - accessorKey: "plak_number", - header: "پلاک", - id: "plak_number", - enableColumnFilter: true, - datatype: "text", - filterMode: "equals", - sortDescFirst: false, - columnFilterModeOptions: ["equals", "contains"], - grow: false, - size: 100, - }, - { - accessorKey: "status", - header: "وضعیت", - id: "status", - enableColumnFilter: true, - datatype: "numeric", - filterMode: "equals", - grow: false, - size: 100, - columnSelectOption: () => { - return statusOptions.map((status) => ({ - value: status.value, - label: status.label, - })); - }, - Cell: ({ row }) => { - const status = statusOptions.find((option) => option.value == row.original.status); - return {status?.label || ""}; - }, - }, - ], - }, - ]; - }, []); - - return ( - <> - - - - - ); -}; -export default MachinesList; +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { GET_MACHINES_LIST } from "@/core/utils/routes"; +import { Box, Typography } from "@mui/material"; +import { useMemo } from "react"; +import RowActions from "./RowActions"; + +const statusOptions = [ + { value: "", label: "همه وضعیت ها" }, + { value: 1, label: "آماده" }, + { value: 2, label: "رزرو" }, + { value: 3, label: "در ماموریت" }, + { value: 4, label: "خارج از رده" }, + { value: 5, label: "در تعمیر" }, + { value: 6, label: "فروخته شده" }, + { value: 7, label: "نیاز به تعمیر" }, +]; + +const MachinesList = () => { + const columns = useMemo(() => { + return [ + { + accessorKey: "id", + header: "کد یکتا", // Unique Code + id: "id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 50, + }, + + { + header: "اطلاعات ماشین‌ها", + id: "machines_info", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + columns: [ + { + accessorKey: "car_name", + header: "نام ماشین", + id: "car_name", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "car_type", + header: "نوع ماشین", + id: "car_type", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "machine_code", + header: "کد ماشین", + id: "machine_code", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "plak_number", + header: "پلاک", + id: "plak_number", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "status", + header: "وضعیت", + id: "status", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + grow: false, + size: 100, + columnSelectOption: () => { + return statusOptions.map((status) => ({ + value: status.value, + label: status.label, + })); + }, + Cell: ({ row }) => { + const status = statusOptions.find((option) => option.value == row.original.status); + return {status?.label || ""}; + }, + }, + ], + }, + ]; + }, []); + + return ( + <> + + + + + ); +}; +export default MachinesList; diff --git a/src/components/dashboard/machines/RowActions/DeleteDialog/DeleteContent.jsx b/src/components/dashboard/machines/RowActions/DeleteDialog/DeleteContent.jsx index 3f3095a..bf0a3c5 100644 --- a/src/components/dashboard/machines/RowActions/DeleteDialog/DeleteContent.jsx +++ b/src/components/dashboard/machines/RowActions/DeleteDialog/DeleteContent.jsx @@ -1,39 +1,39 @@ -import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material"; -import React, { useState } from "react"; -import useRequest from "@/lib/hooks/useRequest"; -import { DELETE_TOLL_HOUSE_ITEM } from "@/core/utils/routes"; - -const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => { - const [submitting, setSubmitting] = useState(false); - const requestServer = useRequest({ notificationSuccess: true }); - const handleClick = () => { - setSubmitting(true); - requestServer(`${DELETE_TOLL_HOUSE_ITEM}/${rowId}`, "delete") - .then(() => { - mutate(); - setOpenDeleteDialog(false); - setSubmitting(false); - }) - .catch(() => { - setSubmitting(false); - }); - }; - return ( - <> - - - آیا از حذف راهدارخانه اطمینان دارید؟ - - - - - - - - ); -}; -export default DeleteContent; +import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material"; +import React, { useState } from "react"; +import useRequest from "@/lib/hooks/useRequest"; +import { DELETE_TOLL_HOUSE_ITEM } from "@/core/utils/routes"; + +const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => { + const [submitting, setSubmitting] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const handleClick = () => { + setSubmitting(true); + requestServer(`${DELETE_TOLL_HOUSE_ITEM}/${rowId}`, "delete") + .then(() => { + mutate(); + setOpenDeleteDialog(false); + setSubmitting(false); + }) + .catch(() => { + setSubmitting(false); + }); + }; + return ( + <> + + + آیا از حذف راهدارخانه اطمینان دارید؟ + + + + + + + + ); +}; +export default DeleteContent; diff --git a/src/components/dashboard/machines/RowActions/DeleteDialog/index.jsx b/src/components/dashboard/machines/RowActions/DeleteDialog/index.jsx index 54278bd..37eefdf 100644 --- a/src/components/dashboard/machines/RowActions/DeleteDialog/index.jsx +++ b/src/components/dashboard/machines/RowActions/DeleteDialog/index.jsx @@ -1,34 +1,34 @@ -import DeleteIcon from "@mui/icons-material/Delete"; -import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; -import { useState } from "react"; -import DeleteContent from "./DeleteContent"; -const DeleteDialog = ({ rowId, mutate }) => { - const [openDeleteDialog, setOpenDeleteDialog] = useState(false); - - return ( - <> - - setOpenDeleteDialog(true)}> - - - - setOpenDeleteDialog(false)} - PaperProps={{ - sx: { - boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", - }, - }} - dir="rtl" - maxWidth={"md"} - > - حذف راهدارخانه - {openDeleteDialog && ( - - )} - - - ); -}; -export default DeleteDialog; +import DeleteIcon from "@mui/icons-material/Delete"; +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import DeleteContent from "./DeleteContent"; +const DeleteDialog = ({ rowId, mutate }) => { + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + + return ( + <> + + setOpenDeleteDialog(true)}> + + + + setOpenDeleteDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + حذف راهدارخانه + {openDeleteDialog && ( + + )} + + + ); +}; +export default DeleteDialog; diff --git a/src/components/dashboard/machines/RowActions/Edit/Form/index.jsx b/src/components/dashboard/machines/RowActions/Edit/Form/index.jsx index 6cfcd9d..5608c57 100644 --- a/src/components/dashboard/machines/RowActions/Edit/Form/index.jsx +++ b/src/components/dashboard/machines/RowActions/Edit/Form/index.jsx @@ -1,281 +1,281 @@ -import StyledForm from "@/core/components/StyledForm"; -import { - GET_CITIES_LIST, - GET_PROVINCES_LIST, - GET_STATIONS_LIST, - UPDATE_MACHINE_STATUS_AND_LOCATION, -} from "@/core/utils/routes"; -import useRequest from "@/lib/hooks/useRequest"; -import { yupResolver } from "@hookform/resolvers/yup"; -import { Beenhere, ExitToApp } from "@mui/icons-material"; -import { - Button, - CircularProgress, - DialogActions, - DialogContent, - DialogTitle, - FormControl, - InputLabel, - MenuItem, - Select, - Stack, -} from "@mui/material"; -import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog"; -import { useEffect, useState } from "react"; -import { Controller, useForm, useWatch } from "react-hook-form"; -import { number, object } from "yup"; - -const statusOptions = [ - { value: 1, label: "آماده" }, - { value: 2, label: "رزرو" }, - { value: 3, label: "در ماموریت" }, - { value: 4, label: "خارج از رده" }, - { value: 5, label: "در تعمیر" }, - { value: 6, label: "فروخته شده" }, - { value: 7, label: "نیاز به تعمیر" }, -]; - -const validationSchema = object({ - status: number().required("انتخاب وضعیت الزامی است"), - province_id: number().required("انتخاب استان الزامی است"), - city_id: number().required("انتخاب شهر الزامی است"), - station_id: number().required("انتخاب راهداری الزامی است"), -}); - -const EditFormContent = ({ row, rowId, mutate, setOpen }) => { - const requestServer = useRequest(); - - const [provinces, setProvinces] = useState([]); - const [cities, setCities] = useState([]); - const [stations, setStations] = useState([]); - - const [loadingProvinces, setLoadingProvinces] = useState(false); - const [loadingCities, setLoadingCities] = useState(false); - const [loadingStations, setLoadingStations] = useState(false); - - const { - control, - handleSubmit, - setValue, - formState: { isSubmitting }, - } = useForm({ - defaultValues: { - status: row.original.status, - province_id: row.original.province_id, - city_id: row.original.city_id, - station_id: row.original.station_id, - }, - resolver: yupResolver(validationSchema), - mode: "all", - }); - - const provinceId = useWatch({ - control, - name: "province_id", - }); - - const cityId = useWatch({ - control, - name: "city_id", - }); - - const loadProvinces = async () => { - try { - setLoadingProvinces(true); - const response = await requestServer(GET_PROVINCES_LIST, "get"); - setProvinces(response.data?.data || []); - } finally { - setLoadingProvinces(false); - } - }; - - const loadCities = async (provinceId) => { - if (!provinceId) return; - - try { - setLoadingCities(true); - const response = await requestServer(`${GET_CITIES_LIST}?province_id=${provinceId}`, "get"); - setCities(response.data?.data || []); - } finally { - setLoadingCities(false); - } - }; - - const loadStations = async (cityId) => { - if (!cityId) return; - - try { - setLoadingStations(true); - const response = await requestServer(`${GET_STATIONS_LIST}/search_by_city?city_id=${cityId}`, "get"); - setStations(response.data?.data || []); - } finally { - setLoadingStations(false); - } - }; - - useEffect(() => { - loadProvinces(); - }, []); - - /** - * Initial edit mode loading - */ - useEffect(() => { - if (row.original.province_id) { - loadCities(row.original.province_id); - } - - if (row.original.city_id) { - loadStations(row.original.city_id); - } - }, []); - - useEffect(() => { - if (!provinceId) return; - - loadCities(provinceId); - - if (provinceId !== row.original.province_id) { - setValue("city_id", ""); - setValue("station_id", ""); - setStations([]); - } - }, [provinceId]); - - useEffect(() => { - if (!cityId) return; - - loadStations(cityId); - - if (cityId !== row.original.city_id) { - setValue("station_id", ""); - } - }, [cityId]); - - const onSubmit = async (data) => { - const formData = new FormData(); - - formData.append("status", String(data.status)); - formData.append("province_id", String(data.province_id)); - formData.append("city_id", String(data.city_id)); - formData.append("station_id", String(data.station_id)); - - try { - await requestServer(`${UPDATE_MACHINE_STATUS_AND_LOCATION}/${rowId}`, "post", { - data: formData, - }); - - mutate(); - setOpen(false); - } catch (error) {} - }; - - return ( - <> - - ویرایش ماشین - - - - - - ( - - استان - - - - )} - /> - - ( - - شهر - - - - )} - /> - - ( - - راهداری - - - - )} - /> - ( - - وضعیت - - - - )} - /> - - {(loadingCities || loadingStations) && ( - - - - )} - - - - - - - - - - - ); -}; - -export default EditFormContent; +import StyledForm from "@/core/components/StyledForm"; +import { + GET_CITIES_LIST, + GET_PROVINCES_LIST, + GET_STATIONS_LIST, + UPDATE_MACHINE_STATUS_AND_LOCATION, +} from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Beenhere, ExitToApp } from "@mui/icons-material"; +import { + Button, + CircularProgress, + DialogActions, + DialogContent, + DialogTitle, + FormControl, + InputLabel, + MenuItem, + Select, + Stack, +} from "@mui/material"; +import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog"; +import { useEffect, useState } from "react"; +import { Controller, useForm, useWatch } from "react-hook-form"; +import { number, object } from "yup"; + +const statusOptions = [ + { value: 1, label: "آماده" }, + { value: 2, label: "رزرو" }, + { value: 3, label: "در ماموریت" }, + { value: 4, label: "خارج از رده" }, + { value: 5, label: "در تعمیر" }, + { value: 6, label: "فروخته شده" }, + { value: 7, label: "نیاز به تعمیر" }, +]; + +const validationSchema = object({ + status: number().required("انتخاب وضعیت الزامی است"), + province_id: number().required("انتخاب استان الزامی است"), + city_id: number().required("انتخاب شهر الزامی است"), + station_id: number().required("انتخاب راهداری الزامی است"), +}); + +const EditFormContent = ({ row, rowId, mutate, setOpen }) => { + const requestServer = useRequest(); + + const [provinces, setProvinces] = useState([]); + const [cities, setCities] = useState([]); + const [stations, setStations] = useState([]); + + const [loadingProvinces, setLoadingProvinces] = useState(false); + const [loadingCities, setLoadingCities] = useState(false); + const [loadingStations, setLoadingStations] = useState(false); + + const { + control, + handleSubmit, + setValue, + formState: { isSubmitting }, + } = useForm({ + defaultValues: { + status: row.original.status, + province_id: row.original.province_id, + city_id: row.original.city_id, + station_id: row.original.station_id, + }, + resolver: yupResolver(validationSchema), + mode: "all", + }); + + const provinceId = useWatch({ + control, + name: "province_id", + }); + + const cityId = useWatch({ + control, + name: "city_id", + }); + + const loadProvinces = async () => { + try { + setLoadingProvinces(true); + const response = await requestServer(GET_PROVINCES_LIST, "get"); + setProvinces(response.data?.data || []); + } finally { + setLoadingProvinces(false); + } + }; + + const loadCities = async (provinceId) => { + if (!provinceId) return; + + try { + setLoadingCities(true); + const response = await requestServer(`${GET_CITIES_LIST}?province_id=${provinceId}`, "get"); + setCities(response.data?.data || []); + } finally { + setLoadingCities(false); + } + }; + + const loadStations = async (cityId) => { + if (!cityId) return; + + try { + setLoadingStations(true); + const response = await requestServer(`${GET_STATIONS_LIST}/search_by_city?city_id=${cityId}`, "get"); + setStations(response.data?.data || []); + } finally { + setLoadingStations(false); + } + }; + + useEffect(() => { + loadProvinces(); + }, []); + + /** + * Initial edit mode loading + */ + useEffect(() => { + if (row.original.province_id) { + loadCities(row.original.province_id); + } + + if (row.original.city_id) { + loadStations(row.original.city_id); + } + }, []); + + useEffect(() => { + if (!provinceId) return; + + loadCities(provinceId); + + if (provinceId !== row.original.province_id) { + setValue("city_id", ""); + setValue("station_id", ""); + setStations([]); + } + }, [provinceId]); + + useEffect(() => { + if (!cityId) return; + + loadStations(cityId); + + if (cityId !== row.original.city_id) { + setValue("station_id", ""); + } + }, [cityId]); + + const onSubmit = async (data) => { + const formData = new FormData(); + + formData.append("status", String(data.status)); + formData.append("province_id", String(data.province_id)); + formData.append("city_id", String(data.city_id)); + formData.append("station_id", String(data.station_id)); + + try { + await requestServer(`${UPDATE_MACHINE_STATUS_AND_LOCATION}/${rowId}`, "post", { + data: formData, + }); + + mutate(); + setOpen(false); + } catch (error) {} + }; + + return ( + <> + + ویرایش ماشین + + + + + + ( + + استان + + + + )} + /> + + ( + + شهر + + + + )} + /> + + ( + + راهداری + + + + )} + /> + ( + + وضعیت + + + + )} + /> + + {(loadingCities || loadingStations) && ( + + + + )} + + + + + + + + + + + ); +}; + +export default EditFormContent; diff --git a/src/components/dashboard/machines/RowActions/Edit/index.jsx b/src/components/dashboard/machines/RowActions/Edit/index.jsx index f3cec95..b777d54 100644 --- a/src/components/dashboard/machines/RowActions/Edit/index.jsx +++ b/src/components/dashboard/machines/RowActions/Edit/index.jsx @@ -1,44 +1,44 @@ -import { SwapHoriz } from "@mui/icons-material"; -import CloseIcon from "@mui/icons-material/Close"; -import { Dialog, IconButton, Tooltip } from "@mui/material"; -import { useState } from "react"; -import EditFormContent from "./Form"; - -const Edit = ({ row, mutate, rowId }) => { - const [openEditDialog, setOpenEditDialog] = useState(false); - - return ( - <> - - { - setOpenEditDialog(true); - }} - > - - - - - setOpenEditDialog(false)} - sx={(theme) => ({ - position: "absolute", - right: 8, - top: 8, - zIndex: 50, - color: theme.palette.grey[500], - })} - > - - - {openEditDialog && ( - - )} - - - ); -}; -export default Edit; +import { SwapHoriz } from "@mui/icons-material"; +import CloseIcon from "@mui/icons-material/Close"; +import { Dialog, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import EditFormContent from "./Form"; + +const Edit = ({ row, mutate, rowId }) => { + const [openEditDialog, setOpenEditDialog] = useState(false); + + return ( + <> + + { + setOpenEditDialog(true); + }} + > + + + + + setOpenEditDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openEditDialog && ( + + )} + + + ); +}; +export default Edit; diff --git a/src/components/dashboard/machines/RowActions/index.jsx b/src/components/dashboard/machines/RowActions/index.jsx index 526fe10..1e1d4eb 100644 --- a/src/components/dashboard/machines/RowActions/index.jsx +++ b/src/components/dashboard/machines/RowActions/index.jsx @@ -1,14 +1,14 @@ -import { usePermissions } from "@/lib/hooks/usePermissions"; -import { Box } from "@mui/material"; -import Edit from "./Edit"; - -const RowActions = ({ row, mutate }) => { - const { data: userPermissions } = usePermissions(); - const hasEditPermission = userPermissions.some((item) => ["update-machine"].includes(item)); - return ( - - {hasEditPermission && } - - ); -}; -export default RowActions; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { Box } from "@mui/material"; +import Edit from "./Edit"; + +const RowActions = ({ row, mutate }) => { + const { data: userPermissions } = usePermissions(); + const hasEditPermission = userPermissions.some((item) => ["update-machine"].includes(item)); + return ( + + {hasEditPermission && } + + ); +}; +export default RowActions; diff --git a/src/components/dashboard/machines/ShowProjectArea/ShowBound.jsx b/src/components/dashboard/machines/ShowProjectArea/ShowBound.jsx index 24db476..43dd0b8 100644 --- a/src/components/dashboard/machines/ShowProjectArea/ShowBound.jsx +++ b/src/components/dashboard/machines/ShowProjectArea/ShowBound.jsx @@ -1,29 +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 ; -}; -export default ShowBound; +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 ; +}; +export default ShowBound; diff --git a/src/components/dashboard/machines/ShowProjectArea/index.jsx b/src/components/dashboard/machines/ShowProjectArea/index.jsx index 250cd52..29f2697 100644 --- a/src/components/dashboard/machines/ShowProjectArea/index.jsx +++ b/src/components/dashboard/machines/ShowProjectArea/index.jsx @@ -1,65 +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 ( - - - setOpenShowAreaDialog(true)}> - - - - 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" - > - - - محدوده طرح - - setOpenShowAreaDialog(false)} size="small"> - - - - - - - - - - - - - - - ); -}; - -export default ShowProjectArea; +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 ( + + + setOpenShowAreaDialog(true)}> + + + + 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" + > + + + محدوده طرح + + setOpenShowAreaDialog(false)} size="small"> + + + + + + + + + + + + + + + ); +}; + +export default ShowProjectArea; diff --git a/src/components/dashboard/machines/index.jsx b/src/components/dashboard/machines/index.jsx index ed3db7c..6074488 100644 --- a/src/components/dashboard/machines/index.jsx +++ b/src/components/dashboard/machines/index.jsx @@ -1,14 +1,14 @@ -"use client"; -import PageTitle from "@/core/components/PageTitle"; -import { Stack } from "@mui/material"; -import MachinesList from "./MachinesList"; - -const MachinesPage = () => { - return ( - - - - - ); -}; -export default MachinesPage; +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { Stack } from "@mui/material"; +import MachinesList from "./MachinesList"; + +const MachinesPage = () => { + return ( + + + + + ); +}; +export default MachinesPage; diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/MapControlPolyline/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/MapControlPolyline/index.jsx index 8d9b5d0..0a14736 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/MapControlPolyline/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/MapControlPolyline/index.jsx @@ -1,116 +1,113 @@ -import { Delete, Route } from "@mui/icons-material"; -import { Box, Button, Stack, Typography } from "@mui/material"; -import { useReducer } from "react"; -import DrawBound from "./DrawBound"; +"use client"; -const statusType = [ - { - id: 0, - message: "برای آغاز ترسیم مسیر، کلیک کنید!", - buttons: [ - { - label: "شروع ترسیم مسیر", - key: "start", - color: "primary", - icon: , - onclick: (controlDispach) => { - controlDispach({ type: "SET_STATUS", status: 1 }); - }, - }, - ], - }, - { - id: 1, - message: "مسیر موردنظر را با کلیک روی نقشه مشخص کنید. برای اتمام، روی نقطه‌ی پایانی کلیک کنید!", - buttons: [], - }, - { - id: 2, - message: "برای اصلاح مسیر، گوشه‌ها را بکشید. برای ترسیم دوباره، آن را حذف کنید", - buttons: [ - { - label: "حذف", - key: "end", - color: "error", - icon: , - onclick: (controlDispach) => { - controlDispach({ type: "SET_STATUS", status: 0 }); - }, - }, - ], - }, -]; +import { useEffect, useState } from "react"; +import { Marker, Polyline, useMap } from "react-leaflet"; +import L from "leaflet"; -const createInitialState = (bound) => { - if (bound) { - return { status: 2 }; - } - return { status: 0 }; -}; +// ===== Icons ===== +const originIcon = new L.Icon({ + iconUrl: "/icons/origin-icon.svg", + iconSize: [32, 32], + iconAnchor: [16, 32], +}); -const reducer = (state, action) => { - switch (action.type) { - case "SET_STATUS": - return { ...state, status: action.status }; - default: - return state; - } -}; +const destinationIcon = new L.Icon({ + iconUrl: "/icons/destination-icon.svg", + iconSize: [32, 32], + iconAnchor: [16, 32], +}); -const MapControlPolyline = ({ bound, setBound }) => { - const [control, controlDispach] = useReducer(reducer, bound, createInitialState); +// ===== Track Center ===== +function MapCenterTracker({ setCenter }) { + const map = useMap(); + + useEffect(() => { + const updateCenter = () => { + setCenter(map.getCenter()); + }; + + updateCenter(); + + map.on("move", updateCenter); + + return () => { + map.off("move", updateCenter); + }; + }, [map, setCenter]); + + return null; +} + +// ===== Fixed Center Marker ===== +function FixedCenterMarker() { + return ( +
+ center +
+ ); +} + +export default function Example() { + const [center, setCenter] = useState(null); + + const [origin, setOrigin] = useState(null); + const [destination, setDestination] = useState(null); + + const [step, setStep] = useState("origin"); + + const handleOk = () => { + if (!center) return; + + if (step === "origin") { + setOrigin(center); + setStep("destination"); + return; + } + + if (step === "destination") { + setDestination(center); + setStep("done"); + } + }; return ( - <> - - - theme.palette.info.main, - borderBottomLeftRadius: 8, - borderBottomRightRadius: 8, - py: 0.5, - px: 2, - }} - > - - {statusType.find((st) => st.id == control.status).message} - - - - +
- - {statusType - .find((st) => st.id == control.status) - .buttons.map((button) => ( - - ))} - - - + + + {/* saved markers */} + {origin && } + + {destination && } + + {/* route line */} + {origin && destination && } + + {/* fixed center pin */} + {step !== "done" && } +
+ +
+ +
+ ); -}; -export default MapControlPolyline; +} diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/AlarmText.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/AlarmText.jsx new file mode 100644 index 0000000..0ddf8a0 --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/AlarmText.jsx @@ -0,0 +1,28 @@ +import { Box, Typography } from "@mui/material"; + +const AlarmText = ({ isSource }) => { + if (isSource) return null; + return ( + + + با کلیک بر روی مارکر موقعیت را انتخاب کنید + + + ); +}; + +export default AlarmText; diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/DataBox.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/DataBox.jsx new file mode 100644 index 0000000..5768f75 --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/DataBox.jsx @@ -0,0 +1,53 @@ +import { Box, Button, TextField } from "@mui/material"; + +const DataBox = ({ isSource, isDest, sourcePosition, destPosition, editingTarget, onEditSource, onEditDest }) => { + if (!isSource) return null; + + return ( + +
+ + {isSource && isDest && ( + + )} +
+ {isDest && ( +
+ + +
+ )} +
+ ); +}; + +export default DataBox; diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/ShowRoute.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/ShowRoute.jsx new file mode 100644 index 0000000..7d47ba8 --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/ShowRoute.jsx @@ -0,0 +1,46 @@ +import { useEffect, useRef } from "react"; +import { Marker, Polyline, useMap } from "react-leaflet"; +import L from "leaflet"; +import polyline from "@mapbox/polyline"; +import SourceIcon from "@/assets/images/source-icon.svg"; +import DestIcon from "@/assets/images/destination-icon.svg"; + +const ShowRoute = ({ area }) => { + const map = useMap(); + const defaultIconSize = [35, 35]; + + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + useEffect(() => { + if (coords.length > 0) { + map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 }); + } + }, [area]); + const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src); + const destIcon = createCustomIcon(defaultIconSize, DestIcon.src); + + if (!area) return null; + const coords = polyline.decode(area, 6); + + if (coords.length === 0) return null; + + const sourcePosition = coords[0]; + const destPosition = coords[coords.length - 1]; + + return ( + <> + + + + + ); +}; + +export default ShowRoute; diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/index.jsx new file mode 100644 index 0000000..49c4651 --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/Routing/index.jsx @@ -0,0 +1,219 @@ +import { Marker, Polyline, useMapEvents } from "react-leaflet"; +import L from "leaflet"; +import polyline from "@mapbox/polyline"; +import SourceIcon from "@/assets/images/source-icon.svg"; +import PrevSourceIcon from "@/assets/images/prev-source-icon.svg"; +import PrevDestIcon from "@/assets/images/prev-destination-icon.svg"; +import DestIcon from "@/assets/images/destination-icon.svg"; +import { useEffect, useRef, useState } from "react"; +import DataBox from "./DataBox"; +import AlarmText from "./AlarmText"; + +const Routing = ({ setArea, setLocations, locations, area }) => { + const mapPrevSourceMarker = useRef(); + const mapPrevDestMarker = useRef(); + const mapRef = useRef(null); + const defaultIconSize = [35, 35]; + const [isSource, setIsSource] = useState(false); + const [sourcePosition, setSourcePosition] = useState(null); + const [isDest, setIsDest] = useState(false); + const [destPosition, setDestPosition] = useState(null); + const [routeCoords, setRouteCoords] = useState([]); + const [alternateCoords, setAlternateCoords] = useState([]); + const [editingTarget, setEditingTarget] = useState(null); + const [selectedRoute, setSelectedRoute] = useState(null); + + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + const defaultIcon = createCustomIcon( + defaultIconSize, + isSource && editingTarget !== "source" ? SourceIcon.src : PrevSourceIcon.src + ); + + const prevDestIcon = createCustomIcon( + defaultIconSize, + isDest && editingTarget !== "dest" ? DestIcon.src : PrevDestIcon.src + ); + + const isMovingSource = !isSource || editingTarget === "source"; + const isMovingDest = isSource && (!isDest || editingTarget === "dest"); + + const map = useMapEvents({ + move(e) { + if (isMovingSource) mapPrevSourceMarker.current.setLatLng(e.target.getCenter()); + if (isMovingDest && mapPrevDestMarker.current) mapPrevDestMarker.current.setLatLng(e.target.getCenter()); + }, + movestart() { + if (isMovingSource) mapPrevSourceMarker.current.setIcon(createCustomIcon([45, 45], PrevSourceIcon.src)); + if (isMovingDest && mapPrevDestMarker.current) + mapPrevDestMarker.current.setIcon(createCustomIcon([45, 45], PrevDestIcon.src)); + }, + moveend() { + if (isMovingSource) + mapPrevSourceMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevSourceIcon.src)); + if (isMovingDest && mapPrevDestMarker.current) + mapPrevDestMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevDestIcon.src)); + }, + }); + + useEffect(() => { + mapRef.current = map; + }, [map]); + + useEffect(() => { + if (locations.length < 2) return; + const restoredSource = { lat: locations[0].lat, lng: locations[0].lon }; + const restoredDest = { lat: locations[1].lat, lng: locations[1].lon }; + setSourcePosition(restoredSource); + setDestPosition(restoredDest); + setIsSource(true); + setIsDest(true); + if (area) { + const decoded = polyline.decode(area, 6); + setRouteCoords(decoded); + setSelectedRoute({ index: "main", coords: decoded }); + setTimeout(() => { + mapRef.current?.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); + }, 100); + } else { + fetchRoute(restoredSource, restoredDest); + } + }, []); + + const fetchRoute = async (source, dest) => { + const locs = [ + { lat: source.lat, lon: source.lng }, + { lat: dest.lat, lon: dest.lng }, + ]; + try { + const response = await fetch("https://jouya.141.ir/api/router/drive", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ locations: locs }), + }); + const data = await response.json(); + const shape = data.trip.legs[0].shape; + setLocations(locs); + setArea(shape); + const decoded = polyline.decode(shape, 6); + setRouteCoords(decoded); + setSelectedRoute({ index: "main", coords: decoded }); + + if (data.alternates?.length > 0) { + const decodedAlternates = data.alternates.map((alt) => polyline.decode(alt.trip.legs[0].shape, 6)); + setAlternateCoords(decodedAlternates); + } else { + setAlternateCoords([]); + } + map.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); + } catch (error) { + console.error("Routing error:", error); + } + }; + + const handleMarkerClick = async () => { + if (!isSource || editingTarget === "source") { + const clickedPosition = mapPrevSourceMarker.current.getLatLng(); + setSourcePosition(clickedPosition); + setIsSource(true); + setEditingTarget(null); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + + if (editingTarget === "source" && destPosition) { + await fetchRoute(clickedPosition, destPosition); + } + } + }; + + const handleDestMarkerClick = async () => { + if (!isDest || editingTarget === "dest") { + const clickedPosition = mapPrevDestMarker.current.getLatLng(); + setDestPosition(clickedPosition); + setIsDest(true); + setEditingTarget(null); + + await fetchRoute(sourcePosition, clickedPosition); + } + }; + + const handleEditSource = () => { + setEditingTarget("source"); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + }; + + const handleEditDest = () => { + setEditingTarget("dest"); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + }; + + const handleSelectRoute = (index, coords) => { + setSelectedRoute({ index, coords }); + const encoded = polyline.encode(coords, 6); + setArea(encoded); + map.flyToBounds(coords, { padding: [50, 50], duration: 0.3 }); + }; + + return ( + <> + + + + {isSource && ( + + )} + {alternateCoords.map((coords, index) => ( + handleSelectRoute(index, coords) }} + /> + ))} + {routeCoords.length > 0 && ( + handleSelectRoute("main", routeCoords) }} + /> + )} + + ); +}; + +export default Routing; diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/index.jsx index a4240b3..200c1e0 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Area/index.jsx @@ -2,19 +2,21 @@ import MapLoading from "@/core/components/MapLayer/Loading"; import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material"; import dynamic from "next/dynamic"; import { useCallback, useState } from "react"; -import MapControlPolyline from "./MapControlPolyline"; +import Routing from "./Routing"; const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { loading: () => , ssr: false, }); const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - const [bound, setBound] = useState(allData.bound); + const [area, setArea] = useState(allData.area); + const [locations, setLocations] = useState(allData.locations); const handleNext = useCallback(() => { - setAllData({ bound: bound }); + setAllData({ area: area }); + setAllData({ locations: locations }); setTabState((s) => s + 1); - }, [bound]); + }, [area]); return ( <> @@ -22,7 +24,7 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - + @@ -31,8 +33,8 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx index 57515c4..e48883e 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/Form/Verify/index.jsx @@ -7,7 +7,7 @@ import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typogr import moment from "jalali-moment"; import dynamic from "next/dynamic"; import { useCallback } from "react"; -import ShowBound from "../../../showBound"; +import ShowRoute from "../Area/Routing/ShowRoute"; const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { loading: () => , ssr: false, @@ -23,11 +23,16 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => { - - - - - + + + + + + + + + + diff --git a/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx b/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx index 3726f9e..2595dca 100644 --- a/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx +++ b/src/components/dashboard/roadMissions/operator/Actions/Create/index.jsx @@ -19,8 +19,6 @@ const Create = ({ mutate }) => { const submitForm = async (result) => { setSubmitting(true); - const bound = result.bound.getLatLngs(); - let area = bound.map((latlng) => [latlng.lng, latlng.lat]); await requestServer(REQUEST_MISSION, "post", { data: { @@ -31,10 +29,7 @@ const Create = ({ mutate }) => { road_observed_id: result.road_observed_id, } : {}), - area: { - type: result.bound_type, - coordinates: area, - }, + encoded_route: result.area, ...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}), requested_machines: result.requested_machines, type: result.type, @@ -101,8 +96,8 @@ const Create = ({ mutate }) => { road_observed_id: "", rahdaran: [], requested_machines: [], - bound: null, - bound_type: "polyline", + area: null, + locations: [], type: "", start_date: "", start_time: null, diff --git a/src/components/dashboard/roadMissions/operator/OperatorList.jsx b/src/components/dashboard/roadMissions/operator/OperatorList.jsx index e343866..8c277e3 100644 --- a/src/components/dashboard/roadMissions/operator/OperatorList.jsx +++ b/src/components/dashboard/roadMissions/operator/OperatorList.jsx @@ -1,21 +1,21 @@ "use client"; +import DescriptionDialog from "@/components/dashboard/roadMissions/operator/RowActions/DescriptionDialog"; +import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; import DataTableWithAuth from "@/core/components/DataTableWithAuth"; import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes"; import { missionRegions } from "@/core/utils/missionRegions"; import { missionTypes } from "@/core/utils/missionTypes"; import { GET_ROAD_MISSIONS_OPERATOR_LIST } from "@/core/utils/routes"; +import useCities from "@/lib/hooks/useCities"; +import useProvinces from "@/lib/hooks/useProvince"; import { Box, Stack } from "@mui/material"; import moment from "jalali-moment"; import { useMemo } from "react"; -import ShowArea from "./Actions/showArea"; import RowActions from "./RowActions"; import MachinesDialog from "./RowActions/Machines"; -import Toolbar from "./Toolbar"; import RahdaranDialog from "./RowActions/Rahdaran"; -import DescriptionDialog from "@/components/dashboard/roadMissions/operator/RowActions/DescriptionDialog"; -import useProvinces from "@/lib/hooks/useProvince"; -import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; -import useCities from "@/lib/hooks/useCities"; +import RouteDetail from "./RowActions/RouteDetail"; +import Toolbar from "./Toolbar"; const OperatorList = () => { const columns = useMemo(() => { @@ -247,7 +247,7 @@ const OperatorList = () => { }, { header: "منطقه عملیاتی", - id: "area", + id: "encoded_route", enableColumnFilter: false, datatype: "numeric", filterMode: "equals", @@ -263,9 +263,9 @@ const OperatorList = () => { }, }, Cell: ({ row }) => - row.original.area ? ( + row.original.encoded_route ? ( - + ) : ( "-" diff --git a/src/components/dashboard/roadMissions/operator/RowActions/RouteDetail/index.jsx b/src/components/dashboard/roadMissions/operator/RowActions/RouteDetail/index.jsx new file mode 100644 index 0000000..10a3f0b --- /dev/null +++ b/src/components/dashboard/roadMissions/operator/RowActions/RouteDetail/index.jsx @@ -0,0 +1,57 @@ +import { Box, Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import MapIcon from "@mui/icons-material/Map"; +import MapLayer from "@/core/components/MapLayer"; +import ShowRoute from "../../Actions/Create/Form/Area/Routing/ShowRoute"; + +const RouteDetail = ({ encoded_route }) => { + const [openRouteDetailDialog, setOpenRouteDetailDialog] = useState(false); + return ( + <> + + setOpenRouteDetailDialog(true)}> + + + + setOpenRouteDetailDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"sm"} + > + مسیر انتخابی + + + + + + + + + + + + + ); +}; +export default RouteDetail; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/ExcelPrint/index.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/ExcelPrint/index.jsx index b398579..938d1e7 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/ExcelPrint/index.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/ExcelPrint/index.jsx @@ -1,77 +1,77 @@ -"use client"; -import { - EXPORT_MISSIONS_REPORT_TARADOD_COUNTRY_EXCEL_ACTIVITY, - EXPORT_MISSIONS_REPORT_TARADOD_PROVINCE_EXCEL_ACTIVITY, -} from "@/core/utils/routes"; -import useRequest from "@/lib/hooks/useRequest"; -import { useTheme } from "@emotion/react"; -import DescriptionIcon from "@mui/icons-material/Description"; -import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; -import FileSaver from "file-saver"; -import moment from "jalali-moment"; -import { useMemo, useState } from "react"; - -const PrintExcel = ({ table, filterData }) => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down("sm")); - const [loading, setLoading] = useState(false); - const requestServer = useRequest({ notificationSuccess: true }); - const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { - if (filter.value) { - acc.push({ id: filter.id, 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); - const CountryOrProvince = activeFilters.find((item) => item.id === "province_id"); - const requestUrl = - CountryOrProvince.value === "-1" - ? EXPORT_MISSIONS_REPORT_TARADOD_COUNTRY_EXCEL_ACTIVITY - : EXPORT_MISSIONS_REPORT_TARADOD_PROVINCE_EXCEL_ACTIVITY; - requestServer(`${requestUrl}?${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 ? ( - - - - ) : ( - - )} - - ); -}; -export default PrintExcel; +"use client"; +import { + EXPORT_MISSIONS_REPORT_TARADOD_COUNTRY_EXCEL_ACTIVITY, + EXPORT_MISSIONS_REPORT_TARADOD_PROVINCE_EXCEL_ACTIVITY, +} from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { useTheme } from "@emotion/react"; +import DescriptionIcon from "@mui/icons-material/Description"; +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import FileSaver from "file-saver"; +import moment from "jalali-moment"; +import { useMemo, useState } from "react"; + +const PrintExcel = ({ table, filterData }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [loading, setLoading] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { + if (filter.value) { + acc.push({ id: filter.id, 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); + const CountryOrProvince = activeFilters.find((item) => item.id === "province_id"); + const requestUrl = + CountryOrProvince.value === "-1" + ? EXPORT_MISSIONS_REPORT_TARADOD_COUNTRY_EXCEL_ACTIVITY + : EXPORT_MISSIONS_REPORT_TARADOD_PROVINCE_EXCEL_ACTIVITY; + requestServer(`${requestUrl}?${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 ? ( + + + + ) : ( + + )} + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/MissionsReportLists.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/MissionsReportLists.jsx index 12de496..a80cabb 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/MissionsReportLists.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/MissionsReportLists.jsx @@ -1,138 +1,138 @@ -"use client"; -import DataTableWithAuth from "@/core/components/DataTableWithAuth"; -import { Box } from "@mui/material"; -import { useEffect, useMemo } from "react"; -import Toolbar from "./Toolbar"; - -const TaradodReportLists = ({ data, specialFilter }) => { - const columns = useMemo(() => { - return [ - { - accessorKey: "edare_name", - header: "اداره کل", - id: "edare_name", - enableColumnFilter: false, - enableSorting: false, - datatype: "text", - grow: false, - size: 50, - }, - { - accessorKey: "total", - header: "کل ماموریت ها", - id: "total", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "khareg_mahdode", - header: "تردد خارج از محدوده", - id: "khareg_mahdode", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "durations", - header: "جمع ساعات ماموریت", - id: "duration", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "gps", - header: "جمع ساعات gps", - id: "gps", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "far_h", - header: "با فرایند ساعتی", - id: "far_h", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "far_d", - header: "با فرایند روزانه", - id: "far_d", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "un_far_h", - header: "بدون فرایند ساعتی", - id: "un_far_h", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "un_far_d", - header: "بدون فرایند روزانه", - id: "un_far_d", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "item", - header: "فعالیت روزانه", - id: "item", - enableColumnFilter: false, - enableSorting: false, - datatype: "text", - grow: false, - size: 50, - }, - { - accessorKey: "patrol", - header: "گشت راهداری", - id: "patrol", - enableColumnFilter: false, - enableSorting: false, - datatype: "text", - grow: false, - size: 50, - }, - ]; - }, []); - - return ( - - - - ); -}; - -export default TaradodReportLists; +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; +import { useEffect, useMemo } from "react"; +import Toolbar from "./Toolbar"; + +const TaradodReportLists = ({ data, specialFilter }) => { + const columns = useMemo(() => { + return [ + { + accessorKey: "edare_name", + header: "اداره کل", + id: "edare_name", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + { + accessorKey: "total", + header: "کل ماموریت ها", + id: "total", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "khareg_mahdode", + header: "تردد خارج از محدوده", + id: "khareg_mahdode", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "durations", + header: "جمع ساعات ماموریت", + id: "duration", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "gps", + header: "جمع ساعات gps", + id: "gps", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "far_h", + header: "با فرایند ساعتی", + id: "far_h", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "far_d", + header: "با فرایند روزانه", + id: "far_d", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "un_far_h", + header: "بدون فرایند ساعتی", + id: "un_far_h", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "un_far_d", + header: "بدون فرایند روزانه", + id: "un_far_d", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "item", + header: "فعالیت روزانه", + id: "item", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + { + accessorKey: "patrol", + header: "گشت راهداری", + id: "patrol", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + ]; + }, []); + + return ( + + + + ); +}; + +export default TaradodReportLists; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/Search/FromDateController.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/Search/FromDateController.jsx index 490ee7b..0a81488 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/Search/FromDateController.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/Search/FromDateController.jsx @@ -1,27 +1,27 @@ -import MuiDatePicker from "@/core/components/MuiDatePicker"; -import moment from "jalali-moment"; -import { Controller } from "react-hook-form"; - -const FromDateController = ({ control }) => { - return ( - ( - { - const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); - onChange(formattedDate); // Update the field value in react-hook-form - }} - helperText={error ? error.message : null} - /> - )} - /> - ); -}; -export default FromDateController; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import moment from "jalali-moment"; +import { Controller } from "react-hook-form"; + +const FromDateController = ({ control }) => { + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default FromDateController; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/Search/SearchReportField.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/Search/SearchReportField.jsx index bde2a4c..e1b71cd 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/Search/SearchReportField.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/Search/SearchReportField.jsx @@ -1,44 +1,44 @@ -import SearchIcon from "@mui/icons-material/Search"; -import { Button, Grid } from "@mui/material"; -import { Controller } from "react-hook-form"; -import FromDateController from "./FromDateController"; -import SelectProvince from "./SelectProvince"; -import ToDateController from "./ToDateController"; - -const SearchReportField = ({ control, hasProvincesPermission }) => { - return ( - - - - - - - - {hasProvincesPermission && ( - - - - )} - - ( - - )} - /> - - - ); -}; - -export default SearchReportField; +import SearchIcon from "@mui/icons-material/Search"; +import { Button, Grid } from "@mui/material"; +import { Controller } from "react-hook-form"; +import FromDateController from "./FromDateController"; +import SelectProvince from "./SelectProvince"; +import ToDateController from "./ToDateController"; + +const SearchReportField = ({ control, hasProvincesPermission }) => { + return ( + + + + + + + + {hasProvincesPermission && ( + + + + )} + + ( + + )} + /> + + + ); +}; + +export default SearchReportField; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/Search/SelectProvince.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/Search/SelectProvince.jsx index 2b24d67..89395f0 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/Search/SelectProvince.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/Search/SelectProvince.jsx @@ -1,45 +1,45 @@ -import useProvinces from "@/lib/hooks/useProvince"; -import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material"; -import { Controller } from "react-hook-form"; - -const SelectProvince = ({ control }) => { - const { provinces, loadingProvinces, errorProvinces } = useProvinces(); - return ( - ( - - استان - - {error && {error.message}} - - )} - /> - ); -}; -export default SelectProvince; +import useProvinces from "@/lib/hooks/useProvince"; +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material"; +import { Controller } from "react-hook-form"; + +const SelectProvince = ({ control }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + ( + + استان + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/Search/ToDateController.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/Search/ToDateController.jsx index fc4a776..ef3ff49 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/Search/ToDateController.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/Search/ToDateController.jsx @@ -1,30 +1,30 @@ -import MuiDatePicker from "@/core/components/MuiDatePicker"; -import moment from "jalali-moment"; -import { Controller, useWatch } from "react-hook-form"; - -const ToDateController = ({ control }) => { - const minDate = useWatch({ control, name: "from_date" }); - return ( - ( - { - const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); - onChange(formattedDate); // Update the field value in react-hook-form - }} - helperText={error ? error.message : null} - /> - )} - /> - ); -}; -export default ToDateController; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import moment from "jalali-moment"; +import { Controller, useWatch } from "react-hook-form"; + +const ToDateController = ({ control }) => { + const minDate = useWatch({ control, name: "from_date" }); + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default ToDateController; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/Search/index.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/Search/index.jsx index f29d302..9d504f2 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/Search/index.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/Search/index.jsx @@ -1,13 +1,13 @@ -import StyledForm from "@/core/components/StyledForm"; -import SearchReportField from "./SearchReportField"; - -const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { - return ( - <> - - - - - ); -}; -export default SearchReportList; +import StyledForm from "@/core/components/StyledForm"; +import SearchReportField from "./SearchReportField"; + +const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { + return ( + <> + + + + + ); +}; +export default SearchReportList; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/Toolbar.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/Toolbar.jsx index 0f726c7..afc132c 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/Toolbar.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/Toolbar.jsx @@ -1,11 +1,11 @@ -import { Box } from "@mui/material"; -import PrintExcel from "./ExcelPrint"; - -const Toolbar = ({ table, filterData, mutate }) => { - return ( - - - - ); -}; -export default Toolbar; +import { Box } from "@mui/material"; +import PrintExcel from "./ExcelPrint"; + +const Toolbar = ({ table, filterData, mutate }) => { + return ( + + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/roadMissions/reports/taradodReports/index.jsx b/src/components/dashboard/roadMissions/reports/taradodReports/index.jsx index ce41101..1af4608 100644 --- a/src/components/dashboard/roadMissions/reports/taradodReports/index.jsx +++ b/src/components/dashboard/roadMissions/reports/taradodReports/index.jsx @@ -1,142 +1,142 @@ -"use client"; -import PageTitle from "@/core/components/PageTitle"; -import { - GET_MISSIONS_REPORT_TARADOD_COUNTRY_ACTIVITY, - GET_MISSIONS_REPORT_TARADOD_PROVINCE_ACTIVITY, -} from "@/core/utils/routes"; -import { useAuth } from "@/lib/contexts/auth"; -import { usePermissions } from "@/lib/hooks/usePermissions"; -import useRequest from "@/lib/hooks/useRequest"; -import { yupResolver } from "@hookform/resolvers/yup"; -import { LinearProgress, Stack } from "@mui/material"; -import moment from "jalali-moment"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import * as yup from "yup"; -import TaradodReportLists from "./MissionsReportLists"; -import SearchReportList from "./Search"; - -const TaradodReportPage = () => { - const [data, setData] = useState(null); - const { data: userPermissions } = usePermissions(); - const { user } = useAuth(); - const requestServer = useRequest(); - const [provinceId, setProvinceId] = useState(user.province_id || "-1"); - const hasProvincesPermission = userPermissions?.includes("mission-report-country"); - - const defaultValues = { - from_date: moment(new Date()).format("YYYY-MM-DD"), - date_to: moment(new Date()).format("YYYY-MM-DD"), - province_id: provinceId, - }; - const defaultFilter = [ - { value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, - { value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" }, - { value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" }, - ]; - const [specialFilter, setSpecialFilter] = useState(defaultFilter); - - const onSearchSubmit = async (data) => { - const params = new URLSearchParams(); - params.set("from_date", `${data.from_date}`); - params.set("date_to", `${data.date_to}`); - setSpecialFilter([ - { value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, - { value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" }, - { value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" }, - ]); - if (data.province_id === "-1") { - try { - // params.set("province_id", `${data.province_id}`); - const response = await requestServer(`${GET_MISSIONS_REPORT_TARADOD_COUNTRY_ACTIVITY}?${params}`); - const result = response.data.data; - const nationalReportForItem = result.activities.find((report) => report.province_id === "-1"); - const nationalReport = { - edare_name: "کل کشور", - ...nationalReportForItem, - }; - - setData({ - data: [ - nationalReport, - ...result.provinces.map((province) => { - const filteredReports = result.activities.find( - (report) => report.province_id == province.id - ); - return { - ...filteredReports, - edare_name: province.name_fa, - }; - }), - ], - filters: data, - }); - } catch (e) {} - } else { - try { - params.set("province_id", `${data.province_id}`); - const response = await requestServer(`${GET_MISSIONS_REPORT_TARADOD_PROVINCE_ACTIVITY}?${params}`); - const result = response.data.data; - - const nationalReportForItem = result.activities.find((report) => report.city_id === "-1"); - - const nationalReport = { - ...nationalReportForItem, - edare_name: "کل استان", - }; - setData({ - data: [ - nationalReport, - ...result.city.map((city) => { - const filteredReports = result.activities.find((report) => report.city_id == city.id); - return { - edare_name: city.name_fa, - ...filteredReports, - }; - }), - ], - filters: data, - }); - } catch (e) {} - } - }; - - useEffect(() => { - if (!userPermissions) return; - onSearchSubmit(defaultValues); - }, [userPermissions]); - - const validationSchema = yup.object().shape({ - from_date: yup - .string() - .required("تاریخ شروع الزامی است!") - .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") - .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), - date_to: yup - .string() - .required("تاریخ پایان الزامی است!") - .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") - .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), - province_id: yup.string().nullable(), - }); - - const { control, handleSubmit } = useForm({ - defaultValues, - resolver: yupResolver(validationSchema), - mode: "all", - }); - - return ( - - - - {!data ? : } - - ); -}; -export default TaradodReportPage; +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { + GET_MISSIONS_REPORT_TARADOD_COUNTRY_ACTIVITY, + GET_MISSIONS_REPORT_TARADOD_PROVINCE_ACTIVITY, +} from "@/core/utils/routes"; +import { useAuth } from "@/lib/contexts/auth"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import useRequest from "@/lib/hooks/useRequest"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { LinearProgress, Stack } from "@mui/material"; +import moment from "jalali-moment"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import * as yup from "yup"; +import TaradodReportLists from "./MissionsReportLists"; +import SearchReportList from "./Search"; + +const TaradodReportPage = () => { + const [data, setData] = useState(null); + const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); + const requestServer = useRequest(); + const [provinceId, setProvinceId] = useState(user.province_id || "-1"); + const hasProvincesPermission = userPermissions?.includes("mission-report-country"); + + const defaultValues = { + from_date: moment(new Date()).format("YYYY-MM-DD"), + date_to: moment(new Date()).format("YYYY-MM-DD"), + province_id: provinceId, + }; + const defaultFilter = [ + { value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]; + const [specialFilter, setSpecialFilter] = useState(defaultFilter); + + const onSearchSubmit = async (data) => { + const params = new URLSearchParams(); + params.set("from_date", `${data.from_date}`); + params.set("date_to", `${data.date_to}`); + setSpecialFilter([ + { value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]); + if (data.province_id === "-1") { + try { + // params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_MISSIONS_REPORT_TARADOD_COUNTRY_ACTIVITY}?${params}`); + const result = response.data.data; + const nationalReportForItem = result.activities.find((report) => report.province_id === "-1"); + const nationalReport = { + edare_name: "کل کشور", + ...nationalReportForItem, + }; + + setData({ + data: [ + nationalReport, + ...result.provinces.map((province) => { + const filteredReports = result.activities.find( + (report) => report.province_id == province.id + ); + return { + ...filteredReports, + edare_name: province.name_fa, + }; + }), + ], + filters: data, + }); + } catch (e) {} + } else { + try { + params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_MISSIONS_REPORT_TARADOD_PROVINCE_ACTIVITY}?${params}`); + const result = response.data.data; + + const nationalReportForItem = result.activities.find((report) => report.city_id === "-1"); + + const nationalReport = { + ...nationalReportForItem, + edare_name: "کل استان", + }; + setData({ + data: [ + nationalReport, + ...result.city.map((city) => { + const filteredReports = result.activities.find((report) => report.city_id == city.id); + return { + edare_name: city.name_fa, + ...filteredReports, + }; + }), + ], + filters: data, + }); + } catch (e) {} + } + }; + + useEffect(() => { + if (!userPermissions) return; + onSearchSubmit(defaultValues); + }, [userPermissions]); + + const validationSchema = yup.object().shape({ + from_date: yup + .string() + .required("تاریخ شروع الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") + .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + date_to: yup + .string() + .required("تاریخ پایان الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") + .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + province_id: yup.string().nullable(), + }); + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "all", + }); + + return ( + + + + {!data ? : } + + ); +}; +export default TaradodReportPage; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/ExcelPrint/index.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/ExcelPrint/index.jsx index b7487f6..3968fff 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/ExcelPrint/index.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/ExcelPrint/index.jsx @@ -1,77 +1,77 @@ -"use client"; -import { - EXPORT_MISSIONS_REPORT_VIOLATION_COUNTRY_EXCEL_ACTIVITY, - EXPORT_MISSIONS_REPORT_VIOLATION_PROVINCE_EXCEL_ACTIVITY, -} from "@/core/utils/routes"; -import useRequest from "@/lib/hooks/useRequest"; -import { useTheme } from "@emotion/react"; -import DescriptionIcon from "@mui/icons-material/Description"; -import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; -import FileSaver from "file-saver"; -import moment from "jalali-moment"; -import { useMemo, useState } from "react"; - -const PrintExcel = ({ table, filterData }) => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down("sm")); - const [loading, setLoading] = useState(false); - const requestServer = useRequest({ notificationSuccess: true }); - const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { - if (filter.value) { - acc.push({ id: filter.id, 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); - const CountryOrProvince = activeFilters.find((item) => item.id === "province_id"); - const requestUrl = - CountryOrProvince.value === "-1" - ? EXPORT_MISSIONS_REPORT_VIOLATION_COUNTRY_EXCEL_ACTIVITY - : EXPORT_MISSIONS_REPORT_VIOLATION_PROVINCE_EXCEL_ACTIVITY; - requestServer(`${requestUrl}?${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 ? ( - - - - ) : ( - - )} - - ); -}; -export default PrintExcel; +"use client"; +import { + EXPORT_MISSIONS_REPORT_VIOLATION_COUNTRY_EXCEL_ACTIVITY, + EXPORT_MISSIONS_REPORT_VIOLATION_PROVINCE_EXCEL_ACTIVITY, +} from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { useTheme } from "@emotion/react"; +import DescriptionIcon from "@mui/icons-material/Description"; +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import FileSaver from "file-saver"; +import moment from "jalali-moment"; +import { useMemo, useState } from "react"; + +const PrintExcel = ({ table, filterData }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [loading, setLoading] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { + if (filter.value) { + acc.push({ id: filter.id, 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); + const CountryOrProvince = activeFilters.find((item) => item.id === "province_id"); + const requestUrl = + CountryOrProvince.value === "-1" + ? EXPORT_MISSIONS_REPORT_VIOLATION_COUNTRY_EXCEL_ACTIVITY + : EXPORT_MISSIONS_REPORT_VIOLATION_PROVINCE_EXCEL_ACTIVITY; + requestServer(`${requestUrl}?${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 ? ( + + + + ) : ( + + )} + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/Search/FromDateController.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/Search/FromDateController.jsx index 490ee7b..0a81488 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/Search/FromDateController.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/Search/FromDateController.jsx @@ -1,27 +1,27 @@ -import MuiDatePicker from "@/core/components/MuiDatePicker"; -import moment from "jalali-moment"; -import { Controller } from "react-hook-form"; - -const FromDateController = ({ control }) => { - return ( - ( - { - const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); - onChange(formattedDate); // Update the field value in react-hook-form - }} - helperText={error ? error.message : null} - /> - )} - /> - ); -}; -export default FromDateController; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import moment from "jalali-moment"; +import { Controller } from "react-hook-form"; + +const FromDateController = ({ control }) => { + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default FromDateController; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/Search/SearchReportField.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/Search/SearchReportField.jsx index bde2a4c..e1b71cd 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/Search/SearchReportField.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/Search/SearchReportField.jsx @@ -1,44 +1,44 @@ -import SearchIcon from "@mui/icons-material/Search"; -import { Button, Grid } from "@mui/material"; -import { Controller } from "react-hook-form"; -import FromDateController from "./FromDateController"; -import SelectProvince from "./SelectProvince"; -import ToDateController from "./ToDateController"; - -const SearchReportField = ({ control, hasProvincesPermission }) => { - return ( - - - - - - - - {hasProvincesPermission && ( - - - - )} - - ( - - )} - /> - - - ); -}; - -export default SearchReportField; +import SearchIcon from "@mui/icons-material/Search"; +import { Button, Grid } from "@mui/material"; +import { Controller } from "react-hook-form"; +import FromDateController from "./FromDateController"; +import SelectProvince from "./SelectProvince"; +import ToDateController from "./ToDateController"; + +const SearchReportField = ({ control, hasProvincesPermission }) => { + return ( + + + + + + + + {hasProvincesPermission && ( + + + + )} + + ( + + )} + /> + + + ); +}; + +export default SearchReportField; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/Search/SelectProvince.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/Search/SelectProvince.jsx index 2b24d67..89395f0 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/Search/SelectProvince.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/Search/SelectProvince.jsx @@ -1,45 +1,45 @@ -import useProvinces from "@/lib/hooks/useProvince"; -import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material"; -import { Controller } from "react-hook-form"; - -const SelectProvince = ({ control }) => { - const { provinces, loadingProvinces, errorProvinces } = useProvinces(); - return ( - ( - - استان - - {error && {error.message}} - - )} - /> - ); -}; -export default SelectProvince; +import useProvinces from "@/lib/hooks/useProvince"; +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material"; +import { Controller } from "react-hook-form"; + +const SelectProvince = ({ control }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + ( + + استان + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/Search/ToDateController.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/Search/ToDateController.jsx index 2dd7263..9b9ac81 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/Search/ToDateController.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/Search/ToDateController.jsx @@ -1,30 +1,30 @@ -import MuiDatePicker from "@/core/components/MuiDatePicker"; -import moment from "jalali-moment"; -import { Controller, useWatch } from "react-hook-form"; - -const ToDateController = ({ control }) => { - const minDate = useWatch({ control, name: "from_date" }); - return ( - ( - { - const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); - onChange(formattedDate); // Update the field value in react-hook-form - }} - helperText={error ? error.message : null} - /> - )} - /> - ); -}; -export default ToDateController; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import moment from "jalali-moment"; +import { Controller, useWatch } from "react-hook-form"; + +const ToDateController = ({ control }) => { + const minDate = useWatch({ control, name: "from_date" }); + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default ToDateController; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/Search/index.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/Search/index.jsx index f29d302..9d504f2 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/Search/index.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/Search/index.jsx @@ -1,13 +1,13 @@ -import StyledForm from "@/core/components/StyledForm"; -import SearchReportField from "./SearchReportField"; - -const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { - return ( - <> - - - - - ); -}; -export default SearchReportList; +import StyledForm from "@/core/components/StyledForm"; +import SearchReportField from "./SearchReportField"; + +const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { + return ( + <> + + + + + ); +}; +export default SearchReportList; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/Toolbar.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/Toolbar.jsx index 0f726c7..afc132c 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/Toolbar.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/Toolbar.jsx @@ -1,11 +1,11 @@ -import { Box } from "@mui/material"; -import PrintExcel from "./ExcelPrint"; - -const Toolbar = ({ table, filterData, mutate }) => { - return ( - - - - ); -}; -export default Toolbar; +import { Box } from "@mui/material"; +import PrintExcel from "./ExcelPrint"; + +const Toolbar = ({ table, filterData, mutate }) => { + return ( + + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/ViolationsReportsList.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/ViolationsReportsList.jsx index 60966f8..61f7282 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/ViolationsReportsList.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/ViolationsReportsList.jsx @@ -1,98 +1,98 @@ -"use client"; -import DataTableWithAuth from "@/core/components/DataTableWithAuth"; -import { Box } from "@mui/material"; -import { useEffect, useMemo } from "react"; -import Toolbar from "./Toolbar"; - -const ViolationsReportsList = ({ data, specialFilter }) => { - const columns = useMemo(() => { - return [ - { - accessorKey: "edare_name", - header: "اداره کل", - id: "edare_name", - enableColumnFilter: false, - enableSorting: false, - datatype: "text", - grow: false, - size: 50, - }, - { - accessorKey: "total", - header: "کل تخلفات", - id: "total", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "done", - header: "اقدام شده", - id: "done", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "undone", - header: "اقدام نشده", - id: "un_done", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "gps", - header: "GPS", - id: "gps", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "herasat", - header: "ثبت حراست", - id: "herasat", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - { - accessorKey: "mil_gps", - header: "کیلومتر GPS", - id: "mil_gps", - enableColumnFilter: false, - enableSorting: false, - datatype: "numeric", - grow: false, - size: 50, - }, - ]; - }, []); - - return ( - - - - ); -}; - -export default ViolationsReportsList; +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; +import { useEffect, useMemo } from "react"; +import Toolbar from "./Toolbar"; + +const ViolationsReportsList = ({ data, specialFilter }) => { + const columns = useMemo(() => { + return [ + { + accessorKey: "edare_name", + header: "اداره کل", + id: "edare_name", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + { + accessorKey: "total", + header: "کل تخلفات", + id: "total", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "done", + header: "اقدام شده", + id: "done", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "undone", + header: "اقدام نشده", + id: "un_done", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "gps", + header: "GPS", + id: "gps", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "herasat", + header: "ثبت حراست", + id: "herasat", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + { + accessorKey: "mil_gps", + header: "کیلومتر GPS", + id: "mil_gps", + enableColumnFilter: false, + enableSorting: false, + datatype: "numeric", + grow: false, + size: 50, + }, + ]; + }, []); + + return ( + + + + ); +}; + +export default ViolationsReportsList; diff --git a/src/components/dashboard/roadMissions/reports/violationsReports/index.jsx b/src/components/dashboard/roadMissions/reports/violationsReports/index.jsx index 37d765f..d7f9a5a 100644 --- a/src/components/dashboard/roadMissions/reports/violationsReports/index.jsx +++ b/src/components/dashboard/roadMissions/reports/violationsReports/index.jsx @@ -1,141 +1,141 @@ -"use client"; -import PageTitle from "@/core/components/PageTitle"; -import { - GET_MISSIONS_REPORT_VIOLATION_COUNTRY_ACTIVITY, - GET_MISSIONS_REPORT_VIOLATION_PROVINCE_ACTIVITY, -} from "@/core/utils/routes"; -import { useAuth } from "@/lib/contexts/auth"; -import { usePermissions } from "@/lib/hooks/usePermissions"; -import useRequest from "@/lib/hooks/useRequest"; -import { yupResolver } from "@hookform/resolvers/yup"; -import { LinearProgress, Stack } from "@mui/material"; -import moment from "jalali-moment"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import * as yup from "yup"; -import ViolationsReportsList from "./ViolationsReportsList"; -import SearchReportList from "./Search"; - -const ViolationsReportsPage = () => { - const [data, setData] = useState(null); - const { data: userPermissions } = usePermissions(); - const { user } = useAuth(); - const requestServer = useRequest(); - const hasProvincesPermission = userPermissions?.includes("mission-report-country"); - - const defaultValues = { - from_date: moment(new Date()).format("YYYY-MM-DD"), - date_to: moment(new Date()).format("YYYY-MM-DD"), - province_id: user.province_id || "-1", - }; - const defaultFilter = [ - { value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, - { value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" }, - { value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" }, - ]; - const [specialFilter, setSpecialFilter] = useState(defaultFilter); - - const onSearchSubmit = async (data) => { - const params = new URLSearchParams(); - params.set("from_date", `${data.from_date}`); - params.set("date_to", `${data.date_to}`); - setSpecialFilter([ - { value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, - { value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" }, - { value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" }, - ]); - if (data.province_id === "-1") { - try { - // params.set("province_id", `${data.province_id}`); - const response = await requestServer(`${GET_MISSIONS_REPORT_VIOLATION_COUNTRY_ACTIVITY}?${params}`); - const result = response.data.data; - const nationalReportForItem = result.activities.find((report) => report.province_id === "-1"); - const nationalReport = { - edare_name: "کل کشور", - ...nationalReportForItem, - }; - - setData({ - data: [ - nationalReport, - ...result.provinces.map((province) => { - const filteredReports = result.activities.find( - (report) => report.province_id == province.id - ); - return { - ...filteredReports, - edare_name: province.name_fa, - }; - }), - ], - filters: data, - }); - } catch (e) {} - } else { - try { - params.set("province_id", `${data.province_id}`); - const response = await requestServer(`${GET_MISSIONS_REPORT_VIOLATION_PROVINCE_ACTIVITY}?${params}`); - const result = response.data.data; - - const nationalReportForItem = result.activities.find((report) => report.city_id === "-1"); - - const nationalReport = { - ...nationalReportForItem, - edare_name: "کل استان", - }; - setData({ - data: [ - nationalReport, - ...result.city.map((city) => { - const filteredReports = result.activities.find((report) => report.city_id == city.id); - return { - edare_name: city.name_fa, - ...filteredReports, - }; - }), - ], - filters: data, - }); - } catch (e) {} - } - }; - - useEffect(() => { - if (!userPermissions) return; - onSearchSubmit(defaultValues); - }, [userPermissions]); - - const validationSchema = yup.object().shape({ - from_date: yup - .string() - .required("تاریخ شروع الزامی است!") - .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") - .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), - date_to: yup - .string() - .required("تاریخ پایان الزامی است!") - .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") - .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), - province_id: yup.string().nullable(), - }); - - const { control, handleSubmit } = useForm({ - defaultValues, - resolver: yupResolver(validationSchema), - mode: "all", - }); - - return ( - - - - {!data ? : } - - ); -}; -export default ViolationsReportsPage; +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { + GET_MISSIONS_REPORT_VIOLATION_COUNTRY_ACTIVITY, + GET_MISSIONS_REPORT_VIOLATION_PROVINCE_ACTIVITY, +} from "@/core/utils/routes"; +import { useAuth } from "@/lib/contexts/auth"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import useRequest from "@/lib/hooks/useRequest"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { LinearProgress, Stack } from "@mui/material"; +import moment from "jalali-moment"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import * as yup from "yup"; +import ViolationsReportsList from "./ViolationsReportsList"; +import SearchReportList from "./Search"; + +const ViolationsReportsPage = () => { + const [data, setData] = useState(null); + const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); + const requestServer = useRequest(); + const hasProvincesPermission = userPermissions?.includes("mission-report-country"); + + const defaultValues = { + from_date: moment(new Date()).format("YYYY-MM-DD"), + date_to: moment(new Date()).format("YYYY-MM-DD"), + province_id: user.province_id || "-1", + }; + const defaultFilter = [ + { value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]; + const [specialFilter, setSpecialFilter] = useState(defaultFilter); + + const onSearchSubmit = async (data) => { + const params = new URLSearchParams(); + params.set("from_date", `${data.from_date}`); + params.set("date_to", `${data.date_to}`); + setSpecialFilter([ + { value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]); + if (data.province_id === "-1") { + try { + // params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_MISSIONS_REPORT_VIOLATION_COUNTRY_ACTIVITY}?${params}`); + const result = response.data.data; + const nationalReportForItem = result.activities.find((report) => report.province_id === "-1"); + const nationalReport = { + edare_name: "کل کشور", + ...nationalReportForItem, + }; + + setData({ + data: [ + nationalReport, + ...result.provinces.map((province) => { + const filteredReports = result.activities.find( + (report) => report.province_id == province.id + ); + return { + ...filteredReports, + edare_name: province.name_fa, + }; + }), + ], + filters: data, + }); + } catch (e) {} + } else { + try { + params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_MISSIONS_REPORT_VIOLATION_PROVINCE_ACTIVITY}?${params}`); + const result = response.data.data; + + const nationalReportForItem = result.activities.find((report) => report.city_id === "-1"); + + const nationalReport = { + ...nationalReportForItem, + edare_name: "کل استان", + }; + setData({ + data: [ + nationalReport, + ...result.city.map((city) => { + const filteredReports = result.activities.find((report) => report.city_id == city.id); + return { + edare_name: city.name_fa, + ...filteredReports, + }; + }), + ], + filters: data, + }); + } catch (e) {} + } + }; + + useEffect(() => { + if (!userPermissions) return; + onSearchSubmit(defaultValues); + }, [userPermissions]); + + const validationSchema = yup.object().shape({ + from_date: yup + .string() + .required("تاریخ شروع الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") + .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + date_to: yup + .string() + .required("تاریخ پایان الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") + .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + province_id: yup.string().nullable(), + }); + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "all", + }); + + return ( + + + + {!data ? : } + + ); +}; +export default ViolationsReportsPage; diff --git a/src/components/dashboard/roadMissions/transportation/RowActions/Allocation/Form/index.jsx b/src/components/dashboard/roadMissions/transportation/RowActions/Allocation/Form/index.jsx index c35018d..be001cc 100644 --- a/src/components/dashboard/roadMissions/transportation/RowActions/Allocation/Form/index.jsx +++ b/src/components/dashboard/roadMissions/transportation/RowActions/Allocation/Form/index.jsx @@ -14,16 +14,10 @@ import MachinesDialog from "./MachinesDialog"; import Reject from "./Reject"; import SelectBox from "@/core/components/SelectBox"; import { Controller, useForm } from "react-hook-form"; +import RouteDetail from "../../RouteDetail"; +import ShowRoute from "../../RouteDetail/ShowRoute"; const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => { - const latLngs = useMemo(() => { - return row.area.coordinates.map(([lng, lat]) => [lat, lng]); - }, [row.area.coordinates]); - - const bound = useMemo( - () => (row.area.type === "polygon" ? L.polygon(latLngs) : L.polyline(latLngs)), - [row.area.type, latLngs] - ); const requestServer = useRequest({ notificationSuccess: true }); const [machine, setMachine] = useState(); const [driver, setDriver] = useState(); @@ -180,10 +174,32 @@ const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => { )} - - - - + + {row.encoded_route ? ( + + + + ) : ( + + مختصات برای این ماموریت در دسترس نمیباشد + + )} diff --git a/src/components/dashboard/roadMissions/transportation/RowActions/RouteDetail/ShowRoute.jsx b/src/components/dashboard/roadMissions/transportation/RowActions/RouteDetail/ShowRoute.jsx new file mode 100644 index 0000000..7d47ba8 --- /dev/null +++ b/src/components/dashboard/roadMissions/transportation/RowActions/RouteDetail/ShowRoute.jsx @@ -0,0 +1,46 @@ +import { useEffect, useRef } from "react"; +import { Marker, Polyline, useMap } from "react-leaflet"; +import L from "leaflet"; +import polyline from "@mapbox/polyline"; +import SourceIcon from "@/assets/images/source-icon.svg"; +import DestIcon from "@/assets/images/destination-icon.svg"; + +const ShowRoute = ({ area }) => { + const map = useMap(); + const defaultIconSize = [35, 35]; + + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + useEffect(() => { + if (coords.length > 0) { + map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 }); + } + }, [area]); + const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src); + const destIcon = createCustomIcon(defaultIconSize, DestIcon.src); + + if (!area) return null; + const coords = polyline.decode(area, 6); + + if (coords.length === 0) return null; + + const sourcePosition = coords[0]; + const destPosition = coords[coords.length - 1]; + + return ( + <> + + + + + ); +}; + +export default ShowRoute; diff --git a/src/components/dashboard/roadMissions/transportation/RowActions/RouteDetail/index.jsx b/src/components/dashboard/roadMissions/transportation/RowActions/RouteDetail/index.jsx new file mode 100644 index 0000000..48b84a2 --- /dev/null +++ b/src/components/dashboard/roadMissions/transportation/RowActions/RouteDetail/index.jsx @@ -0,0 +1,57 @@ +import { Box, Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import MapIcon from "@mui/icons-material/Map"; +import MapLayer from "@/core/components/MapLayer"; +import ShowRoute from "./ShowRoute"; + +const RouteDetail = ({ encoded_route }) => { + const [openRouteDetailDialog, setOpenRouteDetailDialog] = useState(false); + return ( + <> + + setOpenRouteDetailDialog(true)}> + + + + setOpenRouteDetailDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"sm"} + > + مسیر انتخابی + + + + + + + + + + + + + ); +}; +export default RouteDetail; diff --git a/src/components/dashboard/roadMissions/transportation/TransportationList.jsx b/src/components/dashboard/roadMissions/transportation/TransportationList.jsx index ab735d2..51872c9 100644 --- a/src/components/dashboard/roadMissions/transportation/TransportationList.jsx +++ b/src/components/dashboard/roadMissions/transportation/TransportationList.jsx @@ -4,13 +4,13 @@ import { GET_ROAD_MISSIONS_TRANSPORTATION_LIST } from "@/core/utils/routes"; import { Box, Stack } from "@mui/material"; import moment from "jalali-moment"; import { useMemo } from "react"; -import ShowArea from "./Actions/showArea"; import RowActions from "./RowActions"; import { missionRegions } from "@/core/utils/missionRegions"; import { missionTypes } from "@/core/utils/missionTypes"; import useProvinces from "@/lib/hooks/useProvince"; import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; import useCities from "@/lib/hooks/useCities"; +import RouteDetail from "./RowActions/RouteDetail"; const TransportationList = () => { const columns = useMemo(() => { @@ -230,9 +230,9 @@ const TransportationList = () => { }, }, Cell: ({ row }) => - row.original.area ? ( + row.original.encoded_route ? ( - + ) : ( "-" diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/DrawBound/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/DrawBound/index.jsx deleted file mode 100644 index 5a253a9..0000000 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/DrawBound/index.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import L from "leaflet"; -import { FeatureGroup, useMap } from "react-leaflet"; -import "leaflet-draw"; - -const DrawBound = ({ control, controlDispach, bound, setBound }) => { - const map = useMap(); - const featureRef = useRef(null); - const [area, setArea] = useState(); - const drawControlRef = useRef(null); - - const safeFlyToBounds = (layer) => { - if (!layer || !map) return; - try { - const latLngs = layer.getLatLngs(); - map.flyToBounds(latLngs, { - paddingTopLeft: [20, 35], - paddingBottomRight: [20, 55], - }); - } catch (e) { - console.warn("flyToBounds failed:", e); - } - }; - - const safeFitBounds = (layer) => { - if (!layer || !map) return; - try { - const latLngs = layer.getLatLngs(); - map.fitBounds(latLngs, { - paddingTopLeft: [20, 35], - paddingBottomRight: [20, 55], - }); - } catch (e) { - console.warn("fitBounds failed:", e); - } - }; - - const bindEditEvent = (layer) => { - if (!layer) return; - layer.on("edit", function (e) { - const editedLayer = e.target; - setArea(editedLayer); - setBound(editedLayer); - safeFlyToBounds(editedLayer); - }); - }; - - const handlerCreatedBound = useCallback((event) => { - const { layer } = event; - layer.editing.enable(); - featureRef.current.addLayer(layer); // 🟢 اول اضافه به نقشه - setArea(layer); - setBound(layer); - safeFlyToBounds(layer); - bindEditEvent(layer); - controlDispach({ type: "SET_STATUS", status: 2 }); - }, []); - - useEffect(() => { - if ( - control.status === 1 && - drawControlRef.current && - drawControlRef.current._toolbars?.draw?._modes?.polygon?.handler - ) { - drawControlRef.current._toolbars.draw._modes.polygon.handler.enable(); - } - }, [control.status]); - - useEffect(() => { - if (control.status === 0 && area && featureRef.current) { - featureRef.current.removeLayer(area); - setArea(null); - setBound(null); - } - }, [control.status, area]); - - useEffect(() => { - if (control.status === 2 && bound && featureRef.current) { - setArea(bound); - featureRef.current.addLayer(bound); - bound.editing.enable(); - safeFitBounds(bound); - bindEditEvent(bound); - } - }, [control.status, bound]); - - useEffect(() => { - if (!featureRef.current) return; - - L.drawLocal.draw.handlers.polygon.tooltip.start = "برای شروع ترسیم محدوده، روی نقشه کلیک کنید"; - L.drawLocal.draw.handlers.polygon.tooltip.cont = "برای ادامه ترسیم، نقاط بعدی را کلیک کنید"; - L.drawLocal.draw.handlers.polygon.tooltip.end = "برای بستن محدوده، روی نقطه‌ی شروع کلیک کنید"; - - const drawControl = new L.Control.Draw({ - draw: { - polygon: { - shapeOptions: { - color: "#3388ff", - weight: 3, - clickable: true, - }, - }, - polyline: false, - rectangle: false, - circle: false, - marker: false, - circlemarker: false, - }, - edit: { - featureGroup: featureRef.current, - edit: true, - remove: false, - }, - }); - - drawControlRef.current = drawControl; - map.addControl(drawControl); - map.on(L.Draw.Event.CREATED, handlerCreatedBound); - - return () => { - map.off(L.Draw.Event.CREATED, handlerCreatedBound); - map.removeControl(drawControl); - }; - }, [map, handlerCreatedBound]); - - return ; -}; - -export default DrawBound; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/index.jsx deleted file mode 100644 index 718600c..0000000 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/index.jsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Delete, Route } from "@mui/icons-material"; -import { Box, Button, Stack, Typography } from "@mui/material"; -import { useReducer } from "react"; -import DrawBound from "./DrawBound"; - -const statusType = [ - { - id: 0, - message: "برای آغاز ترسیم محدوده، کلیک کنید!", - buttons: [ - { - label: "شروع ترسیم محدوده", - key: "start", - color: "primary", - icon: , - onclick: (controlDispach) => { - controlDispach({ type: "SET_STATUS", status: 1 }); - }, - }, - ], - }, - { - id: 1, - message: "محدوده‌ی موردنظر را با کلیک روی نقشه مشخص کنید. برای اتمام، روی نقطه‌ی ابتدایی کلیک کنید!", - buttons: [], - }, - { - id: 2, - message: "برای اصلاح محدوده، گوشه‌ها را بکشید. برای ترسیم دوباره، آن را حذف کنید", - buttons: [ - { - label: "حذف", - key: "end", - color: "error", - icon: , - onclick: (controlDispach) => { - controlDispach({ type: "SET_STATUS", status: 0 }); - }, - }, - ], - }, -]; - -const createInitialState = (bound) => { - if (bound) { - return { status: 2 }; - } - return { status: 0 }; -}; - -const reducer = (state, action) => { - switch (action.type) { - case "SET_STATUS": - return { ...state, status: action.status }; - default: - return state; - } -}; - -const MapControlPolygon = ({ bound, setBound }) => { - const [control, controlDispach] = useReducer(reducer, bound, createInitialState); - - return ( - <> - - - theme.palette.info.main, - borderBottomLeftRadius: 8, - borderBottomRightRadius: 8, - py: 0.5, - px: 2, - }} - > - - {statusType.find((st) => st.id == control.status).message} - - - - - - {statusType - .find((st) => st.id == control.status) - .buttons.map((button) => ( - - ))} - - - - ); -}; -export default MapControlPolygon; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/DrawBound/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/DrawBound/index.jsx deleted file mode 100644 index 5456d12..0000000 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/DrawBound/index.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import L from "leaflet"; -import { FeatureGroup, useMap } from "react-leaflet"; -import "leaflet-draw"; - -const DrawBound = ({ control, controlDispach, bound, setBound }) => { - const map = useMap(); - const featureRef = useRef(null); - const [area, setArea] = useState(); - const drawControlRef = useRef(null); - - const safeFlyToBounds = (layer) => { - if (!layer || !map) return; - try { - const latLngs = layer.getLatLngs(); - map.flyToBounds(latLngs, { - paddingTopLeft: [20, 35], - paddingBottomRight: [20, 55], - }); - } catch (e) { - console.warn("flyToBounds failed:", e); - } - }; - - const safeFitBounds = (layer) => { - if (!layer || !map) return; - try { - const latLngs = layer.getLatLngs(); - map.fitBounds(latLngs, { - paddingTopLeft: [20, 35], - paddingBottomRight: [20, 55], - }); - } catch (e) { - console.warn("fitBounds failed:", e); - } - }; - - const bindEditEvent = (layer) => { - if (!layer) return; - layer.on("edit", function (e) { - const editedLayer = e.target; - setArea(editedLayer); - setBound(editedLayer); - safeFlyToBounds(editedLayer); - }); - }; - - const handlerCreatedBound = useCallback((event) => { - const { layer } = event; - layer.editing.enable(); - featureRef.current.addLayer(layer); // 🟢 اول اضافه به نقشه - setArea(layer); - setBound(layer); - safeFlyToBounds(layer); - bindEditEvent(layer); - controlDispach({ type: "SET_STATUS", status: 2 }); - }, []); - - useEffect(() => { - if ( - control.status === 1 && - drawControlRef.current && - drawControlRef.current._toolbars?.draw?._modes?.polyline?.handler - ) { - drawControlRef.current._toolbars.draw._modes.polyline.handler.enable(); - } - }, [control.status]); - - useEffect(() => { - if (control.status === 0 && area && featureRef.current) { - featureRef.current.removeLayer(area); - setArea(null); - setBound(null); - } - }, [control.status, area]); - - useEffect(() => { - if (control.status === 2 && bound && featureRef.current) { - setArea(bound); - featureRef.current.addLayer(bound); - bound.editing.enable(); - safeFitBounds(bound); - bindEditEvent(bound); - } - }, [control.status, bound]); - - useEffect(() => { - if (!featureRef.current) return; - - L.drawLocal.draw.handlers.polyline.tooltip.start = "برای شروع ترسیم مسیر، روی نقشه کلیک کنید"; - L.drawLocal.draw.handlers.polyline.tooltip.cont = "برای ادامه ترسیم، نقاط بعدی را کلیک کنید"; - L.drawLocal.draw.handlers.polyline.tooltip.end = "برای بستن مسیر، روی نقطه پایانی کلیک کنید"; - - const drawControl = new L.Control.Draw({ - draw: { - polyline: { - shapeOptions: { - color: "#3388ff", - weight: 3, - clickable: true, - }, - }, - polygon: false, - rectangle: false, - circle: false, - marker: false, - circlemarker: false, - }, - edit: { - featureGroup: featureRef.current, - edit: true, - remove: false, - }, - }); - - drawControlRef.current = drawControl; - map.addControl(drawControl); - map.on(L.Draw.Event.CREATED, handlerCreatedBound); - - return () => { - map.off(L.Draw.Event.CREATED, handlerCreatedBound); - map.removeControl(drawControl); - }; - }, [map, handlerCreatedBound]); - - return ; -}; - -export default DrawBound; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/index.jsx deleted file mode 100644 index 8d9b5d0..0000000 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/index.jsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Delete, Route } from "@mui/icons-material"; -import { Box, Button, Stack, Typography } from "@mui/material"; -import { useReducer } from "react"; -import DrawBound from "./DrawBound"; - -const statusType = [ - { - id: 0, - message: "برای آغاز ترسیم مسیر، کلیک کنید!", - buttons: [ - { - label: "شروع ترسیم مسیر", - key: "start", - color: "primary", - icon: , - onclick: (controlDispach) => { - controlDispach({ type: "SET_STATUS", status: 1 }); - }, - }, - ], - }, - { - id: 1, - message: "مسیر موردنظر را با کلیک روی نقشه مشخص کنید. برای اتمام، روی نقطه‌ی پایانی کلیک کنید!", - buttons: [], - }, - { - id: 2, - message: "برای اصلاح مسیر، گوشه‌ها را بکشید. برای ترسیم دوباره، آن را حذف کنید", - buttons: [ - { - label: "حذف", - key: "end", - color: "error", - icon: , - onclick: (controlDispach) => { - controlDispach({ type: "SET_STATUS", status: 0 }); - }, - }, - ], - }, -]; - -const createInitialState = (bound) => { - if (bound) { - return { status: 2 }; - } - return { status: 0 }; -}; - -const reducer = (state, action) => { - switch (action.type) { - case "SET_STATUS": - return { ...state, status: action.status }; - default: - return state; - } -}; - -const MapControlPolyline = ({ bound, setBound }) => { - const [control, controlDispach] = useReducer(reducer, bound, createInitialState); - - return ( - <> - - - theme.palette.info.main, - borderBottomLeftRadius: 8, - borderBottomRightRadius: 8, - py: 0.5, - px: 2, - }} - > - - {statusType.find((st) => st.id == control.status).message} - - - - - - {statusType - .find((st) => st.id == control.status) - .buttons.map((button) => ( - - ))} - - - - ); -}; -export default MapControlPolyline; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/AlarmText.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/AlarmText.jsx new file mode 100644 index 0000000..0ddf8a0 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/AlarmText.jsx @@ -0,0 +1,28 @@ +import { Box, Typography } from "@mui/material"; + +const AlarmText = ({ isSource }) => { + if (isSource) return null; + return ( + + + با کلیک بر روی مارکر موقعیت را انتخاب کنید + + + ); +}; + +export default AlarmText; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/DataBox.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/DataBox.jsx new file mode 100644 index 0000000..5768f75 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/DataBox.jsx @@ -0,0 +1,53 @@ +import { Box, Button, TextField } from "@mui/material"; + +const DataBox = ({ isSource, isDest, sourcePosition, destPosition, editingTarget, onEditSource, onEditDest }) => { + if (!isSource) return null; + + return ( + +
+ + {isSource && isDest && ( + + )} +
+ {isDest && ( +
+ + +
+ )} +
+ ); +}; + +export default DataBox; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/ShowRoute.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/ShowRoute.jsx new file mode 100644 index 0000000..6827cde --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/ShowRoute.jsx @@ -0,0 +1,46 @@ +import { useEffect, useRef } from "react"; +import { Marker, Polyline, useMap } from "react-leaflet"; +import L from "leaflet"; +import polyline from "@mapbox/polyline"; +import SourceIcon from "@/assets/images/source-icon.svg"; +import DestIcon from "@/assets/images/destination-icon.svg"; + +const ShowRoute = ({ area }) => { + const map = useMap(); + const defaultIconSize = [35, 35]; + + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src); + const destIcon = createCustomIcon(defaultIconSize, DestIcon.src); + useEffect(() => { + if (coords.length > 0) { + map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 }); + } + }, [area]); + + if (!area) return null; + const coords = polyline.decode(area, 6); + + if (coords.length === 0) return null; + + const sourcePosition = coords[0]; + const destPosition = coords[coords.length - 1]; + + return ( + <> + + + + + ); +}; + +export default ShowRoute; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/index.jsx new file mode 100644 index 0000000..49c4651 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/Routing/index.jsx @@ -0,0 +1,219 @@ +import { Marker, Polyline, useMapEvents } from "react-leaflet"; +import L from "leaflet"; +import polyline from "@mapbox/polyline"; +import SourceIcon from "@/assets/images/source-icon.svg"; +import PrevSourceIcon from "@/assets/images/prev-source-icon.svg"; +import PrevDestIcon from "@/assets/images/prev-destination-icon.svg"; +import DestIcon from "@/assets/images/destination-icon.svg"; +import { useEffect, useRef, useState } from "react"; +import DataBox from "./DataBox"; +import AlarmText from "./AlarmText"; + +const Routing = ({ setArea, setLocations, locations, area }) => { + const mapPrevSourceMarker = useRef(); + const mapPrevDestMarker = useRef(); + const mapRef = useRef(null); + const defaultIconSize = [35, 35]; + const [isSource, setIsSource] = useState(false); + const [sourcePosition, setSourcePosition] = useState(null); + const [isDest, setIsDest] = useState(false); + const [destPosition, setDestPosition] = useState(null); + const [routeCoords, setRouteCoords] = useState([]); + const [alternateCoords, setAlternateCoords] = useState([]); + const [editingTarget, setEditingTarget] = useState(null); + const [selectedRoute, setSelectedRoute] = useState(null); + + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + const defaultIcon = createCustomIcon( + defaultIconSize, + isSource && editingTarget !== "source" ? SourceIcon.src : PrevSourceIcon.src + ); + + const prevDestIcon = createCustomIcon( + defaultIconSize, + isDest && editingTarget !== "dest" ? DestIcon.src : PrevDestIcon.src + ); + + const isMovingSource = !isSource || editingTarget === "source"; + const isMovingDest = isSource && (!isDest || editingTarget === "dest"); + + const map = useMapEvents({ + move(e) { + if (isMovingSource) mapPrevSourceMarker.current.setLatLng(e.target.getCenter()); + if (isMovingDest && mapPrevDestMarker.current) mapPrevDestMarker.current.setLatLng(e.target.getCenter()); + }, + movestart() { + if (isMovingSource) mapPrevSourceMarker.current.setIcon(createCustomIcon([45, 45], PrevSourceIcon.src)); + if (isMovingDest && mapPrevDestMarker.current) + mapPrevDestMarker.current.setIcon(createCustomIcon([45, 45], PrevDestIcon.src)); + }, + moveend() { + if (isMovingSource) + mapPrevSourceMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevSourceIcon.src)); + if (isMovingDest && mapPrevDestMarker.current) + mapPrevDestMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevDestIcon.src)); + }, + }); + + useEffect(() => { + mapRef.current = map; + }, [map]); + + useEffect(() => { + if (locations.length < 2) return; + const restoredSource = { lat: locations[0].lat, lng: locations[0].lon }; + const restoredDest = { lat: locations[1].lat, lng: locations[1].lon }; + setSourcePosition(restoredSource); + setDestPosition(restoredDest); + setIsSource(true); + setIsDest(true); + if (area) { + const decoded = polyline.decode(area, 6); + setRouteCoords(decoded); + setSelectedRoute({ index: "main", coords: decoded }); + setTimeout(() => { + mapRef.current?.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); + }, 100); + } else { + fetchRoute(restoredSource, restoredDest); + } + }, []); + + const fetchRoute = async (source, dest) => { + const locs = [ + { lat: source.lat, lon: source.lng }, + { lat: dest.lat, lon: dest.lng }, + ]; + try { + const response = await fetch("https://jouya.141.ir/api/router/drive", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ locations: locs }), + }); + const data = await response.json(); + const shape = data.trip.legs[0].shape; + setLocations(locs); + setArea(shape); + const decoded = polyline.decode(shape, 6); + setRouteCoords(decoded); + setSelectedRoute({ index: "main", coords: decoded }); + + if (data.alternates?.length > 0) { + const decodedAlternates = data.alternates.map((alt) => polyline.decode(alt.trip.legs[0].shape, 6)); + setAlternateCoords(decodedAlternates); + } else { + setAlternateCoords([]); + } + map.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 }); + } catch (error) { + console.error("Routing error:", error); + } + }; + + const handleMarkerClick = async () => { + if (!isSource || editingTarget === "source") { + const clickedPosition = mapPrevSourceMarker.current.getLatLng(); + setSourcePosition(clickedPosition); + setIsSource(true); + setEditingTarget(null); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + + if (editingTarget === "source" && destPosition) { + await fetchRoute(clickedPosition, destPosition); + } + } + }; + + const handleDestMarkerClick = async () => { + if (!isDest || editingTarget === "dest") { + const clickedPosition = mapPrevDestMarker.current.getLatLng(); + setDestPosition(clickedPosition); + setIsDest(true); + setEditingTarget(null); + + await fetchRoute(sourcePosition, clickedPosition); + } + }; + + const handleEditSource = () => { + setEditingTarget("source"); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + }; + + const handleEditDest = () => { + setEditingTarget("dest"); + setRouteCoords([]); + setAlternateCoords([]); + setSelectedRoute(null); + }; + + const handleSelectRoute = (index, coords) => { + setSelectedRoute({ index, coords }); + const encoded = polyline.encode(coords, 6); + setArea(encoded); + map.flyToBounds(coords, { padding: [50, 50], duration: 0.3 }); + }; + + return ( + <> + + + + {isSource && ( + + )} + {alternateCoords.map((coords, index) => ( + handleSelectRoute(index, coords) }} + /> + ))} + {routeCoords.length > 0 && ( + handleSelectRoute("main", routeCoords) }} + /> + )} + + ); +}; + +export default Routing; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/SelectBoundType/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/SelectBoundType/index.jsx deleted file mode 100644 index ebb3c69..0000000 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/SelectBoundType/index.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import SelectBox from "@/core/components/SelectBox"; - -export const boundTypes = [ - { id: "polygon", name_fa: "محدوده" }, - { id: "polyline", name_fa: "مسیر" }, -]; - -const SelectBoundType = ({ boundType, setBoundType, setBound, setAllData }) => { - return ( - <> - { - setBound(null); - setBoundType(newValue); - setAllData({ bound_type: newValue }); - }} - /> - - ); -}; -export default SelectBoundType; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/index.jsx index 3d666fd..200c1e0 100644 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/index.jsx +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/index.jsx @@ -2,40 +2,29 @@ import MapLoading from "@/core/components/MapLayer/Loading"; import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material"; import dynamic from "next/dynamic"; import { useCallback, useState } from "react"; -import MapControlPolygon from "./MapControlPolygon"; -import MapControlPolyline from "./MapControlPolyline"; -import SelectBoundType from "./SelectBoundType"; +import Routing from "./Routing"; const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { loading: () => , ssr: false, }); const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - const [bound, setBound] = useState(allData.bound); - const [boundType, setBoundType] = useState(allData.bound_type); + const [area, setArea] = useState(allData.area); + const [locations, setLocations] = useState(allData.locations); const handleNext = useCallback(() => { - setAllData({ bound: bound }); + setAllData({ area: area }); + setAllData({ locations: locations }); setTabState((s) => s + 1); - }, [bound]); + }, [area]); return ( <> - - {boundType == "polygon" ? ( - - ) : ( - - )} + @@ -44,8 +33,8 @@ const Area = ({ allData, setAllData, handlePrev, setTabState }) => { - diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Verify/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Verify/index.jsx index 09f865f..68f99e7 100644 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Verify/index.jsx +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Verify/index.jsx @@ -5,7 +5,7 @@ import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typogr import moment from "jalali-moment"; import dynamic from "next/dynamic"; import { useCallback } from "react"; -import ShowBound from "../../../showBound"; +import ShowRoute from "../Area/Routing/ShowRoute"; const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { loading: () => , ssr: false, @@ -22,6 +22,16 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => { + + + + + + + + + + diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/index.jsx index 96de65d..0f9c7b2 100644 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/index.jsx +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/index.jsx @@ -1,4 +1,4 @@ -import { AccessTime, Engineering, InsertDriveFile, Person, Verified } from "@mui/icons-material"; +import { AccessTime, Engineering, InsertDriveFile, Person, Verified, Map } from "@mui/icons-material"; import { Box, Tab, Tabs } from "@mui/material"; import { useReducer, useState } from "react"; import GetDateTime from "./GetDateTime"; @@ -6,6 +6,7 @@ import GetItemInfo from "./GetItemInfo"; import MachineAndDriver from "./MachineAndDriver"; import Rahdaran from "./Rahdaran"; import Verify from "./Verify"; +import Area from "./Area"; function TabPanel(props) { const { children, value, index } = props; @@ -61,8 +62,9 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => { } label="مشخصات" /> } label="زمانبندی" /> } label="راننده" /> - } label="همراهان" /> - } label="بررسی نهایی" /> + } label="مختصات" /> + } label="همراهان" /> + } label="بررسی نهایی" /> { /> - { dispatch({ type: "changeData", data }); @@ -105,6 +107,16 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => { /> + { + dispatch({ type: "changeData", data }); + }} + handlePrev={handlePrev} + setTabState={setTabState} + /> + + diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/index.jsx index 0e1f42b..f2d3d09 100644 --- a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/index.jsx +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/index.jsx @@ -28,6 +28,7 @@ const CreateWithoutProcess = ({ row, mutate }) => { start_date: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`, end_date: `${result.end_date} ${moment(result.end_time).format("HH:mm")}`, driver: result.driver.id, + encoded_route: result.area, }, hasSidebarUpdate: true, }) @@ -49,7 +50,7 @@ const CreateWithoutProcess = ({ row, mutate }) => { - + setOpen(false)} @@ -77,6 +78,8 @@ const CreateWithoutProcess = ({ row, mutate }) => { end_point: "", region: "", driver: null, + area: null, + locations: [], }} submitForm={submitForm} submitting={submitting} diff --git a/src/components/dashboard/roadPatrols/reports/index.jsx b/src/components/dashboard/roadPatrols/reports/index.jsx index a77efac..0731eb6 100644 --- a/src/components/dashboard/roadPatrols/reports/index.jsx +++ b/src/components/dashboard/roadPatrols/reports/index.jsx @@ -5,7 +5,7 @@ import SearchReportList from "./Search"; import { useEffect, useState } from "react"; import ReportLists from "./ReportLists"; import useRequest from "@/lib/hooks/useRequest"; -import { GET_CITY_ACTIVITY_REPORT, GET_PROVINCE_ACTIVITY_REPORT } from "@/core/utils/routes"; +import { GET_COUNTRY_ACTIVITY_REPORT, GET_PROVINCE_ACTIVITY_REPORT } from "@/core/utils/routes"; import moment from "jalali-moment"; import * as yup from "yup"; import { useForm } from "react-hook-form"; @@ -70,7 +70,7 @@ const ReportPage = () => { } else { try { params.set("province_id", `${data.province_id}`); - const response = await requestServer(`${GET_CITY_ACTIVITY_REPORT}?${params}`); + const response = await requestServer(`${GET_COUNTRY_ACTIVITY_REPORT}?${params}`); const result = response.data.data; const nationalReportForItem = result.activities.find((report) => report.edare_id === "-1"); const nationalReport = {