Merge branch 'release/v1.7.3'

This commit is contained in:
AmirHossein Mahmoodi
2025-09-07 10:13:09 +03:30
64 changed files with 3201 additions and 713 deletions

View File

@@ -1,4 +1,4 @@
HOST="rms.witel.ir"
NEXT_PUBLIC_VERSION="1.7.0"
NEXT_PUBLIC_VERSION="1.7.3"
NEXT_PUBLIC_API_URL="https://rms.witel.ir"
NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map"

View File

@@ -3,16 +3,19 @@ import StyledForm from "@/core/components/StyledForm";
import validateNationalCode from "@/core/utils/nationalCodeValidation";
import { yupResolver } from "@hookform/resolvers/yup";
import { Beenhere, ExitToApp } from "@mui/icons-material";
import { Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
import { Button, DialogActions, DialogContent, Grid, Stack, TextField } from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup";
const validationSchema = object({
name: string().required("وارد کردن نام فارسی الزامیست!"),
name: string().required("وارد کردن نام و نام خانوادگی الزامیست!"),
code: string()
.required("لطفا کد ملی را وارد کنید!!!")
.test("max", "کد ملی باید شامل 10 رقم باشد", (value) => value.toString().length === 10)
.test("validation", "کد ملی صحیح نمی باشد", (value) => validateNationalCode(value)),
phone_number: string()
.matches(/^09\d{9}$/, "شماره موبایل باید با 09 شروع شده و 11 رقم باشد")
.required("لطفا تلفن همراه را وارد کنید!!!"),
});
const CreateFormContent = ({ setOpen, defaultValues, onSubmitBase }) => {
@@ -44,10 +47,10 @@ const CreateFormContent = ({ setOpen, defaultValues, onSubmitBase }) => {
<PersianTextField
fullWidth
size="small"
label="نام فارسی"
label="نام و نام خانوادگی"
value={value}
onChange={onChange}
placeholder="نام فارسی را وارد کنید"
placeholder="نام و نام خانوادگی را وارد کنید"
error={!!error}
helperText={error ? error.message : null}
variant="outlined"
@@ -82,6 +85,43 @@ const CreateFormContent = ({ setOpen, defaultValues, onSubmitBase }) => {
</Grid>
</Grid>
</Stack>
<Stack>
<Grid spacing={2}>
<Grid item xs={12} sm={6}>
<Controller
name="phone_number"
control={control}
render={({ field, fieldState: { error } }) => (
<TextField
value={field.value}
variant="outlined"
name="phone_number"
type={"tel"}
inputProps={{
inputMode: "number",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
const inputValue = event.target.value;
if (isNaN(Number(inputValue))) {
return;
}
field.onChange(inputValue);
}}
label="شماره همراه"
placeholder={"شماره همراه را وارد کنید"}
fullWidth
size="small"
error={error}
helperText={error?.message}
InputLabelProps={{ shrink: true }}
/>
)}
/>
</Grid>
</Grid>
</Stack>
</Stack>
</DialogContent>
<DialogActions>

View File

@@ -10,11 +10,13 @@ const CreateForm = ({ open, setOpen, mutate }) => {
const defaultValues = {
name: "",
code: "",
phone_number: "",
};
const onSubmit = async (result) => {
const formData = new FormData();
formData.append("name", result.name);
formData.append("code", result.code);
formData.append("mobile", result.phone_number);
await requestServer(CREATE_RAHDARAN_ITEM, "post", {
data: formData,

View File

@@ -10,11 +10,13 @@ const EditController = ({ rowId, mutate, setOpen, row }) => {
const defaultData = {
name: row.original?.name || "",
code: row.original?.code || "",
phone_number: row.original?.mobile || "",
};
const handleSubmit = async (result) => {
const formData = new FormData();
formData.append("name", result.name);
formData.append("code", result.code);
formData.append("mobile", result.phone_number);
await requestServer(`${UPDATE_RAHDARAN_ITEM}/${rowId}`, "post", {
data: formData,

View File

@@ -43,6 +43,17 @@ const RahdaranList = () => {
grow: false,
size: 100,
},
{
accessorKey: "mobile",
header: "شماره همراه",
id: "mobile",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
[]
);

View File

@@ -1,20 +1,20 @@
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { array, mixed, number, object, string } from "yup";
import { useTheme } from "@emotion/react";
import { Box, Button, DialogActions, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material";
import StyledForm from "@/core/components/StyledForm";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import FileCopyIcon from "@mui/icons-material/FileCopy";
import InfoIcon from "@mui/icons-material/Info";
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
import BeenhereIcon from "@mui/icons-material/Beenhere";
import GetItemInfo from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo";
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 StyledForm from "@/core/components/StyledForm";
import { useTheme } from "@emotion/react";
import { yupResolver } from "@hookform/resolvers/yup";
import BeenhereIcon from "@mui/icons-material/Beenhere";
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
import FileCopyIcon from "@mui/icons-material/FileCopy";
import InfoIcon from "@mui/icons-material/Info";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
import { Box, Button, DialogActions, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material";
import { format } from "date-fns";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { mixed, number, object, string } from "yup";
function TabPanel(props) {
const { children, value, index } = props;
@@ -29,24 +29,7 @@ const validationSchema = object({
item_id: number().required("نوع آیتم را مشخص کنید!"),
sub_item_id: number().required("اقدام انجام شده را مشخص کنید!"),
amount: string().required("وارد کردن مقدار الزامیست!"),
cmms_machines: array()
.test("cmms-machines-conditional", "وارد کردن کد خودرو الزامیست!", function (value) {
const { is_gasht } = this.options.context;
if (is_gasht) {
return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد
}
return Array.isArray(value) && value.length > 0; // حداقل یک کد باید وجود داشته باشد
})
.min(1, "حداقل یک کد خودرو باید وارد شود!"), // پیام خطا برای زمانی که شرط تست برقرار باشد
rahdaran_id: array()
.test("rahdaran-id-conditional", "وارد کردن کد راهداران الزامیست!", function (value) {
const { is_gasht } = this.options.context;
if (is_gasht) {
return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد
}
return Array.isArray(value) && value.length > 0; // حداقل یک کد باید وجود داشته باشد
})
.min(1, "حداقل یک کد راهدار باید وارد شود!"), // پیام خطا برای زمانی که شرط تست برقرار باشد
mission_id: object().required("ماموریت را مشخص کنید!"),
activity_time: string().test("activity-time-conditional", "لطفا زمان فعالیت را انتخاب کنید!", function (value) {
const { is_gasht } = this.options.context; // دسترسی به context
if (is_gasht) {
@@ -110,10 +93,9 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
start_point: "",
end_point: "",
...(!is_gasht && {
mission_id: null,
activity_time: "",
activity_date: "",
cmms_machines: [],
rahdaran_id: [],
}),
};
@@ -139,8 +121,6 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
"activity_date",
"before_image",
"after_image",
"cmms_machines",
"rahdaran_id",
"start_point",
"end_point",
];
@@ -195,20 +175,9 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
}
if (!is_gasht) {
result.rahdaran_id.forEach((rahdar, index) => {
result[`rahdaran_id[${index}]`] = rahdar.id;
});
delete result.rahdaran_id;
result.cmms_machines.forEach((cmms_machine, index) => {
result[`machines_id[${index}]`] = cmms_machine.id;
});
delete result.cmms_machines;
result[`activity_time`] = format(new Date(result.activity_time), "HH:mm");
result.mission_id = result.mission_id.id;
} else {
delete result.rahdaran_id;
delete result.cmms_machines;
delete result.activity_date;
delete result.activity_time;
}

View File

@@ -1,173 +1,173 @@
import { FormControl, FormHelperText, Grid } from "@mui/material";
import UploadSystem from "@/core/components/UploadSystem";
import { Controller } from "react-hook-form";
import React, { useEffect, useState } from "react";
const ImageUpload = ({ control, setValue, errors, getValues, beforeImage = null, afterImage = null }) => {
const [beforeImg, setBeforeImg] = useState(beforeImage ? beforeImage : null);
const [beforeFileType, setBeforeFileType] = useState(beforeImage ? "image/" : null);
const [beforeFileName, setBeforeFileName] = useState(null);
const [showBeforeImage, setShowBeforeImage] = useState(!beforeImage);
const [afterImg, setAfterImg] = useState(afterImage ? afterImage : null);
const [afterFileType, setAfterFileType] = useState(afterImage ? "image/" : null);
const [afterFileName, setAfterFileName] = useState(null);
const [showAfterImage, setShowAfterImage] = useState(!afterImage);
useEffect(() => {
const beforeImage = getValues("before_image");
const afterImage = getValues("after_image");
if (beforeImage) {
setShowBeforeImage(false);
if (typeof beforeImage === "string") {
setBeforeImg(beforeImage);
setBeforeFileType("image/");
} else if (beforeImage instanceof File) {
setBeforeImg(URL.createObjectURL(beforeImage));
setBeforeFileType(beforeImage.type);
}
}
if (afterImage) {
setShowAfterImage(false);
if (typeof afterImage === "string") {
setAfterImg(afterImage);
setAfterFileType("image/");
} else if (afterImage instanceof File) {
setAfterImg(URL.createObjectURL(afterImage));
setAfterFileType(afterImage.type);
}
}
}, [getValues]);
const handleBeforeFileChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setBeforeImg(URL.createObjectURL(uploadedFile));
setBeforeFileType(fileType);
setBeforeFileName(fileName);
setValue("before_image", uploadedFile);
setShowBeforeImage(false);
}
};
const handleAfterFileChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setAfterImg(URL.createObjectURL(uploadedFile));
setAfterFileType(fileType);
setAfterFileName(fileName);
setValue("after_image", uploadedFile);
setShowAfterImage(false);
}
};
return (
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Controller
name="before_image"
control={control}
render={({ field: { value, onChange } }) => {
return (
<FormControl
error={errors.before_image}
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<FormHelperText
sx={{
fontSize: 12,
border: 1,
borderBottom: 0,
borderColor: "divider",
px: 2,
py: 0.5,
borderRadius: 2,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
}}
id="before_image"
>
عکس قبل از اقدام
</FormHelperText>
<UploadSystem
selectedImage={beforeImg}
handleUploadChange={handleBeforeFileChange}
fileType={beforeFileType}
fileName={beforeFileName}
setSelectedImage={setBeforeImg}
imageSize={[250, 150]}
setShowAddIcon={setShowBeforeImage}
showAddIcon={showBeforeImage}
/>
<FormHelperText id="before_image">
{errors.before_image ? errors.before_image.message : null}
</FormHelperText>
</FormControl>
);
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Controller
name="after_image"
control={control}
render={({ field: { value, onChange } }) => {
return (
<FormControl
error={errors.before_image}
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<FormHelperText
sx={{
fontSize: 12,
border: 1,
borderBottom: 0,
borderColor: "divider",
px: 2,
py: 0.5,
borderRadius: 2,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
}}
id="before_image"
>
عکس بعد از اقدام
</FormHelperText>
<UploadSystem
selectedImage={afterImg}
handleUploadChange={handleAfterFileChange}
fileType={afterFileType}
fileName={afterFileName}
setSelectedImage={setAfterImg}
imageSize={[250, 150]}
setShowAddIcon={setShowAfterImage}
showAddIcon={showAfterImage}
/>
<FormHelperText id="after_image">
{errors.after_image ? errors.after_image.message : null}
</FormHelperText>
</FormControl>
);
}}
/>
</Grid>
</Grid>
);
};
export default ImageUpload;
import { FormControl, FormHelperText, Grid } from "@mui/material";
import UploadSystem from "@/core/components/UploadSystem";
import { Controller } from "react-hook-form";
import React, { useEffect, useState } from "react";
const ImageUpload = ({ control, setValue, errors, getValues, beforeImage = null, afterImage = null }) => {
const [beforeImg, setBeforeImg] = useState(beforeImage ? beforeImage : null);
const [beforeFileType, setBeforeFileType] = useState(beforeImage ? "image/" : null);
const [beforeFileName, setBeforeFileName] = useState(null);
const [showBeforeImage, setShowBeforeImage] = useState(!beforeImage);
const [afterImg, setAfterImg] = useState(afterImage ? afterImage : null);
const [afterFileType, setAfterFileType] = useState(afterImage ? "image/" : null);
const [afterFileName, setAfterFileName] = useState(null);
const [showAfterImage, setShowAfterImage] = useState(!afterImage);
useEffect(() => {
const beforeImage = getValues("before_image");
const afterImage = getValues("after_image");
if (beforeImage) {
setShowBeforeImage(false);
if (typeof beforeImage === "string") {
setBeforeImg(beforeImage);
setBeforeFileType("image/");
} else if (beforeImage instanceof File) {
setBeforeImg(URL.createObjectURL(beforeImage));
setBeforeFileType(beforeImage.type);
}
}
if (afterImage) {
setShowAfterImage(false);
if (typeof afterImage === "string") {
setAfterImg(afterImage);
setAfterFileType("image/");
} else if (afterImage instanceof File) {
setAfterImg(URL.createObjectURL(afterImage));
setAfterFileType(afterImage.type);
}
}
}, [getValues]);
const handleBeforeFileChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setBeforeImg(URL.createObjectURL(uploadedFile));
setBeforeFileType(fileType);
setBeforeFileName(fileName);
setValue("before_image", uploadedFile);
setShowBeforeImage(false);
}
};
const handleAfterFileChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setAfterImg(URL.createObjectURL(uploadedFile));
setAfterFileType(fileType);
setAfterFileName(fileName);
setValue("after_image", uploadedFile);
setShowAfterImage(false);
}
};
return (
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Controller
name="before_image"
control={control}
render={({ field: { value, onChange } }) => {
return (
<FormControl
error={errors.before_image}
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<FormHelperText
sx={{
fontSize: 12,
border: 1,
borderBottom: 0,
borderColor: "divider",
px: 2,
py: 0.5,
borderRadius: 2,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
}}
id="before_image"
>
عکس قبل از اقدام
</FormHelperText>
<UploadSystem
selectedImage={beforeImg}
handleUploadChange={handleBeforeFileChange}
fileType={beforeFileType}
fileName={beforeFileName}
setSelectedImage={setBeforeImg}
imageSize={[250, 150]}
setShowAddIcon={setShowBeforeImage}
showAddIcon={showBeforeImage}
/>
<FormHelperText id="before_image">
{errors.before_image ? errors.before_image.message : null}
</FormHelperText>
</FormControl>
);
}}
/>
</Grid>
<Grid item xs={12} md={6}>
<Controller
name="after_image"
control={control}
render={({ field: { value, onChange } }) => {
return (
<FormControl
error={errors.before_image}
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<FormHelperText
sx={{
fontSize: 12,
border: 1,
borderBottom: 0,
borderColor: "divider",
px: 2,
py: 0.5,
borderRadius: 2,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
}}
id="before_image"
>
عکس بعد از اقدام
</FormHelperText>
<UploadSystem
selectedImage={afterImg}
handleUploadChange={handleAfterFileChange}
fileType={afterFileType}
fileName={afterFileName}
setSelectedImage={setAfterImg}
imageSize={[250, 150]}
setShowAddIcon={setShowAfterImage}
showAddIcon={showAfterImage}
/>
<FormHelperText id="after_image">
{errors.after_image ? errors.after_image.message : null}
</FormHelperText>
</FormControl>
);
}}
/>
</Grid>
</Grid>
);
};
export default ImageUpload;

View File

@@ -0,0 +1,23 @@
import { Done } from "@mui/icons-material";
import { Button } from "@mui/material";
const Allocate = ({ row, setMission, setOpenDialog }) => {
const handleClick = () => {
setMission(row.original);
setOpenDialog(false);
};
return (
<>
<Button
color="primary"
sx={{ textTransform: "unset", alignSelf: "center" }}
onClick={handleClick}
startIcon={<Done />}
>
انتخاب
</Button>
</>
);
};
export default Allocate;

View File

@@ -0,0 +1,6 @@
import Allocate from "./Allocate";
const RowActions = ({ row, setMission, setOpenDialog }) => {
return <Allocate row={row} setMission={setMission} setOpenDialog={setOpenDialog} />;
};
export default RowActions;

View File

@@ -0,0 +1,111 @@
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_ROAD_MISSIONS_OPERATOR_LIST } from "@/core/utils/routes";
import { Box } from "@mui/material";
import { useMemo } from "react";
import RowActions from "./RowActions";
import moment from "jalali-moment";
const MissionList = ({ setMission, setOpenDialog }) => {
const columns = useMemo(
() => [
{
accessorKey: "id",
header: "کد یکتا",
id: "id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "explanation",
header: "موضوع",
id: "explanation",
enableColumnFilter: false,
datatype: "numeric",
filterMode: "equals",
grow: false,
size: 100,
},
{
accessorKey: "zone",
header: "محدوده",
id: "zone",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
sortDescFirst: true,
grow: false,
size: 100,
columnSelectOption: () => {
return missionRegions.map((region) => ({
value: region.id,
label: region.name_fa,
}));
},
Cell: ({ row }) => row.original.zone_fa,
},
{
accessorFn: (row) =>
row.type == 1
? moment(row.start_date).locale("fa").format("HH:mm | yyyy/MM/DD")
: moment(row.start_date).locale("fa").format("yyyy/MM/DD"),
header: "تاریخ شروع",
id: "start_date",
enableColumnFilter: true,
datatype: "date",
filterMode: "between",
grow: false,
size: 100,
},
{
accessorFn: (row) =>
row.type == 1
? moment(row.end_date).locale("fa").format("HH:mm | yyyy/MM/DD")
: moment(row.end_date).locale("fa").format("yyyy/MM/DD"),
header: "تاریخ پایان",
id: "end_date",
enableColumnFilter: true,
datatype: "date",
filterMode: "between",
grow: false,
size: 100,
},
{
accessorKey: "end_point",
header: "مقصد",
id: "end_point",
enableColumnFilter: false,
datatype: "numeric",
filterMode: "equals",
grow: false,
size: 100,
},
],
[]
);
return (
<>
<Box sx={{ mt: 3, p: 1 }}>
<DataTableWithAuth
need_filter={true}
table_title="لیست ماموریت ها"
columns={columns}
sorting={[{ id: "id", desc: true }]}
table_url={GET_ROAD_MISSIONS_OPERATOR_LIST}
page_name={"roadItemsOperator"}
table_name={"roadItemsOperatorListMissionList"}
enableRowActions
positionActionsColumn={"first"}
RowActions={(props) => (
<RowActions {...props} setMission={setMission} setOpenDialog={setOpenDialog} />
)}
/>
</Box>
</>
);
};
export default MissionList;

View File

@@ -0,0 +1,38 @@
import { Close, Edit } from "@mui/icons-material";
import { Button, Dialog, IconButton } from "@mui/material";
import { useState } from "react";
import MissionList from "./MissionsList";
const MissionsDialog = ({ setMission, mode }) => {
const [open, setOpen] = useState(false);
return (
<>
{mode == "edit" ? (
<IconButton onClick={() => setOpen(true)}>
<Edit />
</IconButton>
) : (
<Button onClick={() => setOpen(true)} variant="contained" fullWidth>
ماموریت انتخاب کنید
</Button>
)}
<Dialog open={open} fullWidth maxWidth="sm">
<IconButton
aria-label="close"
onClick={() => setOpen(false)}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
zIndex: 50,
color: theme.palette.grey[500],
})}
>
<Close />
</IconButton>
{open && <MissionList setMission={setMission} setOpenDialog={setOpen} />}
</Dialog>
</>
);
};
export default MissionsDialog;

View File

@@ -1,43 +1,43 @@
import { Box, Chip, Divider, Grid, Typography } from "@mui/material";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import FileCopyIcon from "@mui/icons-material/FileCopy";
const PreviousStatesInfo = ({ itemsList, subItemsList }) => {
return (
<Grid container spacing={2} sx={{ alignItems: "center", justifyContent: "space-around" }}>
<Grid item xs={12} sm={12} sx={{ display: "flex", alignItems: "center" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Chip
icon={<InsertDriveFileIcon />}
label={
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.700" }}>
آیتم انتخاب شده
</Typography>
}
/>
</Box>
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.800" }}>
{itemsList.name || "هیچ آیتمی انتخاب نشده است"}
</Typography>
</Grid>
<Grid item xs={12} sm={12} sx={{ display: "flex", alignItems: "center" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Chip
icon={<FileCopyIcon />}
label={
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.700" }}>
اقدام انجام شده
</Typography>
}
/>
</Box>
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.800" }}>
{subItemsList?.name || "هیچ اقدامی انتخاب نشده است"}
</Typography>
</Grid>
</Grid>
);
};
export default PreviousStatesInfo;
import { Box, Chip, Divider, Grid, Typography } from "@mui/material";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import FileCopyIcon from "@mui/icons-material/FileCopy";
const PreviousStatesInfo = ({ itemsList, subItemsList }) => {
return (
<Grid container spacing={2} sx={{ alignItems: "center", justifyContent: "space-around" }}>
<Grid item xs={12} sm={12} sx={{ display: "flex", alignItems: "center" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Chip
icon={<InsertDriveFileIcon />}
label={
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.700" }}>
آیتم انتخاب شده
</Typography>
}
/>
</Box>
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.800" }}>
{itemsList.name || "هیچ آیتمی انتخاب نشده است"}
</Typography>
</Grid>
<Grid item xs={12} sm={12} sx={{ display: "flex", alignItems: "center" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Chip
icon={<FileCopyIcon />}
label={
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.700" }}>
اقدام انجام شده
</Typography>
}
/>
</Box>
<Divider sx={{ flex: 1, mx: 2 }} />
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.800" }}>
{subItemsList?.name || "هیچ اقدامی انتخاب نشده است"}
</Typography>
</Grid>
</Grid>
);
};
export default PreviousStatesInfo;

View File

@@ -1,124 +1,109 @@
import { Grid, Stack } from "@mui/material";
import ImageUpload from "./ImageUpload";
import MuiDatePicker from "@/core/components/MuiDatePicker";
import MuiTimePicker from "@/core/components/MuiTimePicker";
import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
import { Controller } from "react-hook-form";
import CarCode from "@/core/components/CarCode";
import RahdarCode from "@/core/components/RahdarCode";
import PreviousStatesInfo from "./PreviousStatesInfo";
import NumberField from "@/core/components/NumberField";
const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues, is_gasht }) => {
return (
<Stack spacing={3}>
<Stack>
<PreviousStatesInfo itemsList={itemsList} subItemsList={subItemsList} />
</Stack>
<Stack>
<NumberField
{...register("amount")}
label={`مقدار (${subItemsList.unit})`}
error={!!errors.amount}
value={watch("amount")}
type="text"
size={"small"}
unit={subItemsList.unit}
placeholder={`مقدار را وارد کنید`}
inputProps={{
inputMode: "number",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
if (!isNaN(event.target.value)) {
setValue("amount", event.target.value);
} else {
setValue("amount", watch("amount"));
}
}}
helperText={errors.amount ? errors.amount.message : null}
variant="outlined"
fullWidth
/>
</Stack>
{!is_gasht && (
<Stack>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<CarCode
carCode={field.value || []}
setCarCode={(value) => field.onChange(value || [])}
error={error}
multiple={true}
/>
);
}}
name={"cmms_machines"}
/>
</Stack>
)}
{!is_gasht && (
<Stack>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<RahdarCode
rahdarsCode={field.value || []}
setRahdarsCode={(value) => field.onChange(value || [])}
error={error}
/>
);
}}
name={"rahdaran_id"}
/>
</Stack>
)}
<Stack>
{subItemsList.needs_image ? (
<ImageUpload setValue={setValue} control={control} errors={errors} getValues={getValues} />
) : null}
</Stack>
{!is_gasht ? (
<Stack>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<MuiDatePicker
name="activity_date"
error={errors.activity_date ? errors.activity_date.message : null}
value={watch("activity_date")}
placeholder={"تاریخ فعالیت را وارد کنید"}
label={"تاریخ فعالیت"}
setFieldValue={setValue}
helperText={errors.activity_date ? errors.activity_date.message : null}
/>
</Grid>
<Grid item xs={12} md={6}>
<MuiTimePicker
value={watch("activity_time")}
name="activity_time"
error={errors.activity_time ? errors.activity_time.message : null}
placeholder={"زمان فعالیت را وارد کنید"}
label={"زمان فعالیت"}
setFieldValue={setValue}
helperText={errors.activity_time ? errors.activity_time.message : null}
/>
</Grid>
</Grid>
</Stack>
) : null}
<Stack>
{subItemsList?.needs_end_point === 1 ? (
<MapInfoTwoMarker setValue={setValue} errors={errors} />
) : (
<MapInfoOneMarker setValue={setValue} errors={errors} />
)}
</Stack>
</Stack>
);
};
export default GetItemInfo;
import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
import MuiDatePicker from "@/core/components/MuiDatePicker";
import MuiTimePicker from "@/core/components/MuiTimePicker";
import NumberField from "@/core/components/NumberField";
import { Chip, Divider, FormHelperText, Grid, Stack, Typography } from "@mui/material";
import ImageUpload from "./ImageUpload";
import MissionsDialog from "./MissionsDialog";
import PreviousStatesInfo from "./PreviousStatesInfo";
import { Controller } from "react-hook-form";
const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues, is_gasht }) => {
return (
<Stack spacing={3}>
<Stack>
<PreviousStatesInfo itemsList={itemsList} subItemsList={subItemsList} />
</Stack>
<Controller
name="mission_id"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => {
return value ? (
<Stack spacing={1}>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Chip label={value ? `کد ماموریت: ${value.id}` : "ماموریت انتخاب نشده"} />
<Divider sx={{ flex: 1 }} />
<MissionsDialog setMission={(m) => onChange(m)} mode={"edit"} />
</Stack>
<FormHelperText error={!!error}>{error ? error.message : null}</FormHelperText>
</Stack>
) : (
<Stack spacing={1}>
<MissionsDialog setMission={(m) => onChange(m)} />
<FormHelperText error={!!error}>{error ? error.message : null}</FormHelperText>
</Stack>
);
}}
/>
<Stack>
<NumberField
{...register("amount")}
label={`مقدار (${subItemsList.unit})`}
error={!!errors.amount}
value={watch("amount")}
type="text"
size={"small"}
unit={subItemsList.unit}
placeholder={`مقدار را وارد کنید`}
inputProps={{
inputMode: "number",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
if (!isNaN(event.target.value)) {
setValue("amount", event.target.value);
} else {
setValue("amount", watch("amount"));
}
}}
helperText={errors.amount ? errors.amount.message : null}
variant="outlined"
fullWidth
/>
</Stack>
{!is_gasht ? (
<Stack>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<MuiDatePicker
name="activity_date"
error={errors.activity_date ? errors.activity_date.message : null}
value={watch("activity_date")}
placeholder={"تاریخ فعالیت را وارد کنید"}
label={"تاریخ فعالیت"}
setFieldValue={setValue}
helperText={errors.activity_date ? errors.activity_date.message : null}
/>
</Grid>
<Grid item xs={12} md={6}>
<MuiTimePicker
value={watch("activity_time")}
name="activity_time"
error={errors.activity_time ? errors.activity_time.message : null}
placeholder={"زمان فعالیت را وارد کنید"}
label={"زمان فعالیت"}
setFieldValue={setValue}
helperText={errors.activity_time ? errors.activity_time.message : null}
/>
</Grid>
</Grid>
</Stack>
) : null}
<Stack>
{subItemsList.needs_image ? (
<ImageUpload setValue={setValue} control={control} errors={errors} getValues={getValues} />
) : null}
</Stack>
<Stack>
{subItemsList?.needs_end_point === 1 ? (
<MapInfoTwoMarker setValue={setValue} errors={errors} />
) : (
<MapInfoOneMarker setValue={setValue} errors={errors} />
)}
</Stack>
</Stack>
);
};
export default GetItemInfo;

View File

@@ -219,60 +219,6 @@ const OperatorList = () => {
);
},
},
{
header: "خودرو ها",
id: "cmmsMachines__machine_code",
enableColumnFilter: true,
datatype: "text",
filterMode: "contains",
enableSorting: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue, row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<MachinesCodeDialog rowId={row.original.id} />
</Stack>
);
},
},
{
header: "راهداران",
id: "rahdaran__code",
enableColumnFilter: true,
enableSorting: false,
datatype: "text",
filterMode: "contains",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue, row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<RahdaranDialog rowId={row.original.id} />
</Stack>
);
},
},
{
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ فعالیت", // Start Date

View File

@@ -1,36 +1,15 @@
import { Button, DialogActions, DialogContent, Stack } from "@mui/material";
import ImageUpload from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload";
import NumberField from "@/core/components/NumberField";
import { Controller, useForm } from "react-hook-form";
import CarCode from "@/core/components/CarCode";
import RahdarCode from "@/core/components/RahdarCode";
import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
import NumberField from "@/core/components/NumberField";
import StyledForm from "@/core/components/StyledForm";
import React from "react";
import { yupResolver } from "@hookform/resolvers/yup";
import { array, mixed, object, string } from "yup";
import { Button, DialogActions, DialogContent, Stack } from "@mui/material";
import { useForm } from "react-hook-form";
import { mixed, object, string } from "yup";
import ImageUpload from "../../Actions/ObservedGashtCreate/RowActions/ImageUpload";
const validationSchema = object({
amount: string().required("وارد کردن مقدار الزامیست!"),
cmms_machines: array()
.test("cmms-machines-conditional", "وارد کردن کد خودرو الزامیست!", function (value) {
const { is_gasht } = this.options.context;
if (is_gasht) {
return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد
}
return Array.isArray(value) && value.length > 0; // حداقل یک کد باید وجود داشته باشد
})
.min(1, "حداقل یک کد خودرو باید وارد شود!"), // پیام خطا برای زمانی که شرط تست برقرار باشد
rahdaran_id: array()
.test("rahdaran-id-conditional", "وارد کردن کد راهداران الزامیست!", function (value) {
const { is_gasht } = this.options.context;
if (is_gasht) {
return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد
}
return Array.isArray(value) && value.length > 0; // حداقل یک کد باید وجود داشته باشد
})
.min(1, "حداقل یک کد راهدار باید وارد شود!"),
before_image: mixed()
.nullable()
.test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
@@ -124,42 +103,6 @@ const EditFormCreate = ({ defaultData, subItem, onSubmitBase, defaultValues, set
fullWidth
/>
</Stack>
{!is_gasht && (
<Stack>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<CarCode
carCode={field.value || []}
setCarCode={(value) => field.onChange(value || [])}
inputValueDefault={defaultData?.cmms_machines}
multiple={true}
error={error} // اگر خطا وجود داشته باشد
/>
);
}}
name={"cmms_machines"}
/>
</Stack>
)}
{!is_gasht && (
<Stack>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<RahdarCode
rahdarsCode={field.value || []}
setRahdarsCode={(value) => field.onChange(value || [])}
error={error}
/>
);
}}
name={"rahdaran_id"}
/>
</Stack>
)}
<Stack>
{subItem.needs_end_point === 1 ? (
<MapInfoTwoMarker

View File

@@ -8,7 +8,7 @@ const Toolbar = ({ table, filterData, mutate }) => {
<Stack direction={"row"} spacing={1}>
<PrintExcel table={table} filterData={filterData} />
<OperatorCreate mutate={mutate} />
<ObservedGashtCreate mutate={mutate} />
{/* <ObservedGashtCreate mutate={mutate} /> */}
</Stack>
);
};

View File

@@ -316,60 +316,6 @@ const SupervisorList = () => {
);
},
},
{
header: "خودرو ها",
id: "cmmsMachines__machine_code",
enableColumnFilter: true,
datatype: "text",
filterMode: "contains",
enableSorting: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue, row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<MachinesCodeDialog rowId={row.original.id} />
</Stack>
);
},
},
{
header: "راهداران",
id: "rahdaran__code",
enableColumnFilter: true,
enableSorting: false,
datatype: "text",
filterMode: "contains",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue, row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<RahdaranDialog rowId={row.original.id} />
</Stack>
);
},
},
{
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ فعالیت", // Start Date

View File

@@ -102,6 +102,17 @@ const ControlList = () => {
);
},
},
{
accessorFn: (row) =>
row.start_time ? moment(row.start_time).locale("fa").format("HH:mm | yyyy/MM/DD") : null,
header: "تاریخ خروج",
id: "start_time",
enableColumnFilter: true,
datatype: "date",
filterMode: "between",
grow: false,
size: 100,
},
],
[]
);

