diff --git a/src/app/(withAuth)/dashboard/road-items/reports/items-report/page.js b/src/app/(withAuth)/dashboard/road-items/reports/items-report/page.js new file mode 100644 index 0000000..ab991b0 --- /dev/null +++ b/src/app/(withAuth)/dashboard/road-items/reports/items-report/page.js @@ -0,0 +1,18 @@ +import WithPermission from "@/core/middlewares/withPermission"; +import ReportPage from "@/components/dashboard/roadItems/reports/ItemsReports"; + +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/dashboard/road-items/reports/sub-items-report/page.js b/src/app/(withAuth)/dashboard/road-items/reports/sub-items-report/page.js new file mode 100644 index 0000000..d605501 --- /dev/null +++ b/src/app/(withAuth)/dashboard/road-items/reports/sub-items-report/page.js @@ -0,0 +1,17 @@ +import WithPermission from "@/core/middlewares/withPermission"; +import SubItemsReports from "@/components/dashboard/roadItems/reports/SubItemsReports"; +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/dashboard/road-patrols/reports/page.js b/src/app/(withAuth)/dashboard/road-patrols/reports/page.js new file mode 100644 index 0000000..5f6cfcf --- /dev/null +++ b/src/app/(withAuth)/dashboard/road-patrols/reports/page.js @@ -0,0 +1,18 @@ +import WithPermission from "@/core/middlewares/withPermission"; +import ReportPage from "@/components/dashboard/roadPatrols/reports"; + +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/dashboard/road-patrols/supervisor/page.js b/src/app/(withAuth)/dashboard/road-patrols/supervisor/page.js index 4093c15..1ef92e4 100644 --- a/src/app/(withAuth)/dashboard/road-patrols/supervisor/page.js +++ b/src/app/(withAuth)/dashboard/road-patrols/supervisor/page.js @@ -3,7 +3,9 @@ import SupervisorPage from "@/components/dashboard/roadPatrols/supervisor"; import ActivityCodeLog from "@/core/components/ActivityCodeLog"; const Page = () => { return ( - + 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 cf29fec..7b9b9a5 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx @@ -14,6 +14,7 @@ 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 { format } from "date-fns"; function TabPanel(props) { const { children, value, index } = props; @@ -28,7 +29,7 @@ const validationSchema = object({ item_id: number().required("نوع آیتم را مشخص کنید!"), sub_item_id: number().required("موضوع مشاهده شده را مشخص کنید!"), amount: string().required("وارد کردن مقدار الزامیست!"), - cmms_machines: array().required("وارد کردن کد خودرو الزامیست!"), + cmms_machines: array().required("وارد کردن کد خودرو الزامیست!").min(1, "حداقل یک کد خودرو باید وارد شود!"), rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"), // بررسی حداقل یک آیتم, activity_time: string().test("activity-time-conditional", "لطفا زمان فعالیت را انتخاب کنید!", function (value) { const { is_gasht } = this.options.context; // دسترسی به context @@ -184,6 +185,8 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => { }); delete result.cmms_machines; + result[`activity_time`] = format(new Date(result.activity_time), "HH:mm"); + await onSubmit({ result, data: { @@ -194,7 +197,6 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => { }, }); }; - console.log(errors); return ( 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 a802b89..a80f4af 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx @@ -12,7 +12,7 @@ import NumberField from "@/core/components/NumberField"; const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues, is_gasht }) => { return ( - + @@ -25,6 +25,7 @@ const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, err type="text" size={"small"} unit={subItemsList.unit} + placeholder={`مقدار را وارد کنید`} inputProps={{ inputMode: "number", min: 0, @@ -86,7 +87,8 @@ const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, err name="activity_date" error={errors.activity_date ? errors.activity_date.message : null} value={watch("activity_date")} - placeholder={"تاریخ شروع فعالیت"} + placeholder={"تاریخ فعالیت را وارد کنید"} + label={"تاریخ فعالیت"} setFieldValue={setValue} helperText={errors.activity_date ? errors.activity_date.message : null} /> @@ -96,7 +98,8 @@ const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, err value={watch("activity_time")} name="activity_time" error={errors.activity_time ? errors.activity_time.message : null} - placeholder={"زمان فعالیت"} + placeholder={"زمان فعالیت را وارد کنید"} + label={"زمان فعالیت"} setFieldValue={setValue} helperText={errors.activity_time ? errors.activity_time.message : null} /> diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm.jsx index 2a60079..8d9098a 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm.jsx @@ -16,7 +16,7 @@ const GetSubItemsForm = ({ setValue, watch, setSubItemsList, tabState, setTabSta ) : ( {subItemsList.map((item) => ( - + {item.name} 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 a545b4d..17a9904 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload.jsx @@ -84,7 +84,20 @@ const ImageUpload = ({ control, setValue, errors, getValues, beforeImage = null, justifyContent: "center", }} > - + عکس قبل از اقدام - + عکس بعد از اقدام { + const [tabState, setTabState] = useState(0); return ( - - + + ); }; diff --git a/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreateContent.jsx b/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreateContent.jsx index a37e36f..70884e7 100644 --- a/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreateContent.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreateContent.jsx @@ -36,8 +36,7 @@ const defaultFilter = [ { value: `${defaultValues.end_date}`, datatype: "date", id: "roadPatrol.end_time", fn: "equals" }, ]; -const GashtCreateContent = ({ mutate, setOpen }) => { - const [tabState, setTabState] = useState(0); +const GashtCreateContent = ({ mutate, setOpen, tabState, setTabState }) => { const [itemInfo, setItemInfo] = useState(null); const [submitCompleteForm, setSubmitCompleteForm] = useState(false); const [specialFilter, setSpecialFilter] = useState(defaultFilter); @@ -137,7 +136,7 @@ const GashtCreateContent = ({ mutate, setOpen }) => { + )} + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/ReportLists.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/ReportLists.jsx new file mode 100644 index 0000000..dcd9c1f --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/ReportLists.jsx @@ -0,0 +1,92 @@ +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; +import { useMemo } from "react"; + +const ReportLists = ({ itemsList, data }) => { + const columns = useMemo(() => { + const dynamicColumns = + itemsList?.map((item) => ({ + accessorKey: `item_${item.id}`, + header: `${item.name}`, + id: `item_${item.id}`, + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + size: 50, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + })) || []; + + return [ + { + accessorKey: "edare_name", + header: "اداره", + id: "edare_name", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + { + accessorKey: "sum", + header: "مجموع", + id: "sum", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + grow: false, + size: 50, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + header: "ایتم های فعالیت روزانه", + id: "items", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + columns: [...dynamicColumns], + }, + ]; + }, [itemsList]); + + return ( + + + + ); +}; + +export default ReportLists; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/Search/FromDateController.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/Search/FromDateController.jsx new file mode 100644 index 0000000..2cfa8af --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/Search/FromDateController.jsx @@ -0,0 +1,27 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const FromDateController = ({ control }) => { + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default FromDateController; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/Search/SearchReportField.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/Search/SearchReportField.jsx new file mode 100644 index 0000000..9cce1c1 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/Search/SearchReportField.jsx @@ -0,0 +1,45 @@ +import { Button, Grid } from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import React from "react"; +import FromDateController from "./FromDateController"; +import ToDateController from "./ToDateController"; +import SelectProvince from "./SelectProvince"; +import { Controller } from "react-hook-form"; + +const SearchReportField = ({ control, hasProvincesPermission }) => { + return ( + + + + + + + + {hasProvincesPermission && ( + + + + )} + + ( + + )} + /> + + + ); +}; + +export default SearchReportField; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/Search/SelectProvince.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/Search/SelectProvince.jsx new file mode 100644 index 0000000..b1d8a39 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/Search/SelectProvince.jsx @@ -0,0 +1,47 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import { FormControl, FormHelperText, InputLabel, LinearProgress, MenuItem, Select, Typography } from "@mui/material"; +import useProvinces from "@/lib/hooks/useProvince"; + +const SelectProvince = ({ control }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + ( + + استان + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/Search/ToDateController.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/Search/ToDateController.jsx new file mode 100644 index 0000000..003ac20 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/Search/ToDateController.jsx @@ -0,0 +1,29 @@ +import { Controller, useWatch } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const ToDateController = ({ control }) => { + const minDate = useWatch({ control, name: "from_date" }); + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default ToDateController; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/Search/index.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/Search/index.jsx new file mode 100644 index 0000000..f29d302 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/Search/index.jsx @@ -0,0 +1,13 @@ +import StyledForm from "@/core/components/StyledForm"; +import SearchReportField from "./SearchReportField"; + +const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { + return ( + <> + + + + + ); +}; +export default SearchReportList; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/index.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/index.jsx new file mode 100644 index 0000000..137ed83 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/index.jsx @@ -0,0 +1,166 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { LinearProgress, Stack, Typography } from "@mui/material"; +import SearchReportList from "./Search"; +import { useEffect, useState } from "react"; +import ReportLists from "./ReportLists"; +import useRequest from "@/lib/hooks/useRequest"; +import { GET_CITY_ACTIVITY_PER_ITEM, GET_PROVINCE_ACTIVITY_PER_ITEM } from "@/core/utils/routes"; +import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems"; +import moment from "jalali-moment"; +import * as yup from "yup"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; + +const ReportPage = () => { + const [data, setData] = useState(null); + const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems(); + const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); + const requestServer = useRequest(); + const hasProvincesPermission = userPermissions.includes("show-road-item-supervise-cartable"); + + const defaultValues = { + from_date: moment(new Date()).format("YYYY-MM-DD"), + date_to: moment(new Date()).format("YYYY-MM-DD"), + province_id: hasProvincesPermission ? "-1" : user.province_id, + }; + + const onSearchSubmit = async (data) => { + const params = new URLSearchParams(); + params.set("from_date", `${data.from_date}`); + params.set("date_to", `${data.date_to}`); + if (data.province_id === "-1") { + try { + const response = await requestServer(`${GET_PROVINCE_ACTIVITY_PER_ITEM}?${params}`); + const result = response.data.data; + const nationalReport = { + edare_name: "کل کشور", + sum: result.activities.length + ? result.activities + .filter((report) => report.p === -1) + .reduce((acc, report) => acc + (report.s || 0), 0) + : 0, + }; + itemsList.forEach((item) => { + const nationalReportForItem = result.activities.find( + (report) => report.p === -1 && report.i === item.id + ); + nationalReport[`item_${item.id}`] = nationalReportForItem ? nationalReportForItem.s : 0; + }); + + setData({ + data: [ + nationalReport, + ...result.provinces.map((province) => { + const filteredReports = result.activities.filter((report) => report.p === province.id); + const sum = filteredReports.reduce((acc, report) => acc + (report.s || 0), 0); + const rowData = { + edare_name: province.name_fa, + sum, + }; + itemsList.forEach((item) => { + const reportForItem = filteredReports.find((report) => report.i === item.id); + rowData[`item_${item.id}`] = reportForItem ? reportForItem.s : 0; + }); + return rowData; + }), + ], + filters: data, + }); + } catch (e) {} + } else { + try { + params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_CITY_ACTIVITY_PER_ITEM}?${params}`); + const result = response.data.data; + const nationalReport = { + edare_name: "کل استان", + sum: result.activities.length + ? result.activities + .filter((report) => report.c === -1) + .reduce((acc, report) => acc + (report.s || 0), 0) + : 0, + }; + + itemsList.forEach((item) => { + const nationalReportForItem = result.activities.find( + (report) => report.c === -1 && report.i === item.id + ); + nationalReport[`item_${item.id}`] = nationalReportForItem ? nationalReportForItem.s : 0; + }); + + setData({ + data: [ + nationalReport, + ...result.edarateShahri.map((edare) => { + const filteredReports = result.activities.filter((report) => report.c === edare.id); + const sum = filteredReports.reduce((acc, report) => acc + (report.s || 0), 0); + + const rowData = { + edare_name: edare.name_fa, + sum, + }; + + itemsList.forEach((item) => { + const reportForItem = filteredReports.find((report) => report.i === item.id); + rowData[`item_${item.id}`] = reportForItem ? reportForItem.s : 0; + }); + + return rowData; + }), + ], + filters: data, + }); + } catch (e) {} + } + }; + + useEffect(() => { + if (itemsList.length === 0) return; + onSearchSubmit(defaultValues); + }, [itemsList]); + + const validationSchema = yup.object().shape({ + from_date: yup + .string() + .required("تاریخ شروع الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") + .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + date_to: yup + .string() + .required("تاریخ پایان الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") + .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + province_id: yup.string().nullable(), + }); + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + return ( + + + + {!data || loadingItemsList ? ( + + ) : errorItemsList ? ( + + خطا در دریافت اطلاعات!!! + + ) : ( + + )} + + ); +}; +export default ReportPage; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/FromDateController.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/FromDateController.jsx new file mode 100644 index 0000000..2cfa8af --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/FromDateController.jsx @@ -0,0 +1,27 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const FromDateController = ({ control }) => { + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default FromDateController; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SearchReportField.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SearchReportField.jsx new file mode 100644 index 0000000..5487b7f --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SearchReportField.jsx @@ -0,0 +1,48 @@ +import { Button, Grid } from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import React from "react"; +import FromDateController from "./FromDateController"; +import ToDateController from "./ToDateController"; +import SelectProvince from "./SelectProvince"; +import { Controller } from "react-hook-form"; +import SelectItems from "./SelectItems"; + +const SearchReportField = ({ control, hasProvincesPermission }) => { + return ( + + + + + + + + {hasProvincesPermission && ( + + + + )} + + + + + ( + + )} + /> + + + ); +}; +export default SearchReportField; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectItems.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectItems.jsx new file mode 100644 index 0000000..57bcc86 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectItems.jsx @@ -0,0 +1,42 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material"; +import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems"; + +const SelectProvince = ({ control }) => { + const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems(); + return ( + ( + + ایتم + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectProvince.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectProvince.jsx new file mode 100644 index 0000000..b1d8a39 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectProvince.jsx @@ -0,0 +1,47 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import { FormControl, FormHelperText, InputLabel, LinearProgress, MenuItem, Select, Typography } from "@mui/material"; +import useProvinces from "@/lib/hooks/useProvince"; + +const SelectProvince = ({ control }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + ( + + استان + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/ToDateController.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/ToDateController.jsx new file mode 100644 index 0000000..003ac20 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/ToDateController.jsx @@ -0,0 +1,29 @@ +import { Controller, useWatch } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const ToDateController = ({ control }) => { + const minDate = useWatch({ control, name: "from_date" }); + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default ToDateController; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/index.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/index.jsx new file mode 100644 index 0000000..0ca7a3f --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/index.jsx @@ -0,0 +1,12 @@ +import StyledForm from "@/core/components/StyledForm"; +import SearchReportField from "./SearchReportField"; +const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { + return ( + <> + + + + + ); +}; +export default SearchReportList; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/SubItemsList.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/SubItemsList.jsx new file mode 100644 index 0000000..f047491 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/SubItemsList.jsx @@ -0,0 +1,16 @@ +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; + +const SubItemsList = ({ data }) => { + return ( + + + + ); +}; +export default SubItemsList; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/index.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/index.jsx new file mode 100644 index 0000000..57521d7 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/index.jsx @@ -0,0 +1,242 @@ +"use client"; +import SubItemsList from "./SubItemsList"; +import PageTitle from "@/core/components/PageTitle"; +import { LinearProgress, Stack } from "@mui/material"; +import SearchReportList from "./Search"; +import { useEffect, useState } from "react"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; +import useRequest from "@/lib/hooks/useRequest"; +import moment from "jalali-moment"; +import { GET_CITY_ACTIVITY_PER_SUB_ITEM, GET_PROVINCE_ACTIVITY_PER_SUB_ITEM } from "@/core/utils/routes"; +import * as yup from "yup"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; + +const createColumns = (sub_items) => { + const dynamicColumns = + sub_items?.map((sub_item) => ({ + accessorKey: `sub_item_${sub_item.sub_item}`, + header: `${sub_item.sub_item_str}`, + id: `sub_item_${sub_item.sub_item}`, + columns: [ + { + accessorKey: `sub_item_${sub_item.sub_item}_count`, + header: "تعداد", + id: `sub_item_${sub_item.sub_item}_count`, + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + size: 50, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: `sub_item_${sub_item.sub_item}_sum`, + header: `مجموع (${sub_item.sub_item_unit})`, + id: `sub_item_${sub_item.sub_item}_sum`, + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + grow: false, + size: 50, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + ], + })) || []; + + return [ + { + accessorKey: "edare_name", + header: "اداره", + id: "edare_name", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + { + header: "موارد مشاهده شده فعالیت روزانه", + enableColumnFilter: false, + id: "subItems", + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + columns: [...dynamicColumns], + }, + ]; +}; + +const SubItemsReports = () => { + const [data, setData] = useState(null); + const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); + const requestServer = useRequest(); + const hasProvincesPermission = userPermissions.includes("show-road-item-supervise-cartable"); + + const defaultValues = { + from_date: moment(new Date()).format("YYYY-MM-DD"), + date_to: moment(new Date()).format("YYYY-MM-DD"), + province_id: hasProvincesPermission ? "-1" : user.province_id, + item: 1, + }; + + const onSearchSubmit = async (data) => { + const params = new URLSearchParams(); + params.set("from_date", `${data.from_date}`); + params.set("date_to", `${data.date_to}`); + params.set("item", `${data.item}`); + if (data.province_id === "-1") { + try { + const response = await requestServer(`${GET_PROVINCE_ACTIVITY_PER_SUB_ITEM}?${params}`); + const result = response.data.data; + const nationalReport = { + edare_name: "کل کشور", + }; + + result.sub_items.forEach((subItem) => { + const nationalReportForSubItem = result.activities.find( + (report) => report.p === -1 && report.t === subItem.sub_item + ); + nationalReport[`sub_item_${subItem.sub_item}_count`] = nationalReportForSubItem + ? nationalReportForSubItem.c + : 0; + nationalReport[`sub_item_${subItem.sub_item}_sum`] = nationalReportForSubItem + ? nationalReportForSubItem.s + : 0; + }); + + setData({ + data: [ + nationalReport, + ...result.provinces.map((province) => { + const filteredReports = result.activities.filter((report) => report.p === province.id); + const rowData = { + edare_name: province.name_fa, + }; + + result.sub_items.forEach((subItem) => { + const reportForItem = filteredReports.find((report) => report.t === subItem.sub_item); + rowData[`sub_item_${subItem.sub_item}_count`] = reportForItem ? reportForItem.c : 0; + rowData[`sub_item_${subItem.sub_item}_sum`] = reportForItem ? reportForItem.s : 0; + }); + + return rowData; + }), + ], + columns: createColumns(result.sub_items), + filters: data, + }); + } catch (e) {} + } else { + try { + params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_CITY_ACTIVITY_PER_SUB_ITEM}?${params}`); + const result = response.data.data; + const nationalReport = { + edare_name: "کل استان", + }; + + result.sub_items.forEach((subItem) => { + const nationalReportForSubItem = result.activities.find( + (report) => report.ci === -1 && report.t === subItem.sub_item + ); + nationalReport[`sub_item_${subItem.sub_item}_count`] = nationalReportForSubItem + ? nationalReportForSubItem.c + : 0; + nationalReport[`sub_item_${subItem.sub_item}_sum`] = nationalReportForSubItem + ? nationalReportForSubItem.s + : 0; + }); + + setData({ + data: [ + nationalReport, + ...result.edarateShahri.map((edare) => { + const filteredReports = result.activities.filter((report) => report.ci === edare.id); + const rowData = { + edare_name: edare.name_fa, + }; + + result.sub_items.forEach((subItem) => { + const reportForItem = filteredReports.find((report) => report.t === subItem.sub_item); + rowData[`sub_item_${subItem.sub_item}_count`] = reportForItem ? reportForItem.c : 0; + rowData[`sub_item_${subItem.sub_item}_sum`] = reportForItem ? reportForItem.s : 0; + }); + + return rowData; + }), + ], + columns: createColumns(result.sub_items), + filters: data, + }); + } catch (e) { + console.log(e); + } + } + }; + + useEffect(() => { + onSearchSubmit(defaultValues); + }, []); + + const validationSchema = yup.object().shape({ + from_date: yup + .string() + .required("تاریخ شروع الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") + .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + date_to: yup + .string() + .required("تاریخ پایان الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") + .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + province_id: yup.string().nullable(), + item: yup.number().required("ایتم مورد نظر را وارد کنید!!!"), + }); + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + return ( + + + + {!data ? : } + + ); +}; +export default SubItemsReports; diff --git a/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx b/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx index 8ef88db..1dfae7c 100644 --- a/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx +++ b/src/components/dashboard/roadItems/supervisor/SupervisorList.jsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Box, Stack, Typography } from "@mui/material"; import DataTableWithAuth from "@/core/components/DataTableWithAuth"; import Toolbar from "./Toolbar"; @@ -14,6 +14,8 @@ import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems"; import useRoadItemGetSubItems from "@/lib/hooks/useRoadItemGetISubtems"; import useEdaratLists from "@/lib/hooks/useEdaratLists"; import MachinesCodeDialog from "./RowActions/MachinesCodeForm"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; const SupervisorList = () => { const statusOptions = [ @@ -21,8 +23,47 @@ const SupervisorList = () => { { value: 1, label: "تایید" }, { value: 2, label: "عدم تایید" }, ]; - const columns = useMemo( - () => [ + const { data: userPermissions } = usePermissions(); + const hasCountryPermission = userPermissions.includes("show-road-item-supervise-cartable"); + const { user } = useAuth(); + const columns = useMemo(() => { + const dynamicColumns = { + accessorKey: "province_id", + header: "استان", // Province + id: "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: props.column.header }, + ...provinces.map((province) => ({ + value: province.id, + label: province.name_fa, + })), + ]; + }, [provinces, errorProvinces, loadingProvinces]); + return ( + + ); + }, + Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}, + }; + return [ { accessorKey: "id", header: "کد یکتا", // Unique Code @@ -35,37 +76,7 @@ const SupervisorList = () => { grow: false, size: 100, }, - { - accessorKey: "province_id", - header: "استان", // Province - id: "province_id", - enableColumnFilter: true, - datatype: "numeric", - filterMode: "equals", - grow: false, - size: 130, - ColumnSelectComponent: (props) => { - const { provinces, errorProvinces, loadingProvinces } = useProvinces(); - const getColumnSelectOptions = () => { - if (loadingProvinces) { - return [{ value: "loading", label: "در حال بارگذاری..." }]; - } - if (errorProvinces) { - return [{ value: "error", label: "خطا در بارگذاری" }]; - } - return [ - { value: "", label: props.column.header }, - ...provinces.map((province) => ({ - value: province.id, - label: province.name_fa, - })), - ]; - }; - const columnSelectOption = getColumnSelectOptions(); - return ; - }, - Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}, - }, + ...(hasCountryPermission ? [dynamicColumns] : []), { accessorKey: "edarat_id", header: "اداره", // Office @@ -73,16 +84,19 @@ const SupervisorList = () => { enableColumnFilter: true, datatype: "numeric", filterMode: "equals", - dependencyId: "province_id", + dependencyId: hasCountryPermission ? "province_id" : null, grow: false, size: 120, ColumnSelectComponent: (props) => { const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists( - props.dependencyFieldValue.value + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id + ); + const [prevDependency, setPrevDependency] = useState( + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id ); - const getColumnSelectOptions = () => { - if (props.dependencyFieldValue.value === "") { + const getColumnSelectOptions = useMemo(() => { + if (hasCountryPermission && props.dependencyFieldValue.value === "") { return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }]; } if (loadingEdaratList) { @@ -98,16 +112,33 @@ const SupervisorList = () => { label: edare.name_fa, })), ]; - }; - const columnSelectOption = getColumnSelectOptions(); - return ; + }, [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: ({ renderedCellValue, row }) => <>{row.original.edarat_name}, }, { - accessorKey: "item_fa", + accessorKey: "item", header: "آیتم فعالیت", - id: "item_fa", + id: "item", enableColumnFilter: true, datatype: "numeric", filterMode: "equals", @@ -115,7 +146,8 @@ const SupervisorList = () => { size: 100, ColumnSelectComponent: (props) => { const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems(); - const getColumnSelectOptions = () => { + + const getColumnSelectOptions = useMemo(() => { if (loadingItemsList) { return [{ value: "loading", label: "در حال بارگذاری..." }]; } @@ -129,24 +161,35 @@ const SupervisorList = () => { label: item.name, })), ]; - }; - const columnSelectOption = getColumnSelectOptions(); - return ; + }, [itemsList, loadingItemsList, errorItemsList]); + + return ( + + ); + }, + Cell: ({ row }) => { + return row.original.item_fa; }, }, { - accessorKey: "sub_item_fa", + accessorKey: "sub_item", header: "موضوع مشاهده شده", - id: "sub_item_fa", + id: "sub_item", enableColumnFilter: true, datatype: "numeric", filterMode: "equals", - dependencyId: "item_fa", + dependencyId: "item", ColumnSelectComponent: (props) => { const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetSubItems( props.dependencyFieldValue.value ); - const getColumnSelectOptions = () => { + const [prevDependency, setPrevDependency] = useState(props.dependencyFieldValue.value); + + const getColumnSelectOptions = useMemo(() => { if (props.dependencyFieldValue.value === "") { return [{ value: "empty", label: "ابتدا آیتم فعالیت را انتخاب کنید" }]; } @@ -159,13 +202,33 @@ const SupervisorList = () => { return [ { value: "", label: props.column.header }, ...subItemsList.map((item) => ({ - value: item.id, + value: item.sub_item, label: item.name, })), ]; - }; - const columnSelectOption = getColumnSelectOptions(); - return ; + }, [subItemsList, loadingSubItemsList, errorSubItemsList]); + + useEffect(() => { + if (prevDependency === props.dependencyFieldValue.value) return; + props.handleChange({ ...props.filterParameters, value: "" }); + setPrevDependency(props.dependencyFieldValue.value); + }, [props.dependencyFieldValue.value]); + return ( + + ); + }, + Cell: ({ row }) => { + return row.original.sub_item_fa; }, }, { @@ -173,6 +236,7 @@ const SupervisorList = () => { header: "مقدار", // Value id: "value", enableColumnFilter: false, + enableSorting: false, datatype: "text", filterMode: "equals", grow: false, @@ -289,10 +353,12 @@ const SupervisorList = () => { { accessorKey: "rahdaran", header: "راهداران", - id: "rahdaran", - enableColumnFilter: false, + id: "rahdaran__code", + enableColumnFilter: true, enableSorting: false, - datatype: "array", + datatype: "text", + filterMode: "contains", + columnFilterModeOptions: ["equals", "contains"], grow: false, size: 100, muiTableBodyCellProps: { @@ -321,8 +387,7 @@ const SupervisorList = () => { id: "activity_date_time", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, @@ -332,8 +397,7 @@ const SupervisorList = () => { id: "created_at", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, @@ -355,9 +419,8 @@ const SupervisorList = () => { }, Cell: ({ renderedCellValue, row }) => <>{row.original.status_fa}, }, - ], - [] - ); + ]; + }, []); return ( <> diff --git a/src/components/dashboard/roadItems/supervisor/Toolbar.jsx b/src/components/dashboard/roadItems/supervisor/Toolbar.jsx index 32c4395..47cb9a9 100644 --- a/src/components/dashboard/roadItems/supervisor/Toolbar.jsx +++ b/src/components/dashboard/roadItems/supervisor/Toolbar.jsx @@ -1,10 +1,6 @@ import PrintExcel from "./ExcelPrint"; const Toolbar = ({ table, filterData }) => { - return ( - <> - - - ); + return <>{/**/}; }; export default Toolbar; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/CompeleteRequest.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/CompeleteRequest.jsx index 3299e47..51816c8 100644 --- a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/CompeleteRequest.jsx +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/CompeleteRequest.jsx @@ -140,7 +140,7 @@ const CompeleteRequest = ({ watch, isSubmitting, setValue, register }) => { {watch("road_patrol_rahdaran_id")?.map((rahdar, index) => ( - + نام و کد راهدار diff --git a/src/components/dashboard/roadPatrols/operator/OperatorList.jsx b/src/components/dashboard/roadPatrols/operator/OperatorList.jsx index c5e8d7a..e96d08d 100644 --- a/src/components/dashboard/roadPatrols/operator/OperatorList.jsx +++ b/src/components/dashboard/roadPatrols/operator/OperatorList.jsx @@ -7,8 +7,8 @@ import { GET_ROAD_PATROL_OPERATOR_LIST } from "@/core/utils/routes"; import moment from "jalali-moment"; import RowActions from "./RowActions"; import ReportForm from "./RowActions/ReportForm"; -import MachinesCodeDialog from "./RowActions/MachinesCodeForm"; import RahdaranDialog from "./RowActions/RahdaranForm"; +import MachinePerformanceForm from "./RowActions/MachinePerformanceForm"; const OperatorList = () => { const columns = useMemo( @@ -25,14 +25,13 @@ const OperatorList = () => { size: 100, }, { - accessorKey: "cmms_machines", - header: "خودرو", // Car ID - id: "cmmsMachines__machine_code", + header: "عملکرد خودرو", // Car ID enableColumnFilter: true, + id: "cmmsMachines__machine_code", datatype: "text", filterMode: "contains", - enableSorting: false, columnFilterModeOptions: ["equals", "contains"], + enableSorting: false, grow: false, size: 100, muiTableBodyCellProps: { @@ -45,23 +44,22 @@ const OperatorList = () => { }, }, Cell: ({ renderedCellValue }) => { - if (renderedCellValue) { - return ( - - - - ); - } - return کد خودرویی وجود ندارد; + return ( + + + + ); }, }, { accessorKey: "rahdaran", header: "راهداران", - id: "rahdaran", - enableColumnFilter: false, + id: "rahdaran__code", + enableColumnFilter: true, enableSorting: false, - datatype: "array", + datatype: "text", + filterMode: "contains", + columnFilterModeOptions: ["equals", "contains"], grow: false, size: 100, muiTableBodyCellProps: { @@ -85,35 +83,32 @@ const OperatorList = () => { }, }, { - accessorFn: (row) => moment(row.start_date).locale("fa").format("HH:mm | yyyy/MM/DD"), + accessorFn: (row) => moment(row.start_time).locale("fa").format("HH:mm | yyyy/MM/DD"), header: "تاریخ شروع", - id: "start_date", + id: "start_time", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, { - accessorFn: (row) => moment(row.end_date).locale("fa").format("HH:mm | yyyy/MM/DD"), + accessorFn: (row) => moment(row.end_time).locale("fa").format("HH:mm | yyyy/MM/DD"), header: "تاریخ پایان", - id: "end_date", + id: "end_time", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, { - accessorFn: (row) => moment(row.register_date).locale("fa").format("HH:mm | yyyy/MM/DD"), + accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"), header: "تاریخ ثبت", - id: "register_date", + id: "created_at", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, diff --git a/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx new file mode 100644 index 0000000..a38a935 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx @@ -0,0 +1,76 @@ +import { Box, DialogContent, Stack } from "@mui/material"; +import React, { useEffect, useState, useRef } from "react"; +import { Marker, useMap } from "react-leaflet"; +import AzmayeshActiveIcon from "@/assets/images/examine_marker_active.png"; +import StopsInfo from "@/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo"; +import MapLayer from "@/core/components/MapLayer"; +const defaultIconSize = [35, 35]; +const MachinePerformanceContent = ({ machinesLists }) => { + const [selectedPoint, setSelectedPoint] = useState(null); + const mapRef = useRef(null); + const isFirstRender = useRef(true); + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + const MapWithBounds = () => { + const map = useMap(); + useEffect(() => { + if (machinesLists.stopPoints.length > 0) { + const bounds = machinesLists.stopPoints.map((point) => [point.latitude, point.longitude]); + if (isFirstRender.current) { + map.fitBounds(bounds, { + padding: [50, 50], + }); + isFirstRender.current = false; + } else if (mapRef.current) { + mapRef.current.setView(map.getCenter(), map.getZoom()); + } + } + }, [machinesLists.stopPoints, map]); + return null; + }; + const handleMarkerClick = (point) => { + setSelectedPoint(point); + }; + + return ( + <> + + + + + + + + {machinesLists.stopPoints.map((point, index) => ( + handleMarkerClick(point), + }} + > + ))} + + + + + + + ); +}; +export default MachinePerformanceContent; diff --git a/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo.jsx b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo.jsx new file mode 100644 index 0000000..4483996 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo.jsx @@ -0,0 +1,125 @@ +import { Box, Chip, Divider, Grid, Typography } from "@mui/material"; +import AccessAlarmsIcon from "@mui/icons-material/AccessAlarms"; +import PinDropIcon from "@mui/icons-material/PinDrop"; +import LocalGasStationIcon from "@mui/icons-material/LocalGasStation"; +import DirectionsCarIcon from "@mui/icons-material/DirectionsCar"; + +const StopsInfo = ({ selectedPoint, staticData }) => { + return ( + + + + } + label={ + + کد ماشین + + } + /> + + + + {staticData.cmmsMachineCode || ""} + + + + + } + label={ + + مصرف سوخت + + } + /> + + + + {staticData.fuelConsumption || ""} لیتر + + + + + } + label={ + + مسافت طی‌شده + + } + /> + + + + {staticData.mileage || ""} کیلومتر + + + + + } + label={ + + مدت زمان روشن بودن خودرو + + } + /> + + + + {(staticData.accOnDuration / 60)?.toFixed(2) || ""} دقیقه + + + + + } + label={ + + طول جغرافیایی + + } + /> + + + + {selectedPoint ? selectedPoint.longitude : "نقطه مورد نظر را انتخاب کنید"} + + + + + } + label={ + + عرض جغرافیایی + + } + /> + + + + {selectedPoint ? selectedPoint.latitude : "نقطه مورد نظر را انتخاب کنید"} + + + + + } + label={ + + مدت زمان توقف + + } + /> + + + + {selectedPoint ? `${selectedPoint.duration} ثانیه` : "نقطه مورد نظر را انتخاب کنید"} + + + + ); +}; +export default StopsInfo; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinesCodeForm/index.jsx b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/index.jsx similarity index 54% rename from src/components/dashboard/roadPatrols/supervisor/RowActions/MachinesCodeForm/index.jsx rename to src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/index.jsx index 9696333..242902f 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinesCodeForm/index.jsx +++ b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/index.jsx @@ -1,13 +1,40 @@ import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; import DirectionsCarIcon from "@mui/icons-material/DirectionsCar"; +import MachinePerformanceContent from "./MachinePerformanceContent"; import { useState } from "react"; -import MachinesCodeContent from "./MachinesCodeContent"; -const MachinesCodeDialog = ({ machinesLists }) => { +const MachinePerformanceForm = () => { const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false); + const machineData = { + cmms_machine_code: 245, + resultCode: 0, + mileage: 17645, + accOnDuration: 3599, + fuelConsumption: 6.1757503, + stopPoints: [ + { + startDT: "2024-12-23T13:55:40", + duration: 114, + latitude: 37.521317, + longitude: 46.103584, + }, + { + startDT: "2024-12-23T14:31:11", + duration: 14, + latitude: 37.566532, + longitude: 46.17688, + }, + { + startDT: "2024-12-23T14:35:36", + duration: 55, + latitude: 37.569958, + longitude: 46.18166, + }, + ], + }; return ( <> - + setOpenMachinesCodeDialog(true)}> @@ -24,8 +51,8 @@ const MachinesCodeDialog = ({ machinesLists }) => { dir="rtl" maxWidth={"md"} > - کد خودرو - + عملکرد خودرو + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/roadPatrols/reports/ReportLists.jsx b/src/components/dashboard/roadPatrols/reports/ReportLists.jsx new file mode 100644 index 0000000..4a3d8e5 --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/ReportLists.jsx @@ -0,0 +1,209 @@ +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; +import { useMemo } from "react"; +import Toolbar from "./Toolbar"; + +const ReportLists = ({ data }) => { + const columns = useMemo(() => { + return [ + { + accessorKey: "edare_name", + header: "اداره کل", + id: "edare_name", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + + { + accessorKey: "tedade", + header: "تعداد کل", + id: "tedade", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + header: "اقدامات لازم جهت رسیدگی آتی", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + columns: [ + { + accessorKey: "tedad_mavarede_moshede_shode_adi", + header: "عادی", + id: "tedad_mavarede_moshede_shode_adi", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "tedad_mavarede_moshede_shode_fori", + header: "فوری", + id: "tedad_mavarede_moshede_shode_fori", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "tedad_mavarede_moshede_shode_ani", + header: "آنی", + id: "tedad_mavarede_moshede_shode_ani", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + ], + }, + { + accessorKey: "tedad_mavarede_peygiri_shode", + header: "اقدامات رسیدگی شده", + id: "tedad_mavarede_peygiri_shode", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "masafat_tey_shode", + header: "مجموع مسافت پیمایش شده", + id: "masafat_tey_shode", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "tedad_faliyat_sabt_shode", + header: "تعداد فعالیت صورت گرفته حین گشت", + id: "tedad_faliyat_sabt_shode", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + ]; + }, []); + + return ( + + + + ); +}; + +export default ReportLists; diff --git a/src/components/dashboard/roadPatrols/reports/Search/FromDateController.jsx b/src/components/dashboard/roadPatrols/reports/Search/FromDateController.jsx new file mode 100644 index 0000000..2cfa8af --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/Search/FromDateController.jsx @@ -0,0 +1,27 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const FromDateController = ({ control }) => { + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default FromDateController; diff --git a/src/components/dashboard/roadPatrols/reports/Search/SearchReportField.jsx b/src/components/dashboard/roadPatrols/reports/Search/SearchReportField.jsx new file mode 100644 index 0000000..ab8442c --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/Search/SearchReportField.jsx @@ -0,0 +1,45 @@ +import { Button, Grid, LinearProgress, Typography } from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import React from "react"; +import FromDateController from "./FromDateController"; +import ToDateController from "./ToDateController"; +import SelectProvince from "./SelectProvince"; +import { Controller } from "react-hook-form"; + +const SearchReportField = ({ control, hasProvincesPermission }) => { + return ( + + + + + + + + {hasProvincesPermission && ( + + + + )} + + ( + + )} + /> + + + ); +}; + +export default SearchReportField; diff --git a/src/components/dashboard/roadPatrols/reports/Search/SelectProvince.jsx b/src/components/dashboard/roadPatrols/reports/Search/SelectProvince.jsx new file mode 100644 index 0000000..b1d8a39 --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/Search/SelectProvince.jsx @@ -0,0 +1,47 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import { FormControl, FormHelperText, InputLabel, LinearProgress, MenuItem, Select, Typography } from "@mui/material"; +import useProvinces from "@/lib/hooks/useProvince"; + +const SelectProvince = ({ control }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + ( + + استان + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/roadPatrols/reports/Search/ToDateController.jsx b/src/components/dashboard/roadPatrols/reports/Search/ToDateController.jsx new file mode 100644 index 0000000..003ac20 --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/Search/ToDateController.jsx @@ -0,0 +1,29 @@ +import { Controller, useWatch } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const ToDateController = ({ control }) => { + const minDate = useWatch({ control, name: "from_date" }); + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default ToDateController; diff --git a/src/components/dashboard/roadPatrols/reports/Search/index.jsx b/src/components/dashboard/roadPatrols/reports/Search/index.jsx new file mode 100644 index 0000000..f29d302 --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/Search/index.jsx @@ -0,0 +1,13 @@ +import StyledForm from "@/core/components/StyledForm"; +import SearchReportField from "./SearchReportField"; + +const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { + return ( + <> + + + + + ); +}; +export default SearchReportList; diff --git a/src/components/dashboard/roadPatrols/reports/Toolbar.jsx b/src/components/dashboard/roadPatrols/reports/Toolbar.jsx new file mode 100644 index 0000000..5dc6e42 --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/Toolbar.jsx @@ -0,0 +1,7 @@ +import PrintExcel from "./ExcelPrint"; +import { Box } from "@mui/material"; + +const Toolbar = ({ table, filterData, mutate }) => { + return {/**/}; +}; +export default Toolbar; diff --git a/src/components/dashboard/roadPatrols/reports/index.jsx b/src/components/dashboard/roadPatrols/reports/index.jsx new file mode 100644 index 0000000..12743aa --- /dev/null +++ b/src/components/dashboard/roadPatrols/reports/index.jsx @@ -0,0 +1,122 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { LinearProgress, Stack } from "@mui/material"; +import SearchReportList from "./Search"; +import { useEffect, useState } from "react"; +import ReportLists from "./ReportLists"; +import useRequest from "@/lib/hooks/useRequest"; +import { GET_CITY_ACTIVITY_REPORT, GET_PROVINCE_ACTIVITY_REPORT } from "@/core/utils/routes"; +import moment from "jalali-moment"; +import * as yup from "yup"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; + +const ReportPage = () => { + const [data, setData] = useState(null); + const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); + const requestServer = useRequest(); + const hasProvincesPermission = userPermissions.includes("show-road-patrol-supervise-cartable"); + + const defaultValues = { + from_date: moment(new Date()).format("YYYY-MM-DD"), + date_to: moment(new Date()).format("YYYY-MM-DD"), + province_id: hasProvincesPermission ? "-1" : user.province_id, + }; + + const onSearchSubmit = async (data) => { + const params = new URLSearchParams(); + params.set("from_date", `${data.from_date}`); + params.set("date_to", `${data.date_to}`); + if (data.province_id === "-1") { + try { + const response = await requestServer(`${GET_PROVINCE_ACTIVITY_REPORT}?${params}`); + const result = response.data.data; + const nationalReportForItem = result.activities.find((report) => report.province_id === -1); + const nationalReport = { + edare_name: "کل کشور", + ...nationalReportForItem, + }; + + setData({ + data: [ + nationalReport, + ...result.provinces.map((province) => { + const filteredReports = result.activities.find( + (report) => report.province_id === province.id + ); + return { + ...filteredReports, + edare_name: province.name_fa, + }; + }), + ], + filters: data, + }); + } catch (e) {} + } else { + try { + params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_CITY_ACTIVITY_REPORT}?${params}`); + const result = response.data.data; + const nationalReportForItem = result.activities.find((report) => report.edare_id === "-1"); + const nationalReport = { + ...nationalReportForItem, + edare_name: "کل استان", + }; + setData({ + data: [ + nationalReport, + ...result.edarateShahri.map((edare) => { + const filteredReports = result.activities.find((report) => report.edare_id == edare.id); + return { + edare_name: edare.name_fa, + ...filteredReports, + }; + }), + ], + filters: data, + }); + } catch (e) {} + } + }; + + useEffect(() => { + onSearchSubmit(defaultValues); + }, []); + + const validationSchema = yup.object().shape({ + from_date: yup + .string() + .required("تاریخ شروع الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") + .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + date_to: yup + .string() + .required("تاریخ پایان الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") + .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + province_id: yup.string().nullable(), + }); + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + return ( + + + + {!data ? : } + + ); +}; +export default ReportPage; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/DeleteContent.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/DeleteContent.jsx index bb9303d..3e6386f 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/DeleteContent.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/DeleteContent.jsx @@ -3,7 +3,13 @@ import useRequest from "@/lib/hooks/useRequest"; import React, { useState } from "react"; import { DELETE_ROAD_PATROL_SUPERVISOR } from "@/core/utils/routes"; -const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => { +const DeleteContent = ({ + rowId, + mutate, + setOpenDeleteDialog, + hasCompleteDeletePermission, + hasPartialDeletePermission, +}) => { const requestServer = useRequest({ notificationSuccess: true }); const [submittingPartial, setSubmittingPartial] = useState(false); const [submittingGeneral, setSubmittingGeneral] = useState(false); @@ -49,41 +55,59 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => { لطفاً نوع حذف مورد نظر را انتخاب کنید. - - حذف جزئی : حذف گشت بدون اقدامات انجام شده. - - - حذف کلی : حذف گشت با اقدامات انجام شده. - + {hasPartialDeletePermission && ( + + حذف جزئی : حذف گشت بدون اقدامات انجام شده. + + )} + {hasCompleteDeletePermission && ( + + حذف کلی : حذف گشت با اقدامات انجام شده. + + )} - + - - + {hasPartialDeletePermission && ( + + )} + {hasCompleteDeletePermission && ( + + )} ); diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx index a7218df..07a903f 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/DeleteForm/index.jsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; import DeleteContent from "./DeleteContent"; -const DeleteForm = ({ rowId, mutate }) => { +const DeleteForm = ({ rowId, mutate, hasCompleteDeletePermission, hasPartialDeletePermission }) => { const [openDeleteDialog, setOpenDeleteDialog] = useState(false); return ( @@ -24,7 +24,13 @@ const DeleteForm = ({ rowId, mutate }) => { maxWidth={"md"} > حذف گشت - + ); diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx new file mode 100644 index 0000000..09e5af6 --- /dev/null +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx @@ -0,0 +1,76 @@ +import { Box, DialogContent, Stack } from "@mui/material"; +import React, { useEffect, useState, useRef } from "react"; +import { Marker, useMap } from "react-leaflet"; +import AzmayeshActiveIcon from "@/assets/images/examine_marker_active.png"; +import StopsInfo from "./StopsInfo"; +import MapLayer from "@/core/components/MapLayer"; +const defaultIconSize = [35, 35]; +const MachinePerformanceContent = ({ machinesLists }) => { + const [selectedPoint, setSelectedPoint] = useState(null); + const mapRef = useRef(null); + const isFirstRender = useRef(true); + const createCustomIcon = (size, iconUrl) => { + return L.icon({ + iconUrl: iconUrl, + iconSize: size, + iconAnchor: [size[0] / 2, size[1]], + popupAnchor: [0, -size[1]], + }); + }; + const MapWithBounds = () => { + const map = useMap(); + useEffect(() => { + if (machinesLists.stopPoints.length > 0) { + const bounds = machinesLists.stopPoints.map((point) => [point.latitude, point.longitude]); + if (isFirstRender.current) { + map.fitBounds(bounds, { + padding: [50, 50], + }); + isFirstRender.current = false; + } else if (mapRef.current) { + mapRef.current.setView(map.getCenter(), map.getZoom()); + } + } + }, [machinesLists.stopPoints, map]); + return null; + }; + const handleMarkerClick = (point) => { + setSelectedPoint(point); + }; + + return ( + <> + + + + + + + + {machinesLists.stopPoints.map((point, index) => ( + handleMarkerClick(point), + }} + > + ))} + + + + + + + ); +}; +export default MachinePerformanceContent; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/StopsInfo.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/StopsInfo.jsx new file mode 100644 index 0000000..25d9238 --- /dev/null +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/StopsInfo.jsx @@ -0,0 +1,129 @@ +import { Box, Chip, Divider, Grid, Typography } from "@mui/material"; +import AccessAlarmsIcon from "@mui/icons-material/AccessAlarms"; +import PinDropIcon from "@mui/icons-material/PinDrop"; +import LocalGasStationIcon from "@mui/icons-material/LocalGasStation"; +import DirectionsCarIcon from "@mui/icons-material/DirectionsCar"; +const StopsInfo = ({ selectedPoint, staticData }) => { + return ( + + + + } + label={ + + کد ماشین + + } + /> + + + + {staticData.cmmsMachineCode || ""} + + + + + } + label={ + + مصرف سوخت + + } + /> + + + + {staticData.fuelConsumption || ""} لیتر + + + + + } + label={ + + مسافت طی‌شده + + } + /> + + + + {staticData.mileage || ""} کیلومتر + + + + + } + label={ + + مدت زمان روشن بودن خودرو + + } + /> + + + + {(staticData.accOnDuration / 60)?.toFixed(2) || ""} دقیقه + + + + {selectedPoint && ( + <> + + + } + label={ + + طول جغرافیایی + + } + /> + + + + {selectedPoint.longitude || ""} + + + + + } + label={ + + عرض جغرافیایی + + } + /> + + + + {selectedPoint.latitude || ""} + + + + + } + label={ + + مدت زمان توقف + + } + /> + + + + {selectedPoint.duration || ""} ثانیه + + + + )} + + ); +}; +export default StopsInfo; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/index.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/index.jsx new file mode 100644 index 0000000..242902f --- /dev/null +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/index.jsx @@ -0,0 +1,70 @@ +import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import DirectionsCarIcon from "@mui/icons-material/DirectionsCar"; +import MachinePerformanceContent from "./MachinePerformanceContent"; +import { useState } from "react"; + +const MachinePerformanceForm = () => { + const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false); + const machineData = { + cmms_machine_code: 245, + resultCode: 0, + mileage: 17645, + accOnDuration: 3599, + fuelConsumption: 6.1757503, + stopPoints: [ + { + startDT: "2024-12-23T13:55:40", + duration: 114, + latitude: 37.521317, + longitude: 46.103584, + }, + { + startDT: "2024-12-23T14:31:11", + duration: 14, + latitude: 37.566532, + longitude: 46.17688, + }, + { + startDT: "2024-12-23T14:35:36", + duration: 55, + latitude: 37.569958, + longitude: 46.18166, + }, + ], + }; + return ( + <> + + setOpenMachinesCodeDialog(true)}> + + + + setOpenMachinesCodeDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + عملکرد خودرو + + + + + + + ); +}; +export default MachinePerformanceForm; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinesCodeForm/MachinesCodeContent.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinesCodeForm/MachinesCodeContent.jsx deleted file mode 100644 index 1eef774..0000000 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinesCodeForm/MachinesCodeContent.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; -import ShowPlak from "@/core/components/ShowPlak"; - -const MachinesCodeContent = ({ machinesLists }) => { - return ( - <> - - - - - - - کد ماشین - نام خودرو - پلاک - - - - {machinesLists.map((machine) => { - return ( - - {machine.machine_code} - {machine.car_name} - - {machine.plak_number ? ( - - ) : null} - - - ); - })} - -
-
-
-
- - ); -}; -export default MachinesCodeContent; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/OfficerDescription/index.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/OfficerDescription/index.jsx index 04b5946..1ba3991 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/OfficerDescription/index.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/OfficerDescription/index.jsx @@ -37,7 +37,7 @@ const OfficerDescriptionForm = ({ description }) => { - + {description || "هیچ توضیحی موجود نیست"} diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/index.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/index.jsx index 09d7502..0b3d0a0 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/index.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/index.jsx @@ -1,10 +1,21 @@ import { Box } from "@mui/material"; import DeleteForm from "./DeleteForm"; +import { usePermissions } from "@/lib/hooks/usePermissions"; const RowActions = ({ row, mutate }) => { + const { data: userPermissions } = usePermissions(); + const hasPartialDeletePermission = userPermissions.includes("partial-delete-road-patrol"); + const hasCompleteDeletePermission = userPermissions.includes("complete-delete-road-patrol"); return ( - + {hasPartialDeletePermission | hasCompleteDeletePermission && ( + + )} ); }; diff --git a/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx b/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx index 5ec3369..6408d4d 100644 --- a/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/SupervisorList.jsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo } from "react"; -import {Box, Stack, Typography} from "@mui/material"; +import { useEffect, useMemo, useState } from "react"; +import { Box, Stack, Typography } from "@mui/material"; import DataTableWithAuth from "@/core/components/DataTableWithAuth"; import Toolbar from "./Toolbar"; import { GET_ROAD_PATROL_SUPERVISOR_LIST } from "@/core/utils/routes"; @@ -11,12 +11,53 @@ import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsT import useEdaratLists from "@/lib/hooks/useEdaratLists"; import OfficerDescriptionForm from "./RowActions/OfficerDescription"; import ReportForm from "./RowActions/ReportForm"; -import MachinesCodeDialog from "./RowActions/MachinesCodeForm"; import RahdaranDialog from "./RowActions/RahdaranForm"; +import MachinePerformanceForm from "./RowActions/MachinePerformanceForm"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; -const OperatorList = () => { - const columns = useMemo( - () => [ +const SupervisorList = () => { + const { data: userPermissions } = usePermissions(); + const hasCountryPermission = userPermissions.includes("show-road-patrol-supervise-cartable"); + const { user } = useAuth(); + const columns = useMemo(() => { + const dynamicColumns = { + accessorKey: "province_id", + header: "استان", // Province + id: "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: props.column.header }, + ...provinces.map((province) => ({ + value: province.id, + label: province.name_fa, + })), + ]; + }, [provinces, errorProvinces, loadingProvinces]); + return ( + + ); + }, + Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}, + }; + return [ { accessorKey: "id", header: "کد یکتا", @@ -28,35 +69,7 @@ const OperatorList = () => { grow: false, size: 100, }, - { - accessorKey: "province_id", - header: "استان", - id: "province_id", - enableColumnFilter: true, - datatype: "numeric", - filterMode: "equals", - ColumnSelectComponent: (props) => { - const { provinces, errorProvinces, loadingProvinces } = useProvinces(); - const getColumnSelectOptions = () => { - if (loadingProvinces) { - return [{ value: "loading", label: "در حال بارگذاری..." }]; - } - if (errorProvinces) { - return [{ value: "error", label: "خطا در بارگذاری" }]; - } - return [ - { value: "", label: props.column.header }, - ...provinces.map((province) => ({ - value: province.id, - label: province.name_fa, - })), - ]; - }; - const columnSelectOption = getColumnSelectOptions(); - return ; - }, - Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}, - }, + ...(hasCountryPermission ? [dynamicColumns] : []), { accessorKey: "edare_id", header: "اداره", @@ -64,15 +77,19 @@ const OperatorList = () => { enableColumnFilter: true, datatype: "numeric", filterMode: "equals", - dependencyId: "province_id", + dependencyId: hasCountryPermission ? "province_id" : null, grow: false, size: 120, ColumnSelectComponent: (props) => { const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists( - props.dependencyFieldValue.value + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id ); - const getColumnSelectOptions = () => { - if (props.dependencyFieldValue.value === "") { + 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) { @@ -88,21 +105,37 @@ const OperatorList = () => { label: edare.name_fa, })), ]; - }; - const columnSelectOption = getColumnSelectOptions(); - return ; + }, [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: ({ renderedCellValue, row }) => <>{row.original.edare_name}, }, { - accessorKey: "cmms_machines", - header: "خودرو", // Car ID - id: "cmmsMachines__machine_code", + header: "عملکرد خودرو", enableColumnFilter: true, + id: "cmmsMachines__machine_code", datatype: "text", filterMode: "contains", - enableSorting: false, columnFilterModeOptions: ["equals", "contains"], + enableSorting: false, grow: false, size: 100, muiTableBodyCellProps: { @@ -114,24 +147,21 @@ const OperatorList = () => { }, }, }, - Cell: ({ renderedCellValue }) => { - if (renderedCellValue) { - return ( - - - - ); - } - return کد خودرویی وجود ندارد; - }, + Cell: () => ( + + + + ), }, { accessorKey: "rahdaran", header: "راهداران", - id: "rahdaran", - enableColumnFilter: false, + id: "rahdaran__code", + enableColumnFilter: true, enableSorting: false, - datatype: "array", + datatype: "text", + filterMode: "contains", + columnFilterModeOptions: ["equals", "contains"], grow: false, size: 100, muiTableBodyCellProps: { @@ -143,47 +173,42 @@ const OperatorList = () => { }, }, }, - Cell: ({ renderedCellValue }) => { - if (renderedCellValue) { - return ( - - - - ); - } - return بدون شخص; - }, + Cell: ({ renderedCellValue }) => + renderedCellValue ? ( + + + + ) : ( + بدون شخص + ), }, { - accessorFn: (row) => moment(row.start_date).locale("fa").format("HH:mm | yyyy/MM/DD"), + accessorFn: (row) => moment(row.start_time).locale("fa").format("HH:mm | yyyy/MM/DD"), header: "تاریخ شروع", - id: "start_date", + id: "start_time", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, { - accessorFn: (row) => moment(row.end_date).locale("fa").format("HH:mm | yyyy/MM/DD"), + accessorFn: (row) => moment(row.end_time).locale("fa").format("HH:mm | yyyy/MM/DD"), header: "تاریخ پایان", - id: "end_date", + id: "end_time", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, { - accessorFn: (row) => moment(row.register_date).locale("fa").format("HH:mm | yyyy/MM/DD"), + accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"), header: "تاریخ ثبت", - id: "register_date", + id: "created_at", enableColumnFilter: true, datatype: "date", - filterMode: "equals", - columnFilterModeOptions: ["equals", "contains"], + filterMode: "between", grow: false, size: 100, }, @@ -204,13 +229,11 @@ const OperatorList = () => { }, }, }, - Cell: ({ renderedCellValue }) => { - return ( - - - - ); - }, + Cell: ({ renderedCellValue }) => ( + + + + ), }, { header: "گزارش مامور", @@ -227,17 +250,14 @@ const OperatorList = () => { }, }, }, - Cell: ({ renderedCellValue, row }) => { - return ( - - - - ); - }, + Cell: ({ row }) => ( + + + + ), }, - ], - [] - ); + ]; + }, []); return ( <> @@ -258,4 +278,4 @@ const OperatorList = () => { ); }; -export default OperatorList; +export default SupervisorList; diff --git a/src/components/dashboard/roadPatrols/supervisor/Toolbar.jsx b/src/components/dashboard/roadPatrols/supervisor/Toolbar.jsx index 32c4395..47cb9a9 100644 --- a/src/components/dashboard/roadPatrols/supervisor/Toolbar.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/Toolbar.jsx @@ -1,10 +1,6 @@ import PrintExcel from "./ExcelPrint"; const Toolbar = ({ table, filterData }) => { - return ( - <> - - - ); + return <>{/**/}; }; export default Toolbar; diff --git a/src/components/dashboard/roadPatrols/supervisor/index.jsx b/src/components/dashboard/roadPatrols/supervisor/index.jsx index 67f9f63..3fa490b 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 SuperviserList from "./SupervisorList"; +import SupervisorList from "./SupervisorList"; const SupervisorPage = () => { return ( - + ); }; diff --git a/src/core/components/ActivityCodeLog.jsx b/src/core/components/ActivityCodeLog.jsx index cf284e6..f301549 100644 --- a/src/core/components/ActivityCodeLog.jsx +++ b/src/core/components/ActivityCodeLog.jsx @@ -3,6 +3,7 @@ import { useEffect, useState, memo } from "react"; import useRequest from "@/lib/hooks/useRequest"; import { ACTIVITY_LOG } from "@/core/utils/routes"; +import NumberField from "@/core/components/NumberField"; const ActivityCodeLog = memo(({ activity_code }) => { const requestServer = useRequest({ notificationShow: false }); @@ -32,5 +33,6 @@ const ActivityCodeLog = memo(({ activity_code }) => { return null; }); +ActivityCodeLog.displayName = "ActivityCodeLog"; export default ActivityCodeLog; diff --git a/src/core/components/CarCode.jsx b/src/core/components/CarCode.jsx index a5e5ea7..60d7cda 100644 --- a/src/core/components/CarCode.jsx +++ b/src/core/components/CarCode.jsx @@ -77,10 +77,14 @@ const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error, multiple renderInput={(params) => ( { setSortData(event); }; - const fetchUrl = useMemo(() => { + const fetchUrl = useCallback(() => { const params = new URLSearchParams(); params.set("start", `${pagination.pageIndex * pagination.pageSize}`); params.set("size", pagination.pageSize); @@ -76,7 +76,7 @@ const DataTable_Main = (props) => { } }; - const { data, isValidating, mutate } = useSWR(fetchUrl, fetcher, { + const { data, isValidating, mutate } = useSWR(props.data ? "" : fetchUrl(), fetcher, { revalidateIfStale: true, revalidateOnFocus: false, revalidateOnReconnect: true, @@ -129,7 +129,11 @@ const DataTable_Main = (props) => { onSortingChange: onSortingChange, rowCount: totalRowCount, renderTopToolbarCustomActions: ({ table }) => ( - <>{TableToolbar && } + <> + {TableToolbar && ( + + )} + ), renderRowActions: ({ row }) => , ...props, @@ -146,6 +150,7 @@ const DataTable_Main = (props) => { user_id={user_id} page_name={page_name} table_name={table_name} + special_data={props.data} /> ); }; diff --git a/src/core/components/DataTable/filter/FilterBody.jsx b/src/core/components/DataTable/filter/FilterBody.jsx index d0eb7cf..32d3e3f 100644 --- a/src/core/components/DataTable/filter/FilterBody.jsx +++ b/src/core/components/DataTable/filter/FilterBody.jsx @@ -9,6 +9,7 @@ import { useEffect, useState } from "react"; import { Controller, useForm, useWatch } from "react-hook-form"; import * as Yup from "yup"; import FilterBodyController from "./FilterBodyController"; +import FilterBodyControllerWithDependency from "@/core/components/DataTable/filter/FilterBodyControllerWithDependency"; const ScrollBox = styled(Box)({ flexGrow: 1, @@ -74,7 +75,7 @@ function FilterBody({ columns, drawerState, setDrawerState }) { } = useForm({ defaultValues: filterData, resolver: yupResolver(validationSchema), - mode: "onChange", + mode: "onBlur", }); const onSubmit = (data) => { @@ -93,9 +94,17 @@ function FilterBody({ columns, drawerState, setDrawerState }) {
- {columns.map( - (column) => - column.enableColumnFilter && ( + {columns.map((column) => + column.enableColumnFilter ? ( + column.dependencyId ? ( + + ) : ( ) + ) : null )} { - const dependencyField = useWatch({ control, name: column.dependencyId }); - return ( ( - reset()} - errors={errors} - /> - )} + render={({ field: { value, name, onBlur, onChange } }) => { + return ( + reset()} + errors={errors} + /> + ); + }} /> ); }; diff --git a/src/core/components/DataTable/filter/FilterBodyControllerWithDependency.jsx b/src/core/components/DataTable/filter/FilterBodyControllerWithDependency.jsx new file mode 100644 index 0000000..f531c48 --- /dev/null +++ b/src/core/components/DataTable/filter/FilterBodyControllerWithDependency.jsx @@ -0,0 +1,27 @@ +import { Controller, useWatch } from "react-hook-form"; +import FilterBodyField from "./FilterBodyField"; + +const FilterBodyControllerWithDependency = ({ column, control, reset, errors }) => { + const dependencyField = useWatch({ control, name: column.dependencyId }); + + return ( + { + return ( + reset()} + errors={errors} + /> + ); + }} + /> + ); +}; +export default FilterBodyControllerWithDependency; diff --git a/src/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange.jsx b/src/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange.jsx index 4839f77..498c0a2 100644 --- a/src/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange.jsx +++ b/src/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange.jsx @@ -8,6 +8,7 @@ function CustomDatePickerRange({ column, filterParameters, defaultFilterTranslat return ( { @@ -22,6 +23,7 @@ function CustomDatePickerRange({ column, filterParameters, defaultFilterTranslat } /> { diff --git a/src/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker.jsx b/src/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker.jsx index 9cfa8c5..0e91257 100644 --- a/src/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker.jsx +++ b/src/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker.jsx @@ -6,7 +6,7 @@ import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; import moment from "jalali-moment"; -function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) { +function MuiDatePicker({ label, value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) { return ( ), }, + InputLabelProps: { + shrink: true, + }, }, }} /> diff --git a/src/core/components/DataTable/filter/fieldsType/CustomSelectByDependency.jsx b/src/core/components/DataTable/filter/fieldsType/CustomSelectByDependency.jsx index 5658bdd..5d809e3 100644 --- a/src/core/components/DataTable/filter/fieldsType/CustomSelectByDependency.jsx +++ b/src/core/components/DataTable/filter/fieldsType/CustomSelectByDependency.jsx @@ -6,24 +6,11 @@ import { useCallback, useEffect } from "react"; function CustomSelectByDependency({ column, filterParameters, + value, defaultFilterTranslation, handleChange, columnSelectOption, }) { - const isValidValue = columnSelectOption.some((option) => option.value === filterParameters.value); - - const handleInvalidValue = useCallback(() => { - if (!isValidValue && filterParameters.value !== "") { - handleChange({ ...filterParameters, value: columnSelectOption[0]?.value || "" }); - } - }, [isValidValue, filterParameters, handleChange, columnSelectOption]); - - useEffect(() => { - handleInvalidValue(); - }, [handleInvalidValue]); - - const selectedValue = isValidValue ? filterParameters.value : columnSelectOption[0]?.value || ""; - return ( {column.header} @@ -31,7 +18,7 @@ function CustomSelectByDependency({ labelId={`label${column.id}`} id={column.id} name={`${column.id}.value`} - value={selectedValue} + value={value} input={} onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })} > diff --git a/src/core/components/DataTable/table/Paper.js b/src/core/components/DataTable/table/Paper.js index f972030..14665a3 100644 --- a/src/core/components/DataTable/table/Paper.js +++ b/src/core/components/DataTable/table/Paper.js @@ -86,6 +86,7 @@ const DataTable_Paper = ({ page_name={page_name} table_name={table_name} table_title={table_title} + {...paperProps} /> ))} diff --git a/src/core/components/DataTable/toolbar/TopToolbar.js b/src/core/components/DataTable/toolbar/TopToolbar.js index cdbbf4c..2c322ff 100644 --- a/src/core/components/DataTable/toolbar/TopToolbar.js +++ b/src/core/components/DataTable/toolbar/TopToolbar.js @@ -17,6 +17,7 @@ const DataTable_TopToolbar = ({ page_name, table_name, table_title, + special_data, }) => { const { getState, @@ -96,8 +97,12 @@ const DataTable_TopToolbar = ({ order: 3, }} > - - + {!special_data && ( + <> + + + + )} { setStartPosition({ lat: center.lat, lng: center.lng }); setIsStartLocked(true); setStartIconColor("#1CAC66"); - map.panBy([25, 25]) + map.panBy([25, 25]); } }, }} @@ -183,13 +183,10 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => { setEndPosition({ lat: center.lat, lng: center.lng }); setIsEndLocked(true); setEndIconColor("#D13131"); - map.fitBounds( - [ - startPosition, - map.getCenter(), - ], - { paddingTopLeft: [16, 16], paddingBottomRight: [16, 16] } - ); + map.fitBounds([startPosition, map.getCenter()], { + paddingTopLeft: [16, 16], + paddingBottomRight: [16, 16], + }); } }, }} diff --git a/src/core/components/MuiDatePicker.jsx b/src/core/components/MuiDatePicker.jsx index 10d5dff..0b5d8b1 100644 --- a/src/core/components/MuiDatePicker.jsx +++ b/src/core/components/MuiDatePicker.jsx @@ -6,7 +6,7 @@ import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; import moment from "jalali-moment"; -function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) { +function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error, label }) { return ( { const date = new Date(value); @@ -34,6 +34,9 @@ function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperTex error: error, size: "small", placeholder: placeholder, + InputLabelProps: { + shrink: true, + }, InputProps: { endAdornment: ( diff --git a/src/core/components/MuiTimePicker.jsx b/src/core/components/MuiTimePicker.jsx index 8aeace6..b3336d4 100644 --- a/src/core/components/MuiTimePicker.jsx +++ b/src/core/components/MuiTimePicker.jsx @@ -1,16 +1,16 @@ import React from "react"; -import { LocalizationProvider, MobileTimePicker } from "@mui/x-date-pickers"; +import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; +import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker"; import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali"; import { faIR } from "@mui/x-date-pickers/locales"; import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material"; import ClearIcon from "@mui/icons-material/Clear"; -import { parseISO, format } from "date-fns"; -function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperText, placeholder, error }) { +function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperText, placeholder, error, label }) { // Ensure value, minTime, maxTime are Date objects - const parsedValue = value ? (typeof value === "string" ? parseISO(value) : value) : null; - const parsedMinTime = minTime ? (typeof minTime === "string" ? parseISO(minTime) : minTime) : null; - const parsedMaxTime = maxTime && (typeof maxTime === "string" ? parseISO(maxTime) : null); + const parsedValue = value || null; + const parsedMinTime = minTime || null; + const parsedMaxTime = maxTime || null; return ( @@ -23,14 +23,13 @@ function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperTex sx={{ width: "100%" }} id={name} closeOnSelect - label={helperText ? helperText : placeholder} + label={label} ampm={false} timeSteps={{ hours: 1, minutes: 5 }} views={["hours", "minutes"]} name={name} onChange={(newValue) => { - const formattedTime = newValue ? format(new Date(newValue), "HH:mm") : ""; - setFieldValue(name, formattedTime); + setFieldValue(name, newValue); }} minTime={parsedMinTime} maxTime={parsedMaxTime} @@ -39,6 +38,9 @@ function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperTex placeholder: placeholder, size: "small", error: error, + InputLabelProps: { + shrink: true, + }, InputProps: { endAdornment: ( diff --git a/src/core/components/RahdarCode.jsx b/src/core/components/RahdarCode.jsx index 1c374ee..c1f6c48 100644 --- a/src/core/components/RahdarCode.jsx +++ b/src/core/components/RahdarCode.jsx @@ -72,10 +72,14 @@ const RahdarCode = ({ rahdarsCode, setRahdarsCode, error, multiple = true }) => renderInput={(params) => ( + {showAddIcon ? ( , - permissions: [ - "show-road-item-supervise-cartable", - "show-road-item-supervise-cartable-province", - "create-road-item", + hasSubitems: true, + Subitems: [ + { + id: "roadItemManagmentReportProvince", + label: "تعداد فعالیت ها به تفکیک ایتم", + type: "page", + route: "/dashboard/road-items/reports/items-report", + permissions: [ + "show-road-item-supervise-cartable", + "show-road-item-supervise-cartable-province", + "create-road-item", + ], + }, + { + id: "roadItemManagmentReportItems", + label: "تعداد و حجم فعالیت ها به تفکیک موارد مشاهده شده", + type: "page", + route: "/dashboard/road-items/reports/sub-items-report", + permissions: [ + "show-road-item-supervise-cartable", + "show-road-item-supervise-cartable-province", + "create-road-item", + ], + }, ], }, ], @@ -220,7 +239,7 @@ export const pageMenu = [ id: "roadPatrolManagmentReport", label: "گزارش ها", type: "page", - route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_patrols/report", + route: "/dashboard/road-patrols/reports", icon: , permissions: [ "add-road-patrol", diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js index 06fe15d..b13b921 100644 --- a/src/core/utils/routes.js +++ b/src/core/utils/routes.js @@ -37,6 +37,8 @@ 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"; +export const GET_PROVINCE_ACTIVITY_REPORT = api + "/api/v3/road_patrol_reports/province_activity"; +export const GET_CITY_ACTIVITY_REPORT = api + "/api/v3/road_patrol_reports/city_activity"; // road items export const GET_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_index"; @@ -55,6 +57,10 @@ export const RESTORE_BY_SUPERVISOR = api + "/api/v3/road_items/restore"; export const GET_CAR_LIST_SEARCH = api + "/api/v3/cmms_machines/search"; export const GET_RAHDARANS_LIST_SEARCH = api + "/api/v3/rahdaran/search"; export const GET_OBSERVED_GASHT_LIST = api + "/api/v3/observed_items/filter"; +export const GET_PROVINCE_ACTIVITY_PER_ITEM = api + "/api/v3/road_item_reports/province_activity_per_item"; +export const GET_CITY_ACTIVITY_PER_ITEM = api + "/api/v3/road_item_reports/city_activity_per_item"; +export const GET_PROVINCE_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/province_activity_per_sub_item"; +export const GET_CITY_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/city_activity_per_sub_item"; // activity code log export const ACTIVITY_LOG = api + "/api/v3/profile/add_activity"; diff --git a/src/core/utils/theme.js b/src/core/utils/theme.js index 7cde9e0..21a3056 100644 --- a/src/core/utils/theme.js +++ b/src/core/utils/theme.js @@ -1,6 +1,6 @@ "use client"; import { createTheme } from "@mui/material"; -import { grey } from '@mui/material/colors'; +import { grey } from "@mui/material/colors"; const theme = createTheme({ direction: "rtl", @@ -19,7 +19,7 @@ const theme = createTheme({ }, secondary: { main: grey[700], - } + }, }, components: { MuiBackdrop: { diff --git a/src/lib/contexts/DataTable.js b/src/lib/contexts/DataTable.js index f283da7..5c9752a 100644 --- a/src/lib/contexts/DataTable.js +++ b/src/lib/contexts/DataTable.js @@ -27,7 +27,17 @@ const DataTableProvider = ({ children, user_id, page_name, table_name, columns, JSON.stringify(sortData) !== JSON.stringify(initSort) || JSON.stringify(filterData) !== JSON.stringify(initFilter); - const CheckFilterValues = Object.values(filterData).every((innerObject) => innerObject.value === ""); + const CheckFilterValues = Object.values(filterData).every((innerObject) => { + if (innerObject.datatype === "date" && innerObject.filterMode === "between") { + return ( + Array.isArray(innerObject.value) && + innerObject.value.length === 2 && + innerObject.value.every((v) => v === "") + ); + } else { + return innerObject.value === ""; + } + }); const CheckHideValues = Object.values(hideData).every((value) => value === true); hasAnyConflict = diff --git a/src/lib/hooks/useCities.js b/src/lib/hooks/useCities.js index e1af9aa..6ac535d 100644 --- a/src/lib/hooks/useCities.js +++ b/src/lib/hooks/useCities.js @@ -1,5 +1,4 @@ import { useState, useEffect } from "react"; -import axios from "axios"; import { GET_CITY_LISTS } from "@/core/utils/routes"; import useRequest from "@/lib/hooks/useRequest";