diff --git a/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/violation/page.js b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/violation/page.js new file mode 100644 index 0000000..99cde66 --- /dev/null +++ b/src/app/(withAuth)/(dashboardLayout)/dashboard/road-missions/violation/page.js @@ -0,0 +1,17 @@ +import ViolationsPage from "@/components/dashboard/roadMissions/violations"; +import WithPermission from "@/core/middlewares/withPermission"; +export const metadata = { + title: "کارتابل تخلفات", +}; +const Page = () => { + return ( + <> + + + {/* */} + + + ); +}; + +export default Page; diff --git a/src/components/dashboard/inquiryPrivacy/privacy-office/PrivacyOfficeList.jsx b/src/components/dashboard/inquiryPrivacy/privacy-office/PrivacyOfficeList.jsx index b6932ca..487c46d 100644 --- a/src/components/dashboard/inquiryPrivacy/privacy-office/PrivacyOfficeList.jsx +++ b/src/components/dashboard/inquiryPrivacy/privacy-office/PrivacyOfficeList.jsx @@ -168,9 +168,12 @@ const PrivacyOfficeList = () => { grow: false, size: 100, Cell: ({ row }) => - row.original.primary_area ? ( + row.original.primary_area && row.original.forbidden_area ? ( - + ) : ( "-" diff --git a/src/components/dashboard/inquiryPrivacy/privacy-office/ShowPrimaryArea/ShowForbiddenBound.jsx b/src/components/dashboard/inquiryPrivacy/privacy-office/ShowPrimaryArea/ShowForbiddenBound.jsx new file mode 100644 index 0000000..c8d28d1 --- /dev/null +++ b/src/components/dashboard/inquiryPrivacy/privacy-office/ShowPrimaryArea/ShowForbiddenBound.jsx @@ -0,0 +1,29 @@ +import { useEffect, useRef } from "react"; +import { FeatureGroup, useMap } from "react-leaflet"; + +const ShowForbiddenBound = ({ 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 ShowForbiddenBound; diff --git a/src/components/dashboard/inquiryPrivacy/privacy-office/ShowPrimaryArea/index.jsx b/src/components/dashboard/inquiryPrivacy/privacy-office/ShowPrimaryArea/index.jsx index 98e24d0..996a2cd 100644 --- a/src/components/dashboard/inquiryPrivacy/privacy-office/ShowPrimaryArea/index.jsx +++ b/src/components/dashboard/inquiryPrivacy/privacy-office/ShowPrimaryArea/index.jsx @@ -1,27 +1,27 @@ -const { - Button, - IconButton, - Tooltip, - Box, - Dialog, - DialogTitle, - Typography, - DialogContent, - DialogActions, -} = require("@mui/material"); -import LayersIcon from "@mui/icons-material/Layers"; -import CloseIcon from "@mui/icons-material/Close"; -import { useMemo, useState } from "react"; +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 { useMemo, useState } from "react"; import ShowBound from "./ShowBound"; +import ShowForbiddenBound from "./ShowForbiddenBound"; -const ShowPrimaryArea = ({ primaryArea }) => { +const ShowPrimaryArea = ({ primaryArea, forbiddenArea }) => { const [openShowAreaDialog, setOpenShowAreaDialog] = useState(false); const bound = useMemo(() => { const latLngCoords = primaryArea.coordinates.map((coord) => [coord[1], coord[0]]); return primaryArea.type === "polygon" ? L.polygon(latLngCoords) : L.polyline(latLngCoords); }, [primaryArea]); + const forbiddenBound = useMemo(() => { + const latLngCoords = forbiddenArea.coordinates.map((coord) => [coord[1], coord[0]]); + return forbiddenArea.type === "polygon" + ? L.polygon(latLngCoords, { + color: "#ff5555", + }) + : L.polyline(latLngCoords); + }, [forbiddenArea]); + return ( @@ -57,6 +57,7 @@ const ShowPrimaryArea = ({ primaryArea }) => { + 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 new file mode 100644 index 0000000..a2134c5 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/DrawBound/index.jsx @@ -0,0 +1,129 @@ +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 new file mode 100644 index 0000000..186a0b6 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolygon/index.jsx @@ -0,0 +1,116 @@ +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 new file mode 100644 index 0000000..682be24 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/DrawBound/index.jsx @@ -0,0 +1,129 @@ +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 new file mode 100644 index 0000000..edd6cac --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/MapControlPolyline/index.jsx @@ -0,0 +1,116 @@ +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/SelectBoundType/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/SelectBoundType/index.jsx new file mode 100644 index 0000000..a69a0ba --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/SelectBoundType/index.jsx @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..91dd0d3 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Area/index.jsx @@ -0,0 +1,54 @@ +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"; +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 handleNext = useCallback(() => { + setAllData({ bound: bound }); + setTabState((s) => s + 1); + }, [bound]); + + return ( + <> + + + + + + {boundType == "polygon" ? ( + + ) : ( + + )} + + + + + + + + + + ); +}; +export default Area; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetDateTime/MissionDates/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetDateTime/MissionDates/index.jsx new file mode 100644 index 0000000..c002df3 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetDateTime/MissionDates/index.jsx @@ -0,0 +1,119 @@ +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import MuiTimePicker from "@/core/components/MuiTimePicker"; +import { makeDateTime } from "@/core/utils/makeDateTime"; +import { Grid } from "@mui/material"; +import { Controller, useWatch } from "react-hook-form"; + +const MissionDates = ({ control }) => { + const start_date = useWatch({ control, name: "start_date" }); + const end_date = useWatch({ control, name: "end_date" }); + const start_time = useWatch({ control, name: "start_time" }); + const end_time = useWatch({ control, name: "end_time" }); + + const startDateTime = makeDateTime(start_date, start_time); + const endDateTime = makeDateTime(end_date, end_time); + const now = new Date(); + + return ( + <> + + ( + field.onChange(value || null)} + helperText={error ? error.message : null} + /> + )} + /> + + + { + return ( + field.onChange(value || null)} + helperText={error ? error.message : null} + /> + ); + }} + /> + + + ( + field.onChange(value || null)} + helperText={error ? error.message : null} + /> + )} + /> + + + { + return ( + field.onChange(value || null)} + helperText={error ? error.message : null} + /> + ); + }} + /> + + + ); +}; + +export default MissionDates; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetDateTime/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetDateTime/index.jsx new file mode 100644 index 0000000..9e35f41 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetDateTime/index.jsx @@ -0,0 +1,59 @@ +import SelectBox from "@/core/components/SelectBox"; +import StyledForm from "@/core/components/StyledForm"; +import { missionTypes } from "@/core/utils/missionTypes"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material"; +import moment from "jalali-moment"; +import { Controller, useForm } from "react-hook-form"; +import { object, string } from "yup"; +import MissionDates from "./MissionDates"; + +const validationSchema = object({ + start_date: string().required("تاریخ شروع ماموریت را مشخص کنید!"), + start_time: string().required("زمان شروع ماموریت را مشخص کنید!"), + end_date: string().required("تاریخ پایان ماموریت را مشخص کنید!"), + end_time: string().required("زمان شروع ماموریت را مشخص کنید!"), +}); + +const GetDateTime = ({ allData, setAllData, handlePrev, setTabState }) => { + const defaultValues = { + start_date: allData.start_date, + start_time: allData.start_time ? moment(allData.start_time).toDate() : null, + end_date: allData.end_date, + end_time: allData.end_time ? moment(allData.end_time).toDate() : null, + }; + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "all", + }); + + const handleNext = (data) => { + setAllData(data); + setTabState((s) => s + 1); + }; + + return ( + + + + + + + + + + + + + + + + ); +}; +export default GetDateTime; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/RowActions/SelectId/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/RowActions/SelectId/index.jsx new file mode 100644 index 0000000..1e7e881 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/RowActions/SelectId/index.jsx @@ -0,0 +1,20 @@ +import { Done } from "@mui/icons-material"; +import { IconButton, Tooltip } from "@mui/material"; + +const SelectId = ({ row, onChange, setOpenFastReactDialog }) => { + const handleClick = () => { + onChange(row.original.id); + setOpenFastReactDialog(false); + }; + + return ( + <> + + + + + + + ); +}; +export default SelectId; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/RowActions/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/RowActions/index.jsx new file mode 100644 index 0000000..3d0b958 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/RowActions/index.jsx @@ -0,0 +1,10 @@ +import SelectId from "./SelectId"; + +const RowActions = ({ row, onChange, setOpenFastReactDialog }) => { + return ( + <> + + + ); +}; +export default RowActions; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/index.jsx new file mode 100644 index 0000000..82a0472 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/List/index.jsx @@ -0,0 +1,274 @@ +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { GET_FAST_REACT_COMPLAINTS } from "@/core/utils/routes"; +import { Box, Stack, Typography } from "@mui/material"; +import { useMemo } from "react"; +import RowActions from "./RowActions"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; +import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; +import DescriptionForm from "@/components/dashboard/fastReact/complaintList/RowActions/DescriptionDialog"; +import LocationForm from "@/components/dashboard/fastReact/complaintList/RowActions/LocationDialog"; +import moment from "jalali-moment"; +import useProvinces from "@/lib/hooks/useProvince"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; + +const FastReactList = ({ onChange, setOpenFastReactDialog }) => { + const { data: userPermissions } = usePermissions(); + const hasCountryPermission = userPermissions?.includes("show-fast-react"); + const { user } = useAuth(); + const columns = useMemo(() => { + const dynamicColumns = { + header: "استان", + id: "road_observeds__province_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + grow: false, + size: 130, + ColumnSelectComponent: (props) => { + const { provinces, errorProvinces, loadingProvinces } = useProvinces(); + const getColumnSelectOptions = useMemo(() => { + if (loadingProvinces) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorProvinces) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل کشور" }, + ...provinces.map((province) => ({ + value: province.id, + label: province.name_fa, + })), + ]; + }, [provinces, errorProvinces, loadingProvinces]); + return ( + + ); + }, + Cell: ({ row }) => <>{row.original.province_fa}, + }; + return [ + { + accessorKey: "id", + header: "کد یکتا", + id: "road_observeds__id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + ...(hasCountryPermission ? [dynamicColumns] : []), + { + header: "اداره", + id: "road_observeds__edarate_shahri_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + dependencyId: hasCountryPermission ? "road_observeds__province_id" : null, + grow: false, + size: 120, + ColumnSelectComponent: (props) => { + const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists( + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id + ); + const [prevDependency, setPrevDependency] = useState( + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id + ); + + const getColumnSelectOptions = useMemo(() => { + if (hasCountryPermission && props.dependencyFieldValue.value === "") { + return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }]; + } + if (loadingEdaratList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorEdaratList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل ادارات" }, + ...edaratList.map((edare) => ({ + value: edare.id, + label: edare.name_fa, + })), + ]; + }, [edaratList, loadingEdaratList, errorEdaratList]); + useEffect(() => { + if (hasCountryPermission) return; + if (prevDependency === props.dependencyFieldValue?.value) return; + props.handleChange({ ...props.filterParameters, value: "" }); + setPrevDependency(props.dependencyFieldValue?.value); + }, [props.dependencyFieldValue?.value, hasCountryPermission]); + return ( + + ); + }, + Cell: ({ row }) => <>{row.original.edarate_shahri_name_fa}, + }, + { + header: "اطلاعات ثبت‌ شده در سامانه سوانح", + id: "fkInfo", + enableColumnFilter: false, + enableSorting: false, + grow: false, + size: 50, + columns: [ + { + accessorKey: "fk_RegisteredEventMessage", + header: "کد سوانح", + id: "fk_RegisteredEventMessage", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Title", + header: "موضوع گزارش", + id: "Title", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "FeatureTypeTitle", + header: "نوع گزارش", + id: "FeatureTypeTitle", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Description", + header: "توضیح گزارش", + id: "Description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return -; + }, + }, + { + header: "موقعیت", + id: "location", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + return ( + + + + ); + }, + }, + { + accessorKey: "MobileForSendEventSms", + header: "شماره تماس گیرنده", + id: "MobileForSendEventSms", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorFn: (row) => moment(row.StartTime_DateTime).locale("fa").format("HH:mm | yyyy/MM/DD"), + header: "تاریخ ثبت", + id: "StartTime_DateTime", + enableColumnFilter: true, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + ], + }, + ]; + }, []); + + return ( + <> + + ( + + )} + /> + + + ); +}; +export default FastReactList; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/index.jsx new file mode 100644 index 0000000..013f73f --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/Dialog/index.jsx @@ -0,0 +1,49 @@ +import { Close, Edit } from "@mui/icons-material"; +import { Button, Chip, Dialog, Divider, IconButton } from "@mui/material"; +import FastReactList from "./List"; +import { useState, forwardRef } from "react"; + +const FastReactDialog = forwardRef(({ onChange, value }, ref) => { + const [openFastReactDialog, setOpenFastReactDialog] = useState(false); + + return ( + <> + + {value === "" ? ( + + ) : ( + } + onDelete={() => setOpenFastReactDialog(true)} + /> + )} + + + setOpenFastReactDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openFastReactDialog && ( + + )} + + + ); +}); + +FastReactDialog.displayName = "FastReactDialog"; + +export default FastReactDialog; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/index.jsx new file mode 100644 index 0000000..1545046 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/FastReactCode/index.jsx @@ -0,0 +1,22 @@ +import { Divider, Stack } from "@mui/material"; +import { Controller, useWatch } from "react-hook-form"; +import FastReactDialog from "./Dialog"; + +const FastReactCode = ({ control }) => { + const category_id = useWatch({ control, name: "category_id" }); + + if (category_id != 3) return null; + + return ( + ( + + + + )} + /> + ); +}; +export default FastReactCode; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/index.jsx new file mode 100644 index 0000000..3b3b513 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/GetItemInfo/index.jsx @@ -0,0 +1,153 @@ +import PersianTextField from "@/core/components/PersianTextField"; +import SelectBox from "@/core/components/SelectBox"; +import StyledForm from "@/core/components/StyledForm"; +import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes"; +import { missionRegions } from "@/core/utils/missionRegions"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { ExitToApp } from "@mui/icons-material"; +import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material"; +import { Controller, useForm } from "react-hook-form"; +import { object, string } from "yup"; +import FastReactCode from "./FastReactCode"; + +const validationSchema = object({ + explanation: string().required("موضوع را مشخص کنید!"), + end_point: string().required("مقصد را مشخص کنید!"), + region: string().required("محور ماموریت را مشخص کنید!"), + category_id: string().required("نوع ماموریت را مشخص کنید!"), + road_observed_id: string().when("category_id", { + is: "3", + then: (schema) => schema.required("کد یکتا شکایت را مشخص کنید!"), + otherwise: (schema) => schema.notRequired(), + }), +}); + +const GetItemInfo = ({ allData, setAllData, handlePrev, setTabState }) => { + const defaultValues = { + explanation: allData.explanation, + end_point: allData.end_point, + region: allData.region, + category_id: allData.category_id, + road_observed_id: allData.road_observed_id, + }; + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "all", + }); + + const handleNext = (data) => { + setAllData(data); + setTabState((s) => s + 1); + }; + + return ( + + + + + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + + + + { + return ( + + ); + }} + /> + + + + + + + + + + + ); +}; +export default GetItemInfo; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/DriversSearch/Form/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/DriversSearch/Form/index.jsx new file mode 100644 index 0000000..93bb1d6 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/DriversSearch/Form/index.jsx @@ -0,0 +1,61 @@ +import RahdarNameOrCode from "@/core/components/RahdarNameOrCode"; +import StyledForm from "@/core/components/StyledForm"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Done } from "@mui/icons-material"; +import { Button, Stack } from "@mui/material"; +import { Controller, useForm } from "react-hook-form"; +import { object } from "yup"; + +const schema = object().shape({ + rahdar: object().required("همراه الزامی است."), +}); +const DriverForm = ({ setDriver, setOpenDriversDialog }) => { + const defaultValues = { + rahdar: null, + }; + + const { + control, + handleSubmit, + formState: { isSubmitting, isValid }, + } = useForm({ defaultValues, resolver: yupResolver(schema), mode: "all" }); + + const submit = (data) => { + setDriver(data.rahdar); + setOpenDriversDialog(false); + }; + + return ( + + + + { + return ( + field.onChange(value)} + error={error} + multiple={false} + /> + ); + }} + name={"rahdar"} + /> + + + + + ); +}; +export default DriverForm; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/DriversSearch/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/DriversSearch/index.jsx new file mode 100644 index 0000000..b7136c9 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/DriversSearch/index.jsx @@ -0,0 +1,11 @@ +import { DialogContent } from "@mui/material"; +import DriverForm from "./Form"; + +const DriversSearch = ({ setDriver, setOpenDriversDialog }) => { + return ( + + + + ); +}; +export default DriversSearch; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/index.jsx new file mode 100644 index 0000000..3c1d406 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/DriversDialog/index.jsx @@ -0,0 +1,41 @@ +import { Close, Edit } from "@mui/icons-material"; +import { Button, Dialog, DialogTitle, IconButton } from "@mui/material"; +import { useState } from "react"; +import DriversSearch from "./DriversSearch"; + +const DriversDialog = ({ setDriver, mode }) => { + const [openDriversDialog, setOpenDriversDialog] = useState(false); + return ( + <> + {mode == "edit" ? ( + setOpenDriversDialog(true)} size="small"> + + + ) : ( + + )} + + setOpenDriversDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + انتخاب راننده + {openDriversDialog && ( + + )} + + + ); +}; +export default DriversDialog; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/RowActions/Allocate/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/RowActions/Allocate/index.jsx new file mode 100644 index 0000000..f03579a --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/RowActions/Allocate/index.jsx @@ -0,0 +1,20 @@ +import { Done } from "@mui/icons-material"; +import { IconButton, Tooltip } from "@mui/material"; + +const Allocate = ({ row, setMachine, setOpenMachinesDialog }) => { + const handleClick = () => { + setMachine(row.original); + setOpenMachinesDialog(false); + }; + + return ( + <> + + + + + + + ); +}; +export default Allocate; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/RowActions/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/RowActions/index.jsx new file mode 100644 index 0000000..bcfcc03 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/RowActions/index.jsx @@ -0,0 +1,6 @@ +import Allocate from "./Allocate"; + +const RowActions = ({ row, setMachine, setOpenMachinesDialog }) => { + return ; +}; +export default RowActions; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/index.jsx new file mode 100644 index 0000000..e070b01 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/MachineList/index.jsx @@ -0,0 +1,68 @@ +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; +import { useMemo } from "react"; +import RowActions from "./RowActions"; +import { GET_MACHINES_TABLE_LIST } from "@/core/utils/routes"; + +const MachinesList = ({ machineType, setMachine, setOpenMachinesDialog }) => { + const columns = useMemo( + () => [ + { + accessorKey: "machine_code", + header: "کد خودرو", + id: "machine_code", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "car_name", + header: "نام خودرو", + id: "car_name", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "car_type", + header: "نوع خودرو", + id: "car_type", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + ], + [] + ); + + return ( + <> + + ( + + )} + /> + + + ); +}; +export default MachinesList; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/index.jsx new file mode 100644 index 0000000..8750142 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/MachinesDialog/index.jsx @@ -0,0 +1,40 @@ +import { Close, Edit } from "@mui/icons-material"; +import { Button, Dialog, IconButton } from "@mui/material"; +import { useState } from "react"; +import MachinesList from "./MachineList"; + +const MachinesDialog = ({ setMachine, mode }) => { + const [openMachinesDialog, setOpenMachinesDialog] = useState(false); + return ( + <> + {mode == "edit" ? ( + setOpenMachinesDialog(true)} size="small"> + + + ) : ( + + )} + + setOpenMachinesDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openMachinesDialog && ( + + )} + + + ); +}; +export default MachinesDialog; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/index.jsx new file mode 100644 index 0000000..24bfb15 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/MachineAndDriver/index.jsx @@ -0,0 +1,77 @@ +import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typography } from "@mui/material"; +import DriversDialog from "./DriversDialog"; +import { useState } from "react"; +import MachinesDialog from "./MachinesDialog"; + +const MachineAndDriver = ({ setTabState, allData, setAllData, handlePrev }) => { + const [machine, setMachine] = useState(allData.machine); + const [driver, setDriver] = useState(allData.driver); + + const handleNext = (data) => { + setAllData(data); + setTabState((s) => s + 1); + }; + + return ( + <> + + + + + {machine ? ( + <> + خودرو + + + + + + ) : ( + + )} + + {machine && ( + + {driver ? ( + <> + راننده + + + + + + ) : ( + + )} + + )} + + + + + + + + + ); +}; +export default MachineAndDriver; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/Form/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/Form/index.jsx new file mode 100644 index 0000000..a4449fe --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/Form/index.jsx @@ -0,0 +1,66 @@ +import RahdarNameOrCode from "@/core/components/RahdarNameOrCode"; +import StyledForm from "@/core/components/StyledForm"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Add } from "@mui/icons-material"; +import { Button, Stack } from "@mui/material"; +import { Controller, useForm } from "react-hook-form"; +import { object } from "yup"; + +const schema = object().shape({ + rahdar: object().required("همراه الزامی است."), +}); +const RahdaranForm = ({ setRahdaran }) => { + const defaultValues = { + rahdar: null, + }; + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting, isValid }, + } = useForm({ defaultValues, resolver: yupResolver(schema), mode: "all" }); + + const submit = (data) => { + setRahdaran((prev) => { + const alreadyExists = prev.some((r) => r.id === data.rahdar.id); + if (alreadyExists) return prev; + return [...prev, data.rahdar]; + }); + reset(); + }; + + return ( + + + + { + return ( + field.onChange(value)} + error={error} + multiple={false} + /> + ); + }} + name={"rahdar"} + /> + + + + + ); +}; +export default RahdaranForm; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/List/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/List/index.jsx new file mode 100644 index 0000000..ebc4edc --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/List/index.jsx @@ -0,0 +1,56 @@ +import { AccountCircle, Delete } from "@mui/icons-material"; +import { Card, Collapse, IconButton, Stack, Typography } from "@mui/material"; +import { TransitionGroup } from "react-transition-group"; + +const RahdaranList = ({ rahdaran, setRahdaran }) => { + const handleRemove = (index) => { + setRahdaran((prev) => prev.filter((_, i) => i !== index)); + }; + + return ( + + + {rahdaran.map((rahdar, index) => ( + + + + + + + نام و نام خانوادگی: + {rahdar.name} + + + کدملی: + {rahdar.code} + + + + handleRemove(index)}> + + + + + ))} + {rahdaran.length == 0 && ( + + همراهی ثبت نشده است + + )} + + + ); +}; +export default RahdaranList; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/index.jsx new file mode 100644 index 0000000..af01fe6 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Rahdaran/index.jsx @@ -0,0 +1,36 @@ +import { Box, Button, Chip, DialogActions, DialogContent, Divider } from "@mui/material"; +import { useCallback, useState } from "react"; +import RahdaranForm from "./Form"; +import RahdaranList from "./List"; + +const Rahdaran = ({ allData, setAllData, setTabState, handlePrev }) => { + const [rahdaran, setRahdaran] = useState(allData.rahdaran); + + const handleNext = useCallback(() => { + setAllData({ rahdaran: rahdaran }); + setTabState((s) => s + 1); + }, [rahdaran]); + + return ( + <> + + + + + + + + + + + + + + + ); +}; +export default Rahdaran; 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 new file mode 100644 index 0000000..0393018 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/Verify/index.jsx @@ -0,0 +1,130 @@ +import MapLoading from "@/core/components/MapLayer/Loading"; +import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes"; +import { missionRegions } from "@/core/utils/missionRegions"; +import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typography } from "@mui/material"; +import moment from "jalali-moment"; +import dynamic from "next/dynamic"; +import { useCallback } from "react"; +import ShowBound from "../../../showBound"; +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const Verify = ({ allData, handlePrev, submitForm, submitting }) => { + const handleNext = useCallback(() => { + submitForm(allData); + }, [allData]); + + return ( + <> + + + + + + + + + + + + + + موضوع ماموریت + + + + + محدوده + + r.id == allData.region).name_fa} /> + + + نوع ماموریت + + t.id == allData.category_id).name_fa} + /> + + {allData.category_id == 3 && ( + + کد شکایت + + + + )} + + مقصد + + + + + تاریخ شروع ماموریت + + + + + تاریخ پایان ماموریت + + + + + + + + + + خودرو + + + + + راننده + + + + + + + + + {allData.rahdaran.length != 0 ? ( + allData.rahdaran.map((rahdar) => ( + + {rahdar.name} + + + + )) + ) : ( + + همراهی ثبت نشده است + + )} + + + + + + + + + + ); +}; +export default Verify; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/index.jsx new file mode 100644 index 0000000..00f9ef1 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/Form/index.jsx @@ -0,0 +1,125 @@ +import { AccessTime, Engineering, InsertDriveFile, LocalShipping, Route, Verified } from "@mui/icons-material"; +import { Box, Tab, Tabs } from "@mui/material"; +import { useReducer, useState } from "react"; +import Area from "./Area"; +import GetDateTime from "./GetDateTime"; +import GetItemInfo from "./GetItemInfo"; +import Rahdaran from "./Rahdaran"; +import Verify from "./Verify"; +import MachineAndDriver from "./MachineAndDriver"; + +function TabPanel(props) { + const { children, value, index } = props; + return ( + + ); +} + +const reducer = (state, action) => { + switch (action.type) { + case "changeData": + return { ...state, ...action.data }; + default: + return state; + } +}; + +const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => { + const [allData, dispatch] = useReducer(reducer, defaultValues); + const [tabState, setTabState] = useState(0); + const handleClose = () => { + setOpen(false); + }; + const handleChangeTab = (event, newValue) => { + setTabState(newValue); + }; + + const handlePrev = () => { + if (tabState === 0) { + handleClose(); + } else { + setTabState((t) => t - 1); + } + }; + + return ( + <> + + } label="مشخصات" /> + } label="زمانبندی" /> + } label="منطقه عملیاتی" /> + } label="خودرو و راننده" /> + } label="همراهان" /> + } label="بررسی نهایی" /> + + + { + dispatch({ type: "changeData", data }); + }} + handlePrev={handlePrev} + setTabState={setTabState} + /> + + + { + dispatch({ type: "changeData", data }); + }} + handlePrev={handlePrev} + setTabState={setTabState} + /> + + + { + dispatch({ type: "changeData", data }); + }} + handlePrev={handlePrev} + setTabState={setTabState} + /> + + + { + dispatch({ type: "changeData", data }); + }} + handlePrev={handlePrev} + setTabState={setTabState} + /> + + + { + dispatch({ type: "changeData", data }); + }} + handlePrev={handlePrev} + setTabState={setTabState} + /> + + + + + + ); +}; +export default CreateForm; diff --git a/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/index.jsx b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/index.jsx new file mode 100644 index 0000000..2e3cb75 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/Actions/CreateWithoutProcess/index.jsx @@ -0,0 +1,119 @@ +import { REQUEST_MISSION_WITHOUT_PROCESS } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { AddCircle, AddCircleOutline, Close } from "@mui/icons-material"; +import { Button, Dialog, IconButton, useMediaQuery, useTheme } from "@mui/material"; +import moment from "jalali-moment"; +import { useState } from "react"; +import CreateForm from "./Form"; + +const CreateWithoutProcess = ({ mutate }) => { + const [submitting, setSubmitting] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + const submitForm = async (result) => { + setSubmitting(true); + const bound = result.bound.getLatLngs(); + let area = + result.bound_type == "polygon" + ? bound.map((ring) => ring.map((latlng) => [latlng.lat, latlng.lng]))[0] + : bound.map((latlng) => [latlng.lat, latlng.lng]); + + await requestServer(REQUEST_MISSION_WITHOUT_PROCESS, "post", { + data: { + explanation: result.explanation, + category_id: result.category_id, + ...(result.category_id == 3 + ? { + road_observed_id: result.road_observed_id, + } + : {}), + area: { + type: result.bound_type, + coordinates: area, + }, + ...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}), + zone: result.region, + end_point: result.end_point, + start_date: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`, + end_date: `${result.end_date} ${moment(result.end_time).format("HH:mm")}`, + machines: [result.machine.id], + driver: result.driver.id, + }, + hasSidebarUpdate: true, + }) + .then((response) => { + mutate(); + setOpen(false); + }) + .catch((error) => {}) + .finally(() => { + setSubmitting(false); + }); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {open && ( + + )} + + + ); +}; +export default CreateWithoutProcess; diff --git a/src/components/dashboard/roadMissions/violations/RowActions/index.jsx b/src/components/dashboard/roadMissions/violations/RowActions/index.jsx new file mode 100644 index 0000000..192bfe1 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/RowActions/index.jsx @@ -0,0 +1,4 @@ +const RowActions = ({ row, mutate }) => { + return <>; +}; +export default RowActions; diff --git a/src/components/dashboard/roadMissions/violations/ViolationsList.jsx b/src/components/dashboard/roadMissions/violations/ViolationsList.jsx new file mode 100644 index 0000000..49223c2 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/ViolationsList.jsx @@ -0,0 +1,83 @@ +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { GET_ROAD_MISSIONS_VIOLATIONS_LIST } from "@/core/utils/routes"; +import { Box } from "@mui/material"; +import moment from "jalali-moment"; +import { useMemo } from "react"; +import RowActions from "./RowActions"; + +const ViolationsList = () => { + const columns = useMemo( + () => [ + { + accessorKey: "id", + header: "کد یکتا", + id: "id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + }, + { + accessorKey: "type", + header: "نوع تخلف", + id: "type", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + sortDescFirst: true, + grow: false, + Cell: ({ row }) => (row.original.type === 1 ? "خروج بدون مجوز" : "عدم تحرک"), + }, + { + accessorKey: "machine_code", + header: "خودرو", + id: "machine_code", + datatype: "text", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + }, + { + accessorKey: "mission_id", + header: "کد ماموریت", + id: "mission_id", + enableColumnFilter: false, + datatype: "numeric", + filterMode: "equals", + grow: false, + Cell: ({ row }) => row.original.mission_id || "-", + }, + { + accessorKey: "request_date", + header: "تاریخ تخلف", + id: "request_date", + enableColumnFilter: true, + datatype: "date", + filterMode: "between", + grow: false, + Cell: ({ row }) => moment(row.original.request_date).locale("fa").format("YYYY/MM/DD"), + }, + ], + [] + ); + + return ( + <> + + + + + ); +}; +export default ViolationsList; diff --git a/src/components/dashboard/roadMissions/violations/index.jsx b/src/components/dashboard/roadMissions/violations/index.jsx new file mode 100644 index 0000000..ace0981 --- /dev/null +++ b/src/components/dashboard/roadMissions/violations/index.jsx @@ -0,0 +1,14 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { Stack } from "@mui/material"; +import ViolationsList from "./ViolationsList"; + +const ViolationsPage = () => { + return ( + + + + + ); +}; +export default ViolationsPage; diff --git a/src/core/utils/pageMenu.js b/src/core/utils/pageMenu.js index 0c9bfd3..f812b52 100644 --- a/src/core/utils/pageMenu.js +++ b/src/core/utils/pageMenu.js @@ -1,30 +1,34 @@ -import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard"; -import GroupsIcon from "@mui/icons-material/Groups"; import AccountTreeIcon from "@mui/icons-material/AccountTree"; -import FactCheckIcon from "@mui/icons-material/FactCheck"; -import FireTruckIcon from "@mui/icons-material/FireTruck"; -import RouteIcon from "@mui/icons-material/Route"; -import DoorbellIcon from "@mui/icons-material/Doorbell"; -import ElectricBoltIcon from "@mui/icons-material/ElectricBolt"; -import CottageIcon from "@mui/icons-material/Cottage"; -import GppMaybeIcon from "@mui/icons-material/GppMaybe"; -import GavelIcon from "@mui/icons-material/Gavel"; -import BallotIcon from "@mui/icons-material/Ballot"; -import AssistantIcon from "@mui/icons-material/Assistant"; import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings"; -import MapIcon from "@mui/icons-material/Map"; -import SpeedIcon from "@mui/icons-material/Speed"; -import EngineeringIcon from "@mui/icons-material/Engineering"; -import ReportIcon from "@mui/icons-material/Report"; import AssessmentIcon from "@mui/icons-material/Assessment"; import AssignmentLateIcon from "@mui/icons-material/AssignmentLate"; -import LockPersonIcon from "@mui/icons-material/LockPerson"; -import LineAxisIcon from "@mui/icons-material/LineAxis"; -import ScienceIcon from "@mui/icons-material/Science"; -import HandymanIcon from "@mui/icons-material/Handyman"; -import DriveEtaIcon from "@mui/icons-material/DriveEta"; +import AssistantIcon from "@mui/icons-material/Assistant"; +import BallotIcon from "@mui/icons-material/Ballot"; import BiotechIcon from "@mui/icons-material/Biotech"; +import CottageIcon from "@mui/icons-material/Cottage"; +import DoorbellIcon from "@mui/icons-material/Doorbell"; +import DriveEtaIcon from "@mui/icons-material/DriveEta"; +import ElectricBoltIcon from "@mui/icons-material/ElectricBolt"; +import EngineeringIcon from "@mui/icons-material/Engineering"; +import FactCheckIcon from "@mui/icons-material/FactCheck"; +import FireTruckIcon from "@mui/icons-material/FireTruck"; +import GavelIcon from "@mui/icons-material/Gavel"; +import GppMaybeIcon from "@mui/icons-material/GppMaybe"; +import GroupsIcon from "@mui/icons-material/Groups"; +import HandymanIcon from "@mui/icons-material/Handyman"; +import LineAxisIcon from "@mui/icons-material/LineAxis"; +import LockPersonIcon from "@mui/icons-material/LockPerson"; +import ManageAccountsIcon from "@mui/icons-material/ManageAccounts"; +import MapIcon from "@mui/icons-material/Map"; +import MapsHomeWorkIcon from "@mui/icons-material/MapsHomeWork"; import Mediation from "@mui/icons-material/Mediation"; +import PsychologyAltIcon from "@mui/icons-material/PsychologyAlt"; +import ReportIcon from "@mui/icons-material/Report"; +import RouteIcon from "@mui/icons-material/Route"; +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"; export const pageMenu = [ { @@ -128,6 +132,14 @@ export const pageMenu = [ badges: [{ key: "mission.control" }], permissions: ["manage-control-unit"], }, + { + id: "roadMissionsViolations", + label: "تخلفات", + type: "page", + route: "/dashboard/road-missions/violation", + // badges: [{ key: "mission.control" }], // TODO: add badge and permission + permissions: ["all"], + }, ], }, { @@ -296,32 +308,36 @@ export const pageMenu = [ hasSubitems: true, Subitems: [ { - id: "cityAdminZaminGov", - label: "استعلام حرائم پنجره واحد", + id: "cityAdmin", + label: "کارتابل شهرستان", type: "page", - route: "/dashboard/inquiry-privacy/city-admin/zamin-gov", - permissions: [], + route: "/dashboard/inquiry-privacy/city-admin", + icon: , + permissions: ["all"], }, { - id: "provinceAdminZaminGov", - label: "استعلام حرائم پنجره واحد", + id: "privacyOffice", + label: "کارتابل اداره حریم", type: "page", - route: "/dashboard/inquiry-privacy/province-admin/zamin-gov", - permissions: [], + route: "/dashboard/inquiry-privacy/privacy-office", + icon: , + permissions: ["all"], }, { - id: "assistantZaminGov", - label: "استعلام حرائم پنجره واحد", + id: "assistant", + label: "کارتابل معاون", type: "page", - route: "/dashboard/inquiry-privacy/assistant/zamin-gov", - permissions: [], + route: "/dashboard/inquiry-privacy/technical-deputy", + icon: , + permissions: ["all"], }, { - id: "generalManagerZaminGov", - label: "استعلام حرائم پنجره واحد", + id: "generalManager", + label: "کارتابل مدیر", type: "page", - route: "/dashboard/inquiry-privacy/general-manager/zamin-gov", - permissions: [], + route: "/dashboard/inquiry-privacy/general-manager", + icon: , + permissions: ["all"], }, ], }, diff --git a/src/core/utils/pageMenuDev.js b/src/core/utils/pageMenuDev.js index 7a68cb6..41e811d 100644 --- a/src/core/utils/pageMenuDev.js +++ b/src/core/utils/pageMenuDev.js @@ -131,6 +131,14 @@ export const pageMenuDev = [ badges: [{ key: "mission.control" }], permissions: ["manage-control-unit"], }, + { + id: "roadMissionsViolations", + label: "تخلفات", + type: "page", + route: "/dashboard/road-missions/violation", + // 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 11cf9b4..6bf7001 100644 --- a/src/core/utils/routes.js +++ b/src/core/utils/routes.js @@ -227,6 +227,7 @@ export const ALLOCATE_REQUEST_MISSION = api + "/api/v3/missions/transportation_u export const DEALLOCATE_REQUEST_MISSION = api + "/api/v3/missions/transportation_unit/deallocate"; 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_MACHINES_TABLE_LIST = api + "/api/v3/cmms_machines";