View File

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

View File

@@ -0,0 +1,129 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import L from "leaflet";
import { FeatureGroup, useMap } from "react-leaflet";
import "leaflet-draw";
const DrawBound = ({ control, controlDispach, bound, setBound }) => {
const map = useMap();
const featureRef = useRef(null);
const [area, setArea] = useState();
const drawControlRef = useRef(null);
const safeFlyToBounds = (layer) => {
if (!layer || !map) return;
try {
const latLngs = layer.getLatLngs();
map.flyToBounds(latLngs, {
paddingTopLeft: [20, 35],
paddingBottomRight: [20, 55],
});
} catch (e) {
console.warn("flyToBounds failed:", e);
}
};
const safeFitBounds = (layer) => {
if (!layer || !map) return;
try {
const latLngs = layer.getLatLngs();
map.fitBounds(latLngs, {
paddingTopLeft: [20, 35],
paddingBottomRight: [20, 55],
});
} catch (e) {
console.warn("fitBounds failed:", e);
}
};
const bindEditEvent = (layer) => {
if (!layer) return;
layer.on("edit", function (e) {
const editedLayer = e.target;
setArea(editedLayer);
setBound(editedLayer);
safeFlyToBounds(editedLayer);
});
};
const handlerCreatedBound = useCallback((event) => {
const { layer } = event;
layer.editing.enable();
featureRef.current.addLayer(layer); // 🟢 اول اضافه به نقشه
setArea(layer);
setBound(layer);
safeFlyToBounds(layer);
bindEditEvent(layer);
controlDispach({ type: "SET_STATUS", status: 2 });
}, []);
useEffect(() => {
if (
control.status === 1 &&
drawControlRef.current &&
drawControlRef.current._toolbars?.draw?._modes?.polygon?.handler
) {
drawControlRef.current._toolbars.draw._modes.polygon.handler.enable();
}
}, [control.status]);
useEffect(() => {
if (control.status === 0 && area && featureRef.current) {
featureRef.current.removeLayer(area);
setArea(null);
setBound(null);
}
}, [control.status, area]);
useEffect(() => {
if (control.status === 2 && bound && featureRef.current) {
setArea(bound);
featureRef.current.addLayer(bound);
bound.editing.enable();
safeFitBounds(bound);
bindEditEvent(bound);
}
}, [control.status, bound]);
useEffect(() => {
if (!featureRef.current) return;
L.drawLocal.draw.handlers.polygon.tooltip.start = "برای شروع ترسیم محدوده، روی نقشه کلیک کنید";
L.drawLocal.draw.handlers.polygon.tooltip.cont = "برای ادامه ترسیم، نقاط بعدی را کلیک کنید";
L.drawLocal.draw.handlers.polygon.tooltip.end = "برای بستن محدوده، روی نقطه‌ی شروع کلیک کنید";
const drawControl = new L.Control.Draw({
draw: {
polygon: {
shapeOptions: {
color: "#3388ff",
weight: 3,
clickable: true,
},
},
polyline: false,
rectangle: false,
circle: false,
marker: false,
circlemarker: false,
},
edit: {
featureGroup: featureRef.current,
edit: true,
remove: false,
},
});
drawControlRef.current = drawControl;
map.addControl(drawControl);
map.on(L.Draw.Event.CREATED, handlerCreatedBound);
return () => {
map.off(L.Draw.Event.CREATED, handlerCreatedBound);
map.removeControl(drawControl);
};
}, [map, handlerCreatedBound]);
return <FeatureGroup ref={featureRef} />;
};
export default DrawBound;

