diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx index db106e3..cf29fec 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx @@ -3,7 +3,6 @@ import { useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import { array, mixed, number, object, string } from "yup"; import { useTheme } from "@emotion/react"; -import useRequest from "@/lib/hooks/useRequest"; import { Box, Button, DialogActions, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material"; import StyledForm from "@/core/components/StyledForm"; import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; @@ -15,7 +14,6 @@ import BeenhereIcon from "@mui/icons-material/Beenhere"; import GetItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm"; import GetSubItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm"; import GetItemInfo from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo"; -import { CREATE_ROAD_ITEMS } from "@/core/utils/routes"; function TabPanel(props) { const { children, value, index } = props; @@ -26,27 +24,30 @@ function TabPanel(props) { ); } -const defaultValues = { - item_id: null, - sub_item_id: null, - amount: "", - activity_time: "", - activity_date: "", - before_image: null, - after_image: null, - cmms_machines: null, - rahdaran_id: null, - start_point: "", - end_point: "", -}; const validationSchema = object({ item_id: number().required("نوع آیتم را مشخص کنید!"), sub_item_id: number().required("موضوع مشاهده شده را مشخص کنید!"), amount: string().required("وارد کردن مقدار الزامیست!"), cmms_machines: array().required("وارد کردن کد خودرو الزامیست!"), rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"), // بررسی حداقل یک آیتم, - activity_time: string().required("لطفا زمان فعالیت را انتخاب کنید!"), - activity_date: string().required("لطفاً تاریخ شروع فعالیت را انتخاب کنید!"), + activity_time: string().test("activity-time-conditional", "لطفا زمان فعالیت را انتخاب کنید!", function (value) { + const { is_gasht } = this.options.context; // دسترسی به context + if (is_gasht) { + return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد + } + return !!value; // در غیر این صورت مقدار فیلد باید وجود داشته باشد + }), + activity_date: string().test( + "activity-date-conditional", + "لطفاً تاریخ شروع فعالیت را انتخاب کنید!", + function (value) { + const { is_gasht } = this.options.context; // دسترسی به context + if (is_gasht) { + return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد + } + return !!value; // در غیر این صورت مقدار فیلد باید وجود داشته باشد + } + ), before_image: mixed() .nullable() .test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) { @@ -82,7 +83,20 @@ const validationSchema = object({ }), }); -const CreateFormContent = ({ setOpen, onSubmit }) => { +const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => { + const defaultValues = { + item_id: null, + sub_item_id: null, + amount: "", + before_image: null, + after_image: null, + cmms_machines: null, + rahdaran_id: null, + start_point: "", + end_point: "", + ...(!is_gasht && { activity_time: "", activity_date: "" }), + }; + const [tabState, setTabState] = useState(0); const [itemsList, setItemsList] = useState(); const [subItemsList, setSubItemsList] = useState([]); @@ -131,7 +145,7 @@ const CreateFormContent = ({ setOpen, onSubmit }) => { defaultValues, resolver: yupResolver(validationSchema), mode: "onBlur", - context: { subItemsList }, + context: { subItemsList, is_gasht }, }); const onSubmitBase = async (data) => { let result = { ...data }; @@ -180,9 +194,10 @@ const CreateFormContent = ({ setOpen, onSubmit }) => { }, }); }; + console.log(errors); return ( - + { register={register} watch={watch} getValues={getValues} + is_gasht={is_gasht} /> @@ -250,6 +266,7 @@ const CreateFormContent = ({ setOpen, onSubmit }) => { size="large" disabled={isSubmitting} type={"submit"} + form="road_items" endIcon={} > {isSubmitting ? "در حال ثبت فعالیت" : "ثبت فعالیت"} diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx index c8a323f..a802b89 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx @@ -1,4 +1,4 @@ -import { Stack, TextField, Grid } from "@mui/material"; +import { Grid, Stack } from "@mui/material"; import ImageUpload from "./ImageUpload"; import MuiDatePicker from "@/core/components/MuiDatePicker"; import MuiTimePicker from "@/core/components/MuiTimePicker"; @@ -9,7 +9,8 @@ import CarCode from "@/core/components/CarCode"; import RahdarCode from "@/core/components/RahdarCode"; import PreviousStatesInfo from "./PreviousStatesInfo"; import NumberField from "@/core/components/NumberField"; -const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues }) => { + +const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues, is_gasht }) => { return ( @@ -77,30 +78,32 @@ const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, err ) : null} - - - - + {!is_gasht ? ( + + + + + + + + - - - - - + + ) : null} {subItemsList?.needs_end_point === 1 ? ( diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx index 3efd1d9..a545b4d 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx @@ -15,15 +15,31 @@ const ImageUpload = ({ control, setValue, errors, getValues, beforeImage = null, const [showAfterImage, setShowAfterImage] = useState(!afterImage); useEffect(() => { - if (getValues("before_image")) { + const beforeImage = getValues("before_image"); + const afterImage = getValues("after_image"); + + if (beforeImage) { setShowBeforeImage(false); - setBeforeImg(getValues("before_image")); - setBeforeFileType("image/"); + + if (typeof beforeImage === "string") { + setBeforeImg(beforeImage); + setBeforeFileType("image/"); + } else if (beforeImage instanceof File) { + setBeforeImg(URL.createObjectURL(beforeImage)); + setBeforeFileType(beforeImage.type); + } } - if (getValues("after_image")) { + + if (afterImage) { setShowAfterImage(false); - setAfterImg(getValues("after_image")); - setAfterFileType("image/"); + + if (typeof afterImage === "string") { + setAfterImg(afterImage); + setAfterFileType("image/"); + } else if (afterImage instanceof File) { + setAfterImg(URL.createObjectURL(afterImage)); + setAfterFileType(afterImage.type); + } } }, [getValues]); diff --git a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx index 164a07a..530583b 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx @@ -14,21 +14,34 @@ const EditFormContent = ({ setOpenEditDialog, onSubmit, defaultData }) => { start_point: defaultData?.start_point || { lat: "", lng: "" }, end_point: defaultData?.end_point || { lat: "", lng: "" }, cmms_machines: defaultData?.cmms_machines || null, - rahdaran_id: defaultData?.rahdaran || null, + rahdaran_id: defaultData?.rahdaran_id || null, }; const onSubmitBase = async (data) => { let result = { ...data }; - (result.before_image === null || result.before_image === defaultValues.before_image) && + if (result.before_image === null) { delete result.before_image; - (result.after_image === null || result.after_image === defaultValues.after_image) && delete result.after_image; - if (subItem.needs_end_point !== 1) { + delete data.before_image; + } + + if (result.after_image === null) { + delete result.after_image; + delete data.after_image; + } + + if (result.end_point === "") { delete result.end_point; + delete data.end_point; } else { result.end_point = `${result.end_point.lat},${result.end_point.lng}`; } - result.start_point = `${result.start_point.lat},${result.start_point.lng}`; + if (result.start_point === "") { + delete result.start_point; + delete data.start_point; + } else { + result.start_point = `${result.start_point.lat},${result.start_point.lng}`; + } result.rahdaran_id.forEach((rahdar, index) => { result[`rahdaran_id[${index}]`] = rahdar.id; @@ -45,8 +58,10 @@ const EditFormContent = ({ setOpenEditDialog, onSubmit, defaultData }) => { data: { ...data, item_name: subItem.item_str, - sub_item_name: subItemsList.name, - unit_fa: subItemsList.unit, + sub_item_name: subItem.name, + unit_fa: subItem.unit, + item_id: subItem.item, + sub_item_id: subItem.sub_item, }, }); }; diff --git a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormCreate.jsx b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormCreate.jsx index 7f2b6d8..f4d3280 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormCreate.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormCreate.jsx @@ -65,15 +65,16 @@ const EditFormCreate = ({ defaultData, subItem, onSubmitBase, defaultValues, set mode: "onBlur", context: { subItem }, }); + return ( - + {subItem.needs_image === 1 ? ( setOpenEditDialog(false)} variant="outlined" color="secondary" autoFocus> بستن - diff --git a/src/components/dashboard/roadPatrols/operator/Actions/Create/index.jsx b/src/components/dashboard/roadPatrols/operator/Actions/Create/index.jsx new file mode 100644 index 0000000..a76a68a --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Actions/Create/index.jsx @@ -0,0 +1,33 @@ +"use client"; + +import { Button, IconButton, useMediaQuery } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import { useState } from "react"; +import { AddCircle } from "@mui/icons-material"; +import CreatePatrol from "../../Forms/CreatePatrol"; + +const OperatorCreate = ({ mutate }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + {open && } + + ); +}; +export default OperatorCreate; diff --git a/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx b/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx index 54df4da..1c635c8 100644 --- a/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx +++ b/src/components/dashboard/roadPatrols/operator/ExcelPrint/index.jsx @@ -45,7 +45,6 @@ const PrintExcel = ({ table, filterData }) => { + + + + {ActionsList.length !== 0 ? ( + + {ActionsList.map((action, index) => ( + + deleteAction(index)} + /> + + ))} + + ) : ( + + + فعالیتی ثبت نکرده اید + + + )} + + + + + + + ); +}; + +export default ActionsDuringPatrol; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/CompeleteRequest.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/CompeleteRequest.jsx new file mode 100644 index 0000000..3299e47 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/CompeleteRequest.jsx @@ -0,0 +1,229 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { + Box, + Button, + Chip, + Divider, + FormControl, + Grid, + InputLabel, + OutlinedInput, + Slide, + Stack, + Typography, +} from "@mui/material"; +import BeenhereIcon from "@mui/icons-material/Beenhere"; +import ForwardToInboxIcon from "@mui/icons-material/ForwardToInbox"; +import { formatCounter } from "@/core/utils/formatCounter"; +import useRequest from "@/lib/hooks/useRequest"; +import { GET_OTP_TOKEN } from "@/core/utils/routes"; +import moment from "jalali-moment"; + +const CompeleteRequest = ({ watch, isSubmitting, setValue, register }) => { + const requestServer = useRequest({ auth: true }); + const [delayPerRequest, setDelayPerRequest] = useState(false); + const [phoneNumber, setPhoneNumber] = useState(""); + const [validPhone, setValidPhone] = useState(false); + const [verificationCode, setVerificationCode] = useState(""); + const [counter, setCounter] = useState(120); + const [otpSended, setOtpSended] = useState(false); + const [readyToVerify, setReadyToVerify] = useState(false); + + const handleNumericInput = (e) => { + e.target.value = e.target.value.replace(/[^0-9]/g, ""); + }; + + useEffect(() => { + if (phoneNumber.length === 11) { + setValidPhone(true); + } else { + setValidPhone(false); + } + if (verificationCode.length === 6 && phoneNumber.length === 11) { + setReadyToVerify(true); + } else { + setReadyToVerify(false); + } + }, [phoneNumber, verificationCode]); + + useEffect(() => { + if (counter === 0) { + setDelayPerRequest(false); + return; + } + if (delayPerRequest && counter > 0) { + const timer = setInterval(() => { + setCounter((prevCounter) => prevCounter - 1); + }, 1000); + + return () => clearInterval(timer); + } + }, [counter, delayPerRequest]); + + const sendOtp = () => { + if (!validPhone) return false; + requestServer(`${GET_OTP_TOKEN}?phone_number=${phoneNumber}`, "get") + .then((response) => { + setOtpSended(true); + setDelayPerRequest(true); + }) + .catch(() => {}); + }; + + return ( + + + + + + {moment(watch("start_time")).locale("fa").format("YYYY/MM/DD | HH:mm")} + + + + + {moment(watch("end_time")).locale("fa").format("YYYY/MM/DD | HH:mm")} + + + + + + + + {watch("vehicle_runtime")} + + + + + {watch("fuel_consumption")} + + + + + {watch("stop_points").length} + + + + + {watch("distance")} + + + + + {watch("observed_items").length} + + + + + + + + + کد + + {watch("road_patrol_machines_id.machine_code")} + + + نام + + {watch("road_patrol_machines_id.car_name")} + + + پلاک + + {watch("road_patrol_machines_id.plak_number")} + + + + + + + {watch("road_patrol_rahdaran_id")?.map((rahdar, index) => ( + + نام و کد راهدار + + + {rahdar.name} | {rahdar.code} + + + ))} + + + + + + شماره تلفن مامور گشت + setPhoneNumber(e.target.value)} + onInput={handleNumericInput} + type="text" + inputProps={{ + maxLength: 11, + }} + /> + + + + + + + کد پیامک شده + setVerificationCode(e.target.value)} + onInput={handleNumericInput} + type="text" + inputProps={{ + maxLength: 6, + inputMode: "numeric", + style: { + textAlign: "center", + letterSpacing: "10px", + }, + }} + /> + + + + + + + + ); +}; + +export default CompeleteRequest; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/EditActionDuringPatrol.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/EditActionDuringPatrol.jsx new file mode 100644 index 0000000..1693c39 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/EditActionDuringPatrol.jsx @@ -0,0 +1,57 @@ +"use client"; + +import React, { useState } from "react"; +import dynamic from "next/dynamic"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import { Dialog, DialogTitle, IconButton } from "@mui/material"; +import EditIcon from "@mui/icons-material/Edit"; +import EditFormContent from "@/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent"; + +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const EditActionDuringPatrol = ({ action, index, setActionsList }) => { + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + const HandleSubmit = async (newData) => { + setActionsList((prev) => { + const updatedList = [...prev]; + updatedList[index] = { + ...updatedList[index], + data: newData.data, + result: newData.result, + }; + return updatedList; + }); + setOpen(false); + }; + + return ( + <> + + + + + ویرایش اطلاعات + + + + ); +}; + +export default EditActionDuringPatrol; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalActionsDuringPatrol.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalActionsDuringPatrol.jsx new file mode 100644 index 0000000..073fd9e --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalActionsDuringPatrol.jsx @@ -0,0 +1,16 @@ +import CreateFormContent from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent"; +import { Dialog } from "@mui/material"; + +const ModalActionsDuringPatrol = ({ open, setOpen, setActionsList }) => { + const HandleSubmit = async (data) => { + setActionsList((prev) => [...prev, data]); + setOpen(false); + }; + + return ( + + + + ); +}; +export default ModalActionsDuringPatrol; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetail.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetail.jsx new file mode 100644 index 0000000..487f38e --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetail.jsx @@ -0,0 +1,203 @@ +"use client"; + +import { Box, Button, Chip, Divider, IconButton, InputAdornment, Stack } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import CarCode from "@/core/components/CarCode"; +import SearchIcon from "@mui/icons-material/Search"; +import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali"; +import { faIR } from "@mui/x-date-pickers/locales"; +import { LocalizationProvider, MobileDateTimePicker } from "@mui/x-date-pickers"; +import ClearIcon from "@mui/icons-material/Clear"; +import moment from "jalali-moment"; +import dynamic from "next/dynamic"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import { Controller } from "react-hook-form"; +import { GET_FMS_DATA } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import PatrolDetailInfo from "./PatrolDetailInfo"; +import PatrolResultCodeErrors from "./PatrolResultCodeErrors"; + +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const PatrolDetail = ({ control, watch, setValue, tabState, setTabState }) => { + const requestServer = useRequest({ notificationSuccess: true }); + const [patrolResultStatus, setPatrolResultStatus] = useState(null); + const [readyToRequest, setReadyToRequest] = useState(false); + const [patrolData, setPatrolData] = useState(null); + + const requestPatrolInfo = async () => { + const formData = new FormData(); + formData.append("machineCode", watch("road_patrol_machines_id").machine_code); + formData.append("startDT", moment(watch("start_time")).format("YYYY-MM-DDTHH:mm")); + formData.append("endDT", moment(watch("end_time")).format("YYYY-MM-DDTHH:mm")); + await requestServer(GET_FMS_DATA, "post", { + data: formData, + }) + .then((response) => { + const data = response.data.data; + setPatrolData({ + ...data, + roadPatrolMachinesId: watch("road_patrol_machines_id"), + start_time: watch("start_time"), + end_time: watch("end_time"), + }); + setPatrolResultStatus(data.resultCode); + }) + .catch((error) => {}); + }; + + useEffect(() => { + const roadPatrolMachinesId = watch("road_patrol_machines_id"); + const startTime = watch("start_time"); + const endTime = watch("end_time"); + + const isReady = roadPatrolMachinesId !== null && startTime !== "" && endTime !== ""; + setReadyToRequest(isReady); + }, [watch("road_patrol_machines_id"), watch("start_time"), watch("end_time")]); + + return ( + + + + { + return ( + field.onChange(value)} + error={error} + /> + ); + }} + name={"road_patrol_machines_id"} + /> + + + + { + const date = new Date(start_time); + const formattedDate = moment(date).locale("en").format("YYYY-MM-DD HH:mm"); + setValue("start_time", formattedDate); + }} + slotProps={{ + textField: { + size: "small", + placeholder: "تاریخ و ساعت شروع گشت", + InputProps: { + endAdornment: ( + + { + event.stopPropagation(); + setValue("start_time", ""); + }} + sx={{ + color: "#bfbfbf", + "&:hover": { + backgroundColor: "rgba(189, 189, 189, 0.1)", + color: "#363434", + }, + }} + > + + + + ), + }, + }, + }} + label="تاریخ و ساعت شروع گشت" + /> + { + const date = new Date(end_time); + const formattedDate = moment(date).locale("en").format("YYYY-MM-DD HH:mm"); + setValue("end_time", formattedDate); + }} + slotProps={{ + textField: { + size: "small", + placeholder: "تاریخ و ساعت پایان گشت", + InputProps: { + endAdornment: ( + + { + event.stopPropagation(); + setValue("end_time", ""); + }} + sx={{ + color: "#bfbfbf", + "&:hover": { + backgroundColor: "rgba(189, 189, 189, 0.1)", + color: "#363434", + }, + }} + > + + + + ), + }, + }, + }} + label="تاریخ و ساعت پایان گشت" + /> + + + + + + + + {patrolResultStatus === 0 ? ( + + ) : ( + + )} + + ); +}; + +export default PatrolDetail; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetailInfo.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetailInfo.jsx new file mode 100644 index 0000000..81fb7f0 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetailInfo.jsx @@ -0,0 +1,177 @@ +"use client"; + +import { Box, Button, Card, CardActions, CardContent, Chip, Divider, Slide, Stack, Typography } from "@mui/material"; +import React, { useEffect } from "react"; +import dynamic from "next/dynamic"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import LocalGasStationIcon from "@mui/icons-material/LocalGasStation"; +import DirectionsCarFilledIcon from "@mui/icons-material/DirectionsCarFilled"; +import WatchLaterIcon from "@mui/icons-material/WatchLater"; +import QueryBuilderIcon from "@mui/icons-material/QueryBuilder"; +import AddRoadIcon from "@mui/icons-material/AddRoad"; +import ShareLocationIcon from "@mui/icons-material/ShareLocation"; +import ElectricCarIcon from "@mui/icons-material/ElectricCar"; +import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft"; +import QrCode2Icon from "@mui/icons-material/QrCode2"; +import moment from "jalali-moment"; +import PatrolMapFeatures from "./PatrolMapFeatures"; +import { useMap } from "react-leaflet"; + +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const MapItemViewBound = ({ patrolData, bound }) => { + const map = useMap(); + + useEffect(() => { + if (bound.length !== 0) { + map.fitBounds(bound, { paddingTopLeft: [16, 16], paddingBottomRight: [16, 130] }); + } + }, [bound]); + + return ( + <> + {patrolData.stopPoints.map((stopPoint, index) => ( + + + + ))} + + ); +}; + +const PatrolDetailInfo = ({ patrolData, tabState, setTabState, setValue }) => { + const bound = patrolData.stopPoints.map((point) => [point.latitude, point.longitude]); + + const SendPatrolInfo = () => { + setValue("vehicle_runtime", patrolData.accOnDuration); + setValue("fuel_consumption", patrolData.fuelConsumption); + setValue("distance", patrolData.mileage); + setValue("stop_points", patrolData.stopPoints); + setTabState(tabState + 1); + }; + + return ( + + + + + + } /> + + + {Math.floor(patrolData.accOnDuration / 60)} دقیقه + + + + } /> + + + {patrolData.roadPatrolMachinesId.car_name} + + + + } /> + + + {patrolData.roadPatrolMachinesId.machine_code} + + + + } /> + + + {patrolData.roadPatrolMachinesId.plak_number} + + + + } /> + + + {moment(patrolData.start_time).locale("fa").format("HH:mm | YYYY/MM/DD")} + + + + } /> + + + {moment(patrolData.end_time).locale("fa").format("HH:mm | YYYY/MM/DD")} + + + + } /> + + + {(patrolData.mileage / 1000).toFixed(1)} کیلومتر + + + + } /> + + + {Math.round(patrolData.fuelConsumption * 10) / 10} لیتر + + + + } /> + + + {patrolData.stopPoints.length} مورد + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default PatrolDetailInfo; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolForms.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolForms.jsx new file mode 100644 index 0000000..bc67467 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolForms.jsx @@ -0,0 +1,179 @@ +"use client"; + +import { Box, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import React, { useEffect, useState } from "react"; +import TaxiAlertIcon from "@mui/icons-material/TaxiAlert"; +import EngineeringIcon from "@mui/icons-material/Engineering"; +import HandymanIcon from "@mui/icons-material/Handyman"; +import VerifiedIcon from "@mui/icons-material/Verified"; +import PatrolTimeLine from "./PatrolTimeLine"; +import PatrolDetail from "./PatrolDetail"; +import SuperVisorsDetail from "./SuperVisorsDetail"; +import ActionsDuringPatrol from "./ActionsDurigPatrol"; +import { useForm } from "react-hook-form"; +import StyledForm from "@/core/components/StyledForm"; +import useRequest from "@/lib/hooks/useRequest"; +import CompeleteRequest from "./CompeleteRequest"; + +function TabPanel(props) { + const { children, value, index } = props; + + return ( + + ); +} + +const defaultValues = { + road_patrol_rahdaran_id: null, + road_patrol_machines_id: null, + start_time: "", + end_time: "", + stop_points: null, + vehicle_runtime: "", + fuel_consumption: "", + distance: "", + observed_items: null, + phone_number: "", + otp_token: "", +}; + +const PatrolForms = ({ mutate, setOpen }) => { + const requestServer = useRequest({ notificationSuccess: true }); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("md")); + const [tabState, setTabState] = useState(0); + const [activeUpTo, setActiveUpTo] = useState(0); + + const handleChangeTab = (event, newValue) => { + setTabState(newValue); + }; + + useEffect(() => { + if (activeUpTo < tabState) { + setActiveUpTo(tabState); + } + }, [tabState]); + + const { + control, + watch, + getValues, + register, + handleSubmit, + setValue, + resetField, + formState: { isSubmitting }, + } = useForm({ + defaultValues, + mode: "onBlur", + }); + + const onSubmit = async (data) => { + console.log("data", data); + // const formData = new FormData(); + // data.road_patrol_rahdaran_id.forEach((rahdar, index) => formData.append(`road_patrol_rahdaran_id[${index}]`, rahdar.id)); + // formData.append(`road_patrol_machines_id[0]`, data.road_patrol_machines_id.id) + // data.stop_points.forEach((stop_point, index) => formData.append(`stop_points[${index}]`, stop_point)) + // data.observed_items.forEach((observed_item, index) => { + // formData.append(`observed_items[${index}][instant_action]`, 1); + // formData.append(`observed_items[${index}][local_name]`, ""); + // formData.append(`observed_items[${index}][start_point]`, observed_item.start_point); + // formData.append(`observed_items[${index}][start_point]`, observed_item.end_point); + // formData.append(`observed_items[${index}][amount]`, observed_item.amount); + // formData.append(`observed_items[${index}][before_image]`, observed_item.before_image); + // formData.append(`observed_items[${index}][after_image]`, observed_item.after_image); + // observed_item.cmms_machines.forEach((machine) => formData.append(`observed_items[${index}][cmms_machines][]`, machine.id)) + // observed_item.rahdaran_id.forEach((rahdar) => formData.append(`observed_items[${index}][rahdaran_id][]`, rahdar.id)) + // formData.append(`observed_items[${index}][sub_item_id]`, observed_item.sub_item_id); + // }) + // formData.append("start_time", data.start_time); + // formData.append("end_time", data.end_time); + // formData.append("vehicle_runtime", data.vehicle_runtime); + // formData.append("fuel_consumption", +data.fuel_consumption); + // formData.append("distance", data.distance); + // formData.append("description", ""); + // formData.append("phone_number", data.phone_number); + // formData.append("verification_code", data.verification_code); + // if (data.phone_number !== "" && data.verification_code !== "") { + // try { + // await requestServer(CREATE_PATROL, "post", { + // data: formData, + // }); + // mutate(); + // setOpen(false); + // } catch (error) { + // } + // } else { + // console.log("error phone number") + // } + }; + + return ( + + + = 0)} icon={} label="مشخصات گشت"> + = 1)} icon={} label="مشخصات راهداران"> + = 2)} icon={} label="اقدامات حین گشت"> + = 3)} icon={} label="تکمیل درخواست"> + + + + + + + + + + + + + + + + + {!isMobile && ( + + + + )} + + + ); +}; +export default PatrolForms; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolMapFeatures.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolMapFeatures.jsx new file mode 100644 index 0000000..1a8b785 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolMapFeatures.jsx @@ -0,0 +1,38 @@ +"use client"; + +import { Typography } from "@mui/material"; +import React, { useRef } from "react"; +import { Marker, Popup } from "react-leaflet"; +import AzmayeshIcon from "@/assets/images/examine_marker.png"; + +const PatrolMapFeatures = ({ stopPoint }) => { + const position = [stopPoint.latitude, stopPoint.longitude]; + const mapPatrolMarker = useRef(); + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + + return ( + + + مدت زمان توقف: {stopPoint.duration} ثانیه + + + ); +}; + +export default PatrolMapFeatures; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolResultCodeErrors.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolResultCodeErrors.jsx new file mode 100644 index 0000000..da28921 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolResultCodeErrors.jsx @@ -0,0 +1,25 @@ +"use client"; + +import { Box, Typography } from "@mui/material"; +import React from "react"; + +const PatrolResultCodeErrors = ({ ResultCode }) => { + const errorMessages = { + [-1]: "نام کاربری یا کلمه عبور صحیح نمیباشد", + [-2]: "ردیاب به خودرو انتخابی متصل نیست", + [-3]: "کاربر دسترسی لازم به اطلاعات خودرو انتخابی را ندارد", + [-4]: "خطای درخواست مکرر", + }; + + const message = errorMessages[ResultCode] || "ابتدا اطلاعات بالا را تکمیل نمایید"; + + return ( + + + {message} + + + ); +}; + +export default PatrolResultCodeErrors; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolTimeLine.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolTimeLine.jsx new file mode 100644 index 0000000..4edaa2f --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolTimeLine.jsx @@ -0,0 +1,119 @@ +"use client"; + +import { Box, CircularProgress, Typography } from "@mui/material"; +import TaxiAlertIcon from "@mui/icons-material/TaxiAlert"; +import EngineeringIcon from "@mui/icons-material/Engineering"; +import HandymanIcon from "@mui/icons-material/Handyman"; +import VerifiedIcon from "@mui/icons-material/Verified"; +import { + Timeline, + TimelineConnector, + TimelineContent, + TimelineDot, + TimelineItem, + timelineItemClasses, + TimelineSeparator, +} from "@mui/lab"; + +const PatrolTimeLine = ({ tabState }) => { + return ( + + + + + + + {tabState === 0 ? ( + + ) : ( + 0 ? "success" : "error"} + sx={{ width: "1.5rem", height: "1.5rem" }} + /> + )} + + + + + مشخصات گشت + + تایید اطلاعات اولیه گشت + + + + + + + + {tabState === 1 ? ( + + ) : ( + 1 ? "success" : "error"} + sx={{ width: "1.5rem", height: "1.5rem" }} + /> + )} + + + + + مشخصات راهداران + + راهدار / راهداران حاضر در گشت + + + + + + + + {tabState === 2 ? ( + + ) : ( + 2 ? "success" : "error"} + sx={{ width: "1.5rem", height: "1.5rem" }} + /> + )} + + + + + اقدامات حین گشت + + ثبت فعالیت های صورت گرفته حین گشت + + + + + + + + {tabState === 3 ? ( + + ) : ( + 3 ? "success" : "error"} + sx={{ width: "1.5rem", height: "1.5rem" }} + /> + )} + + + + + تکمیل درخواست + + تکمیل درخواست و ثبت نهایی + + + + + + ); +}; +export default PatrolTimeLine; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PhoneValidation.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PhoneValidation.jsx new file mode 100644 index 0000000..0c9cb30 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PhoneValidation.jsx @@ -0,0 +1,118 @@ +"use client"; + +import { Box, Button, FormControl, InputLabel, OutlinedInput } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import ForwardToInboxIcon from "@mui/icons-material/ForwardToInbox"; +import useRequest from "@/lib/hooks/useRequest"; +import { GET_OTP_TOKEN, VERIFY_OTP } from "@/core/utils/routes"; +import { formatCounter } from "@/core/utils/formatCounter"; + +const PhoneValidation = ({ tabState, setTabState }) => { + const requestServer = useRequest({ auth: true }); + const [phoneNumber, setPhoneNumber] = useState(""); + const [verificationCode, setVerificationCode] = useState(""); + const [validPhone, setValidPhone] = useState(false); + const [delayPerRequest, setDelayPerRequest] = useState(false); + const [counter, setCounter] = useState(120); + const [otpSended, setOtpSended] = useState(false); + const [readyToVerify, setReadyToVerify] = useState(false); + + const handleNumericInput = (e) => { + e.target.value = e.target.value.replace(/[^0-9]/g, ""); + }; + + useEffect(() => { + if (phoneNumber.length === 11) { + setValidPhone(true); + } else { + setValidPhone(false); + } + if (verificationCode.length === 6 && phoneNumber.length === 11) { + setReadyToVerify(true); + } else { + setReadyToVerify(false); + } + }, [phoneNumber, verificationCode]); + + useEffect(() => { + if (counter === 0) { + setDelayPerRequest(false); + return; + } + if (delayPerRequest && counter > 0) { + const timer = setInterval(() => { + setCounter((prevCounter) => prevCounter - 1); + }, 1000); + + return () => clearInterval(timer); + } + }, [counter, delayPerRequest]); + + const sendOtp = () => { + if (!validPhone) return false; + requestServer(`${GET_OTP_TOKEN}?phone_number=${phoneNumber}`, "get") + .then((response) => {}) + .catch(() => {}); + //////////**** (mohammad) this part should keep in success but for now we go next level ****//////////// + setOtpSended(true); + setDelayPerRequest(true); + }; + + const phoneRegister = () => { + const formData = new FormData(); + formData.append("phone_number", phoneNumber); + formData.append("verification_code", verificationCode); + + setTabState(tabState + 1); + requestServer(VERIFY_OTP, "post", { + data: formData, + }) + .then(() => {}) + .catch(() => {}); + }; + + return ( + + + + شماره تلفن مامور گشت + setPhoneNumber(e.target.value)} + onInput={handleNumericInput} + type="text" + inputProps={{ + maxLength: 11, + }} + /> + + + + + ); +}; + +export default PhoneValidation; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorInfo.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorInfo.jsx new file mode 100644 index 0000000..c70c5f4 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorInfo.jsx @@ -0,0 +1,38 @@ +"use client"; + +import { Box, Card, IconButton, Stack, Typography } from "@mui/material"; +import AccountCircleIcon from "@mui/icons-material/AccountCircle"; +import DeleteIcon from "@mui/icons-material/Delete"; +import React from "react"; + +const SuperVisorInfo = ({ superVisor, deleteSuperVisor }) => { + return ( + + + + + + {superVisor.name} + + کد راهدار: {superVisor.code} + + + + deleteSuperVisor(superVisor.id)}> + + + + + ); +}; + +export default SuperVisorInfo; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorsDetail.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorsDetail.jsx new file mode 100644 index 0000000..668c546 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorsDetail.jsx @@ -0,0 +1,139 @@ +"use client"; + +import { Box, Button, Chip, Divider, Grid, Stack, Typography } from "@mui/material"; +import SaveAltIcon from "@mui/icons-material/SaveAlt"; +import React, { useEffect, useState } from "react"; +import RahdarCode from "@/core/components/RahdarCode"; +import SuperVisorInfo from "@/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorInfo"; +import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight"; +import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft"; +import { Controller } from "react-hook-form"; + +const SuperVisorsDetail = ({ control, watch, setValue, tabState, setTabState }) => { + const [SuperVisorList, setSuperVisorList] = useState([]); + const [readyToRequest, setReadyToRequest] = useState(false); + + const requestPatrolInfo = () => { + setSuperVisorList((prev) => [...prev, watch("road_patrol_rahdaran_id")]); + setReadyToRequest(false); + setValue("road_patrol_rahdaran_id", null); + }; + + const deleteSuperVisor = (id) => { + setSuperVisorList((prev) => prev.filter((superVisor) => superVisor.id !== id)); + }; + + useEffect(() => { + setReadyToRequest(watch("road_patrol_rahdaran_id") != null); + }, [watch("road_patrol_rahdaran_id")]); + + const SendSuperVisor = () => { + setValue("road_patrol_rahdaran_id", SuperVisorList); + setTabState(tabState + 1); + }; + + return ( + + + + + { + return ( + field.onChange(value)} + error={error} + multiple={false} + /> + ); + }} + name={"road_patrol_rahdaran_id"} + /> + + + + + + + + {SuperVisorList.length !== 0 ? ( + <> + + {SuperVisorList.map((superVisor) => ( + + + + ))} + + + + + + + ) : ( + + + ابتدا اطلاعات بالا را تکمیل نمایید + + + )} + + ); +}; + +export default SuperVisorsDetail; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/index.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/index.jsx new file mode 100644 index 0000000..3c1c72c --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/index.jsx @@ -0,0 +1,13 @@ +"use client"; + +import { Dialog } from "@mui/material"; +import PatrolForms from "./PatrolForms"; + +const CreatePatrol = ({ open, setOpen, mutate }) => { + return ( + + + + ); +}; +export default CreatePatrol; diff --git a/src/components/dashboard/roadPatrols/operator/Toolbar.jsx b/src/components/dashboard/roadPatrols/operator/Toolbar.jsx index 32c4395..d10e83d 100644 --- a/src/components/dashboard/roadPatrols/operator/Toolbar.jsx +++ b/src/components/dashboard/roadPatrols/operator/Toolbar.jsx @@ -1,10 +1,13 @@ import PrintExcel from "./ExcelPrint"; +import OperatorCreate from "./Actions/Create"; +import { Box } from "@mui/material"; -const Toolbar = ({ table, filterData }) => { +const Toolbar = ({ table, filterData, mutate }) => { return ( - <> + + - + ); }; export default Toolbar; diff --git a/src/components/dashboard/roadPatrols/operator/index.jsx b/src/components/dashboard/roadPatrols/operator/index.jsx index 7835954..a2b328c 100644 --- a/src/components/dashboard/roadPatrols/operator/index.jsx +++ b/src/components/dashboard/roadPatrols/operator/index.jsx @@ -1,7 +1,7 @@ "use client"; import PageTitle from "@/core/components/PageTitle"; import { Stack } from "@mui/material"; -import OperatorList from "@/components/dashboard/roadPatrols/operator/OperatorList"; +import OperatorList from "./OperatorList"; const OperatorPage = () => { return ( diff --git a/src/components/dashboard/roadPatrols/supervisor/index.jsx b/src/components/dashboard/roadPatrols/supervisor/index.jsx index 3fa490b..67f9f63 100644 --- a/src/components/dashboard/roadPatrols/supervisor/index.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/index.jsx @@ -1,13 +1,13 @@ "use client"; import PageTitle from "@/core/components/PageTitle"; import { Stack } from "@mui/material"; -import SupervisorList from "./SupervisorList"; +import SuperviserList from "./SupervisorList"; const SupervisorPage = () => { return ( - + ); }; diff --git a/src/core/utils/formatCounter.js b/src/core/utils/formatCounter.js new file mode 100644 index 0000000..7cf9122 --- /dev/null +++ b/src/core/utils/formatCounter.js @@ -0,0 +1,7 @@ +export const formatCounter = (counter) => { + const minutes = Math.floor(counter / 60) + .toString() + .padStart(2, "0"); + const seconds = (counter % 60).toString().padStart(2, "0"); + return `${minutes}:${seconds}`; +}; diff --git a/src/core/utils/pageMenu.js b/src/core/utils/pageMenu.js index f77574f..bf392e4 100644 --- a/src/core/utils/pageMenu.js +++ b/src/core/utils/pageMenu.js @@ -195,13 +195,6 @@ export const pageMenu = [ hasSubitems: true, badges: ["road_patrols.operation_cnt"], Subitems: [ - { - id: "roadPatrolManagmentOparationCreate", - label: "ثبت اقدام", - type: "page", - route: "/dashboard/road-patrols/operator", - permissions: ["add-road-patrol"], - }, { id: "roadPatrolManagmentOparationCartable", label: "کارتابل", diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js index cea920d..06fe15d 100644 --- a/src/core/utils/routes.js +++ b/src/core/utils/routes.js @@ -34,6 +34,9 @@ export const EXPORT_ROAD_PATROL_OPERATOR_LIST = "https://rms.witel.ir/v2/road_pa export const GET_ROAD_PATROL_SUPERVISOR_LIST = api + "/api/v3/road_patrols/supervisor_index"; export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.witel.ir/v2/road_patrols/supervisor/cartable/report"; export const DELETE_ROAD_PATROL_SUPERVISOR = api + "/api/v3/road_patrols/delete"; +export const CREATE_PATROL = api + "/api/v3/road_patrols/store"; +export const GET_FMS_DATA = api + "/api/v3/fms_vehicle/get_activity"; +export const GET_OTP_TOKEN = api + "/v2/get_otp_token"; // road items export const GET_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_index"; diff --git a/src/lib/hooks/useRequest.js b/src/lib/hooks/useRequest.js index f160f53..8e6d121 100644 --- a/src/lib/hooks/useRequest.js +++ b/src/lib/hooks/useRequest.js @@ -20,7 +20,6 @@ const useRequest = (initOptions) => { return async (url = "", method = "get", options) => { const mergedOptions = Object.assign({}, _options, options); - try { const response = await axios({ url,