Merge branch 'release/v1.8.5'

This commit is contained in:
2025-12-28 11:46:34 +03:30
32 changed files with 861 additions and 162 deletions

View File

@@ -1,5 +1,5 @@
HOST="rms.witel.ir"
HOST_RMTO="rms.rmto.ir"
NEXT_PUBLIC_VERSION="1.8.4"
NEXT_PUBLIC_VERSION="1.8.5"
NEXT_PUBLIC_API_URL="https://rms.witel.ir"
NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map"

View File

@@ -9,7 +9,7 @@ const Page = () => {
<>
<WithPermission
permission_name={[
"manage-control-unit",
"manage-control-unit-province",
"manage-control-unit-city",
"manage-control-unit-country",
"manage-control-unit-station",

View File

@@ -9,7 +9,7 @@ const Page = () => {
<>
<WithPermission
permission_name={[
"manage-transportation-unit-country",
"manage-transportation-unit-province",
"manage-transportation-unit-city",
"manage-transportation-unit-country",
"manage-transportation-unit-station",

View File

@@ -6,7 +6,14 @@ export const metadata = {
const Page = () => {
return (
<>
<WithPermission permission_name={["all"]}>
<WithPermission
permission_name={[
"manage-violation-city",
"manage-violation-country",
"manage-violation-province",
"manage-violation-station",
]}
>
<ViolationsPage />
{/* <ActivityCodeLog activity_code={1152} /> */}
</WithPermission>

View File

@@ -19,7 +19,6 @@ import LogesticController from "./LogesticController";
const validationSchema = object({
provinceId: string().required("لطفا استان را وارد کنید!!!"),
edarehShahriId: string().required("لطفا اداره را وارد کنید!!!"),
name: string().required("وارد کردن نام و نام خانوادگی الزامیست!"),
code: string()
.required("لطفا کد ملی را وارد کنید!!!")

View File

@@ -33,9 +33,6 @@ const LogesticController = ({ control }) => {
/>
</Stack>
) : null}
<Stack>
<CityController control={control} />
</Stack>
</>
);
};

View File

@@ -17,7 +17,6 @@ const CreateForm = ({ open, setOpen, mutate }) => {
isDriver: "0",
isProvince: !hasCountryPermission,
provinceId: !hasCountryPermission ? user.province_id : "",
edarehShahriId: "",
name: "",
code: "",
phone_number: "",
@@ -26,7 +25,6 @@ const CreateForm = ({ open, setOpen, mutate }) => {
const formData = new FormData();
formData.append("is_driver", result.isDriver);
formData.append("province_id", result.provinceId);
formData.append("edareh_shahri_id", result.edarehShahriId);
formData.append("name", result.name);
formData.append("code", result.code);
formData.append("mobile", result.phone_number);

View File

@@ -10,7 +10,6 @@ const EditController = ({ rowId, mutate, setOpen, row }) => {
const defaultData = {
isDriver: `${row.original?.is_driver}` || "0",
provinceId: row.original?.province_id || "",
edarehShahriId: row.original?.edareh_shahri_id || "",
name: row.original?.name || "",
code: row.original?.code || "",
phone_number: row.original?.mobile || "",
@@ -19,7 +18,6 @@ const EditController = ({ rowId, mutate, setOpen, row }) => {
const formData = new FormData();
formData.append("is_driver", result.isDriver);
formData.append("province_id", result.provinceId);
formData.append("edareh_shahri_id", result.edarehShahriId);
formData.append("name", result.name);
formData.append("code", result.code);
formData.append("mobile", result.phone_number);

View File

@@ -2,18 +2,15 @@
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_RAHDARAN_TABLE_LIST } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useEffect, useMemo, useState } from "react";
import { useMemo } from "react";
import RowActions from "./RowActions";
import Toolbar from "./Toolbar";
import { useAuth } from "@/lib/contexts/auth";
import useProvinces from "@/lib/hooks/useProvince";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import useEdaratLists from "@/lib/hooks/useEdaratLists";
import { usePermissions } from "@/lib/hooks/usePermissions";
const RahdaranList = () => {
const { data: userPermissions } = usePermissions();
const { user } = useAuth();
const hasCountryPermission = userPermissions?.includes("manage-rahdaran-country");
const columns = useMemo(() => {
const provinceColumn = {
@@ -66,63 +63,6 @@ const RahdaranList = () => {
size: 100,
},
...(hasCountryPermission ? [provinceColumn] : []),
{
header: "اداره",
id: "edareh_shahri_id",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
dependencyId: hasCountryPermission ? "province_id" : null,
grow: false,
size: 120,
ColumnSelectComponent: (props) => {
const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(
hasCountryPermission ? props.dependencyFieldValue.value : user.province_id
);
const [prevDependency, setPrevDependency] = useState(
hasCountryPermission ? props.dependencyFieldValue.value : user.province_id
);
const getColumnSelectOptions = useMemo(() => {
if (hasCountryPermission && props.dependencyFieldValue.value === "") {
return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
}
if (loadingEdaratList) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorEdaratList) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "کل ادارات" },
...edaratList.map((edare) => ({
value: edare.id,
label: edare.name_fa,
})),
];
}, [edaratList, loadingEdaratList, errorEdaratList]);
useEffect(() => {
if (hasCountryPermission) return;
if (prevDependency === props.dependencyFieldValue?.value) return;
props.handleChange({ ...props.filterParameters, value: "" });
setPrevDependency(props.dependencyFieldValue?.value);
}, [props.dependencyFieldValue?.value, hasCountryPermission]);
return (
<CustomSelectByDependency
{...props}
value={
props.dependencyFieldValue?.value === ""
? "empty"
: loadingEdaratList
? "loading"
: props.filterParameters.value
}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ renderedCellValue, row }) => <>{row.original.edareh_shahri_name}</>,
},
{
accessorKey: "name",
header: "نام و نام خانوادگی",

View File

@@ -12,8 +12,6 @@ import { Box, Stack, Typography } from "@mui/material";
import moment from "jalali-moment";
import { useEffect, useMemo, useState } from "react";
import ImageDialog from "../operator/RowActions/ImageForm";
import MachinesCodeDialog from "../operator/RowActions/MachinesCodeForm";
import RahdaranDialog from "../operator/RowActions/RahdaranForm";
import RowActions from "./RowActions";
import LocationForm from "./RowActions/LocationForm";
import Toolbar from "./Toolbar";
@@ -364,7 +362,7 @@ const SupervisorList = () => {
label: status.label,
}));
},
Cell: ({ renderedCellValue, row }) => <>{row.original.status_fa}</>,
Cell: ({ row }) => <>{row.original.status_fa}</>,
},
];
}, [hasCountryPermission]);

View File

@@ -7,10 +7,50 @@ import { useMemo } from "react";
import MachinesDialog from "./RowActions/Machines";
import RowActions from "./RowActions";
import RahdaranDialog from "./RowActions/Rahdaran";
import useProvinces from "@/lib/hooks/useProvince";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import useCities from "@/lib/hooks/useCities";
const ControlList = () => {
const columns = useMemo(
() => [
const columns = useMemo(() => {
const dynamicColumns = {
header: "استان",
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: "کل کشور" },
...provinces.map((province) => ({
value: province.id,
label: province.name_fa,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
return (
<CustomSelectByDependency
{...props}
value={loadingProvinces ? "loading" : props.filterParameters.value}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ renderedCellValue, row }) => {
return <>{row.original.province_name}</>;
},
};
return [
{
accessorKey: "id",
header: "کد یکتا",
@@ -22,6 +62,63 @@ const ControlList = () => {
grow: false,
size: 100,
},
...[dynamicColumns],
{
header: "شهر",
id: "city_id",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
dependencyId: "province_id",
grow: false,
size: 120,
ColumnSelectComponent: (props) => {
const { cityList, loadingCityList, errorCityList } = useCities(props.dependencyFieldValue.value);
const getColumnSelectOptions = useMemo(() => {
if (props.dependencyFieldValue.value === "") {
return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
}
if (loadingCityList) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorCityList) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "کل ادارات" },
...cityList.map((edare) => ({
value: edare.id,
label: edare.name_fa,
})),
];
}, [cityList, loadingCityList, errorCityList]);
return (
<CustomSelectByDependency
{...props}
value={
props.dependencyFieldValue?.value === ""
? "empty"
: loadingCityList
? "loading"
: props.filterParameters.value
}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ row }) => <>{row.original.city_name}</>,
},
{
accessorKey: "station_name",
header: "راهدارخانه",
id: "station_name",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
grow: false,
size: 100,
},
{
accessorFn: (row) =>
row.type == 1
@@ -113,9 +210,8 @@ const ControlList = () => {
grow: false,
size: 100,
},
],
[]
);
];
}, []);
return (
<>

View File

@@ -2,6 +2,8 @@ import LtrTextField from "@/core/components/LtrTextField";
import { Close, Edit } from "@mui/icons-material";
import { Button, Dialog, IconButton, Stack } from "@mui/material";
import { useState } from "react";
import { Controller } from "react-hook-form";
import CarCode from "@/core/components/CarCode";
const MachinesDialog = ({ setMachine, mode, machineCode, setShowMachineCodeDialog }) => {
const [openMachinesDialog, setOpenMachinesDialog] = useState(false);
@@ -33,23 +35,14 @@ const MachinesDialog = ({ setMachine, mode, machineCode, setShowMachineCodeDialo
</IconButton>
{openMachinesDialog && (
<Stack sx={{ p: 4, mt: 2 }} spacing={2}>
<LtrTextField
label={`کد خودرو`}
value={machineCode || ""}
type="text"
size={"small"}
inputProps={{
placeholder: "کد خودرو را وارد کنید",
inputMode: "numeric",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
setMachine(event.target.value);
}}
variant="outlined"
fullWidth
/>
<Stack sx={{ minWidth: "200px" }}>
<CarCode
carCode={machineCode}
setCarCode={(value) => {
setMachine(value.machine_code);
}}
/>
</Stack>
<Button
onClick={() => {
setShowMachineCodeDialog(true);

View File

@@ -1,16 +1,16 @@
import MapLoading from "@/core/components/MapLayer/Loading";
import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes";
import { missionRegions } from "@/core/utils/missionRegions";
import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typography } from "@mui/material";
import moment from "jalali-moment";
import dynamic from "next/dynamic";
import { useCallback } from "react";
// import ShowBound from "../../../showBound";
// import MapLoading from "@/core/components/MapLayer/Loading";
// import dynamic from "next/dynamic";
//
// const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
// loading: () => <MapLoading />,
// ssr: false,
// });
import ShowBound from "../../../showBound";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => <MapLoading />,
ssr: false,
});
const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
const handleNext = useCallback(() => {
@@ -22,11 +22,11 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
<DialogContent dividers>
<Box sx={{ flex: 1 }}>
<Stack spacing={2}>
{/*<Box sx={{ width: "100%", height: "200px" }}>*/}
{/* <MapLayer style={{ borderRadius: "4px" }}>*/}
{/* <ShowBound bound={allData.bound} />*/}
{/* </MapLayer>*/}
{/*</Box>*/}
<Box sx={{ width: "100%", height: "200px" }}>
<MapLayer style={{ borderRadius: "4px" }}>
<ShowBound bound={allData.bound} />
</MapLayer>
</Box>
<Stack spacing={1}>
<Divider>
<Chip color="primary" variant="outlined" label="مشخصات ماموریت" />

View File

@@ -1,3 +1,4 @@
import Area from "@/components/dashboard/roadMissions/operator/Actions/Create/Form/Area";
import { AccessTime, Engineering, InsertDriveFile, LocalShipping, Route, Verified } from "@mui/icons-material";
import { Box, Tab, Tabs } from "@mui/material";
import { useReducer, useState } from "react";
@@ -6,7 +7,6 @@ import GetItemInfo from "./GetItemInfo";
import MachineAndDriver from "./MachineAndDriver";
import Rahdaran from "./Rahdaran";
import Verify from "./Verify";
import Area from "@/components/dashboard/roadMissions/operator/Actions/Create/Form/Area";
function TabPanel(props) {
const { children, value, index } = props;
@@ -61,10 +61,10 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
>
<Tab icon={<InsertDriveFile />} label="مشخصات" />
<Tab disabled={tabState < 1} icon={<AccessTime />} label="زمانبندی" />
{/*<Tab disabled={tabState < 2} icon={<Route />} label="منطقه عملیاتی" />*/}
<Tab disabled={tabState < 2} icon={<LocalShipping />} label="خودرو و راننده" />
<Tab disabled={tabState < 3} icon={<Engineering />} label="همراهان" />
<Tab disabled={tabState < 4} icon={<Verified />} label="بررسی نهایی" />
<Tab disabled={tabState < 2} icon={<Route />} label="منطقه عملیاتی" />
<Tab disabled={tabState < 3} icon={<LocalShipping />} label="خودرو و راننده" />
<Tab disabled={tabState < 4} icon={<Engineering />} label="همراهان" />
<Tab disabled={tabState < 5} icon={<Verified />} label="بررسی نهایی" />
</Tabs>
<TabPanel value={tabState} index={0}>
<GetItemInfo
@@ -86,18 +86,8 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
setTabState={setTabState}
/>
</TabPanel>
{/*<TabPanel value={tabState} index={2}>*/}
{/* <Area*/}
{/* allData={allData}*/}
{/* setAllData={(data) => {*/}
{/* dispatch({ type: "changeData", data });*/}
{/* }}*/}
{/* handlePrev={handlePrev}*/}
{/* setTabState={setTabState}*/}
{/* />*/}
{/*</TabPanel>*/}
<TabPanel value={tabState} index={2}>
<MachineAndDriver
<Area
allData={allData}
setAllData={(data) => {
dispatch({ type: "changeData", data });
@@ -107,7 +97,7 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
/>
</TabPanel>
<TabPanel value={tabState} index={3}>
<Rahdaran
<MachineAndDriver
allData={allData}
setAllData={(data) => {
dispatch({ type: "changeData", data });
@@ -117,6 +107,16 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
/>
</TabPanel>
<TabPanel value={tabState} index={4}>
<Rahdaran
allData={allData}
setAllData={(data) => {
dispatch({ type: "changeData", data });
}}
handlePrev={handlePrev}
setTabState={setTabState}
/>
</TabPanel>
<TabPanel value={tabState} index={5}>
<Verify allData={allData} handlePrev={handlePrev} submitForm={submitForm} submitting={submitting} />
</TabPanel>
</>

View File

@@ -19,17 +19,17 @@ const CreateWithoutProcess = ({ mutate }) => {
const submitForm = async (result) => {
setSubmitting(true);
// const bound = result.bound.getLatLngs();
// let area =
// result.bound_type === "polygon"
// ? bound.map((ring) => ring.map((latlng) => [latlng.lng, latlng.lat]))[0]
// : bound.map((latlng) => [latlng.lng, latlng.lat]);
//
// // بستن polygon
// if (result.bound_type === "polygon" && area.length > 0) {
// const firstPoint = area[0];
// area.push({ ...firstPoint });
// }
const bound = result.bound.getLatLngs();
let area =
result.bound_type === "polygon"
? bound.map((ring) => ring.map((latlng) => [latlng.lng, latlng.lat]))[0]
: bound.map((latlng) => [latlng.lng, latlng.lat]);
// بستن polygon
if (result.bound_type === "polygon" && area.length > 0) {
const firstPoint = area[0];
area.push({ ...firstPoint });
}
await requestServer(REQUEST_MISSION_WITHOUT_PROCESS, "post", {
data: {
@@ -38,10 +38,10 @@ const CreateWithoutProcess = ({ mutate }) => {
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}),
zone: result.region,
end_point: result.end_point,
// area: {
// type: result.bound_type,
// coordinates: area,
// },
area: {
type: result.bound_type,
coordinates: area,
},
start_date: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`,
end_date: `${result.end_date} ${moment(result.end_time).format("HH:mm")}`,
driver: result.driver.id,
@@ -100,8 +100,8 @@ const CreateWithoutProcess = ({ mutate }) => {
start_date: "",
start_time: null,
end_date: "",
// bound: null,
// bound_type: "polyline",
bound: null,
bound_type: "polyline",
end_time: null,
end_point: "",
region: "",

View File

@@ -13,10 +13,50 @@ import MachinesDialog from "./RowActions/Machines";
import Toolbar from "./Toolbar";
import RahdaranDialog from "./RowActions/Rahdaran";
import DescriptionDialog from "@/components/dashboard/roadMissions/operator/RowActions/DescriptionDialog";
import useProvinces from "@/lib/hooks/useProvince";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import useCities from "@/lib/hooks/useCities";
const OperatorList = () => {
const columns = useMemo(
() => [
const columns = useMemo(() => {
const dynamicColumns = {
header: "استان",
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: "کل کشور" },
...provinces.map((province) => ({
value: province.id,
label: province.name_fa,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
return (
<CustomSelectByDependency
{...props}
value={loadingProvinces ? "loading" : props.filterParameters.value}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ renderedCellValue, row }) => {
return <>{row.original.province_name}</>;
},
};
return [
{
accessorKey: "id",
header: "کد یکتا",
@@ -38,6 +78,63 @@ const OperatorList = () => {
grow: false,
size: 100,
},
...[dynamicColumns],
{
header: "شهر",
id: "city_id",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
dependencyId: "province_id",
grow: false,
size: 120,
ColumnSelectComponent: (props) => {
const { cityList, loadingCityList, errorCityList } = useCities(props.dependencyFieldValue.value);
const getColumnSelectOptions = useMemo(() => {
if (props.dependencyFieldValue.value === "") {
return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
}
if (loadingCityList) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorCityList) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "کل ادارات" },
...cityList.map((edare) => ({
value: edare.id,
label: edare.name_fa,
})),
];
}, [cityList, loadingCityList, errorCityList]);
return (
<CustomSelectByDependency
{...props}
value={
props.dependencyFieldValue?.value === ""
? "empty"
: loadingCityList
? "loading"
: props.filterParameters.value
}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ row }) => <>{row.original.city_name}</>,
},
{
accessorKey: "station_name",
header: "راهدارخانه",
id: "station_name",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
grow: false,
size: 100,
},
{
accessorKey: "state_name",
header: "وضعیت",
@@ -287,9 +384,8 @@ const OperatorList = () => {
grow: false,
size: 100,
},
],
[]
);
];
}, []);
return (
<>

View File

@@ -35,8 +35,11 @@ const EditController = ({ row, mutate, openEditDialog, setOpenEditDialog }) => {
const submitForm = async (result) => {
setSubmitting(true);
const bound = result.bound.getLatLngs();
let area = bound.map((ring) => ring.map((latlng) => [latlng.lat, latlng.lng]));
const latlngs = result.bound.getLatLngs();
const normalized = result.bound_type === "polygon" ? latlngs : [latlngs];
const area = normalized.map((ring) => ring.map(({ lat, lng }) => [lat, lng]));
await requestServer(`${UPDATE_REQUEST_MISSION}/${row.original.id}`, "post", {
data: {
explanation: result.explanation,

View File

@@ -7,7 +7,7 @@ import { useAuth } from "@/lib/contexts/auth";
const MachinesList = ({ machineType, setMachine, setOpenMachinesDialog }) => {
const {
user: { city_id },
user: { city_id, province_id },
} = useAuth();
const columns = useMemo(
() => [
@@ -47,7 +47,12 @@ const MachinesList = ({ machineType, setMachine, setOpenMachinesDialog }) => {
specialFilter={[
{ value: machineType, datatype: "text", id: "car_type", fn: "equals" },
{ value: 1, datatype: "numeric", id: "status", fn: "equals" },
{ value: city_id, datatype: "numeric", id: "city_id", fn: "equals" },
{
value: city_id ? city_id : province_id,
datatype: "numeric",
id: city_id ? "city_id" : "province_id",
fn: "equals",
},
]}
sorting={[{ id: "id", desc: true }]}
table_url={GET_MACHINES_TABLE_LIST}

View File

@@ -8,10 +8,50 @@ import ShowArea from "./Actions/showArea";
import RowActions from "./RowActions";
import { missionRegions } from "@/core/utils/missionRegions";
import { missionTypes } from "@/core/utils/missionTypes";
import useProvinces from "@/lib/hooks/useProvince";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import useCities from "@/lib/hooks/useCities";
const TransportationList = () => {
const columns = useMemo(
() => [
const columns = useMemo(() => {
const dynamicColumns = {
header: "استان",
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: "کل کشور" },
...provinces.map((province) => ({
value: province.id,
label: province.name_fa,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
return (
<CustomSelectByDependency
{...props}
value={loadingProvinces ? "loading" : props.filterParameters.value}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ renderedCellValue, row }) => {
return <>{row.original.province_name}</>;
},
};
return [
{
accessorKey: "id",
header: "کد یکتا",
@@ -33,6 +73,63 @@ const TransportationList = () => {
grow: false,
size: 100,
},
...[dynamicColumns],
{
header: "شهر",
id: "city_id",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
dependencyId: "province_id",
grow: false,
size: 120,
ColumnSelectComponent: (props) => {
const { cityList, loadingCityList, errorCityList } = useCities(props.dependencyFieldValue.value);
const getColumnSelectOptions = useMemo(() => {
if (props.dependencyFieldValue.value === "") {
return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
}
if (loadingCityList) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorCityList) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "کل ادارات" },
...cityList.map((edare) => ({
value: edare.id,
label: edare.name_fa,
})),
];
}, [cityList, loadingCityList, errorCityList]);
return (
<CustomSelectByDependency
{...props}
value={
props.dependencyFieldValue?.value === ""
? "empty"
: loadingCityList
? "loading"
: props.filterParameters.value
}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ row }) => <>{row.original.city_name}</>,
},
{
accessorKey: "station_name",
header: "راهدارخانه",
id: "station_name",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
grow: false,
size: 100,
},
{
accessorKey: "explanation",
header: "موضوع",
@@ -151,9 +248,8 @@ const TransportationList = () => {
grow: false,
size: 100,
},
],
[]
);
];
}, []);
return (
<>

View File

@@ -0,0 +1,58 @@
import { Button, DialogActions, DialogContent, Stack } from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import CarCode from "@/core/components/CarCode";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import StyledForm from "@/core/components/StyledForm";
const schema = yup.object().shape({
machinesId: yup.object().required("کد خودرو الزامی است."),
});
const CreateViolationForm = ({ setOpenMachinesDialog, defaultValues, onSubmitBase }) => {
const {
control,
handleSubmit,
formState: { isSubmitting },
} = useForm({ defaultValues, resolver: yupResolver(schema), mode: "all" });
const handleOnSubmit = async (data) => {
await onSubmitBase(data);
};
return (
<>
<StyledForm onSubmit={handleSubmit(handleOnSubmit)}>
<DialogContent>
<Stack sx={{ minWidth: "200px" }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<CarCode
carCode={field.value}
setCarCode={(value) => field.onChange(value)}
error={error}
/>
);
}}
name={"machinesId"}
/>
</Stack>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "end" }}>
<Button
onClick={() => setOpenMachinesDialog(false)}
variant="outlined"
color="secondary"
size="large"
>
{"بستن"}
</Button>
<Button variant="contained" disabled={isSubmitting} size="large" type={"submit"}>
{"تایید"}
</Button>
</DialogActions>
</StyledForm>
</>
);
};
export default CreateViolationForm;

View File

@@ -0,0 +1,39 @@
import { DialogTitle } from "@mui/material";
import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog";
import useRequest from "@/lib/hooks/useRequest";
import { CREATE_VIOLATION } from "@/core/utils/routes";
import CreateViolationForm from "@/components/dashboard/roadMissions/violations/Actions/CreateViolation/Form/CreateForm";
const ViolationForm = ({ setOpenMachinesDialog, mutate, openMachinesDialog }) => {
const requestServer = useRequest({ notificationSuccess: true });
const defaultValues = {
machinesId: null,
};
const submitForm = async (result) => {
await requestServer(CREATE_VIOLATION, "post", {
data: {
machine_code: result.machinesId.machine_code,
},
})
.then((response) => {
setOpenMachinesDialog(false);
mutate();
})
.catch((error) => {});
};
return (
<>
<DialogHeader>
<DialogTitle>ثبت تخلف</DialogTitle>
</DialogHeader>
<CreateViolationForm
setOpenMachinesDialog={setOpenMachinesDialog}
defaultValues={defaultValues}
onSubmitBase={submitForm}
/>
</>
);
};
export default ViolationForm;

View File

@@ -0,0 +1,49 @@
import { AddCircle, Close } from "@mui/icons-material";
import { Button, Dialog, IconButton, Tooltip } from "@mui/material";
import { useState } from "react";
import ViolationForm from "./Form";
const CreateViolation = ({ mutate }) => {
const [openMachinesDialog, setOpenMachinesDialog] = useState(false);
return (
<>
<Tooltip title="ثبت تخلف" arrow placement="left">
<Button
size={"small"}
variant="contained"
color="primary"
startIcon={<AddCircle />}
onClick={() => setOpenMachinesDialog(true)}
>
ثبت تخلف
</Button>
</Tooltip>
<Dialog open={openMachinesDialog} fullWidth maxWidth="sm">
<IconButton
aria-label="close"
onClick={() => setOpenMachinesDialog(false)}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
zIndex: 50,
color: theme.palette.grey[500],
})}
>
<Close />
</IconButton>
{openMachinesDialog && (
<ViolationForm
openMachinesDialog={openMachinesDialog}
setOpenMachinesDialog={setOpenMachinesDialog}
mutate={mutate}
/>
)}
</Dialog>
</>
);
};
export default CreateViolation;

View File

@@ -0,0 +1,51 @@
import { EDIT_VIOLATION } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { DialogTitle } from "@mui/material";
import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog";
import EditViolationForm from "./EditViolationForm";
import moment from "jalali-moment";
const EditController = ({ rowId, mutate, setOpen }) => {
const requestServer = useRequest({ notificationSuccess: true });
const defaultData = {
start_date: null,
start_time: null,
end_date: null,
end_time: null,
};
const handleSubmit = async (result) => {
const payload = {};
if (result.start_date && result.start_time) {
payload.enter_time = `${result.start_date} ${moment(result.start_time).format("HH:mm")}`;
}
if (result.end_date && result.end_time) {
payload.exit_time = `${result.end_date} ${moment(result.end_time).format("HH:mm")}`;
}
await requestServer(`${EDIT_VIOLATION}/${rowId}`, "post", {
data: payload,
})
.then(() => {
mutate();
setOpen(false);
})
.catch(() => {});
};
return (
<>
<DialogHeader>
<DialogTitle>ثبت تردد</DialogTitle>
</DialogHeader>
<EditViolationForm
setOpenMachinesDialog={setOpen}
defaultValues={defaultData}
onSubmitBase={handleSubmit}
/>
</>
);
};
export default EditController;

View File

@@ -0,0 +1,95 @@
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import StyledForm from "@/core/components/StyledForm";
import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
import MissionDates from "./MissionDates";
import * as yup from "yup";
export const schema = yup
.object({
start_date: yup.string().nullable(),
start_time: yup.string().nullable(),
end_date: yup.string().nullable(),
end_time: yup.string().nullable(),
})
.test("valid-dates", function (values) {
if (!values) return false;
const hasStartDate = !!values.start_date;
const hasStartTime = !!values.start_time;
const hasEndDate = !!values.end_date;
const hasEndTime = !!values.end_time;
if (hasStartDate !== hasStartTime) {
return this.createError({
message: "تاریخ و زمان شروع باید هر دو وارد شوند",
});
}
if (hasEndDate !== hasEndTime) {
return this.createError({
message: "تاریخ و زمان پایان باید هر دو وارد شوند",
});
}
if (!((hasStartDate && hasStartTime) || (hasEndDate && hasEndTime))) {
return this.createError({
message: "حداقل تاریخ و زمان شروع یا پایان باید مشخص شود",
});
}
return true;
});
const EditViolationForm = ({ setOpenMachinesDialog, defaultValues, onSubmitBase }) => {
const {
control,
handleSubmit,
formState: { isSubmitting, errors },
} = useForm({ defaultValues, resolver: yupResolver(schema), mode: "all" });
const handleOnSubmit = async (data) => {
await onSubmitBase(data);
};
return (
<>
<StyledForm onSubmit={handleSubmit(handleOnSubmit)}>
<DialogContent dividers>
<Box sx={{ flex: 1 }}>
<Stack>
<Grid container spacing={2}>
<MissionDates control={control} />
</Grid>
{errors?.[""]?.message && (
<Box
sx={{
color: "error.main",
textAlign: "center",
mb: 2,
fontSize: 14,
}}
>
{errors?.[""]?.message}
</Box>
)}
</Stack>
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button
onClick={() => setOpenMachinesDialog(false)}
variant="outlined"
color="secondary"
size="large"
>
{"بستن"}
</Button>
<Button disabled={isSubmitting} variant="contained" size="large" type="submit">
ثبت
</Button>
</DialogActions>
</StyledForm>
</>
);
};
export default EditViolationForm;

View File

@@ -0,0 +1,99 @@
import MuiDatePicker from "@/core/components/MuiDatePicker";
import MuiTimePicker from "@/core/components/MuiTimePicker";
import { makeDateTime } from "@/core/utils/makeDateTime";
import { Grid } from "@mui/material";
import { Controller, useWatch } from "react-hook-form";
const MissionDates = ({ control }) => {
const start_date = useWatch({ control, name: "start_date" });
const end_date = useWatch({ control, name: "end_date" });
const now = new Date();
return (
<>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"start_date"}
render={({ field, fieldState: { error } }) => {
return (
<MuiDatePicker
name="start_date"
error={error}
value={field.value}
disableFuture={false}
maxDate={end_date ?? null}
placeholder={"تاریخ شروع ماموریت را وارد کنید"}
label={"تاریخ شروع ماموریت"}
setFieldValue={(name, value) => field.onChange(value || [])}
helperText={error ? error.message : null}
/>
);
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"start_time"}
render={({ field, fieldState: { error } }) => {
return (
<MuiTimePicker
name="start_time"
error={error}
value={field.value}
views={["hours"]}
placeholder={"زمان شروع ماموریت را وارد کنید"}
label={"زمان شروع ماموریت"}
setFieldValue={(name, value) => field.onChange(value || null)}
helperText={error ? error.message : null}
/>
);
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"end_date"}
render={({ field, fieldState: { error } }) => {
return (
<MuiDatePicker
name="end_date"
error={error}
value={field.value}
disableFuture={false}
minDate={start_date ? start_date : now}
placeholder={"تاریخ پایان ماموریت را وارد کنید"}
label={"تاریخ پایان ماموریت"}
setFieldValue={(name, value) => field.onChange(value || [])}
helperText={error ? error.message : null}
/>
);
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"end_time"}
render={({ field, fieldState: { error } }) => {
return (
<MuiTimePicker
name="end_time"
error={error}
value={field.value}
views={["hours"]}
placeholder={"زمان پایان ماموریت را وارد کنید"}
label={"زمان پایان ماموریت"}
setFieldValue={(name, value) => field.onChange(value || null)}
helperText={error ? error.message : null}
/>
);
}}
/>
</Grid>
</>
);
};
export default MissionDates;

View File

@@ -0,0 +1,51 @@
import CloseIcon from "@mui/icons-material/Close";
import { Dialog, IconButton, Tooltip } from "@mui/material";
import { useState } from "react";
import BookmarkAddIcon from "@mui/icons-material/BookmarkAdd";
import EditController from "./EditController";
const EditViolation = ({ mutate, row, rowId }) => {
const [open, setOpen] = useState(false);
return (
<>
<Tooltip title="ثبت تردد" arrow placement="right">
<IconButton
color="primary"
sx={{ textTransform: "unset", alignSelf: "center" }}
onClick={() => {
setOpen(true);
}}
>
<BookmarkAddIcon />
</IconButton>
</Tooltip>
<Dialog
fullWidth
open={open}
maxWidth={"xs"}
PaperProps={{
sx: {
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
},
}}
>
<IconButton
aria-label="close"
onClick={() => setOpen(false)}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
zIndex: 50,
color: theme.palette.grey[500],
})}
>
<CloseIcon />
</IconButton>
{open && <EditController setOpen={setOpen} rowId={rowId} row={row} mutate={mutate} />}
</Dialog>
</>
);
};
export default EditViolation;

View File

@@ -1,7 +1,13 @@
import CreateWithoutProcess from "../Actions/CreateWithoutProcess";
import EditViolation from "../Actions/EditViolation";
const RowActions = ({ row, mutate }) => {
const canEdit = row.original.status === 0 && row.original.type === 1;
return canEdit && <CreateWithoutProcess row={row} mutate={mutate} />;
return (
<>
{!row.original.mileage && <EditViolation row={row} mutate={mutate} rowId={row.getValue("id")} />}
{canEdit && <CreateWithoutProcess row={row} mutate={mutate} />}
</>
);
};
export default RowActions;

View File

@@ -0,0 +1,11 @@
import { Stack } from "@mui/material";
import CreateViolation from "./Actions/CreateViolation";
const Toolbar = ({ mutate }) => {
return (
<Stack direction={"row"} spacing={1}>
<CreateViolation mutate={mutate} />
</Stack>
);
};
export default Toolbar;

View File

@@ -5,6 +5,7 @@ import { Box } from "@mui/material";
import moment from "jalali-moment";
import { useMemo } from "react";
import RowActions from "./RowActions";
import Toolbar from "./Toolbar";
const ViolationsList = () => {
const columns = useMemo(
@@ -68,6 +69,7 @@ const ViolationsList = () => {
<DataTableWithAuth
need_filter={true}
columns={columns}
TableToolbar={Toolbar}
sorting={[{ id: "id", desc: true }]}
table_url={GET_ROAD_MISSIONS_VIOLATIONS_LIST}
page_name={"roadMissionsOperator"}

View File

@@ -157,7 +157,12 @@ export const pageMenu = [
icon: <RemoveRoadIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/road-missions/violation",
// badges: [{ key: "mission.control" }], // TODO: add badge and permission
permissions: ["all"],
permissions: [
"manage-violation-city",
"manage-violation-country",
"manage-violation-province",
"manage-violation-station",
],
},
// {
// id: "roadMissionsReports",

View File

@@ -156,7 +156,12 @@ export const pageMenuDev = [
icon: <RemoveRoadIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/dashboard/road-missions/violation",
// badges: [{ key: "mission.control" }], // TODO: add badge and permission
permissions: ["all"],
permissions: [
"manage-violation-city",
"manage-violation-country",
"manage-violation-province",
"manage-violation-station",
],
},
// {
// id: "roadMissionsReports",

View File

@@ -231,6 +231,8 @@ export const FINISH_MISSION = api + "/api/v3/missions/control_unit/finish";
export const GET_ROAD_MISSIONS_VIOLATIONS_LIST = api + "/api/v3/missions/violation_management";
export const GET_MISSION_PROVINCE_ACTIVITY_REPORT = api + "/api/v3/missions/report/country_activity";
export const GET_MISSION_CITY_ACTIVITY_REPORT = api + "/api/v3/missions/report/country_activity";
export const CREATE_VIOLATION = api + "/api/v3/missions/violation_management";
export const EDIT_VIOLATION = api + "/api/v3/missions/violation_management";
export const GET_MACHINES_TABLE_LIST = api + "/api/v3/cmms_machines";