View File

@@ -0,0 +1,116 @@
import { Delete, Route } from "@mui/icons-material";
import { Box, Button, Stack, Typography } from "@mui/material";
import { useReducer } from "react";
import DrawBound from "./DrawBound";
const statusType = [
{
id: 0,
message: "برای آغاز ترسیم محدوده، کلیک کنید!",
buttons: [
{
label: "شروع ترسیم محدوده",
key: "start",
color: "primary",
icon: <Route />,
onclick: (controlDispach) => {
controlDispach({ type: "SET_STATUS", status: 1 });
},
},
],
},
{
id: 1,
message: "محدوده‌ی موردنظر را با کلیک روی نقشه مشخص کنید. برای اتمام، روی نقطه‌ی ابتدایی کلیک کنید!",
buttons: [],
},
{
id: 2,
message: "برای اصلاح محدوده، گوشه‌ها را بکشید. برای ترسیم دوباره، آن را حذف کنید",
buttons: [
{
label: "حذف",
key: "end",
color: "error",
icon: <Delete />,
onclick: (controlDispach) => {
controlDispach({ type: "SET_STATUS", status: 0 });
},
},
],
},
];
const createInitialState = (bound) => {
if (bound) {
return { status: 2 };
}
return { status: 0 };
};
const reducer = (state, action) => {
switch (action.type) {
case "SET_STATUS":
return { ...state, status: action.status };
default:
return state;
}
};
const MapControlPolygon = ({ bound, setBound }) => {
const [control, controlDispach] = useReducer(reducer, bound, createInitialState);
return (
<>
<DrawBound bound={bound} setBound={setBound} control={control} controlDispach={controlDispach} />
<Stack
direction={"row"}
justifyContent={"center"}
sx={{ position: "absolute", left: 0, top: 0, zIndex: 2000, width: "100%" }}
>
<Box
sx={{
background: (theme) => theme.palette.info.main,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
py: 0.5,
px: 2,
}}
>
<Typography align="center" component={"div"} variant="caption" color={"#fff"}>
{statusType.find((st) => st.id == control.status).message}
</Typography>
</Box>
</Stack>
<Box
sx={{
position: "absolute",
left: 0,
bottom: 0,
zIndex: 2000,
width: "100%",
py: 1,
background: "linear-gradient(to top, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0))",
}}
>
<Stack direction={"row"} justifyContent={"center"} spacing={2}>
{statusType
.find((st) => st.id == control.status)
.buttons.map((button) => (
<Button
size="small"
key={button.key}
variant="contained"
onClick={() => button.onclick(controlDispach)}
color={button.color}
startIcon={button.icon}
>
{button.label}
</Button>
))}
</Stack>
</Box>
</>
);
};
export default MapControlPolygon;

View File

