diff --git a/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/page.js b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/page.js new file mode 100644 index 0000000..8b51a44 --- /dev/null +++ b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/reports/page.js @@ -0,0 +1,18 @@ +import WithPermission from "@/core/middlewares/withPermission"; +import ReportPage from "@/components/dashboard/roadMissions/reports"; +export const metadata = { + title: "کارتابل گزارش ماموریت", +}; +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/MissionsList/index.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/MissionsList/index.jsx index 9d52581..278aa0e 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/MissionsList/index.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/MissionsList/index.jsx @@ -4,8 +4,11 @@ import { Box } from "@mui/material"; import { useMemo } from "react"; import RowActions from "./RowActions"; import moment from "jalali-moment"; +import { useAuth } from "@/lib/contexts/auth"; +import { missionRegions } from "@/core/utils/missionRegions"; const MissionList = ({ setMission, setOpenDialog }) => { + const { user } = useAuth(); const columns = useMemo( () => [ { @@ -88,6 +91,7 @@ const MissionList = ({ setMission, setOpenDialog }) => { need_filter={true} table_title="لیست ماموریت ها" columns={columns} + specialFilter={[{ value: user.id, datatype: "text", id: "user_id", fn: "equals" }]} sorting={[{ id: "id", desc: true }]} table_url={GET_ROAD_MISSIONS_OPERATOR_LIST} page_name={"roadItemsOperator"} diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/index.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/index.jsx index 3e25b0a..667845b 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/index.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo/MissionsDialog/index.jsx @@ -13,7 +13,7 @@ const MissionsDialog = ({ setMission, mode }) => { ) : ( )} diff --git a/src/components/dashboard/roadMissions/reports/ExcelPrint/index.jsx b/src/components/dashboard/roadMissions/reports/ExcelPrint/index.jsx new file mode 100644 index 0000000..5a514fb --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/ExcelPrint/index.jsx @@ -0,0 +1,72 @@ +"use client"; +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import { useMemo, useState } from "react"; +import moment from "jalali-moment"; +import FileSaver from "file-saver"; +import DescriptionIcon from "@mui/icons-material/Description"; +import useRequest from "@/lib/hooks/useRequest"; +import { useTheme } from "@emotion/react"; +import { EXPORT_COUNTRY_ROAD_PATROL_REPORTS, EXPORT_PROVINCE_ROAD_PATROL_REPORTS } from "@/core/utils/routes"; + +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_COUNTRY_ROAD_PATROL_REPORTS : EXPORT_PROVINCE_ROAD_PATROL_REPORTS; + 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/ReportLists.jsx b/src/components/dashboard/roadMissions/reports/ReportLists.jsx new file mode 100644 index 0000000..19b5e71 --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/ReportLists.jsx @@ -0,0 +1,84 @@ +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; +import { useMemo } from "react"; +import Toolbar from "./Toolbar"; + +const ReportLists = ({ data, specialFilter }) => { + const columns = useMemo(() => { + return [ + { + accessorKey: "province_name", + header: "اداره کل", + id: "province_name", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + { + accessorKey: "no_mission_count", + header: "تردد بدون ماموریت", + id: "no_mission_count", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return renderedCellValue; + }, + }, + { + accessorKey: "out_of_aria_count", + header: "تردد خارج محدوده", + id: "out_of_aria_count", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return renderedCellValue; + }, + }, + ]; + }, []); + + return ( + + + + ); +}; + +export default ReportLists; diff --git a/src/components/dashboard/roadMissions/reports/Search/FromDateController.jsx b/src/components/dashboard/roadMissions/reports/Search/FromDateController.jsx new file mode 100644 index 0000000..45002e1 --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/Search/FromDateController.jsx @@ -0,0 +1,28 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +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/Search/SearchReportField.jsx b/src/components/dashboard/roadMissions/reports/Search/SearchReportField.jsx new file mode 100644 index 0000000..5c6aa23 --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/Search/SearchReportField.jsx @@ -0,0 +1,45 @@ +import { Button, Grid, LinearProgress, Typography } from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import React from "react"; +import FromDateController from "./FromDateController"; +import ToDateController from "./ToDateController"; +import SelectProvince from "./SelectProvince"; +import { Controller } from "react-hook-form"; + +const SearchReportField = ({ control, hasProvincesPermission }) => { + return ( + + + + + + + + {hasProvincesPermission && ( + + + + )} + + ( + + )} + /> + + + ); +}; + +export default SearchReportField; diff --git a/src/components/dashboard/roadMissions/reports/Search/SelectProvince.jsx b/src/components/dashboard/roadMissions/reports/Search/SelectProvince.jsx new file mode 100644 index 0000000..7f738bc --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/Search/SelectProvince.jsx @@ -0,0 +1,47 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import { FormControl, FormHelperText, InputLabel, LinearProgress, MenuItem, Select, Typography } from "@mui/material"; +import useProvinces from "@/lib/hooks/useProvince"; + +const SelectProvince = ({ control }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + ( + + استان + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/roadMissions/reports/Search/ToDateController.jsx b/src/components/dashboard/roadMissions/reports/Search/ToDateController.jsx new file mode 100644 index 0000000..4b5f2a8 --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/Search/ToDateController.jsx @@ -0,0 +1,30 @@ +import { Controller, useWatch } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +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/Search/index.jsx b/src/components/dashboard/roadMissions/reports/Search/index.jsx new file mode 100644 index 0000000..9d504f2 --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/Search/index.jsx @@ -0,0 +1,13 @@ +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/Toolbar.jsx b/src/components/dashboard/roadMissions/reports/Toolbar.jsx new file mode 100644 index 0000000..3e5ab8c --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/Toolbar.jsx @@ -0,0 +1,11 @@ +import PrintExcel from "./ExcelPrint"; +import { Box } from "@mui/material"; + +const Toolbar = ({ table, filterData, mutate }) => { + return ( + + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/roadMissions/reports/index.jsx b/src/components/dashboard/roadMissions/reports/index.jsx new file mode 100644 index 0000000..5dfd3a7 --- /dev/null +++ b/src/components/dashboard/roadMissions/reports/index.jsx @@ -0,0 +1,134 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { LinearProgress, Stack } from "@mui/material"; +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_MISSION_CITY_ACTIVITY_REPORT } from "@/core/utils/routes"; +import moment from "jalali-moment"; +import * as yup from "yup"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; + +const ReportPage = () => { + const [data, setData] = useState(null); + const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); + const requestServer = useRequest(); + const hasProvincesPermission = userPermissions?.includes("all"); // TODO : change permission + + const defaultValues = { + from_date: moment(new Date()).format("YYYY-MM-DD"), + date_to: moment(new Date()).format("YYYY-MM-DD"), + province_id: hasProvincesPermission ? "-1" : user.province_id, + }; + 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_CITY_ACTIVITY_REPORT}?${params}`); + const result = response.data.data; + console.log(result); + 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_MISSION_CITY_ACTIVITY_REPORT}?${params}`); + // const result = response.data.data; + // const nationalReportForItem = result.activities.find((report) => report.edare_id === "-1"); + // const nationalReport = { + // ...nationalReportForItem, + // edare_name: "کل استان", + // }; + // setData({ + // data: [ + // nationalReport, + // ...result.edarateShahri.map((edare) => { + // const filteredReports = result.activities.find((report) => report.edare_id == edare.id); + // return { + // edare_name: edare.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 ReportPage; diff --git a/src/components/dashboard/roadMissions/violations/Actions/showBound/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/showBound/index.jsx index 24db476..43dd0b8 100644 --- a/src/components/dashboard/roadMissions/violations/Actions/showBound/index.jsx +++ b/src/components/dashboard/roadMissions/violations/Actions/showBound/index.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/core/utils/pageMenu.js b/src/core/utils/pageMenu.js index f812b52..1388709 100644 --- a/src/core/utils/pageMenu.js +++ b/src/core/utils/pageMenu.js @@ -29,6 +29,10 @@ import ScienceIcon from "@mui/icons-material/Science"; import SecurityIcon from "@mui/icons-material/Security"; import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard"; import SpeedIcon from "@mui/icons-material/Speed"; +import ContactPageIcon from "@mui/icons-material/ContactPage"; +import DirectionsBusIcon from "@mui/icons-material/DirectionsBus"; +import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle"; +import RemoveRoadIcon from "@mui/icons-material/RemoveRoad"; export const pageMenu = [ { @@ -107,6 +111,7 @@ export const pageMenu = [ { id: "roadMissionsManagemnetRequests", label: "کارتابل", + icon: , type: "page", route: "/dashboard/road-missions/operator", badges: [{ key: "mission.request_portal" }], @@ -120,6 +125,7 @@ export const pageMenu = [ id: "roadMissionsManagemnetNaqlie", label: "نقلیه", type: "page", + icon: , route: "/dashboard/road-missions/transportation", badges: [{ key: "mission.transportation" }], permissions: ["manage-transportation-unit"], @@ -128,6 +134,7 @@ export const pageMenu = [ id: "roadMissionsManagemnetControl", label: "کنترل", type: "page", + icon: , route: "/dashboard/road-missions/control", badges: [{ key: "mission.control" }], permissions: ["manage-control-unit"], @@ -136,10 +143,20 @@ export const pageMenu = [ id: "roadMissionsViolations", label: "تخلفات", type: "page", + icon: , route: "/dashboard/road-missions/violation", // badges: [{ key: "mission.control" }], // TODO: add badge and permission permissions: ["all"], }, + // { + // id: "roadMissionsReports", + // label: "گزارشات", + // type: "page", + // route: "/dashboard/road-missions/reports", + // icon: , + // // badges: [{ key: "mission.control" }], // TODO: add badge and permission + // permissions: ["all"], + // }, ], }, { diff --git a/src/core/utils/pageMenuDev.js b/src/core/utils/pageMenuDev.js index 41e811d..27b37c1 100644 --- a/src/core/utils/pageMenuDev.js +++ b/src/core/utils/pageMenuDev.js @@ -28,6 +28,10 @@ import PsychologyAltIcon from "@mui/icons-material/PsychologyAlt"; import MapsHomeWorkIcon from "@mui/icons-material/MapsHomeWork"; import SecurityIcon from "@mui/icons-material/Security"; import ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; +import DirectionsBusIcon from "@mui/icons-material/DirectionsBus"; +import ContactPageIcon from "@mui/icons-material/ContactPage"; +import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle"; +import RemoveRoadIcon from "@mui/icons-material/RemoveRoad"; export const pageMenuDev = [ { @@ -106,6 +110,7 @@ export const pageMenuDev = [ { id: "roadMissionsManagemnetRequests", label: "کارتابل", + icon: , type: "page", route: "/dashboard/road-missions/operator", badges: [{ key: "mission.request_portal" }], @@ -119,6 +124,7 @@ export const pageMenuDev = [ id: "roadMissionsManagemnetNaqlie", label: "نقلیه", type: "page", + icon: , route: "/dashboard/road-missions/transportation", badges: [{ key: "mission.transportation" }], permissions: ["manage-transportation-unit"], @@ -127,6 +133,7 @@ export const pageMenuDev = [ id: "roadMissionsManagemnetControl", label: "کنترل", type: "page", + icon: , route: "/dashboard/road-missions/control", badges: [{ key: "mission.control" }], permissions: ["manage-control-unit"], @@ -135,10 +142,20 @@ export const pageMenuDev = [ id: "roadMissionsViolations", label: "تخلفات", type: "page", + icon: , route: "/dashboard/road-missions/violation", // badges: [{ key: "mission.control" }], // TODO: add badge and permission permissions: ["all"], }, + // { + // id: "roadMissionsReports", + // label: "گزارشات", + // type: "page", + // route: "/dashboard/road-missions/reports", + // icon: , + // // badges: [{ key: "mission.control" }], // TODO: add badge and permission + // permissions: ["all"], + // }, ], }, { diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js index f22a48a..411ef1b 100644 --- a/src/core/utils/routes.js +++ b/src/core/utils/routes.js @@ -228,6 +228,8 @@ export const DEALLOCATE_REQUEST_MISSION = api + "/api/v3/missions/transportation export const START_MISSION = api + "/api/v3/missions/control_unit/start"; export const FINISH_MISSION = api + "/api/v3/missions/control_unit/finish"; export const GET_ROAD_MISSIONS_VIOLATIONS_LIST = api + "/api/v3/missions/violation_management"; +export const GET_MISSION_PROVINCE_ACTIVITY_REPORT = api + "/api/v3/missions/report/country_activity"; +export const GET_MISSION_CITY_ACTIVITY_REPORT = api + "/api/v3/missions/report/country_activity"; export const GET_MACHINES_TABLE_LIST = api + "/api/v3/cmms_machines";