@@ -0,0 +1,129 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import L from "leaflet";
import { FeatureGroup, useMap } from "react-leaflet";
import "leaflet-draw";
const DrawBound = ({ control, controlDispach, bound, setBound }) => {
const map = useMap();
const featureRef = useRef(null);
const [area, setArea] = useState();
const drawControlRef = useRef(null);
const safeFlyToBounds = (layer) => {
if (!layer || !map) return;
try {
const latLngs = layer.getLatLngs();
map.flyToBounds(latLngs, {
paddingTopLeft: [20, 35],
paddingBottomRight: [20, 55],
});
} catch (e) {
console.warn("flyToBounds failed:", e);
}
};
const safeFitBounds = (layer) => {
if (!layer || !map) return;
try {
const latLngs = layer.getLatLngs();
map.fitBounds(latLngs, {
paddingTopLeft: [20, 35],
paddingBottomRight: [20, 55],
});
} catch (e) {
console.warn("fitBounds failed:", e);
}
};
const bindEditEvent = (layer) => {
if (!layer) return;
layer.on("edit", function (e) {
const editedLayer = e.target;
setArea(editedLayer);
setBound(editedLayer);
safeFlyToBounds(editedLayer);
});
};
const handlerCreatedBound = useCallback((event) => {
const { layer } = event;
layer.editing.enable();
featureRef.current.addLayer(layer); // 🟢 اول اضافه به نقشه
setArea(layer);
setBound(layer);
safeFlyToBounds(layer);
bindEditEvent(layer);
controlDispach({ type: "SET_STATUS", status: 2 });
}, []);
useEffect(() => {
if (
control.status === 1 &&
drawControlRef.current &&
drawControlRef.current._toolbars?.draw?._modes?.polyline?.handler
) {
drawControlRef.current._toolbars.draw._modes.polyline.handler.enable();
}
}, [control.status]);
useEffect(() => {
if (control.status === 0 && area && featureRef.current) {
featureRef.current.removeLayer(area);
setArea(null);
setBound(null);
}
}, [control.status, area]);
useEffect(() => {
if (control.status === 2 && bound && featureRef.current) {
setArea(bound);
featureRef.current.addLayer(bound);
bound.editing.enable();
safeFitBounds(bound);
bindEditEvent(bound);
}
}, [control.status, bound]);
useEffect(() => {
if (!featureRef.current) return;
L.drawLocal.draw.handlers.polyline.tooltip.start = "برای شروع ترسیم مسیر، روی نقشه کلیک کنید";
L.drawLocal.draw.handlers.polyline.tooltip.cont = "برای ادامه ترسیم، نقاط بعدی را کلیک کنید";
L.drawLocal.draw.handlers.polyline.tooltip.end = "برای بستن مسیر، روی نقطه پایانی کلیک کنید";
const drawControl = new L.Control.Draw({
draw: {
polyline: {
shapeOptions: {
color: "#3388ff",
weight: 3,
clickable: true,
},
},
polygon: false,
rectangle: false,
circle: false,
marker: false,
circlemarker: false,
},
edit: {
featureGroup: featureRef.current,
edit: true,
remove: false,
},
});
drawControlRef.current = drawControl;
map.addControl(drawControl);
map.on(L.Draw.Event.CREATED, handlerCreatedBound);
return () => {
map.off(L.Draw.Event.CREATED, handlerCreatedBound);
map.removeControl(drawControl);
};
}, [map, handlerCreatedBound]);
return <FeatureGroup ref={featureRef} />;
};
export default DrawBound;

View File

@@ -0,0 +1,116 @@
import { Delete, Route } from "@mui/icons-material";
import { Box, Button, Stack, Typography } from "@mui/material";
import { useReducer } from "react";
import DrawBound from "./DrawBound";
const statusType = [
{
id: 0,
message: "برای آغاز ترسیم مسیر، کلیک کنید!",
buttons: [
{
label: "شروع ترسیم مسیر",
key: "start",
color: "primary",
icon: <Route />,
onclick: (controlDispach) => {
controlDispach({ type: "SET_STATUS", status: 1 });
},
},
],
},
{
id: 1,
message: "مسیر موردنظر را با کلیک روی نقشه مشخص کنید. برای اتمام، روی نقطه‌ی پایانی کلیک کنید!",
buttons: [],
},
{
id: 2,
message: "برای اصلاح مسیر، گوشه‌ها را بکشید. برای ترسیم دوباره، آن را حذف کنید",
buttons: [
{
label: "حذف",
key: "end",
color: "error",
icon: <Delete />,
onclick: (controlDispach) => {
controlDispach({ type: "SET_STATUS", status: 0 });
},
},
],
},
];
const createInitialState = (bound) => {
if (bound) {
return { status: 2 };
}
return { status: 0 };
};
const reducer = (state, action) => {
switch (action.type) {
case "SET_STATUS":
return { ...state, status: action.status };
default:
return state;
}
};
const MapControlPolyline = ({ bound, setBound }) => {
const [control, controlDispach] = useReducer(reducer, bound, createInitialState);
return (
<>
<DrawBound bound={bound} setBound={setBound} control={control} controlDispach={controlDispach} />
<Stack
direction={"row"}
justifyContent={"center"}
sx={{ position: "absolute", left: 0, top: 0, zIndex: 2000, width: "100%" }}
>
<Box
sx={{
background: (theme) => theme.palette.info.main,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
py: 0.5,
px: 2,
}}
>
<Typography align="center" component={"div"} variant="caption" color={"#fff"}>
{statusType.find((st) => st.id == control.status).message}
</Typography>
</Box>
</Stack>
<Box
sx={{
position: "absolute",
left: 0,
bottom: 0,
zIndex: 2000,
width: "100%",
py: 1,
background: "linear-gradient(to top, rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0))",
}}
>
<Stack direction={"row"} justifyContent={"center"} spacing={2}>
{statusType
.find((st) => st.id == control.status)
.buttons.map((button) => (
<Button
size="small"
key={button.key}
variant="contained"
onClick={() => button.onclick(controlDispach)}
color={button.color}
startIcon={button.icon}
>
{button.label}
</Button>
))}
</Stack>
</Box>
</>
);
};
export default MapControlPolyline;

View File

@@ -0,0 +1,25 @@
import SelectBox from "@/core/components/SelectBox";
export const boundTypes = [
{ id: "polygon", name_fa: "محدوده" },
{ id: "polyline", name_fa: "مسیر" },
];
const SelectBoundType = ({ boundType, setBoundType, setBound, setAllData }) => {
return (
<>
<SelectBox
value={boundType}
label="نوع منطقه عملیاتی"
selectors={boundTypes}
schema={{ name: "name_fa", value: "id" }}
onChange={(newValue) => {
setBound(null);
setBoundType(newValue);
setAllData({ bound_type: newValue });
}}
/>
</>
);
};
export default SelectBoundType;

View File

@@ -0,0 +1,54 @@
import MapLoading from "@/core/components/MapLayer/Loading";
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
import dynamic from "next/dynamic";
import { useCallback, useState } from "react";
import MapControlPolygon from "./MapControlPolygon";
import MapControlPolyline from "./MapControlPolyline";
import SelectBoundType from "./SelectBoundType";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => <MapLoading />,
ssr: false,
});
const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
const [bound, setBound] = useState(allData.bound);
const [boundType, setBoundType] = useState(allData.bound_type);
const handleNext = useCallback(() => {
setAllData({ bound: bound });
setTabState((s) => s + 1);
}, [bound]);
return (
<>
<DialogContent dividers>
<Stack spacing={2}>
<SelectBoundType
boundType={boundType}
setBoundType={setBoundType}
setBound={setBound}
setAllData={setAllData}
/>
<Box sx={{ width: "100%", height: "400px" }}>
<MapLayer style={{ borderRadius: "4px" }}>
{boundType == "polygon" ? (
<MapControlPolygon bound={bound} setBound={setBound} boundType={boundType} />
) : (
<MapControlPolyline bound={bound} setBound={setBound} boundType={boundType} />
)}
</MapLayer>
</Box>
</Stack>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
{"مرحله قبلی"}
</Button>
<Button variant="contained" size="large" disabled={!bound} onClick={handleNext}>
{!bound ? "در انتظار ترسیم" : "مرحله بعد"}
</Button>
</DialogActions>
</>
);
};
export default Area;

View File

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

View File

@@ -0,0 +1,59 @@
import SelectBox from "@/core/components/SelectBox";
import StyledForm from "@/core/components/StyledForm";
import { missionTypes } from "@/core/utils/missionTypes";
import { yupResolver } from "@hookform/resolvers/yup";
import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
import moment from "jalali-moment";
import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup";
import MissionDates from "./MissionDates";
const validationSchema = object({
start_date: string().required("تاریخ شروع ماموریت را مشخص کنید!"),
start_time: string().required("زمان شروع ماموریت را مشخص کنید!"),
end_date: string().required("تاریخ پایان ماموریت را مشخص کنید!"),
end_time: string().required("زمان شروع ماموریت را مشخص کنید!"),
});
const GetDateTime = ({ allData, setAllData, handlePrev, setTabState }) => {
const defaultValues = {
start_date: allData.start_date,
start_time: allData.start_time ? moment(allData.start_time).toDate() : null,
end_date: allData.end_date,
end_time: allData.end_time ? moment(allData.end_time).toDate() : null,
};
const { control, handleSubmit } = useForm({
defaultValues,
resolver: yupResolver(validationSchema),
mode: "all",
});
const handleNext = (data) => {
setAllData(data);
setTabState((s) => s + 1);
};
return (
<StyledForm onSubmit={handleSubmit(handleNext)}>
<DialogContent dividers>
<Box sx={{ flex: 1 }}>
<Stack>
<Grid container spacing={2}>
<MissionDates control={control} />
</Grid>
</Stack>
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
{"مرحله قبلی"}
</Button>
<Button variant="contained" size="large" type="submit">
مرحله بعد
</Button>
</DialogActions>
</StyledForm>
);
};
export default GetDateTime;

View File

@@ -0,0 +1,20 @@
import { Done } from "@mui/icons-material";
import { IconButton, Tooltip } from "@mui/material";
const SelectId = ({ row, onChange, setOpenFastReactDialog }) => {
const handleClick = () => {
onChange(row.original.id);
setOpenFastReactDialog(false);
};
return (
<>
<Tooltip title="انتخاب" arrow placement="right">
<IconButton color="primary" sx={{ textTransform: "unset", alignSelf: "center" }} onClick={handleClick}>
<Done />
</IconButton>
</Tooltip>
</>
);
};
export default SelectId;

View File

@@ -0,0 +1,10 @@
import SelectId from "./SelectId";
const RowActions = ({ row, onChange, setOpenFastReactDialog }) => {
return (
<>
<SelectId row={row} onChange={onChange} setOpenFastReactDialog={setOpenFastReactDialog} />
</>
);
};
export default RowActions;

View File

@@ -0,0 +1,274 @@
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_FAST_REACT_COMPLAINTS } from "@/core/utils/routes";
import { Box, Stack, Typography } from "@mui/material";
import { useMemo } from "react";
import RowActions from "./RowActions";
import { usePermissions } from "@/lib/hooks/usePermissions";
import { useAuth } from "@/lib/contexts/auth";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import DescriptionForm from "@/components/dashboard/fastReact/complaintList/RowActions/DescriptionDialog";
import LocationForm from "@/components/dashboard/fastReact/complaintList/RowActions/LocationDialog";
import moment from "jalali-moment";
import useProvinces from "@/lib/hooks/useProvince";
import useEdaratLists from "@/lib/hooks/useEdaratLists";
const FastReactList = ({ onChange, setOpenFastReactDialog }) => {
const { data: userPermissions } = usePermissions();
const hasCountryPermission = userPermissions?.includes("show-fast-react");
const { user } = useAuth();
const columns = useMemo(() => {
const dynamicColumns = {
header: "استان",
id: "road_observeds__province_id",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
grow: false,
size: 130,
ColumnSelectComponent: (props) => {
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
const getColumnSelectOptions = useMemo(() => {
if (loadingProvinces) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorProvinces) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "کل کشور" },
...provinces.map((province) => ({
value: province.id,
label: province.name_fa,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
return (
<CustomSelectByDependency
{...props}
value={loadingProvinces ? "loading" : props.filterParameters.value}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ row }) => <>{row.original.province_fa}</>,
};
return [
{
accessorKey: "id",
header: "کد یکتا",
id: "road_observeds__id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
...(hasCountryPermission ? [dynamicColumns] : []),
{
header: "اداره",
id: "road_observeds__edarate_shahri_id",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
dependencyId: hasCountryPermission ? "road_observeds__province_id" : null,
grow: false,
size: 120,
ColumnSelectComponent: (props) => {
const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(
hasCountryPermission ? props.dependencyFieldValue.value : user.province_id
);
const [prevDependency, setPrevDependency] = useState(
hasCountryPermission ? props.dependencyFieldValue.value : user.province_id
);
const getColumnSelectOptions = useMemo(() => {
if (hasCountryPermission && props.dependencyFieldValue.value === "") {
return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
}
if (loadingEdaratList) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorEdaratList) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "کل ادارات" },
...edaratList.map((edare) => ({
value: edare.id,
label: edare.name_fa,
})),
];
}, [edaratList, loadingEdaratList, errorEdaratList]);
useEffect(() => {
if (hasCountryPermission) return;
if (prevDependency === props.dependencyFieldValue?.value) return;
props.handleChange({ ...props.filterParameters, value: "" });
setPrevDependency(props.dependencyFieldValue?.value);
}, [props.dependencyFieldValue?.value, hasCountryPermission]);
return (
<CustomSelectByDependency
{...props}
value={
props.dependencyFieldValue?.value === ""
? "empty"
: loadingEdaratList
? "loading"
: props.filterParameters.value
}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ row }) => <>{row.original.edarate_shahri_name_fa}</>,
},
{
header: "اطلاعات ثبت‌ شده در سامانه سوانح",
id: "fkInfo",
enableColumnFilter: false,
enableSorting: false,
grow: false,
size: 50,
columns: [
{
accessorKey: "fk_RegisteredEventMessage",
header: "کد سوانح",
id: "fk_RegisteredEventMessage",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "Title",
header: "موضوع گزارش",
id: "Title",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "FeatureTypeTitle",
header: "نوع گزارش",
id: "FeatureTypeTitle",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "Description",
header: "توضیح گزارش",
id: "Description",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue }) => {
if (renderedCellValue) {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<DescriptionForm description={renderedCellValue} title={"توضیح گزارش"} />
</Stack>
);
}
return <Typography variant="body2">-</Typography>;
},
},
{
header: "موقعیت",
id: "location",
enableColumnFilter: false,
enableSorting: false,
datatype: "array",
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<LocationForm start_lat={row.original.lat} start_lng={row.original.lng} />
</Stack>
);
},
},
{
accessorKey: "MobileForSendEventSms",
header: "شماره تماس گیرنده",
id: "MobileForSendEventSms",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorFn: (row) => moment(row.StartTime_DateTime).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ ثبت",
id: "StartTime_DateTime",
enableColumnFilter: true,
datatype: "date",
filterMode: "between",
grow: false,
size: 100,
},
],
},
];
}, []);
return (
<>
<Box sx={{ mt: 3, p: 1 }}>
<DataTableWithAuth
need_filter={true}
table_title="لیست شکایات"
columns={columns}
sorting={[{ id: "StartTime_DateTime", desc: true }]}
table_url={GET_FAST_REACT_COMPLAINTS}
page_name={"roadMissionsOperator"}
table_name={"roadMissionsOperatorFastReactList"}
enableRowActions
positionActionsColumn={"first"}
RowActions={(props) => (
<RowActions {...props} onChange={onChange} setOpenFastReactDialog={setOpenFastReactDialog} />
)}
/>
</Box>
</>
);
};
export default FastReactList;

View File

@@ -0,0 +1,49 @@
import { Close, Edit } from "@mui/icons-material";
import { Button, Chip, Dialog, Divider, IconButton } from "@mui/material";
import FastReactList from "./List";
import { useState, forwardRef } from "react";
const FastReactDialog = forwardRef(({ onChange, value }, ref) => {
const [openFastReactDialog, setOpenFastReactDialog] = useState(false);
return (
<>
<Divider ref={ref}>
{value === "" ? (
<Button onClick={() => setOpenFastReactDialog(true)} variant="outlined">
انتخاب شکایت
</Button>
) : (
<Chip
label={`کد شکایت: ${value}`}
variant="outlined"
deleteIcon={<Edit />}
onDelete={() => setOpenFastReactDialog(true)}
/>
)}
</Divider>
<Dialog open={openFastReactDialog} fullWidth maxWidth="md">
<IconButton
aria-label="close"
onClick={() => setOpenFastReactDialog(false)}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
zIndex: 50,
color: theme.palette.grey[500],
})}
>
<Close />
</IconButton>
{openFastReactDialog && (
<FastReactList onChange={onChange} setOpenFastReactDialog={setOpenFastReactDialog} />
)}
</Dialog>
</>
);
});
FastReactDialog.displayName = "FastReactDialog";
export default FastReactDialog;

View File

@@ -0,0 +1,22 @@
import { Divider, Stack } from "@mui/material";
import { Controller, useWatch } from "react-hook-form";
import FastReactDialog from "./Dialog";
const FastReactCode = ({ control }) => {
const category_id = useWatch({ control, name: "category_id" });
if (category_id != 3) return null;
return (
<Controller
control={control}
name={"road_observed_id"}
render={({ field }) => (
<Stack justifyContent={"center"} sx={{ height: "100%" }}>
<FastReactDialog {...field} />
</Stack>
)}
/>
);
};
export default FastReactCode;

View File

@@ -0,0 +1,153 @@
import PersianTextField from "@/core/components/PersianTextField";
import SelectBox from "@/core/components/SelectBox";
import StyledForm from "@/core/components/StyledForm";
import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes";
import { missionRegions } from "@/core/utils/missionRegions";
import { yupResolver } from "@hookform/resolvers/yup";
import { ExitToApp } from "@mui/icons-material";
import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup";
import FastReactCode from "./FastReactCode";
const validationSchema = object({
explanation: string().required("موضوع را مشخص کنید!"),
end_point: string().required("مقصد را مشخص کنید!"),
region: string().required("محور ماموریت را مشخص کنید!"),
category_id: string().required("نوع ماموریت را مشخص کنید!"),
road_observed_id: string().when("category_id", {
is: "3",
then: (schema) => schema.required("کد یکتا شکایت را مشخص کنید!"),
otherwise: (schema) => schema.notRequired(),
}),
});
const GetItemInfo = ({ allData, setAllData, handlePrev, setTabState }) => {
const defaultValues = {
explanation: allData.explanation,
end_point: allData.end_point,
region: allData.region,
category_id: allData.category_id,
road_observed_id: allData.road_observed_id,
};
const { control, handleSubmit } = useForm({
defaultValues,
resolver: yupResolver(validationSchema),
mode: "all",
});
const handleNext = (data) => {
setAllData(data);
setTabState((s) => s + 1);
};
return (
<StyledForm onSubmit={handleSubmit(handleNext)}>
<DialogContent dividers>
<Box sx={{ flex: 1 }}>
<Stack>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"explanation"}
render={({ field, fieldState: { error } }) => (
<PersianTextField
value={field.value}
variant="outlined"
name="explanation"
onChange={field.onChange}
label="موضوع"
placeholder={"موضوع را وارد کنید"}
fullWidth
size="small"
error={error}
helperText={error?.message}
InputLabelProps={{ shrink: true }}
/>
)}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"region"}
render={({ field, fieldState: { error } }) => (
<SelectBox
value={field.value}
label="محدوده"
selectors={missionRegions}
schema={{ name: "name_fa", value: "id" }}
error={error}
onChange={field.onChange}
helperText={error?.message}
/>
)}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"category_id"}
render={({ field, fieldState: { error } }) => (
<SelectBox
value={field.value}
label="نوع ماموریت"
selectors={missionCategoryTypes}
schema={{ name: "name_fa", value: "id" }}
error={error}
onChange={field.onChange}
helperText={error?.message}
/>
)}
/>
</Grid>
<Grid item xs={12} sm={6}>
<FastReactCode control={control} />
</Grid>
<Grid item xs={12} sm={6}>
<Controller
control={control}
name={"end_point"}
render={({ field, fieldState: { error } }) => {
return (
<PersianTextField
value={field.value}
variant="outlined"
name="end_point"
onChange={field.onChange}
label="مقصد"
placeholder={"مقصد را وارد کنید"}
fullWidth
size="small"
error={error}
helperText={error?.message}
InputLabelProps={{ shrink: true }}
/>
);
}}
/>
</Grid>
</Grid>
</Stack>
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button
onClick={handlePrev}
variant="outlined"
color="secondary"
size="large"
startIcon={<ExitToApp />}
>
{"بستن"}
</Button>
<Button variant="contained" size="large" type="submit">
مرحله بعد
</Button>
</DialogActions>
</StyledForm>
);
};
export default GetItemInfo;

View File

@@ -0,0 +1,61 @@
import RahdarNameOrCode from "@/core/components/RahdarNameOrCode";
import StyledForm from "@/core/components/StyledForm";
import { yupResolver } from "@hookform/resolvers/yup";
import { Done } from "@mui/icons-material";
import { Button, Stack } from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import { object } from "yup";
const schema = object().shape({
rahdar: object().required("همراه الزامی است."),
});
const DriverForm = ({ setDriver, setOpenDriversDialog }) => {
const defaultValues = {
rahdar: null,
};
const {
control,
handleSubmit,
formState: { isSubmitting, isValid },
} = useForm({ defaultValues, resolver: yupResolver(schema), mode: "all" });
const submit = (data) => {
setDriver(data.rahdar);
setOpenDriversDialog(false);
};
return (
<StyledForm onSubmit={handleSubmit(submit)}>
<Stack direction={{ xs: "column", sm: "row" }} justifyContent={"center"} spacing={2}>
<Stack sx={{ minWidth: "250px" }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<RahdarNameOrCode
title="راننده"
rahdarsName={field.value}
setRahdarsName={(value) => field.onChange(value)}
error={error}
multiple={false}
/>
);
}}
name={"rahdar"}
/>
</Stack>
<Button
variant="contained"
type="submit"
sx={{ textWrap: "nowrap", px: 3 }}
disabled={isSubmitting || !isValid}
startIcon={<Done />}
>
انتخاب
</Button>
</Stack>
</StyledForm>
);
};
export default DriverForm;

View File

@@ -0,0 +1,11 @@
import { DialogContent } from "@mui/material";
import DriverForm from "./Form";
const DriversSearch = ({ setDriver, setOpenDriversDialog }) => {
return (
<DialogContent dividers>
<DriverForm setDriver={setDriver} setOpenDriversDialog={setOpenDriversDialog} />
</DialogContent>
);
};
export default DriversSearch;

View File

@@ -0,0 +1,41 @@
import { Close, Edit } from "@mui/icons-material";
import { Button, Dialog, DialogTitle, IconButton } from "@mui/material";
import { useState } from "react";
import DriversSearch from "./DriversSearch";
const DriversDialog = ({ setDriver, mode }) => {
const [openDriversDialog, setOpenDriversDialog] = useState(false);
return (
<>
{mode == "edit" ? (
<IconButton onClick={() => setOpenDriversDialog(true)} size="small">
<Edit />
</IconButton>
) : (
<Button onClick={() => setOpenDriversDialog(true)} variant="contained" fullWidth size="large">
انتخاب راننده
</Button>
)}
<Dialog open={openDriversDialog} fullWidth maxWidth="xs">
<IconButton
aria-label="close"
onClick={() => setOpenDriversDialog(false)}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
zIndex: 50,
color: theme.palette.grey[500],
})}
>
<Close />
</IconButton>
<DialogTitle>انتخاب راننده</DialogTitle>
{openDriversDialog && (
<DriversSearch setDriver={setDriver} setOpenDriversDialog={setOpenDriversDialog} />
)}
</Dialog>
</>
);
};
export default DriversDialog;

View File

@@ -0,0 +1,20 @@
import { Done } from "@mui/icons-material";
import { IconButton, Tooltip } from "@mui/material";
const Allocate = ({ row, setMachine, setOpenMachinesDialog }) => {
const handleClick = () => {
setMachine(row.original);
setOpenMachinesDialog(false);
};
return (
<>
<Tooltip title="تخصیص" arrow placement="right">
<IconButton color="primary" sx={{ textTransform: "unset", alignSelf: "center" }} onClick={handleClick}>
<Done />
</IconButton>
</Tooltip>
</>
);
};
export default Allocate;

View File

@@ -0,0 +1,6 @@
import Allocate from "./Allocate";
const RowActions = ({ row, setMachine, setOpenMachinesDialog }) => {
return <Allocate row={row} setMachine={setMachine} setOpenMachinesDialog={setOpenMachinesDialog} />;
};
export default RowActions;

View File

@@ -0,0 +1,68 @@
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { Box } from "@mui/material";
import { useMemo } from "react";
import RowActions from "./RowActions";
import { GET_MACHINES_TABLE_LIST } from "@/core/utils/routes";
const MachinesList = ({ machineType, setMachine, setOpenMachinesDialog }) => {
const columns = useMemo(
() => [
{
accessorKey: "machine_code",
header: "کد خودرو",
id: "machine_code",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "car_name",
header: "نام خودرو",
id: "car_name",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "car_type",
header: "نوع خودرو",
id: "car_type",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
],
[]
);
return (
<>
<Box sx={{ mt: 3, p: 1 }}>
<DataTableWithAuth
need_filter={true}
table_title="لیست خودرو ها"
columns={columns}
sorting={[{ id: "id", desc: true }]}
table_url={GET_MACHINES_TABLE_LIST}
page_name={"roadMissionsOperator"}
table_name={"roadMissionsOperatorMachinesList"}
enableRowActions
positionActionsColumn={"first"}
RowActions={(props) => (
<RowActions {...props} setMachine={setMachine} setOpenMachinesDialog={setOpenMachinesDialog} />
)}
/>
</Box>
</>
);
};
export default MachinesList;

View File

@@ -0,0 +1,40 @@
import { Close, Edit } from "@mui/icons-material";
import { Button, Dialog, IconButton } from "@mui/material";
import { useState } from "react";
import MachinesList from "./MachineList";
const MachinesDialog = ({ setMachine, mode }) => {
const [openMachinesDialog, setOpenMachinesDialog] = useState(false);
return (
<>
{mode == "edit" ? (
<IconButton onClick={() => setOpenMachinesDialog(true)} size="small">
<Edit />
</IconButton>
) : (
<Button onClick={() => setOpenMachinesDialog(true)} variant="contained" fullWidth size="large">
انتخاب خودرو
</Button>
)}
<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 && (
<MachinesList setMachine={setMachine} setOpenMachinesDialog={setOpenMachinesDialog} />
)}
</Dialog>
</>
);
};
export default MachinesDialog;

View File

@@ -0,0 +1,77 @@
import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typography } from "@mui/material";
import DriversDialog from "./DriversDialog";
import { useState } from "react";
import MachinesDialog from "./MachinesDialog";
const MachineAndDriver = ({ setTabState, allData, setAllData, handlePrev }) => {
const [machine, setMachine] = useState(allData.machine);
const [driver, setDriver] = useState(allData.driver);
const handleNext = (data) => {
setAllData(data);
setTabState((s) => s + 1);
};
return (
<>
<DialogContent dividers>
<Box sx={{ flex: 1 }}>
<Stack sx={{ my: 1 }} spacing={2}>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
{machine ? (
<>
<Typography variant="body2">خودرو</Typography>
<Divider sx={{ flex: 1 }} />
<Chip
label={
machine
? `${machine.machine_code} | ${machine.car_name}`
: "خودرویی انتخاب نشده"
}
size="small"
/>
<Divider sx={{ flex: 1 }} />
<MachinesDialog setMachine={setMachine} mode={"edit"} />
</>
) : (
<MachinesDialog setMachine={setMachine} />
)}
</Stack>
{machine && (
<Stack direction={"row"} alignItems={"center"} spacing={1}>
{driver ? (
<>
<Typography variant="body2">راننده</Typography>
<Divider sx={{ flex: 1 }} />
<Chip
label={driver ? `${driver.code} | ${driver.name}` : "راننده ای انتخاب نشده"}
size="small"
/>
<Divider sx={{ flex: 1 }} />
<DriversDialog setDriver={setDriver} mode={"edit"} />
</>
) : (
<DriversDialog setDriver={setDriver} />
)}
</Stack>
)}
</Stack>
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
{"مرحله قبلی"}
</Button>
<Button
variant="contained"
disabled={!machine || !driver}
size="large"
onClick={() => handleNext({ machine, driver })}
>
مرحله بعد
</Button>
</DialogActions>
</>
);
};
export default MachineAndDriver;

View File

@@ -0,0 +1,66 @@
import RahdarNameOrCode from "@/core/components/RahdarNameOrCode";
import StyledForm from "@/core/components/StyledForm";
import { yupResolver } from "@hookform/resolvers/yup";
import { Add } from "@mui/icons-material";
import { Button, Stack } from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import { object } from "yup";
const schema = object().shape({
rahdar: object().required("همراه الزامی است."),
});
const RahdaranForm = ({ setRahdaran }) => {
const defaultValues = {
rahdar: null,
};
const {
control,
handleSubmit,
reset,
formState: { isSubmitting, isValid },
} = useForm({ defaultValues, resolver: yupResolver(schema), mode: "all" });
const submit = (data) => {
setRahdaran((prev) => {
const alreadyExists = prev.some((r) => r.id === data.rahdar.id);
if (alreadyExists) return prev;
return [...prev, data.rahdar];
});
reset();
};
return (
<StyledForm onSubmit={handleSubmit(submit)}>
<Stack direction={{ xs: "column", sm: "row" }} justifyContent={"center"} spacing={2}>
<Stack sx={{ minWidth: "250px" }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<RahdarNameOrCode
title="همراه"
rahdarsName={field.value}
setRahdarsName={(value) => field.onChange(value)}
error={error}
multiple={false}
/>
);
}}
name={"rahdar"}
/>
</Stack>
<Button
variant="contained"
type="submit"
sx={{ textWrap: "nowrap", px: 3 }}
disabled={isSubmitting || !isValid}
startIcon={<Add />}
>
افزودن همراه
</Button>
</Stack>
</StyledForm>
);
};
export default RahdaranForm;

View File

@@ -0,0 +1,56 @@
import { AccountCircle, Delete } from "@mui/icons-material";
import { Card, Collapse, IconButton, Stack, Typography } from "@mui/material";
import { TransitionGroup } from "react-transition-group";
const RahdaranList = ({ rahdaran, setRahdaran }) => {
const handleRemove = (index) => {
setRahdaran((prev) => prev.filter((_, i) => i !== index));
};
return (
<Stack>
<TransitionGroup>
{rahdaran.map((rahdar, index) => (
<Collapse key={rahdar.code}>
<Card
elevation={0}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
border: 1,
borderColor: "divider",
px: 1,
py: 1,
my: 1,
}}
>
<Stack direction={"row"} spacing={1}>
<AccountCircle color="primary" sx={{ width: "32px", height: "32px" }} />
<Stack>
<Stack direction={"row"} spacing={0.5}>
<Typography variant="caption">نام و نام خانوادگی:</Typography>
<Typography variant="body2">{rahdar.name}</Typography>
</Stack>
<Stack direction={"row"} spacing={0.5}>
<Typography variant="caption">کدملی:</Typography>
<Typography variant="body2">{rahdar.code}</Typography>
</Stack>
</Stack>
</Stack>
<IconButton color="error" onClick={() => handleRemove(index)}>
<Delete />
</IconButton>
</Card>
</Collapse>
))}
{rahdaran.length == 0 && (
<Collapse>
<Typography align="center">همراهی ثبت نشده است</Typography>
</Collapse>
)}
</TransitionGroup>
</Stack>
);
};
export default RahdaranList;

View File

@@ -0,0 +1,36 @@
import { Box, Button, Chip, DialogActions, DialogContent, Divider } from "@mui/material";
import { useCallback, useState } from "react";
import RahdaranForm from "./Form";
import RahdaranList from "./List";
const Rahdaran = ({ allData, setAllData, setTabState, handlePrev }) => {
const [rahdaran, setRahdaran] = useState(allData.rahdaran);
const handleNext = useCallback(() => {
setAllData({ rahdaran: rahdaran });
setTabState((s) => s + 1);
}, [rahdaran]);
return (
<>
<DialogContent dividers>
<Box sx={{ flex: 1 }}>
<RahdaranForm setRahdaran={setRahdaran} />
<Divider sx={{ my: 2 }}>
<Chip label="لیست همراهان" />
</Divider>
<RahdaranList rahdaran={rahdaran} setRahdaran={setRahdaran} />
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
{"مرحله قبلی"}
</Button>
<Button variant="contained" size="large" onClick={handleNext}>
{"مرحله بعد"}
</Button>
</DialogActions>
</>
);
};
export default Rahdaran;

View File

@@ -0,0 +1,130 @@
import MapLoading from "@/core/components/MapLayer/Loading";
import { missionCategoryTypes } from "@/core/utils/missionCategoryTypes";
import { missionRegions } from "@/core/utils/missionRegions";
import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typography } from "@mui/material";
import moment from "jalali-moment";
import dynamic from "next/dynamic";
import { useCallback } from "react";
import ShowBound from "../../../showBound";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => <MapLoading />,
ssr: false,
});
const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
const handleNext = useCallback(() => {
submitForm(allData);
}, [allData]);
return (
<>
<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>
<Stack spacing={1}>
<Divider>
<Chip color="primary" variant="outlined" label="مشخصات ماموریت" />
</Divider>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">موضوع ماموریت</Typography>
<Divider sx={{ flex: 1 }} />
<Chip size="small" label={allData.explanation} />
</Stack>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">محدوده</Typography>
<Divider sx={{ flex: 1 }} />
<Chip size="small" label={missionRegions.find((r) => r.id == allData.region).name_fa} />
</Stack>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">نوع ماموریت</Typography>
<Divider sx={{ flex: 1 }} />
<Chip
size="small"
label={missionCategoryTypes.find((t) => t.id == allData.category_id).name_fa}
/>
</Stack>
{allData.category_id == 3 && (
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">کد شکایت</Typography>
<Divider sx={{ flex: 1 }} />
<Chip size="small" label={allData.road_observed_id} />
</Stack>
)}
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">مقصد</Typography>
<Divider sx={{ flex: 1 }} />
<Chip size="small" label={allData.end_point} />
</Stack>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">تاریخ شروع ماموریت</Typography>
<Divider sx={{ flex: 1 }} />
<Chip
size="small"
label={`${moment(allData.start_time).format("HH:mm")} | ${moment(allData.start_date).locale("fa").format("YYYY/MM/DD")}`}
/>
</Stack>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">تاریخ پایان ماموریت</Typography>
<Divider sx={{ flex: 1 }} />
<Chip
size="small"
label={`${moment(allData.end_time).format("HH:mm")} | ${moment(allData.end_date).locale("fa").format("YYYY/MM/DD")}`}
/>
</Stack>
</Stack>
<Stack spacing={1}>
<Divider>
<Chip color="primary" variant="outlined" label="خودرو و راننده" />
</Divider>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">خودرو</Typography>
<Divider sx={{ flex: 1 }} />
<Chip
size="small"
label={`${allData.machine.machine_code} | ${allData.machine.car_name}`}
/>
</Stack>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">راننده</Typography>
<Divider sx={{ flex: 1 }} />
<Chip size="small" label={`${allData.driver.code} | ${allData.driver.name}`} />
</Stack>
</Stack>
<Stack spacing={1}>
<Divider>
<Chip color="primary" variant="outlined" label="همراهان" />
</Divider>
{allData.rahdaran.length != 0 ? (
allData.rahdaran.map((rahdar) => (
<Stack key={rahdar.id} direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">{rahdar.name}</Typography>
<Divider sx={{ flex: 1 }} />
<Chip size="small" label={rahdar.code} />
</Stack>
))
) : (
<Stack direction={"row"} alignItems={"center"} justifyContent={"center"}>
<Typography variant="body2">همراهی ثبت نشده است</Typography>
</Stack>
)}
</Stack>
</Stack>
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button onClick={handlePrev} variant="outlined" color="secondary" disabled={submitting} size="large">
{"مرحله قبلی"}
</Button>
<Button variant="contained" size="large" disabled={submitting} onClick={handleNext}>
تایید و ثبت ماموریت
</Button>
</DialogActions>
</>
);
};
export default Verify;

View File

@@ -0,0 +1,125 @@
import { AccessTime, Engineering, InsertDriveFile, LocalShipping, Route, Verified } from "@mui/icons-material";
import { Box, Tab, Tabs } from "@mui/material";
import { useReducer, useState } from "react";
import Area from "./Area";
import GetDateTime from "./GetDateTime";
import GetItemInfo from "./GetItemInfo";
import Rahdaran from "./Rahdaran";
import Verify from "./Verify";
import MachineAndDriver from "./MachineAndDriver";
function TabPanel(props) {
const { children, value, index } = props;
return (
<div role="tabpanel" hidden={value !== index}>
{value === index && <Box>{children}</Box>}
</div>
);
}
const reducer = (state, action) => {
switch (action.type) {
case "changeData":
return { ...state, ...action.data };
default:
return state;
}
};
const CreateForm = ({ defaultValues, submitForm, setOpen, submitting }) => {
const [allData, dispatch] = useReducer(reducer, defaultValues);
const [tabState, setTabState] = useState(0);
const handleClose = () => {
setOpen(false);
};
const handleChangeTab = (event, newValue) => {
setTabState(newValue);
};
const handlePrev = () => {
if (tabState === 0) {
handleClose();
} else {
setTabState((t) => t - 1);
}
};
return (
<>
<Tabs
allowScrollButtonsMobile
value={tabState}
onChange={handleChangeTab}
variant={"fullWidth"}
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
backgroundColor: "#efefef",
}}
>
<Tab icon={<InsertDriveFile />} label="مشخصات" />
<Tab disabled={tabState < 1} icon={<AccessTime />} 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
allData={allData}
setAllData={(data) => {
dispatch({ type: "changeData", data });
}}
handlePrev={handlePrev}
setTabState={setTabState}
/>
</TabPanel>
<TabPanel value={tabState} index={1}>
<GetDateTime
allData={allData}
setAllData={(data) => {
dispatch({ type: "changeData", data });
}}
handlePrev={handlePrev}
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={3}>
<MachineAndDriver
allData={allData}
setAllData={(data) => {
dispatch({ type: "changeData", data });
}}
handlePrev={handlePrev}
setTabState={setTabState}
/>
</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>
</>
);
};
export default CreateForm;

View File

@@ -0,0 +1,121 @@
import { REQUEST_MISSION_WITHOUT_PROCESS } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { AddCircle, AddCircleOutline, Close } from "@mui/icons-material";
import { Button, Dialog, IconButton, useMediaQuery, useTheme } from "@mui/material";
import moment from "jalali-moment";
import { useState } from "react";
import CreateForm from "./Form";
const CreateWithoutProcess = ({ mutate }) => {
const [submitting, setSubmitting] = useState(false);
const requestServer = useRequest({ notificationSuccess: true });
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
const submitForm = async (result) => {
setSubmitting(true);
const bound = result.bound.getLatLngs();
let area =
result.bound_type == "polygon"
? bound.map((ring) => ring.map((latlng) => [latlng.lat, latlng.lng]))[0]
: bound.map((latlng) => [latlng.lat, latlng.lng]);
await requestServer(REQUEST_MISSION_WITHOUT_PROCESS, "post", {
data: {
explanation: result.explanation,
category_id: result.category_id,
...(result.category_id == 3
? {
road_observed_id: result.road_observed_id,
}
: {}),
area: {
type: result.bound_type,
coordinates: area,
},
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}),
zone: result.region,
end_point: result.end_point,
start_date: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`,
end_date: `${result.end_date} ${moment(result.end_time).format("HH:mm")}`,
machines: [result.machine.id],
driver: result.driver.id,
},
hasSidebarUpdate: true,
})
.then((response) => {
mutate();
setOpen(false);
})
.catch((error) => {})
.finally(() => {
setSubmitting(false);
});
};
return (
<>
{isMobile ? (
<IconButton aria-label="ثبت ماموریت بدون فرایند" color="primary" onClick={handleOpen}>
<AddCircleOutline sx={{ fontSize: "25px" }} />
</IconButton>
) : (
<Button
size={"small"}
variant="contained"
color="primary"
startIcon={<AddCircle />}
onClick={handleOpen}
>
ثبت ماموریت بدون فرایند
</Button>
)}
<Dialog open={open} fullWidth maxWidth="sm">
<IconButton
aria-label="close"
onClick={() => setOpen(false)}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
zIndex: 50,
color: theme.palette.grey[500],
})}
>
<Close />
</IconButton>
{open && (
<CreateForm
defaultValues={{
explanation: "",
category_id: "",
road_observed_id: "",
rahdaran: [],
bound: null,
bound_type: "polygon",
start_date: "",
start_time: null,
end_date: "",
end_time: null,
end_point: "",
region: "",
machine: null,
driver: null,
}}
submitForm={submitForm}
submitting={submitting}
open={open}
setOpen={setOpen}
mutate={mutate}
/>
)}
</Dialog>
</>
);
};
export default CreateWithoutProcess;

View File

@@ -227,6 +227,28 @@ const OperatorList = () => {
);
},
},
{
accessorFn: (row) =>
row.start_time ? moment(row.start_time).locale("fa").format("HH:mm | yyyy/MM/DD") : null,
header: "تاریخ خروج",
id: "start_time",
enableColumnFilter: true,
datatype: "date",
filterMode: "between",
grow: false,
size: 100,
},
{
accessorFn: (row) =>
row.finish_time ? moment(row.finish_time).locale("fa").format("HH:mm | yyyy/MM/DD") : null,
header: "تاریخ ورود",
id: "finish_time",
enableColumnFilter: true,
datatype: "date",
filterMode: "between",
grow: false,
size: 100,
},
{
accessorFn: (row) => moment(row.request_date).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ ثبت",

View File

@@ -1,10 +1,12 @@
import { Stack } from "@mui/material";
import Create from "./Actions/Create";
import CreateWithoutProcess from "./Actions/CreateWithoutProcess";
const Toolbar = ({ table, filterData, mutate }) => {
return (
<Stack direction={"row"} spacing={1}>
<Create mutate={mutate} />
<CreateWithoutProcess mutate={mutate} />
</Stack>
);
};

View File

@@ -93,7 +93,7 @@ const AllocationForm = ({ row, mutate, setOpenAllocationDialog }) => {
<Divider sx={{ flex: 1 }} />
<Chip
label={
machine
driver
? `${driver.code} | ${driver.name}`
: "راننده ای انتخاب نشده"
}

View File

@@ -6,7 +6,7 @@ const Toolbar = ({ table, filterData, mutate }) => {
return (
<Stack direction={"row"} spacing={1}>
<PrintExcel table={table} filterData={filterData} />
<OperatorCreate table={table} mutate={mutate} />
{/* <OperatorCreate table={table} mutate={mutate} /> */}
</Stack>
);
};

View File

@@ -7,13 +7,15 @@ import OtherHousesIcon from "@mui/icons-material/OtherHouses";
import LocalShippingIcon from "@mui/icons-material/LocalShipping";
import EditLocationAltIcon from "@mui/icons-material/EditLocationAlt";
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import { mixed, number, object, string } from "yup";
import { array, mixed, number, object, string } from "yup";
import TollHouseInfo from "./TollHouseInfo";
import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
import MachineInfo from "@/components/infrastructure/tollHouse/Form/CreateTollHouse/MachineInfo";
import TollHouseLocation from "@/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseLocation";
import { Route } from "@mui/icons-material";
import TollHouseArea from "./TollHouseArea";
function TabPanel(props) {
const { children, value, index } = props;
@@ -54,6 +56,23 @@ const validationSchema = object({
return !!value;
})
.required("لطفاً محل پروژه را مشخص کنید!"),
area: array()
.of(
object().shape({
lat: number()
.typeError("عرض جغرافیایی باید یک عدد معتبر باشد")
.required("عرض جغرافیایی الزامی است")
.min(25, "عرض جغرافیایی نمی‌تواند کمتر از 25 باشد")
.max(40, "عرض جغرافیایی نمی‌تواند بیشتر از 40 باشد"),
lon: number("وارد کردن طول جغرافیایی الزامیست")
.typeError("طول جغرافیایی باید یک عدد معتبر باشد")
.required("طول جغرافیایی الزامی است")
.min(44, "طول جغرافیایی نمی‌تواند کمتر از 44 باشد")
.max(64, "طول جغرافیایی نمی‌تواند بیشتر از 64 باشد"),
})
)
.min(4, "حداقل باید ۴ نقطه وجود داشته باشد")
.required("محدوده الزامی است"),
});
const CreateTollHouseContent = ({ setOpen, SubmitCreateTollHouse, defaultValues }) => {
const [tabState, setTabState] = useState(0);
@@ -137,8 +156,9 @@ const CreateTollHouseContent = ({ setOpen, SubmitCreateTollHouse, defaultValues
}}
>
<Tab icon={<OtherHousesIcon />} label="اطلاعات راهدارخانه" />
<Tab disabled={tabState === 0} icon={<LocalShippingIcon />} label="ماشین آلات" />
<Tab disabled={tabState === 0 || tabState === 1} icon={<EditLocationAltIcon />} label="موقعیت" />
<Tab disabled={tabState < 1} icon={<LocalShippingIcon />} label="ماشین آلات" />
<Tab disabled={tabState < 2} icon={<EditLocationAltIcon />} label="موقعیت" />
<Tab disabled={tabState < 3} icon={<Route />} label="محدوده" />
</Tabs>
<DialogContent dividers sx={{ overflowY: "auto", maxHeight: "70vh" }}>
<Box sx={{ flex: 1 }}>
@@ -151,6 +171,15 @@ const CreateTollHouseContent = ({ setOpen, SubmitCreateTollHouse, defaultValues
<TabPanel value={tabState} index={2}>
<TollHouseLocation control={control} setValue={setValue} errors={errors} />
</TabPanel>
<TabPanel value={tabState} index={3}>
<Controller
control={control}
name="area"
render={({ field, fieldState: { error } }) => (
<TollHouseArea value={field.value} onChange={field.onChange} error={error} />
)}
/>
</TabPanel>
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
@@ -163,7 +192,7 @@ const CreateTollHouseContent = ({ setOpen, SubmitCreateTollHouse, defaultValues
>
{tabState === 0 ? "بستن" : "مرحله قبل"}
</Button>
{tabState !== 2 && (
{tabState !== 3 && (
<Button
onClick={handleNext}
key={"handleNext"}
@@ -174,7 +203,7 @@ const CreateTollHouseContent = ({ setOpen, SubmitCreateTollHouse, defaultValues
مرحله بعد
</Button>
)}
{tabState === 2 && (
{tabState === 3 && (
<Button
variant="contained"
size="large"

View File

@@ -16,9 +16,9 @@ const MachineInfo = ({ control }) => {
<TextField
fullWidth
size="small"
label="ماشین آلات سبک"
label="تعداد ماشین آلات سبک"
value={value}
placeholder="ماشین آلات سبک را وارد کنید"
placeholder="تعداد ماشین آلات سبک را وارد کنید"
error={!!error}
helperText={error ? error.message : null}
variant="outlined"
@@ -48,9 +48,9 @@ const MachineInfo = ({ control }) => {
<TextField
fullWidth
size="small"
label="ماشین آلات نیمه سنگین"
label="تعداد ماشین آلات نیمه سنگین"
value={value}
placeholder="ماشین آلات نیمه سنگین را وارد کنید"
placeholder="تعداد ماشین آلات نیمه سنگین را وارد کنید"
error={!!error}
helperText={error ? error.message : null}
variant="outlined"
@@ -80,9 +80,9 @@ const MachineInfo = ({ control }) => {
<TextField
fullWidth
size="small"
label="ماشین آلات سنگین"
label="تعداد ماشین آلات سنگین"
value={value}
placeholder="ماشین آلات سنگین را وارد کنید"
placeholder="تعداد ماشین آلات سنگین را وارد کنید"
error={!!error}
helperText={error ? error.message : null}
variant="outlined"

View File

@@ -0,0 +1,80 @@
import LtrTextField from "@/core/components/LtrTextField";
import { Add, Close } from "@mui/icons-material";
import { Button, Collapse, Divider, IconButton, Stack, Typography } from "@mui/material";
import { TransitionGroup } from "react-transition-group";
const TollHouseArea = ({ value, onChange, error = [] }) => {
const handleChange = (index, field, newValue) => {
const newPoints = [...value];
newPoints[index] = { ...newPoints[index], [field]: newValue };
onChange(newPoints);
};
const handleAdd = () => {
onChange([...value, { lat: "", lon: "" }]);
};
const handleRemove = (index) => {
if (value.length > 4) {
const newPoints = value.filter((_, i) => i !== index);
onChange(newPoints);
}
};
return (
<TransitionGroup component={Stack} sx={{ my: 2 }}>
{value.map((point, index) => (
<Collapse key={index}>
<Stack direction={"row"} sx={{ my: 2 }} alignItems={"center"} spacing={1}>
<Typography>#{index + 1}</Typography>
{index >= 4 && (
<IconButton color="error" onClick={() => handleRemove(index)}>
<Close />
</IconButton>
)}
<Divider sx={{ flex: 1 }} />
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
<LtrTextField
autoComplete="off"
size="small"
label="عرض جغرافیایی (Latitude)"
sx={{ width: 200 }}
InputLabelProps={{ shrink: true }}
value={point.lat}
onChange={(e) => handleChange(index, "lat", e.target.value)}
error={Boolean(error[index]?.lat)}
helperText={error[index]?.lat?.message}
/>
<LtrTextField
autoComplete="off"
sx={{ width: 200 }}
size="small"
label="طول جغرافیایی (Longitude)"
InputLabelProps={{ shrink: true }}
value={point.lon}
onChange={(e) => handleChange(index, "lon", e.target.value)}
error={Boolean(error[index]?.lon)}
helperText={error[index]?.lon?.message}
/>
</Stack>
</Stack>
</Collapse>
))}
<Collapse>
<Stack direction={"row"} alignItems={"center"} sx={{ mt: 2 }}>
<Divider sx={{ flex: 1 }}>
<Button
variant="outlined"
startIcon={<Add />}
onClick={handleAdd}
sx={{ alignSelf: "flex-start" }}
>
افزودن نقطه
</Button>
</Divider>
</Stack>
</Collapse>
</TransitionGroup>
);
};
export default TollHouseArea;

View File

@@ -106,9 +106,9 @@ const TollHouseInfo = ({ control }) => {
value={field.value}
label="نوع"
selectors={[
{ id: 0, name: "داعمی" },
{ id: 1, name: "موقت" },
{ id: 2, name: "فصلی" },
{ id: 1, name: "دائمی" },
{ id: 2, name: "موقت" },
{ id: 3, name: "فصلی" },
]}
schema={{ name: "name", value: "id" }}
error={error}

View File

@@ -51,6 +51,12 @@ const CreateTollHouse = ({ mutate }) => {
});
formData.append("lat", result.start_point.lat);
formData.append("lng", result.start_point.lng);
formData.append("area[type]", "polygon");
result.area.map((point, pointIndex) => {
formData.append(`area[coordinates][${pointIndex}][0]`, point.lat);
formData.append(`area[coordinates][${pointIndex}][1]`, point.lon);
});
await requestServer(`${CREATE_TOLL_HOUSE}`, "post", {
data: formData,
})
@@ -83,6 +89,12 @@ const CreateTollHouse = ({ mutate }) => {
overview_files_4: null,
overview_files_5: null,
start_point: "",
area: [
{ lat: "", lon: "" },
{ lat: "", lon: "" },
{ lat: "", lon: "" },
{ lat: "", lon: "" },
],
};
return (
<>

View File

@@ -47,6 +47,12 @@ const EditController = ({ rowId, mutate, setOpenEditDialog }) => {
overview_files_3: tollHouseItemDetails?.files[2]?.path || null,
overview_files_4: tollHouseItemDetails?.files[3]?.path || null,
overview_files_5: tollHouseItemDetails?.files[4]?.path || null,
area: tollHouseItemDetails?.area?.coordinates.map(([lat, lon]) => ({ lat, lon })) || [
{ lat: "", lon: "" },
{ lat: "", lon: "" },
{ lat: "", lon: "" },
{ lat: "", lon: "" },
],
};
const HandleSubmit = async (result) => {
const fields = [
@@ -79,6 +85,11 @@ const EditController = ({ rowId, mutate, setOpenEditDialog }) => {
}
formData.append("lat", result.start_point.lat);
formData.append("lng", result.start_point.lng);
formData.append("area[type]", "polygon");
result.area.map((point, pointIndex) => {
formData.append(`area[coordinates][${pointIndex}][0]`, point.lat);
formData.append(`area[coordinates][${pointIndex}][1]`, point.lon);
});
await requestServer(`${UPDATE_TOLL_HOUSE_ITEM}/${rowId}`, "post", {
data: formData,

View File

@@ -12,6 +12,7 @@ import useEdaratLists from "@/lib/hooks/useEdaratLists";
import { usePermissions } from "@/lib/hooks/usePermissions";
import LocationForm from "./RowActions/LocationForm";
import ImageDialog from "./RowActions/ImageForm";
import { useAuth } from "@/lib/contexts/auth";
const statusOptions = [
{ value: "", label: "همه وضعیت ها" },
@@ -21,14 +22,51 @@ const statusOptions = [
const typeOptions = [
{ value: "", label: "همه نوع ها" },
{ value: 0, label: "فصلی" },
{ value: 1, label: "داعمی" },
{ value: 1, label: "دائمی" },
{ value: 2, label: "موقت" },
];
const TollHouseList = () => {
const { data: userPermissions } = usePermissions();
const hasProvincePermission = userPermissions.some((item) => ["add-tollhouse"].includes(item));
const hasCountryPermission = userPermissions?.includes("show-tollhouse");
const { user } = useAuth();
const columns = useMemo(() => {
const provinceColumn = {
accessorKey: "province_id",
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: ({ row }) => <>{row.original.province_name}</>,
};
return [
{
accessorKey: "id",
@@ -64,42 +102,7 @@ const TollHouseList = () => {
grow: false,
size: 100,
},
{
accessorKey: "province_id",
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: ({ row }) => <>{row.original.province_name}</>,
},
...(hasCountryPermission ? [provinceColumn] : []),
{
accessorKey: "city_id",
header: "شهرستان", // Office
@@ -107,17 +110,19 @@ const TollHouseList = () => {
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 [prevDependency, setPrevDependency] = useState(props.dependencyFieldValue.value);
const getColumnSelectOptions = useMemo(() => {
if (props.dependencyFieldValue.value === "") {
if (hasCountryPermission && props.dependencyFieldValue.value === "") {
return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
}
if (loadingEdaratList) {
@@ -135,10 +140,11 @@ const TollHouseList = () => {
];
}, [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]);
}, [props.dependencyFieldValue?.value, hasCountryPermission]);
return (
<CustomSelectByDependency
{...props}
@@ -194,6 +200,31 @@ const TollHouseList = () => {
return <Typography variant={"body2"}>{row.original.type_fa}</Typography>;
},
},
{
header: "موقعیت", // Location
id: "location",
enableColumnFilter: false,
enableSorting: false,
datatype: "array",
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue, row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<LocationForm start_lat={row.original.lat} start_lng={row.original.lng} />
</Stack>
);
},
},
{
accessorKey: "phone",
header: "تلفن راهدارخانه",
@@ -352,31 +383,6 @@ const TollHouseList = () => {
},
],
},
{
header: "موقعیت", // Location
id: "location",
enableColumnFilter: false,
enableSorting: false,
datatype: "array",
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue, row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<LocationForm start_lat={row.original.lat} start_lng={row.original.lng} />
</Stack>
);
},
},
{
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ ثبت",
@@ -402,7 +408,7 @@ const TollHouseList = () => {
sorting={[{ id: "id", desc: true }]}
TableToolbar={Toolbar}
enableRowActions
positionActionsColumn={"last"}
positionActionsColumn={"first"}
RowActions={RowActions}
/>
</Box>

View File

@@ -6,7 +6,18 @@ 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, label }) {
function MuiDatePicker({
value,
disableFuture = true,
setFieldValue,
name,
minDate,
maxDate,
helperText,
placeholder,
error,
label,
}) {
return (
<>
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
@@ -20,7 +31,7 @@ function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperTex
id={name}
name={name}
closeOnSelect
disableFuture
disableFuture={disableFuture}
label={label}
aria-describedby="component-helper-text"
onChange={(value) => {

View File

@@ -1,4 +1,119 @@
export const machineType = [
{ id: "آسفالت تراش", name_fa: "آسفالت تراش" },
{ id: "اتوبوس", name_fa: "اتوبوس" },
{ id: "اتومیکسر", name_fa: "اتومیکسر" },
{ id: "آمبولانس", name_fa: "آمبولانس" },
{ id: "اسپریدر کششی", name_fa: "اسپریدر کششی" },
{ id: "اسنوتراک", name_fa: "اسنوتراک" },
{ id: "بتونیر", name_fa: "بتونیر" },
{ id: "برفخور", name_fa: "برفخور" },
{ id: "برفخور لودری", name_fa: "برفخور لودری" },
{ id: "بلدوزر", name_fa: "بلدوزر" },
{ id: "بیل بکهو", name_fa: "بیل بکهو" },
{ id: "بیل مکانیکی", name_fa: "بیل مکانیکی" },
{ id: "بیل مکانیکی چرخ لاستیکی", name_fa: "بیل مکانیکی چرخ لاستیکی" },
{ id: "تانکر آبرسان", name_fa: "تانکر آبرسان" },
{ id: "تانکر آب", name_fa: "تانکر آب" },
{ id: "تانکر سوخت", name_fa: "تانکر سوخت" },
{ id: "تانکر قیر", name_fa: "تانکر قیر" },
{ id: "تراکتور", name_fa: "تراکتور" },
{ id: "تراگریدر", name_fa: "تراگریدر" },
{ id: "تریلر آبپاش", name_fa: "تریلر آبپاش" },
{ id: "تریلر بونکر", name_fa: "تریلر بونکر" },
{ id: "تریلر حمل قیر", name_fa: "تریلر حمل قیر" },
{ id: "تریلر سوخت رسان", name_fa: "تریلر سوخت رسان" },
{ id: "تریلر کارواش", name_fa: "تریلر کارواش" },
{ id: "تریلر کمپرسی", name_fa: "تریلر کمپرسی" },
{ id: "تیغ برفروب کامیونی", name_fa: "تیغ برفروب کامیونی" },
{ id: "تیغ برفروب لودری", name_fa: "تیغ برفروب لودری" },
{ id: "تیغه تراکتوری", name_fa: "تیغه تراکتوری" },
{ id: "جرثقیل", name_fa: "جرثقیل" },
{ id: "چکش هیدرولیکی", name_fa: "چکش هیدرولیکی" },
{ id: "رله تانک قیر", name_fa: "رله تانک قیر" },
{ id: "سواری بیابانی", name_fa: "سواری بیابانی" },
{ id: "سواری شهری", name_fa: "سواری شهری" },
{ id: "سنگ شکن", name_fa: "سنگ شکن" },
{ id: "سنگ شکن ثابت پارکر", name_fa: "سنگ شکن ثابت پارکر" },
{ id: "سنگ شکن سیار پارکر", name_fa: "سنگ شکن سیار پارکر" },
{ id: "سنگ شکن تلمیت", name_fa: "سنگ شکن تلمیت" },
{ id: "ضربه گیر پشت کامیونی", name_fa: "ضربه گیر پشت کامیونی" },
{ id: "غلتک", name_fa: "غلتک" },
{ id: "غلتک استاتیک", name_fa: "غلتک استاتیک" },
{ id: "غلتک پاچه بزی", name_fa: "غلتک پاچه بزی" },
{ id: "غلتک چرخ آهنی", name_fa: "غلتک چرخ آهنی" },
{ id: "غلتک چرخ لاستیکی", name_fa: "غلتک چرخ لاستیکی" },
{ id: "غلتک دستی", name_fa: "غلتک دستی" },
{ id: "غلتک کششی", name_fa: "غلتک کششی" },
{ id: "غلتک ویبره", name_fa: "غلتک ویبره" },
{ id: "غلطک دستی", name_fa: "غلطک دستی" },
{ id: "غلطک کششی", name_fa: "غلطک کششی" },
{ id: "فینیشر", name_fa: "فینیشر" },
{ id: "قیرپاش کششی", name_fa: "قیرپاش کششی" },
{ id: "کانتینر", name_fa: "کانتینر" },
{ id: "کانکس", name_fa: "کانکس" },
{ id: "کارخانه آسفالت", name_fa: "کارخانه آسفالت" },
{ id: "کارواش ثابت", name_fa: "کارواش ثابت" },
{ id: "کامیون آتشنشانی", name_fa: "کامیون آتشنشانی" },
{ id: "کامیون آب نمک پاش", name_fa: "کامیون آب نمک پاش" },
{ id: "کامیون آبپاش", name_fa: "کامیون آبپاش" },
{ id: "کامیون بالابر", name_fa: "کامیون بالابر" },
{ id: "کامیون باری", name_fa: "کامیون باری" },
{ id: "کامیون برفخور", name_fa: "کامیون برفخور" },
{ id: "کامیون برفروب", name_fa: "کامیون برفروب" },
{ id: "کامیون جاروب", name_fa: "کامیون جاروب" },
{ id: "کامیون جرثقیل", name_fa: "کامیون جرثقیل" },
{ id: "کامیون خط کش", name_fa: "کامیون خط کش" },
{ id: "کامیون فلاسک آسفالت", name_fa: "کامیون فلاسک آسفالت" },
{ id: "کامیون قیرپاش", name_fa: "کامیون قیرپاش" },
{ id: "کامیون کفی", name_fa: "کامیون کفی" },
{ id: "کامیون کمپرسی", name_fa: "کامیون کمپرسی" },
{ id: "کامیون کشنده", name_fa: "کامیون کشنده" },
{ id: "کامیون گاردریل شور", name_fa: "کامیون گاردریل شور" },
{ id: "کامیون میکسر", name_fa: "کامیون میکسر" },
{ id: "کامیون نمکپاش", name_fa: "کامیون نمکپاش" },
{ id: "کامیون کارواش", name_fa: "کامیون کارواش" },
{ id: "کامیونت", name_fa: "کامیونت" },
{ id: "کامیونت آتشنشانی", name_fa: "کامیونت آتشنشانی" },
{ id: "کامیونت بالابر", name_fa: "کامیونت بالابر" },
{ id: "کامیونت باری", name_fa: "کامیونت باری" },
{ id: "کامیونت تانکر آّب", name_fa: "کامیونت تانکر آّب" },
{ id: "کامیونت جرثقیل", name_fa: "کامیونت جرثقیل" },
{ id: "کامیونت جاروب", name_fa: "کامیونت جاروب" },
{ id: "کامیونت خط کش", name_fa: "کامیونت خط کش" },
{ id: "کامیونت سوخت رسان", name_fa: "کامیونت سوخت رسان" },
{ id: "کامیونت عملیات", name_fa: "کامیونت عملیات" },
{ id: "کامیونت کمپرسی", name_fa: "کامیونت کمپرسی" },
{ id: "کفی", name_fa: "کفی" },
{ id: "کمرشکن", name_fa: "کمرشکن" },
{ id: "کمپرسور", name_fa: "کمپرسور" },
{ id: "کوبیت ثابت", name_fa: "کوبیت ثابت" },
{ id: "گریدر", name_fa: "گریدر" },
{ id: "لکه گیر آسفالت", name_fa: "لکه گیر آسفالت" },
{ id: "لودر", name_fa: "لودر" },
{ id: "لیفتراک", name_fa: "لیفتراک" },
{ id: "ماسه ساز", name_fa: "ماسه ساز" },
{ id: "مینی بوس", name_fa: "مینی بوس" },
{ id: "مینی بیل", name_fa: "مینی بیل" },
{ id: "مینی لودر", name_fa: "مینی لودر" },
{ id: "مولد برق", name_fa: "مولد برق" },
{ id: "موتور جوش", name_fa: "موتور جوش" },
{ id: "موتور کارواش", name_fa: "موتور کارواش" },
{ id: "موتورسیکلت", name_fa: "موتورسیکلت" },
{ id: "نمک پاش", name_fa: "نمک پاش" },
{ id: "نمک پاش ثابت", name_fa: "نمک پاش ثابت" },
{ id: "وانت آتشنشانی", name_fa: "وانت آتشنشانی" },
{ id: "وانت تک کابین", name_fa: "وانت تک کابین" },
{ id: "وانت تک کابین بیابانی", name_fa: "وانت تک کابین بیابانی" },
{ id: "وانت تک کابین شهری", name_fa: "وانت تک کابین شهری" },
{ id: "وانت دو کابین شهری", name_fa: "وانت دو کابین شهری" },
{ id: "وانت دوکابین بیابانی", name_fa: "وانت دوکابین بیابانی" },
{ id: "ون", name_fa: "ون" },
{ id: "ویبراتور بتون", name_fa: "ویبراتور بتون" },
{ id: "پمپ کف کش", name_fa: "پمپ کف کش" },
{ id: "تراک میکسر", name_fa: "تراک میکسر" },
{ id: "ترانس جوش", name_fa: "ترانس جوش" },
{ id: "تجهیزات ثابت", name_fa: "تجهیزات ثابت" },
{ id: "دریل واگن", name_fa: "دریل واگن" },
{ id: "کامیون سرویس سیار", name_fa: "کامیون سرویس سیار" },
{ id: "میکسر آب نمک", name_fa: "میکسر آب نمک" },
];

View File

@@ -0,0 +1,9 @@
export const makeDateTime = (date, time) => {
if (!date) return null;
const d = new Date(date);
if (time) {
const t = new Date(time);
d.setHours(t.getHours(), t.getMinutes(), 0, 0);
}
return d;
};

View File

@@ -1,5 +1,5 @@
export const missionCategoryTypes = [
{ id: 1, name_fa: "فعالیت روزانه" },
{ id: 2, name_fa: "گشت راهداری" },
{ id: 3, name_fa: "واکنش سریع" },
// { id: 3, name_fa: "واکنش سریع" },
];

View File

@@ -580,7 +580,7 @@ export const pageMenuDev = [
type: "page",
route: "/dashboard/rahdaran",
icon: <HandymanIcon sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"],
permissions: ["show-rahdaran"],
},
{
id: "adminManagement",

View File

@@ -214,6 +214,7 @@ export const GET_ROAD_MISSIONS_OPERATOR_LIST = api + "/api/v3/missions/request_p
export const GET_ROAD_MISSIONS_TRANSPORTATION_LIST = api + "/api/v3/missions/transportation_unit";
export const GET_ROAD_MISSIONS_CONTROL_LIST = api + "/api/v3/missions/control_unit";
export const REQUEST_MISSION = api + "/api/v3/missions/request_portal";
export const REQUEST_MISSION_WITHOUT_PROCESS = api + "/api/v3/missions/request_portal/no_process";
export const UPDATE_REQUEST_MISSION = api + "/api/v3/missions/request_portal";
export const DELETE_REQUEST_MISSION = api + "/api/v3/missions/request_portal";
export const GET_RAHDARAN_BY_ID = api + "/api/v3/missions/details/rahdaran";