Merge branch 'release/v0.13.0'
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
NEXT_PUBLIC_VERSION="0.12.0"
|
||||
NEXT_PUBLIC_VERSION="0.13.0"
|
||||
NEXT_PUBLIC_API_URL="https://rms.witel.ir"
|
||||
NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map"
|
||||
@@ -1,10 +1,12 @@
|
||||
import AzmayeshPage from "@/components/dashboard/azmayesh";
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import ActivityCodeLog from "@/core/components/ActivityCodeLog";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission permission_name={["azmayesh-management"]}>
|
||||
<AzmayeshPage />
|
||||
<ActivityCodeLog activity_code={1157} />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import AzmayeshTypePage from "@/components/dashboard/azmayeshType";
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import ActivityCodeLog from "@/core/components/ActivityCodeLog";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission permission_name={["azmayesh-type-management"]}>
|
||||
<AzmayeshTypePage />
|
||||
<ActivityCodeLog activity_code={1161} />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import OperatorPage from "@/components/dashboard/roadItems/operator";
|
||||
import ActivityCodeLog from "@/core/components/ActivityCodeLog";
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission permission_name={["create-road-item"]}>
|
||||
<OperatorPage />
|
||||
<ActivityCodeLog activity_code={1152} />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import SupervisorPage from "@/components/dashboard/roadItems/supervisor";
|
||||
import ActivityCodeLog from "@/core/components/ActivityCodeLog";
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission
|
||||
permission_name={["show-road-item-supervise-cartable", "show-road-item-supervise-cartable-province"]}
|
||||
>
|
||||
<SupervisorPage />
|
||||
<ActivityCodeLog activity_code={1148} />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import OperatorPage from "@/components/dashboard/roadPatrols/operator";
|
||||
import ActivityCodeLog from "@/core/components/ActivityCodeLog";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission permission_name={["add-road-patrol"]}>
|
||||
<OperatorPage />
|
||||
<ActivityCodeLog activity_code={1145} />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import SupervisorPage from "@/components/dashboard/roadPatrols/supervisor";
|
||||
import ActivityCodeLog from "@/core/components/ActivityCodeLog";
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission permission_name={["show-road-patrol-supervise-cartable"]}>
|
||||
<SupervisorPage />
|
||||
<ActivityCodeLog activity_code={1143} />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { array, mixed, object, string } from "yup";
|
||||
import { array, mixed, number, object, string } from "yup";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { Box, Button, DialogActions, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
||||
@@ -15,7 +14,6 @@ import BeenhereIcon from "@mui/icons-material/Beenhere";
|
||||
import GetItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm";
|
||||
import GetSubItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm";
|
||||
import GetItemInfo from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo";
|
||||
import { CREATE_ROAD_ITEMS } from "@/core/utils/routes";
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index } = props;
|
||||
@@ -26,27 +24,30 @@ function TabPanel(props) {
|
||||
);
|
||||
}
|
||||
|
||||
const defaultValues = {
|
||||
itemId: null,
|
||||
itemSubId: null,
|
||||
amount: "",
|
||||
action_date: "",
|
||||
start_date: "",
|
||||
before_image: null,
|
||||
after_image: null,
|
||||
cmms_machines: null,
|
||||
rahdaran_id: null,
|
||||
start_point: "",
|
||||
end_point: "",
|
||||
};
|
||||
const validationSchema = object({
|
||||
itemId: string().required("نوع آیتم را مشخص کنید!"),
|
||||
itemSubId: string().required("موضوع مشاهده شده را مشخص کنید!"),
|
||||
item_id: number().required("نوع آیتم را مشخص کنید!"),
|
||||
sub_item_id: number().required("موضوع مشاهده شده را مشخص کنید!"),
|
||||
amount: string().required("وارد کردن مقدار الزامیست!"),
|
||||
cmms_machines: array().required("وارد کردن کد خودرو الزامیست!"),
|
||||
rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"), // بررسی حداقل یک آیتم,
|
||||
action_date: string().required("لطفا زمان فعالیت را انتخاب کنید!"),
|
||||
start_date: string().required("لطفاً تاریخ شروع فعالیت را انتخاب کنید!"),
|
||||
activity_time: string().test("activity-time-conditional", "لطفا زمان فعالیت را انتخاب کنید!", function (value) {
|
||||
const { is_gasht } = this.options.context; // دسترسی به context
|
||||
if (is_gasht) {
|
||||
return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد
|
||||
}
|
||||
return !!value; // در غیر این صورت مقدار فیلد باید وجود داشته باشد
|
||||
}),
|
||||
activity_date: string().test(
|
||||
"activity-date-conditional",
|
||||
"لطفاً تاریخ شروع فعالیت را انتخاب کنید!",
|
||||
function (value) {
|
||||
const { is_gasht } = this.options.context; // دسترسی به context
|
||||
if (is_gasht) {
|
||||
return true; // اگر is_gasht true باشد، این فیلد نیاز به اعتبارسنجی ندارد
|
||||
}
|
||||
return !!value; // در غیر این صورت مقدار فیلد باید وجود داشته باشد
|
||||
}
|
||||
),
|
||||
before_image: mixed()
|
||||
.nullable()
|
||||
.test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
|
||||
@@ -82,12 +83,26 @@ const validationSchema = object({
|
||||
}),
|
||||
});
|
||||
|
||||
const CreateFormContent = ({ setOpen, mutate, onSubmit }) => {
|
||||
const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
|
||||
const defaultValues = {
|
||||
item_id: null,
|
||||
sub_item_id: null,
|
||||
amount: "",
|
||||
before_image: null,
|
||||
after_image: null,
|
||||
cmms_machines: null,
|
||||
rahdaran_id: null,
|
||||
start_point: "",
|
||||
end_point: "",
|
||||
...(!is_gasht && { activity_time: "", activity_date: "" }),
|
||||
};
|
||||
|
||||
const [tabState, setTabState] = useState(0);
|
||||
const [itemsList, setItemsList] = useState();
|
||||
const [subItemsList, setSubItemsList] = useState([]);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
@@ -100,8 +115,8 @@ const CreateFormContent = ({ setOpen, mutate, onSubmit }) => {
|
||||
if (tabState === 2) {
|
||||
const fieldsToReset = [
|
||||
"amount",
|
||||
"action_date",
|
||||
"start_date",
|
||||
"activity_time",
|
||||
"activity_date",
|
||||
"before_image",
|
||||
"after_image",
|
||||
"cmms_machines",
|
||||
@@ -130,11 +145,59 @@ const CreateFormContent = ({ setOpen, mutate, onSubmit }) => {
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "onBlur",
|
||||
context: { subItemsList },
|
||||
context: { subItemsList, is_gasht },
|
||||
});
|
||||
const onSubmitBase = async (data) => {
|
||||
let result = { ...data };
|
||||
|
||||
if (result.before_image === null) {
|
||||
delete result.before_image;
|
||||
delete data.before_image;
|
||||
}
|
||||
|
||||
if (result.after_image === null) {
|
||||
delete result.after_image;
|
||||
delete data.after_image;
|
||||
}
|
||||
|
||||
if (result.end_point === "") {
|
||||
delete result.end_point;
|
||||
delete data.end_point;
|
||||
} else {
|
||||
result.end_point = `${result.end_point.lat},${result.end_point.lng}`;
|
||||
}
|
||||
|
||||
if (result.start_point === "") {
|
||||
delete result.start_point;
|
||||
delete data.start_point;
|
||||
} else {
|
||||
result.start_point = `${result.start_point.lat},${result.start_point.lng}`;
|
||||
}
|
||||
|
||||
result.rahdaran_id.forEach((rahdar, index) => {
|
||||
result[`rahdaran_id[${index}]`] = rahdar.id;
|
||||
});
|
||||
delete result.rahdaran_id;
|
||||
|
||||
result.cmms_machines.forEach((cmms_machine, index) => {
|
||||
result[`machines_id[${index}]`] = cmms_machine.id;
|
||||
});
|
||||
delete result.cmms_machines;
|
||||
|
||||
await onSubmit({
|
||||
result,
|
||||
data: {
|
||||
...data,
|
||||
item_name: itemsList.name,
|
||||
sub_item_name: subItemsList.name,
|
||||
unit_fa: subItemsList.unit,
|
||||
},
|
||||
});
|
||||
};
|
||||
console.log(errors);
|
||||
|
||||
return (
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)}>
|
||||
<StyledForm onSubmit={handleSubmit(onSubmitBase)} id="road_items">
|
||||
<Tabs
|
||||
allowScrollButtonsMobile
|
||||
value={tabState}
|
||||
@@ -142,7 +205,8 @@ const CreateFormContent = ({ setOpen, mutate, onSubmit }) => {
|
||||
variant={`${isMobile ? "scrollable" : "fullWidth"}`}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-around",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
backgroundColor: "#efefef",
|
||||
}}
|
||||
@@ -181,6 +245,7 @@ const CreateFormContent = ({ setOpen, mutate, onSubmit }) => {
|
||||
register={register}
|
||||
watch={watch}
|
||||
getValues={getValues}
|
||||
is_gasht={is_gasht}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Box>
|
||||
@@ -189,7 +254,7 @@ const CreateFormContent = ({ setOpen, mutate, onSubmit }) => {
|
||||
<Button
|
||||
onClick={handlePrev}
|
||||
variant="outlined"
|
||||
color="error"
|
||||
color="secondary"
|
||||
size="large"
|
||||
startIcon={tabState === 0 ? <ExitToAppIcon /> : <KeyboardDoubleArrowRightIcon />}
|
||||
>
|
||||
@@ -201,6 +266,7 @@ const CreateFormContent = ({ setOpen, mutate, onSubmit }) => {
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
type={"submit"}
|
||||
form="road_items"
|
||||
endIcon={<BeenhereIcon />}
|
||||
>
|
||||
{isSubmitting ? "در حال ثبت فعالیت" : "ثبت فعالیت"}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Stack, TextField, Grid } from "@mui/material";
|
||||
import { Grid, Stack } from "@mui/material";
|
||||
import ImageUpload from "./ImageUpload";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import MuiTimePicker from "@/core/components/MuiTimePicker";
|
||||
@@ -10,7 +10,7 @@ import RahdarCode from "@/core/components/RahdarCode";
|
||||
import PreviousStatesInfo from "./PreviousStatesInfo";
|
||||
import NumberField from "@/core/components/NumberField";
|
||||
|
||||
const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues }) => {
|
||||
const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues, is_gasht }) => {
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Stack>
|
||||
@@ -78,30 +78,32 @@ const GetItemInfo = ({ register, itemsList, subItemsList, control, setValue, err
|
||||
<ImageUpload setValue={setValue} control={control} errors={errors} getValues={getValues} />
|
||||
) : null}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<MuiDatePicker
|
||||
name="start_date"
|
||||
error={errors.start_date ? errors.start_date.message : null}
|
||||
value={watch("start_date")}
|
||||
placeholder={"تاریخ شروع فعالیت"}
|
||||
setFieldValue={setValue}
|
||||
helperText={errors.start_date ? errors.start_date.message : null}
|
||||
/>
|
||||
{!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={"تاریخ شروع فعالیت"}
|
||||
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={"زمان فعالیت"}
|
||||
setFieldValue={setValue}
|
||||
helperText={errors.activity_time ? errors.activity_time.message : null}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<MuiTimePicker
|
||||
value={watch("action_date")}
|
||||
name="action_date"
|
||||
error={errors.action_date ? errors.action_date.message : null}
|
||||
placeholder={"زمان فعالیت"}
|
||||
setFieldValue={setValue}
|
||||
helperText={errors.action_date ? errors.action_date.message : null}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Stack>
|
||||
) : null}
|
||||
<Stack>
|
||||
{subItemsList?.needs_end_point === 1 ? (
|
||||
<MapInfoTwoMarker setValue={setValue} errors={errors} />
|
||||
|
||||
@@ -28,8 +28,8 @@ const GetItemsForm = ({ setValue, setItemsList, tabState, setTabState }) => {
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
setValue("itemId", item.id);
|
||||
setItemsList(item.name);
|
||||
setValue("item_id", item.id);
|
||||
setItemsList(item);
|
||||
setTabState(tabState + 1);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Card, CardActionArea, Grid, LinearProgress, Typography } from "@mui/mat
|
||||
import useRoadItemGetSubItems from "@/lib/hooks/useRoadItemGetISubtems";
|
||||
|
||||
const GetSubItemsForm = ({ setValue, watch, setSubItemsList, tabState, setTabState }) => {
|
||||
const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetSubItems(watch("itemId"));
|
||||
const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetSubItems(watch("item_id"));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -27,7 +27,7 @@ const GetSubItemsForm = ({ setValue, watch, setSubItemsList, tabState, setTabSta
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
setValue("itemSubId", item.sub_item);
|
||||
setValue("sub_item_id", item.sub_item);
|
||||
setSubItemsList(item);
|
||||
setTabState(tabState + 1);
|
||||
}}
|
||||
|
||||
@@ -15,15 +15,31 @@ const ImageUpload = ({ control, setValue, errors, getValues, beforeImage = null,
|
||||
const [showAfterImage, setShowAfterImage] = useState(!afterImage);
|
||||
|
||||
useEffect(() => {
|
||||
if (getValues("before_image")) {
|
||||
const beforeImage = getValues("before_image");
|
||||
const afterImage = getValues("after_image");
|
||||
|
||||
if (beforeImage) {
|
||||
setShowBeforeImage(false);
|
||||
setBeforeImg(getValues("before_image"));
|
||||
setBeforeFileType("image/");
|
||||
|
||||
if (typeof beforeImage === "string") {
|
||||
setBeforeImg(beforeImage);
|
||||
setBeforeFileType("image/");
|
||||
} else if (beforeImage instanceof File) {
|
||||
setBeforeImg(URL.createObjectURL(beforeImage));
|
||||
setBeforeFileType(beforeImage.type);
|
||||
}
|
||||
}
|
||||
if (getValues("after_image")) {
|
||||
|
||||
if (afterImage) {
|
||||
setShowAfterImage(false);
|
||||
setAfterImg(getValues("after_image"));
|
||||
setAfterFileType("image/");
|
||||
|
||||
if (typeof afterImage === "string") {
|
||||
setAfterImg(afterImage);
|
||||
setAfterFileType("image/");
|
||||
} else if (afterImage instanceof File) {
|
||||
setAfterImg(URL.createObjectURL(afterImage));
|
||||
setAfterFileType(afterImage.type);
|
||||
}
|
||||
}
|
||||
}, [getValues]);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const PreviousStatesInfo = ({ itemsList, subItemsList }) => {
|
||||
</Box>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, color: "grey.800" }}>
|
||||
{itemsList || "هیچ آیتمی انتخاب نشده است"}
|
||||
{itemsList.name || "هیچ آیتمی انتخاب نشده است"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={12} sx={{ display: "flex", alignItems: "center" }}>
|
||||
|
||||
@@ -6,22 +6,11 @@ import useRequest from "@/lib/hooks/useRequest";
|
||||
|
||||
const OperatorCreateForm = ({ open, setOpen, mutate, rowId }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const HandleSubmit = async (data) => {
|
||||
let endPoint;
|
||||
let startPoint = `${data.start_point.lat},${data.start_point.lng}`;
|
||||
data.end_point !== "" && (endPoint = `${data.end_point.lat},${data.end_point.lng}`);
|
||||
const HandleSubmit = async ({ result }) => {
|
||||
const formData = new FormData();
|
||||
data.rahdaran_id.map((rahdar, index) => formData.append(`rahdaran_id[${index}]`, rahdar.id));
|
||||
formData.append("item_id", data.itemId);
|
||||
formData.append("sub_item_id", data.itemSubId);
|
||||
formData.append("amount", data.amount);
|
||||
formData.append("activity_time", data.action_date);
|
||||
formData.append("activity_date", data.start_date);
|
||||
data.cmms_machines.map((machine, index) => formData.append(`machines_id[${index}]`, machine.id));
|
||||
data.before_image !== null && formData.append("before_image", data.before_image);
|
||||
data.after_image !== null && formData.append("after_image", data.after_image);
|
||||
data.start_point !== "" && formData.append("start_point", startPoint);
|
||||
data.end_point !== "" && formData.append("end_point", endPoint);
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
formData.append(key, value);
|
||||
}
|
||||
await requestServer(CREATE_ROAD_ITEMS, "post", {
|
||||
data: formData,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Button, Dialog, DialogActions, DialogTitle } from "@mui/material";
|
||||
import GashtCreateContent from "./GashtCreateContent";
|
||||
|
||||
const GashtCreate = ({ open, setOpen, mutate, rowId }) => {
|
||||
return (
|
||||
<Dialog open={open} fullWidth maxWidth="md">
|
||||
<GashtCreateContent mutate={mutate} setOpen={setOpen} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default GashtCreate;
|
||||
@@ -0,0 +1,163 @@
|
||||
import { Box, Button, DialogActions, DialogContent, Tab, Tabs } from "@mui/material";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
|
||||
import FileCopyIcon from "@mui/icons-material/FileCopy";
|
||||
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
|
||||
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
|
||||
import BeenhereIcon from "@mui/icons-material/Beenhere";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import React, { useState } from "react";
|
||||
import SearchItemInfo from "./SearchItemInfo";
|
||||
import moment from "jalali-moment";
|
||||
import GetItemInfo from "./RowActions/GetItemInfo";
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index } = props;
|
||||
return (
|
||||
<div role="tabpanel" hidden={value !== index}>
|
||||
{value === index && <Box>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const defaultValues = {
|
||||
start_date: moment(new Date()).format("YYYY-MM-DD"),
|
||||
end_date: moment(new Date()).format("YYYY-MM-DD"),
|
||||
item_id: "",
|
||||
road_patrol_id: "",
|
||||
};
|
||||
|
||||
const defaultFilter = [
|
||||
{ value: `${defaultValues.item_id}`, datatype: "text", id: "item_id", fn: "equals" },
|
||||
{ value: `${defaultValues.road_patrol_id}`, datatype: "text", id: "road_patrol_id", fn: "equals" },
|
||||
{ value: `${defaultValues.start_date}`, datatype: "date", id: "roadPatrol.start_time", fn: "equals" },
|
||||
{ value: `${defaultValues.end_date}`, datatype: "date", id: "roadPatrol.end_time", fn: "equals" },
|
||||
];
|
||||
|
||||
const GashtCreateContent = ({ mutate, setOpen }) => {
|
||||
const [tabState, setTabState] = useState(0);
|
||||
const [itemInfo, setItemInfo] = useState(null);
|
||||
const [submitCompleteForm, setSubmitCompleteForm] = useState(false);
|
||||
const [specialFilter, setSpecialFilter] = useState(defaultFilter);
|
||||
|
||||
const validationSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
start_date: yup.string().nullable(),
|
||||
end_date: yup.string().nullable(),
|
||||
item_id: yup.string().nullable(),
|
||||
road_patrol_id: yup.string().nullable(),
|
||||
})
|
||||
.test("at-least-one", "وارد کردن مقدار ضروریست!!!", (values) => {
|
||||
return (!!values.start_date && !!values.end_date) || !!values.item_id || !!values.road_patrol_id;
|
||||
});
|
||||
|
||||
const handleChangeTab = (event, newValue) => {
|
||||
setTabState(newValue);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handlePrev = () => {
|
||||
if (tabState === 0) {
|
||||
handleClose();
|
||||
} else {
|
||||
setTabState(tabState - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
watch,
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "onBlur",
|
||||
});
|
||||
const onSearchSubmit = async (data) => {
|
||||
setSpecialFilter([
|
||||
{ value: `${data.item_id}`, datatype: "text", id: "item_id", fn: "equals" },
|
||||
{ value: `${data.road_patrol_id}`, datatype: "text", id: "road_patrol_id", fn: "equals" },
|
||||
{ value: `${data.start_date}`, datatype: "date", id: "roadPatrol.start_time", fn: "equals" },
|
||||
{ value: `${data.end_date}`, datatype: "date", id: "roadPatrol.end_time", fn: "equals" },
|
||||
]);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
allowScrollButtonsMobile
|
||||
value={tabState}
|
||||
onChange={handleChangeTab}
|
||||
variant="fullWidth"
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
backgroundColor: "#efefef",
|
||||
}}
|
||||
>
|
||||
<Tab icon={<InsertDriveFileIcon />} label="انتخاب مورد مشاهده شده" />
|
||||
<Tab disabled={tabState === 0} icon={<FileCopyIcon />} label="اطلاعات مورد مشاهده شده" />
|
||||
</Tabs>
|
||||
<DialogContent dividers sx={{ overflowY: "auto", maxHeight: "70vh" }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TabPanel value={tabState} index={0}>
|
||||
<StyledForm onSubmit={handleSubmit(onSearchSubmit)}>
|
||||
<SearchItemInfo
|
||||
isSubmitting={isSubmitting}
|
||||
specialFilter={specialFilter}
|
||||
watch={watch}
|
||||
errors={errors}
|
||||
setValue={setValue}
|
||||
register={register}
|
||||
setTabState={setTabState}
|
||||
setItemInfo={setItemInfo}
|
||||
/>
|
||||
</StyledForm>
|
||||
</TabPanel>
|
||||
<TabPanel value={tabState} index={1}>
|
||||
<GetItemInfo
|
||||
mutate={mutate}
|
||||
setOpen={setOpen}
|
||||
setSubmitCompleteForm={setSubmitCompleteForm}
|
||||
itemInfo={itemInfo}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
|
||||
<Button
|
||||
onClick={handlePrev}
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="large"
|
||||
startIcon={tabState === 0 ? <ExitToAppIcon /> : <KeyboardDoubleArrowRightIcon />}
|
||||
>
|
||||
{tabState === 0 ? "بستن" : "مرحله قبل"}
|
||||
</Button>
|
||||
{tabState === 1 && (
|
||||
<Button
|
||||
form={"CompleteItemForm"}
|
||||
type={"submit"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={submitCompleteForm}
|
||||
endIcon={<BeenhereIcon />}
|
||||
>
|
||||
{submitCompleteForm ? "درحال ثبت فعالیت..." : "ثبت فعالیت"}
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default GashtCreateContent;
|
||||
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import ShowLocationMarker from "@/core/components/ShowLocationMarker";
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Box sx={{ width: "400px", height: "400px" }}>
|
||||
<MapLayer>
|
||||
<ShowLocationMarker start_lat={start_lat} start_lng={start_lng} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||
<Button onClick={() => setOpenLocationDialog(false)} variant="contained" color="error">
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocationFormContent;
|
||||
@@ -0,0 +1,36 @@
|
||||
import React, { useState } from "react";
|
||||
import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
|
||||
import ExploreIcon from "@mui/icons-material/Explore";
|
||||
import LocationFormContent from "./LocationFormContent";
|
||||
const LocationForm = ({ start_lat, start_lng }) => {
|
||||
const [openLocationDialog, setOpenLocationDialog] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="موقعیت">
|
||||
<IconButton color="primary" onClick={() => setOpenLocationDialog(true)}>
|
||||
<ExploreIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
open={openLocationDialog}
|
||||
onClose={() => setOpenLocationDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle>موقعیت</DialogTitle>
|
||||
<LocationFormContent
|
||||
start_lat={start_lat}
|
||||
start_lng={start_lng}
|
||||
setOpenLocationDialog={setOpenLocationDialog}
|
||||
/>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default LocationForm;
|
||||
@@ -0,0 +1,231 @@
|
||||
import { Button, DialogActions, Grid, Stack } from "@mui/material";
|
||||
import PreviousStatesInfo from "./PreviousStatesInfo";
|
||||
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 ImageUpload from "@/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/RowActions/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 { yupResolver } from "@hookform/resolvers/yup";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import { array, mixed, number, object, string } from "yup";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import BeenhereIcon from "@mui/icons-material/Beenhere";
|
||||
import React, { useEffect } from "react";
|
||||
import { CREATE_ROAD_ITEMS } from "@/core/utils/routes";
|
||||
|
||||
const CompleteItem = ({ subItem, itemInfo, setSubmitCompleteForm, mutate, setOpen }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
|
||||
const defaultValues = {
|
||||
item_id: itemInfo?.item_id || null,
|
||||
sub_item_id: itemInfo?.sub_item_id || null,
|
||||
amount: "",
|
||||
activity_time: "",
|
||||
activity_date: "",
|
||||
before_image: null,
|
||||
after_image: null,
|
||||
cmms_machines: null,
|
||||
rahdaran_id: null,
|
||||
start_point: { lat: itemInfo?.start_lat || "", lng: itemInfo?.start_lon || "" },
|
||||
end_point: { lat: itemInfo?.end_lat || "", lng: itemInfo?.end_lon || "" },
|
||||
};
|
||||
|
||||
const validationSchema = object({
|
||||
item_id: number().required("نوع آیتم را مشخص کنید!"),
|
||||
sub_item_id: number().required("موضوع مشاهده شده را مشخص کنید!"),
|
||||
amount: string().required("وارد کردن مقدار الزامیست!"),
|
||||
cmms_machines: array().required("وارد کردن کد خودرو الزامیست!"),
|
||||
rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"), // بررسی حداقل یک آیتم,
|
||||
activity_time: string().required("لطفا زمان فعالیت را انتخاب کنید!"),
|
||||
activity_date: string().required("لطفاً تاریخ شروع فعالیت را انتخاب کنید!"),
|
||||
before_image: mixed()
|
||||
.nullable()
|
||||
.test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsImage = subItem?.needs_image === 1;
|
||||
if (needsImage) {
|
||||
return !!value;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
after_image: mixed()
|
||||
.nullable()
|
||||
.test("after-image-required", "لطفا عکس بعد از اقدام را بارگذاری کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsImage = subItem?.needs_image === 1;
|
||||
if (needsImage) {
|
||||
return !!value;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
start_point: mixed()
|
||||
.test("start-point-required", "لطفاً نقطه شروع را مشخص کنید!", function (value) {
|
||||
return !!value; // چک میکند که مقدار موجود است
|
||||
})
|
||||
.required("لطفاً نقطه شروع را مشخص کنید!"),
|
||||
end_point: mixed().test("end-point-required", "لطفاً نقطه پایان را مشخص کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsEndPoint = subItem?.needs_end_point === 1;
|
||||
if (needsEndPoint) {
|
||||
return !!value; // چک میکند که مقدار موجود است
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
watch,
|
||||
getValues,
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
resetField,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "onBlur",
|
||||
context: { subItem },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setSubmitCompleteForm(isSubmitting);
|
||||
}, [isSubmitting]);
|
||||
const onSubmit = async (data) => {
|
||||
let endPoint;
|
||||
let startPoint = `${data.start_point.lat},${data.start_point.lng}`;
|
||||
if (subItem.needs_end_point === 1) {
|
||||
endPoint = `${data.end_point.lat},${data.end_point.lng}`;
|
||||
}
|
||||
const formData = new FormData();
|
||||
data.after_image !== null && formData.append("after_image", data.after_image);
|
||||
data.before_image !== null && formData.append("before_image", data.before_image);
|
||||
subItem.needs_end_point === 1 && formData.append("end_point", endPoint);
|
||||
formData.append("start_point", startPoint);
|
||||
formData.append("activity_time", data.activity_time);
|
||||
formData.append("activity_date", data.activity_date);
|
||||
formData.append("amount", data.amount);
|
||||
formData.append("item_id", data.item_id);
|
||||
formData.append("sub_item_id", data.sub_item_id);
|
||||
data.cmms_machines.forEach((machine, index) => formData.append(`machines_id[${index}]`, machine.id));
|
||||
data.rahdaran_id.forEach((rahdar, index) => formData.append(`rahdaran_id[${index}]`, rahdar.id));
|
||||
|
||||
await requestServer(CREATE_ROAD_ITEMS, "post", {
|
||||
data: formData,
|
||||
})
|
||||
.then((response) => {
|
||||
mutate();
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((error) => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)} id={"CompleteItemForm"}>
|
||||
<Stack spacing={2}>
|
||||
<Stack>
|
||||
<PreviousStatesInfo itemInfo={itemInfo} />
|
||||
</Stack>
|
||||
<Stack>
|
||||
<NumberField
|
||||
{...register("amount")}
|
||||
label={`مقدار (${subItem.unit})`}
|
||||
error={!!errors.amount}
|
||||
value={watch("amount")}
|
||||
type="text"
|
||||
size={"small"}
|
||||
unit={itemInfo.unit}
|
||||
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>
|
||||
<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>
|
||||
<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_image ? (
|
||||
<ImageUpload setValue={setValue} control={control} errors={errors} getValues={getValues} />
|
||||
) : null}
|
||||
</Stack>
|
||||
<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={"تاریخ شروع فعالیت"}
|
||||
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={"زمان فعالیت"}
|
||||
setFieldValue={setValue}
|
||||
helperText={errors.activity_time ? errors.activity_time.message : null}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
<Stack>
|
||||
{subItem?.needs_end_point === 1 ? (
|
||||
<MapInfoTwoMarker setValue={setValue} errors={errors} />
|
||||
) : (
|
||||
<MapInfoOneMarker setValue={setValue} errors={errors} />
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
export default CompleteItem;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { LinearProgress, Typography } from "@mui/material";
|
||||
import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems";
|
||||
import CompleteItem from "./CompleteItem";
|
||||
|
||||
const GetItemInfo = ({ itemInfo, setSubmitCompleteForm, mutate, setOpen }) => {
|
||||
const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetISubtems(itemInfo?.item_id);
|
||||
const subItem = subItemsList.find((SubItem) => SubItem.sub_item === itemInfo?.sub_item_id);
|
||||
return (
|
||||
<>
|
||||
{loadingSubItemsList ? (
|
||||
<LinearProgress />
|
||||
) : errorSubItemsList ? (
|
||||
<Typography color={"error.main"} textAlign={"center"}>
|
||||
خطا در دریافت اطلاعات!!!
|
||||
</Typography>
|
||||
) : (
|
||||
<CompleteItem
|
||||
setOpen={setOpen}
|
||||
mutate={mutate}
|
||||
setSubmitCompleteForm={setSubmitCompleteForm}
|
||||
subItem={subItem}
|
||||
itemInfo={itemInfo}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default GetItemInfo;
|
||||
@@ -0,0 +1,131 @@
|
||||
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(() => {
|
||||
if (getValues("before_image")) {
|
||||
setShowBeforeImage(false);
|
||||
setBeforeImg(getValues("before_image"));
|
||||
setBeforeFileType("image/");
|
||||
}
|
||||
if (getValues("after_image")) {
|
||||
setShowAfterImage(false);
|
||||
setAfterImg(getValues("after_image"));
|
||||
setAfterFileType("image/");
|
||||
}
|
||||
}, [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: "large" }} 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: "large" }} 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;
|
||||
@@ -0,0 +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 = ({ itemInfo }) => {
|
||||
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" }}>
|
||||
{itemInfo.item_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" }}>
|
||||
{itemInfo?.sub_item_name || "هیچ موضوعی انتخاب نشده است"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
export default PreviousStatesInfo;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { Tooltip, IconButton } from "@mui/material";
|
||||
import UndoIcon from "@mui/icons-material/Undo";
|
||||
const SubItemForm = ({ row, setTabState, setItemInfo }) => {
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="انتخاب این مورد">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setTabState(1);
|
||||
setItemInfo(row.original);
|
||||
}}
|
||||
>
|
||||
<UndoIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SubItemForm;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Box } from "@mui/material";
|
||||
import SubItemForm from "./SubItemForm";
|
||||
|
||||
const RowActions = ({ row, mutate, setTabState, setItemInfo }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<SubItemForm row={row} setTabState={setTabState} setItemInfo={setItemInfo} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default RowActions;
|
||||
@@ -0,0 +1,42 @@
|
||||
import { LinearProgress, Typography } from "@mui/material";
|
||||
import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems";
|
||||
import React from "react";
|
||||
import SearchItems from "./SearchItems";
|
||||
import TableInfo from "./TableInfo";
|
||||
|
||||
const SearchItemInfo = ({
|
||||
watch,
|
||||
errors,
|
||||
setValue,
|
||||
register,
|
||||
specialFilter,
|
||||
setTabState,
|
||||
setItemInfo,
|
||||
isSubmitting,
|
||||
}) => {
|
||||
const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems();
|
||||
return (
|
||||
<>
|
||||
{loadingItemsList ? (
|
||||
<LinearProgress />
|
||||
) : errorItemsList ? (
|
||||
<Typography textAlign={"center"} color={"error.main"}>
|
||||
خطا در دریافت اطلاعات!!!
|
||||
</Typography>
|
||||
) : (
|
||||
<>
|
||||
<SearchItems
|
||||
isSubmitting={isSubmitting}
|
||||
register={register}
|
||||
setValue={setValue}
|
||||
errors={errors}
|
||||
watch={watch}
|
||||
itemsList={itemsList}
|
||||
/>
|
||||
<TableInfo specialFilter={specialFilter} setTabState={setTabState} setItemInfo={setItemInfo} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SearchItemInfo;
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Button, FormControl, FormHelperText, InputLabel, MenuItem, Select, Stack, TextField } from "@mui/material";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import React from "react";
|
||||
|
||||
const SearchItems = ({ setValue, itemsList, watch, errors, register, isSubmitting }) => {
|
||||
const disabledButton =
|
||||
(!!watch("start_date") && !!watch("end_date")) || !!watch("item_id") || !!watch("road_patrol_id");
|
||||
|
||||
return (
|
||||
<Stack display={"flex"} sx={{ pb: 2 }} direction={"row"} spacing={1} alignItems={"center"}>
|
||||
<Stack sx={{ flex: 2 }}>
|
||||
<MuiDatePicker
|
||||
name="start_date"
|
||||
error={errors.start_date ? errors.start_date.message : null}
|
||||
value={watch("start_date")}
|
||||
placeholder={"از تاریخ"}
|
||||
setFieldValue={setValue}
|
||||
helperText={errors.start_date ? errors.start_date.message : null}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack sx={{ flex: 2 }}>
|
||||
<MuiDatePicker
|
||||
name="end_date"
|
||||
minDate={watch("start_date")}
|
||||
error={errors.end_date ? errors.end_date.message : null}
|
||||
value={watch("end_date")}
|
||||
placeholder={"تا تاریخ"}
|
||||
setFieldValue={setValue}
|
||||
helperText={errors.end_date ? errors.end_date.message : null}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack sx={{ flex: 1.5 }}>
|
||||
<FormControl error={!!errors.item_id} size="small" fullWidth>
|
||||
<InputLabel id="demo-error-label">آیتم فعالیت</InputLabel>
|
||||
<Select
|
||||
labelId="demo-error-label"
|
||||
id="item_id"
|
||||
name="item_id"
|
||||
value={watch("item_id") || ""}
|
||||
label="آیتم فعالیت"
|
||||
onChange={(event) => setValue("item_id", event.target.value)}
|
||||
>
|
||||
<MenuItem value={""}>حذف</MenuItem>
|
||||
{itemsList.map((item) => (
|
||||
<MenuItem key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{/*{errors.item_id ? (*/}
|
||||
{/* <FormHelperText>*/}
|
||||
{/* errors.item_id.message*/}
|
||||
{/* </FormHelperText>*/}
|
||||
{/*) : null}*/}
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<Stack sx={{ flex: 1 }}>
|
||||
<TextField
|
||||
{...register("road_patrol_id")}
|
||||
label="کد یکتا گشت"
|
||||
error={!!errors.road_patrol_id}
|
||||
value={watch("road_patrol_id")}
|
||||
type="tel"
|
||||
size={"small"}
|
||||
inputProps={{
|
||||
inputMode: "number",
|
||||
min: 0,
|
||||
pattern: "[0-9]*",
|
||||
}}
|
||||
onChange={(event) => {
|
||||
if (!isNaN(event.target.value)) {
|
||||
setValue("road_patrol_id", event.target.value);
|
||||
} else {
|
||||
setValue("road_patrol_id", watch("road_patrol_id"));
|
||||
}
|
||||
}}
|
||||
helperText={errors.road_patrol_id ? errors.road_patrol_id.message : null}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="medium"
|
||||
type={"submit"}
|
||||
disabled={!disabledButton || isSubmitting}
|
||||
endIcon={<SearchIcon />}
|
||||
>
|
||||
{isSubmitting ? "درحال دریافت اطلاعات" : "جستجو"}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default SearchItems;
|
||||
@@ -0,0 +1,115 @@
|
||||
import { useMemo } from "react";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import RowActions from "./RowActions";
|
||||
import { GET_OBSERVED_GASHT_LIST } from "@/core/utils/routes";
|
||||
import LocationForm from "./LocationForm";
|
||||
|
||||
const TableInfo = ({ specialFilter, setTabState, setItemInfo }) => {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "road_patrol_id",
|
||||
header: "کد یکتا گشت",
|
||||
id: "road_patrol_id",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "priority_fa",
|
||||
header: "اولویت",
|
||||
id: "priority_fa",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 80,
|
||||
},
|
||||
{
|
||||
accessorKey: "local_name",
|
||||
header: "نام محلی",
|
||||
id: "local_name",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "item_name",
|
||||
header: "نام ایتم",
|
||||
id: "item_name",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "sub_item_name",
|
||||
header: "موضوع مشاهده شده",
|
||||
id: "sub_item_name",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "location",
|
||||
header: "موقعیت", // Location
|
||||
id: "location",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "array",
|
||||
grow: false,
|
||||
size: 50,
|
||||
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.start_lat} start_lng={row.original.start_lon} />
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
table_title={"لیست موارد مشاهده شده"}
|
||||
columns={columns}
|
||||
specialFilter={specialFilter}
|
||||
table_url={GET_OBSERVED_GASHT_LIST}
|
||||
page_name={"operator"}
|
||||
table_name={"Observed_Gasht_List"}
|
||||
enableRowActions
|
||||
positionActionsColumn={"last"}
|
||||
RowActions={(props) => (
|
||||
<RowActions {...props} setTabState={setTabState} setItemInfo={setItemInfo} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default TableInfo;
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
import { Button, IconButton, useMediaQuery } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState } from "react";
|
||||
import AddRoadIcon from "@mui/icons-material/AddRoad";
|
||||
import GashtCreate from "./GashtCreate";
|
||||
|
||||
const ObservedGashtCreate = ({ mutate }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton
|
||||
aria-label="ثبت فعالیت برای موارد مشاهده شده در گشت راهداری"
|
||||
color="primary"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<AddRoadIcon sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button
|
||||
size={"small"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddRoadIcon />}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
ثبت فعالیت برای موارد مشاهده شده در گشت راهداری
|
||||
</Button>
|
||||
)}
|
||||
{open && <GashtCreate open={open} setOpen={setOpen} mutate={mutate} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ObservedGashtCreate;
|
||||
@@ -193,7 +193,7 @@ const OperatorList = () => {
|
||||
},
|
||||
{
|
||||
accessorKey: "cmms_machines",
|
||||
header: "کد خودرو", // Car ID
|
||||
header: "خودرو ها", // Car ID
|
||||
id: "cmmsMachines__machine_code",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
@@ -241,20 +241,17 @@ const OperatorList = () => {
|
||||
},
|
||||
},
|
||||
Cell: ({ renderedCellValue }) => {
|
||||
if (renderedCellValue) {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<RahdaranDialog rahdarLists={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return <Typography variant="body2">بدون شخص</Typography>;
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<RahdaranDialog rahdarLists={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ ثبت", // Register Date
|
||||
id: "created_at",
|
||||
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ فعالیت", // Start Date
|
||||
id: "activity_date_time",
|
||||
enableColumnFilter: true,
|
||||
datatype: "date",
|
||||
filterMode: "equals",
|
||||
@@ -263,9 +260,9 @@ const OperatorList = () => {
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ فعالیت", // Start Date
|
||||
id: "activity_date_time",
|
||||
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ ثبت", // Register Date
|
||||
id: "created_at",
|
||||
enableColumnFilter: true,
|
||||
datatype: "date",
|
||||
filterMode: "equals",
|
||||
@@ -296,6 +293,7 @@ const OperatorList = () => {
|
||||
header: "توضیحات کارشناس", // Description
|
||||
id: "supervisor_description",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
@@ -311,11 +309,14 @@ const OperatorList = () => {
|
||||
},
|
||||
},
|
||||
Cell: ({ renderedCellValue }) => {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<DescriptionForm description={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
if (renderedCellValue) {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<DescriptionForm description={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return <Typography variant="body2">بدون توضیحات</Typography>;
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -37,7 +37,7 @@ const DescriptionForm = ({ description }) => {
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Card sx={{ padding: 2, boxShadow: 3, borderRadius: 2 }}>
|
||||
<Card elevation={0}>
|
||||
<CardContent>
|
||||
<Typography variant="body2">{description || "هیچ توضیحی موجود نیست"} </Typography>
|
||||
</CardContent>
|
||||
|
||||
@@ -1,29 +1,54 @@
|
||||
import { LinearProgress, Typography } from "@mui/material";
|
||||
import { Dialog, DialogTitle } from "@mui/material";
|
||||
import EditFormContent from "./EditFormContent";
|
||||
import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems";
|
||||
import { UPDATE_ROAD_ITEMS } from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
|
||||
const EditController = ({ rowId, mutate, setOpenEditDialog, row }) => {
|
||||
const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetISubtems(row.original?.item);
|
||||
const subItem = subItemsList.find((SubItem) => SubItem.sub_item === row.original?.sub_item);
|
||||
const EditController = ({ rowId, mutate, setOpenEditDialog, openEditDialog, row }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
|
||||
const HandleSubmit = async ({ result }) => {
|
||||
const formData = new FormData();
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
formData.append(key, value);
|
||||
}
|
||||
await requestServer(`${UPDATE_ROAD_ITEMS}/${rowId}`, "post", {
|
||||
data: formData,
|
||||
})
|
||||
.then((response) => {
|
||||
mutate();
|
||||
setOpenEditDialog(false);
|
||||
})
|
||||
.catch((error) => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{errorSubItemsList ? (
|
||||
<Typography color={"error"} textAlign={"center"}>
|
||||
خطا در دریافت اطلاعات!!!
|
||||
</Typography>
|
||||
) : loadingSubItemsList ? (
|
||||
<LinearProgress />
|
||||
) : (
|
||||
<EditFormContent
|
||||
subItem={subItem}
|
||||
rowId={rowId}
|
||||
row={row}
|
||||
mutate={mutate}
|
||||
setOpenEditDialog={setOpenEditDialog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<Dialog
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
open={openEditDialog}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>ویرایش اطلاعات</DialogTitle>
|
||||
<EditFormContent
|
||||
setOpenEditDialog={setOpenEditDialog}
|
||||
onSubmit={HandleSubmit}
|
||||
defaultData={{
|
||||
before_image: row.original?.files[0]?.full_path_for_fast_react || null,
|
||||
after_image: row.original?.files[1]?.full_path_for_fast_react || null,
|
||||
item_id: row.original?.item || null,
|
||||
sub_item_id: row.original?.sub_item || null,
|
||||
amount: row.original?.sub_item_data || null,
|
||||
start_point: { lat: row.original?.start_lat || "", lng: row.original?.start_lng || "" },
|
||||
end_point: { lat: row.original?.end_lat || "", lng: row.original?.end_lng || "" },
|
||||
cmms_machines: row.original?.cmms_machines || null,
|
||||
rahdaran_id: row.original?.rahdaran || null,
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default EditController;
|
||||
|
||||
@@ -1,215 +1,88 @@
|
||||
import { Button, DialogActions, DialogContent, Stack, TextField } from "@mui/material";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { array, mixed, object, string } from "yup";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import { LinearProgress, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import ImageUpload from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/ImageUpload";
|
||||
import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
|
||||
import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
|
||||
import { UPDATE_ROAD_ITEMS } from "@/core/utils/routes";
|
||||
import CarCode from "@/core/components/CarCode";
|
||||
import RahdarCode from "@/core/components/RahdarCode";
|
||||
import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems";
|
||||
import NumberField from "@/core/components/NumberField";
|
||||
import EditFormCreate from "@/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormCreate";
|
||||
|
||||
const validationSchema = object({
|
||||
amount: string().required("وارد کردن مقدار الزامیست!"),
|
||||
cmms_machines: array().required("وارد کردن کد خودرو الزامیست!"),
|
||||
rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"),
|
||||
before_image: mixed()
|
||||
.nullable()
|
||||
.test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsImage = subItem?.needs_image === 1;
|
||||
if (needsImage) {
|
||||
return !!value;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
after_image: mixed()
|
||||
.nullable()
|
||||
.test("after-image-required", "لطفا عکس بعد از اقدام را بارگذاری کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsImage = subItem?.needs_image === 1;
|
||||
if (needsImage) {
|
||||
return !!value;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
start_point: mixed()
|
||||
.test("start-point-required", "لطفاً نقطه شروع را مشخص کنید!", function (value) {
|
||||
return !!value; // چک میکند که مقدار موجود است
|
||||
})
|
||||
.required("لطفاً نقطه شروع را مشخص کنید!"),
|
||||
end_point: mixed().test("end-point-required", "لطفاً نقطه پایان را مشخص کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsEndPoint = subItem?.needs_end_point === 1;
|
||||
if (needsEndPoint) {
|
||||
return !!value; // چک میکند که مقدار موجود است
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
const EditFormContent = ({ setOpenEditDialog, onSubmit, defaultData }) => {
|
||||
const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetISubtems(defaultData?.item_id);
|
||||
const subItem = subItemsList.find((SubItem) => SubItem.sub_item === defaultData?.sub_item_id);
|
||||
|
||||
const EditFormContent = ({ row, mutate, setOpenEditDialog, rowId, subItem }) => {
|
||||
const defaultValues = {
|
||||
before_image: row.original?.files[0]?.full_path_for_fast_react || null,
|
||||
after_image: row.original?.files[1]?.full_path_for_fast_react || null,
|
||||
amount: row.original.sub_item_data || null,
|
||||
start_point: { lat: row.original.start_lat || "", lng: row.original.start_lng || "" },
|
||||
end_point: { lat: row.original.end_lat || "", lng: row.original.end_lng || "" },
|
||||
cmms_machines: row.original.cmms_machines || null,
|
||||
rahdaran_id: row.original.rahdaran || null,
|
||||
before_image: defaultData?.before_image || null,
|
||||
after_image: defaultData?.after_image || null,
|
||||
amount: defaultData?.amount || null,
|
||||
start_point: defaultData?.start_point || { lat: "", lng: "" },
|
||||
end_point: defaultData?.end_point || { lat: "", lng: "" },
|
||||
cmms_machines: defaultData?.cmms_machines || null,
|
||||
rahdaran_id: defaultData?.rahdaran_id || null,
|
||||
};
|
||||
const requestServer = useRequest();
|
||||
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
watch,
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "onBlur",
|
||||
context: { subItem },
|
||||
});
|
||||
const onSubmitBase = async (data) => {
|
||||
let result = { ...data };
|
||||
if (result.before_image === null) {
|
||||
delete result.before_image;
|
||||
delete data.before_image;
|
||||
}
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
let endPoint;
|
||||
let startPoint = `${data.start_point.lat},${data.start_point.lng}`;
|
||||
subItem.needs_end_point === 1 && (endPoint = `${data.end_point.lat},${data.end_point.lng}`);
|
||||
const formData = new FormData();
|
||||
subItem.needs_image === 1 &&
|
||||
data.before_image !== defaultValues.before_image &&
|
||||
formData.append("before_image", data.before_image);
|
||||
subItem.needs_image === 1 &&
|
||||
data.after_image !== defaultValues.after_image &&
|
||||
formData.append("after_image", data.after_image);
|
||||
formData.append("amount", data.amount);
|
||||
data.cmms_machines.map((machine, index) => formData.append(`machines_id[${index}]`, machine.id));
|
||||
data.rahdaran_id.map((rahdar, index) => formData.append(`rahdaran_id[${index}]`, rahdar.id));
|
||||
formData.append("start_point", startPoint);
|
||||
subItem.needs_end_point === 1 && formData.append("end_point", endPoint);
|
||||
if (result.after_image === null) {
|
||||
delete result.after_image;
|
||||
delete data.after_image;
|
||||
}
|
||||
|
||||
await requestServer(`${UPDATE_ROAD_ITEMS}/${rowId}`, "post", {
|
||||
notificationSuccess: true,
|
||||
data: formData,
|
||||
})
|
||||
.then((res) => {
|
||||
mutate();
|
||||
setOpenEditDialog(false);
|
||||
})
|
||||
.catch((err) => {});
|
||||
if (result.end_point === "") {
|
||||
delete result.end_point;
|
||||
delete data.end_point;
|
||||
} else {
|
||||
result.end_point = `${result.end_point.lat},${result.end_point.lng}`;
|
||||
}
|
||||
|
||||
if (result.start_point === "") {
|
||||
delete result.start_point;
|
||||
delete data.start_point;
|
||||
} else {
|
||||
result.start_point = `${result.start_point.lat},${result.start_point.lng}`;
|
||||
}
|
||||
|
||||
result.rahdaran_id.forEach((rahdar, index) => {
|
||||
result[`rahdaran_id[${index}]`] = rahdar.id;
|
||||
});
|
||||
delete result.rahdaran_id;
|
||||
|
||||
result.cmms_machines.forEach((cmms_machine, index) => {
|
||||
result[`machines_id[${index}]`] = cmms_machine.id;
|
||||
});
|
||||
delete result.cmms_machines;
|
||||
|
||||
await onSubmit({
|
||||
result,
|
||||
data: {
|
||||
...data,
|
||||
item_name: subItem.item_str,
|
||||
sub_item_name: subItem.name,
|
||||
unit_fa: subItem.unit,
|
||||
item_id: subItem.item,
|
||||
sub_item_id: subItem.sub_item,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)}>
|
||||
<DialogContent sx={{ overflowY: "auto", maxHeight: "70vh" }}>
|
||||
<Stack spacing={2}>
|
||||
<Stack>
|
||||
{subItem.needs_image === 1 ? (
|
||||
<ImageUpload
|
||||
afterImage={row.original?.files[0]?.full_path_for_fast_react}
|
||||
beforeImage={row.original?.files[1]?.full_path_for_fast_react}
|
||||
getValues={getValues}
|
||||
setValue={setValue}
|
||||
control={control}
|
||||
errors={errors}
|
||||
/>
|
||||
) : null}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<NumberField
|
||||
{...register("amount")}
|
||||
label={`مقدار (${subItem.unit})`}
|
||||
error={!!errors.amount}
|
||||
value={watch("amount")}
|
||||
type="text"
|
||||
size={"small"}
|
||||
unit={subItem.unit}
|
||||
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>
|
||||
<Stack>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<CarCode
|
||||
carCode={field.value || []}
|
||||
setCarCode={(value) => field.onChange(value || [])}
|
||||
inputValueDefault={row.original.cmms_machines}
|
||||
multiple={true}
|
||||
error={error} // اگر خطا وجود داشته باشد
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"cmms_machines"}
|
||||
/>
|
||||
</Stack>
|
||||
<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
|
||||
StartPoint={getValues("start_point")}
|
||||
EndPoint={getValues("end_point")}
|
||||
setValue={setValue}
|
||||
errors={errors}
|
||||
/>
|
||||
) : (
|
||||
<MapInfoOneMarker
|
||||
StartPoint={getValues("start_point")}
|
||||
setValue={setValue}
|
||||
errors={errors}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenEditDialog(false)} variant="outlined" color="primary" autoFocus>
|
||||
بستن
|
||||
</Button>
|
||||
<Button disabled={isSubmitting} type={"submit"} variant="contained" color="primary">
|
||||
{isSubmitting ? "در حال ویرایش" : "ویرایش"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</StyledForm>
|
||||
{errorSubItemsList ? (
|
||||
<Typography color={"error"} textAlign={"center"}>
|
||||
خطا در دریافت اطلاعات!!!
|
||||
</Typography>
|
||||
) : loadingSubItemsList ? (
|
||||
<LinearProgress />
|
||||
) : (
|
||||
<EditFormCreate
|
||||
onSubmitBase={onSubmitBase}
|
||||
subItem={subItem}
|
||||
defaultData={defaultData}
|
||||
defaultValues={defaultValues}
|
||||
setOpenEditDialog={setOpenEditDialog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
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 StyledForm from "@/core/components/StyledForm";
|
||||
import React from "react";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { array, mixed, object, string } from "yup";
|
||||
|
||||
const validationSchema = object({
|
||||
amount: string().required("وارد کردن مقدار الزامیست!"),
|
||||
cmms_machines: array().required("وارد کردن کد خودرو الزامیست!"),
|
||||
rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"),
|
||||
before_image: mixed()
|
||||
.nullable()
|
||||
.test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsImage = subItem?.needs_image === 1;
|
||||
if (needsImage) {
|
||||
return !!value;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
after_image: mixed()
|
||||
.nullable()
|
||||
.test("after-image-required", "لطفا عکس بعد از اقدام را بارگذاری کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsImage = subItem?.needs_image === 1;
|
||||
if (needsImage) {
|
||||
return !!value;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
start_point: mixed()
|
||||
.test("start-point-required", "لطفاً نقطه شروع را مشخص کنید!", function (value) {
|
||||
return !!value; // چک میکند که مقدار موجود است
|
||||
})
|
||||
.required("لطفاً نقطه شروع را مشخص کنید!"),
|
||||
end_point: mixed().test("end-point-required", "لطفاً نقطه پایان را مشخص کنید!", function (value) {
|
||||
const { subItem } = this.options.context;
|
||||
const needsEndPoint = subItem?.needs_end_point === 1;
|
||||
if (needsEndPoint) {
|
||||
return !!value;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
const EditFormCreate = ({ defaultData, subItem, onSubmitBase, defaultValues, setOpenEditDialog }) => {
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
watch,
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "onBlur",
|
||||
context: { subItem },
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledForm onSubmit={handleSubmit(onSubmitBase)} id="edit_road_items">
|
||||
<DialogContent sx={{ overflowY: "auto", maxHeight: "70vh" }}>
|
||||
<Stack spacing={2}>
|
||||
<Stack>
|
||||
{subItem.needs_image === 1 ? (
|
||||
<ImageUpload
|
||||
afterImage={defaultData?.after_image}
|
||||
beforeImage={defaultData?.before_image}
|
||||
getValues={getValues}
|
||||
setValue={setValue}
|
||||
control={control}
|
||||
errors={errors}
|
||||
/>
|
||||
) : null}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<NumberField
|
||||
{...register("amount")}
|
||||
label={`مقدار (${subItem.unit})`}
|
||||
error={!!errors.amount}
|
||||
value={watch("amount")}
|
||||
type="text"
|
||||
size={"small"}
|
||||
unit={subItem.unit}
|
||||
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>
|
||||
<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>
|
||||
<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
|
||||
StartPoint={getValues("start_point")}
|
||||
EndPoint={getValues("end_point")}
|
||||
setValue={setValue}
|
||||
errors={errors}
|
||||
/>
|
||||
) : (
|
||||
<MapInfoOneMarker
|
||||
StartPoint={getValues("start_point")}
|
||||
setValue={setValue}
|
||||
errors={errors}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenEditDialog(false)} variant="outlined" color="secondary" autoFocus>
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
form="edit_road_items"
|
||||
disabled={isSubmitting}
|
||||
type={"submit"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
{isSubmitting ? "در حال ویرایش" : "ویرایش"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
export default EditFormCreate;
|
||||
@@ -19,19 +19,13 @@ const EditForm = ({ row, mutate, rowId }) => {
|
||||
<BorderColorIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
open={openEditDialog}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>ویرایش اطلاعات</DialogTitle>
|
||||
<EditController setOpenEditDialog={setOpenEditDialog} rowId={rowId} row={row} mutate={mutate} />
|
||||
</Dialog>
|
||||
<EditController
|
||||
openEditDialog={openEditDialog}
|
||||
setOpenEditDialog={setOpenEditDialog}
|
||||
rowId={rowId}
|
||||
row={row}
|
||||
mutate={mutate}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||
<Button onClick={() => setOpenLocationDialog(false)} variant="contained" color="error">
|
||||
<Button onClick={() => setOpenLocationDialog(false)} variant="outlined" color="secondary">
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import PrintExcel from "./ExcelPrint";
|
||||
import OperatorCreate from "./Actions/Create";
|
||||
import { Stack } from "@mui/material";
|
||||
import ObservedGashtCreate from "./Actions/ObservedGashtCreate";
|
||||
|
||||
const Toolbar = ({ table, filterData, mutate }) => {
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintExcel table={table} filterData={filterData} />
|
||||
<OperatorCreate mutate={mutate} />
|
||||
<ObservedGashtCreate mutate={mutate} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,10 +28,10 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||
<Button onClick={() => setOpenDeleteDialog(false)} variant="contained" color="error">
|
||||
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
|
||||
خیر !
|
||||
</Button>
|
||||
<Button onClick={handleClick} variant="contained" color="primary" disabled={submitting}>
|
||||
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
||||
{submitting ? "درحال ارسال اطلاعات..." : "بله اطمینان دارم"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -25,7 +25,7 @@ const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||
<Button onClick={() => setOpenLocationDialog(false)} variant="contained" color="error">
|
||||
<Button onClick={() => setOpenLocationDialog(false)} variant="outlined" color="secondary">
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -257,7 +257,7 @@ const SupervisorList = () => {
|
||||
},
|
||||
{
|
||||
accessorKey: "cmms_machines",
|
||||
header: "کد خودرو", // Car ID
|
||||
header: "خودرو ها", // Car ID
|
||||
id: "cmmsMachines__machine_code",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
@@ -316,9 +316,9 @@ const SupervisorList = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ ثبت", // Register Date
|
||||
id: "created_at",
|
||||
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ فعالیت", // Start Date
|
||||
id: "activity_date_time",
|
||||
enableColumnFilter: true,
|
||||
datatype: "date",
|
||||
filterMode: "equals",
|
||||
@@ -327,9 +327,9 @@ const SupervisorList = () => {
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ فعالیت", // Start Date
|
||||
id: "activity_date_time",
|
||||
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
header: "تاریخ ثبت", // Register Date
|
||||
id: "created_at",
|
||||
enableColumnFilter: true,
|
||||
datatype: "date",
|
||||
filterMode: "equals",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { Button, IconButton, useMediaQuery } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState } from "react";
|
||||
import { AddCircle } from "@mui/icons-material";
|
||||
import CreatePatrol from "../../Forms/CreatePatrol";
|
||||
|
||||
const OperatorCreate = ({ mutate }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton aria-label="ثبت اقدام جدید" color="primary" onClick={handleOpen}>
|
||||
<AddCircle sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button variant="contained" color="primary" startIcon={<AddCircle />} onClick={handleOpen}>
|
||||
ثبت اقدام
|
||||
</Button>
|
||||
)}
|
||||
{open && <CreatePatrol open={open} setOpen={setOpen} mutate={mutate} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default OperatorCreate;
|
||||
@@ -45,7 +45,6 @@ const PrintExcel = ({ table, filterData }) => {
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit" /> : <DescriptionIcon />}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Chip,
|
||||
Divider,
|
||||
IconButton,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import HandymanIcon from "@mui/icons-material/Handyman";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import DirectionsCarIcon from "@mui/icons-material/DirectionsCar";
|
||||
import PersonIcon from "@mui/icons-material/Person";
|
||||
import DesignServicesIcon from "@mui/icons-material/DesignServices";
|
||||
import EditActionDuringPatrol from "./EditActionDuringPatrol";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const ActionInfo = ({ action, setActionsList, index, deleteAction }) => {
|
||||
return (
|
||||
<Card sx={{ maxWidth: 300 }}>
|
||||
<CardHeader
|
||||
avatar={<HandymanIcon color="primary" sx={{ width: "30px", height: "30px" }} />}
|
||||
title={<Typography sx={{ fontSize: "14px", fontWeight: 500 }}>{action.data.item_name}</Typography>}
|
||||
subheader={action.data.sub_item_name}
|
||||
/>
|
||||
<CardContent>
|
||||
<Stack sx={{ gap: 1 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<DesignServicesIcon />
|
||||
<Typography variant="button">میزان فعالیت انجام شده:</Typography>
|
||||
<Typography variant="button">
|
||||
{action.data.amount}({action.data.unit_fa})
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Divider sx={{ mb: 1 }}>
|
||||
<Chip size="small" label="خودرو ها" icon={<DirectionsCarIcon />} />
|
||||
</Divider>
|
||||
<Stack>
|
||||
{action.data.cmms_machines.map((cmms_machine, index) => (
|
||||
<Typography variant="button" key={index}>
|
||||
{index + 1}: {cmms_machine.car_name}
|
||||
</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Divider sx={{ mb: 1 }}>
|
||||
<Chip size="small" label="راهداران" icon={<PersonIcon />} />
|
||||
</Divider>
|
||||
<Stack>
|
||||
{action.data.rahdaran_id.map((rahdar, index) => (
|
||||
<Typography variant="button" key={index}>
|
||||
{index + 1}: {rahdar.name}
|
||||
</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
<CardActions disableSpacing>
|
||||
<Box>
|
||||
<IconButton aria-label="حذف فعالیت" color="error" onClick={deleteAction}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<EditActionDuringPatrol action={action} index={index} setActionsList={setActionsList} />
|
||||
</Box>
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionInfo;
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Button, Divider, Grid, Typography } from "@mui/material";
|
||||
import AddCircleIcon from "@mui/icons-material/AddCircle";
|
||||
import React, { useState } from "react";
|
||||
import ModalActionsDuringPatrol from "./ModalActionsDuringPatrol";
|
||||
import ActionInfo from "./ActionInfo";
|
||||
import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
|
||||
|
||||
const ActionsDuringPatrol = ({ setValue, tabState, setTabState }) => {
|
||||
const [ActionsList, setActionsList] = useState([]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const deleteAction = (indexToRemove) => {
|
||||
setActionsList((prev) => prev.filter((_, index) => index !== indexToRemove));
|
||||
};
|
||||
|
||||
const SubmitActionDuringPatrol = () => {
|
||||
setTabState(tabState + 1);
|
||||
const resultItems = ActionsList.map((item) => item.result);
|
||||
console.log("resultItems", resultItems);
|
||||
setValue("observed_items", resultItems);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Typography variant="h6">لیست اقدامات حین گشت</Typography>
|
||||
<Button
|
||||
onClick={handleOpen}
|
||||
startIcon={<AddCircleIcon />}
|
||||
variant="outlined"
|
||||
sx={{ mb: 1, alignSelf: "end" }}
|
||||
>
|
||||
ثبت اقدام
|
||||
</Button>
|
||||
<ModalActionsDuringPatrol open={open} setOpen={setOpen} setActionsList={setActionsList} />
|
||||
</Box>
|
||||
<Divider />
|
||||
{ActionsList.length !== 0 ? (
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
sx={{
|
||||
p: 1,
|
||||
mt: 1,
|
||||
maxHeight: "300px",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{ActionsList.map((action, index) => (
|
||||
<Grid key={index} item xs={12} sm={6} lg={4}>
|
||||
<ActionInfo
|
||||
action={action}
|
||||
setActionsList={setActionsList}
|
||||
index={index}
|
||||
deleteAction={() => deleteAction(index)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Box sx={{ my: 5, width: "100%", textAlign: "center" }}>
|
||||
<Typography variant="h6" sx={{ letterSpacing: "2px", color: "#606060" }}>
|
||||
فعالیتی ثبت نکرده اید
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Box sx={{ display: "flex", gap: 1, justifyContent: "center" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={SubmitActionDuringPatrol}
|
||||
disabled={ActionsList === []}
|
||||
endIcon={<KeyboardDoubleArrowLeftIcon />}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
نهایی کردن درخواست
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionsDuringPatrol;
|
||||
@@ -0,0 +1,229 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Divider,
|
||||
FormControl,
|
||||
Grid,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
Slide,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import BeenhereIcon from "@mui/icons-material/Beenhere";
|
||||
import ForwardToInboxIcon from "@mui/icons-material/ForwardToInbox";
|
||||
import { formatCounter } from "@/core/utils/formatCounter";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { GET_OTP_TOKEN } from "@/core/utils/routes";
|
||||
import moment from "jalali-moment";
|
||||
|
||||
const CompeleteRequest = ({ watch, isSubmitting, setValue, register }) => {
|
||||
const requestServer = useRequest({ auth: true });
|
||||
const [delayPerRequest, setDelayPerRequest] = useState(false);
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const [validPhone, setValidPhone] = useState(false);
|
||||
const [verificationCode, setVerificationCode] = useState("");
|
||||
const [counter, setCounter] = useState(120);
|
||||
const [otpSended, setOtpSended] = useState(false);
|
||||
const [readyToVerify, setReadyToVerify] = useState(false);
|
||||
|
||||
const handleNumericInput = (e) => {
|
||||
e.target.value = e.target.value.replace(/[^0-9]/g, "");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (phoneNumber.length === 11) {
|
||||
setValidPhone(true);
|
||||
} else {
|
||||
setValidPhone(false);
|
||||
}
|
||||
if (verificationCode.length === 6 && phoneNumber.length === 11) {
|
||||
setReadyToVerify(true);
|
||||
} else {
|
||||
setReadyToVerify(false);
|
||||
}
|
||||
}, [phoneNumber, verificationCode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (counter === 0) {
|
||||
setDelayPerRequest(false);
|
||||
return;
|
||||
}
|
||||
if (delayPerRequest && counter > 0) {
|
||||
const timer = setInterval(() => {
|
||||
setCounter((prevCounter) => prevCounter - 1);
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
}, [counter, delayPerRequest]);
|
||||
|
||||
const sendOtp = () => {
|
||||
if (!validPhone) return false;
|
||||
requestServer(`${GET_OTP_TOKEN}?phone_number=${phoneNumber}`, "get")
|
||||
.then((response) => {
|
||||
setOtpSended(true);
|
||||
setDelayPerRequest(true);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#777777", fontWeight: 500 }} variant="body1">
|
||||
{moment(watch("start_time")).locale("fa").format("YYYY/MM/DD | HH:mm")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#777777", fontWeight: 500 }} variant="body1">
|
||||
{moment(watch("end_time")).locale("fa").format("YYYY/MM/DD | HH:mm")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Grid container spacing={2} sx={{ alignItems: "start", my: 1 }}>
|
||||
<Grid item xs={12} lg={6} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip label="مدت زمان روشن بودن خودرو" />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("vehicle_runtime")}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={6} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip label="میزان سوخت مصرفی" />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("fuel_consumption")}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={6} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip label="تعداد نقاط توقف" />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("stop_points").length}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={6} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip label="مسافت پیموده شده" />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("distance")}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={6} sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip label="تعداد فعالیت های حین گشت" />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("observed_items").length}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container spacing={3} sx={{ alignItems: "start", my: 2 }}>
|
||||
<Grid item xs={12} lg={6}>
|
||||
<Divider>
|
||||
<Chip label="خودرو گشت" />
|
||||
</Divider>
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 1 }}>
|
||||
<Typography>کد</Typography>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("road_patrol_machines_id.machine_code")}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 1 }}>
|
||||
<Typography>نام</Typography>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("road_patrol_machines_id.car_name")}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 1 }}>
|
||||
<Typography>پلاک</Typography>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{watch("road_patrol_machines_id.plak_number")}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={6}>
|
||||
<Divider>
|
||||
<Chip label="راهدار / راهداران حاضر در گشت" />
|
||||
</Divider>
|
||||
{watch("road_patrol_rahdaran_id")?.map((rahdar, index) => (
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 1 }}>
|
||||
<Typography>نام و کد راهدار</Typography>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>
|
||||
{rahdar.name} | {rahdar.code}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Box sx={{ display: "flex", gap: 2, my: 2, justifyContent: "space-around" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<FormControl size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="phone_number">شماره تلفن مامور گشت</InputLabel>
|
||||
<OutlinedInput
|
||||
id="phone_number"
|
||||
label={"شماره تلفن مامور گشت"}
|
||||
disabled={delayPerRequest}
|
||||
autoComplete="off"
|
||||
size="small"
|
||||
fullWidth
|
||||
{...register("phone_number")}
|
||||
value={phoneNumber}
|
||||
onChange={(e) => setPhoneNumber(e.target.value)}
|
||||
onInput={handleNumericInput}
|
||||
type="text"
|
||||
inputProps={{
|
||||
maxLength: 11,
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={sendOtp}
|
||||
disabled={!validPhone || delayPerRequest}
|
||||
variant="contained"
|
||||
color="success"
|
||||
sx={{ textWrap: "nowrap", px: 4 }}
|
||||
endIcon={<ForwardToInboxIcon />}
|
||||
>
|
||||
{delayPerRequest ? formatCounter(counter) : "دریافت کد"}
|
||||
</Button>
|
||||
</Box>
|
||||
<Slide in={otpSended}>
|
||||
<Box>
|
||||
<FormControl size="small" fullWidth variant="outlined" focused>
|
||||
<InputLabel htmlFor="verification_code">کد پیامک شده</InputLabel>
|
||||
<OutlinedInput
|
||||
id="verification_code"
|
||||
label={"کد پیامک شده"}
|
||||
placeholder="------"
|
||||
autoComplete="off"
|
||||
size="small"
|
||||
fullWidth
|
||||
{...register("verification_code")}
|
||||
value={verificationCode}
|
||||
onChange={(e) => setVerificationCode(e.target.value)}
|
||||
onInput={handleNumericInput}
|
||||
type="text"
|
||||
inputProps={{
|
||||
maxLength: 6,
|
||||
inputMode: "numeric",
|
||||
style: {
|
||||
textAlign: "center",
|
||||
letterSpacing: "10px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</Slide>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
type={"submit"}
|
||||
form="patrol_form"
|
||||
endIcon={<BeenhereIcon />}
|
||||
>
|
||||
{isSubmitting ? "در حال ثبت فعالیت" : "ثبت فعالیت"}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompeleteRequest;
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import { Dialog, DialogTitle, IconButton } from "@mui/material";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import EditFormContent from "@/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const EditActionDuringPatrol = ({ action, index, setActionsList }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const HandleSubmit = async (newData) => {
|
||||
setActionsList((prev) => {
|
||||
const updatedList = [...prev];
|
||||
updatedList[index] = {
|
||||
...updatedList[index],
|
||||
data: newData.data,
|
||||
result: newData.result,
|
||||
};
|
||||
return updatedList;
|
||||
});
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton aria-label="ویرایش فعالیت" color="primary" onClick={handleOpen}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<Dialog
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
open={open}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle>ویرایش اطلاعات</DialogTitle>
|
||||
<EditFormContent setOpenEditDialog={setOpen} onSubmit={HandleSubmit} defaultData={action.data} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditActionDuringPatrol;
|
||||
@@ -0,0 +1,16 @@
|
||||
import CreateFormContent from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent";
|
||||
import { Dialog } from "@mui/material";
|
||||
|
||||
const ModalActionsDuringPatrol = ({ open, setOpen, setActionsList }) => {
|
||||
const HandleSubmit = async (data) => {
|
||||
setActionsList((prev) => [...prev, data]);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} fullWidth maxWidth="sm">
|
||||
<CreateFormContent setOpen={setOpen} onSubmit={HandleSubmit} is_gasht={true} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default ModalActionsDuringPatrol;
|
||||
@@ -0,0 +1,203 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Button, Chip, Divider, IconButton, InputAdornment, Stack } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import CarCode from "@/core/components/CarCode";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||
import { faIR } from "@mui/x-date-pickers/locales";
|
||||
import { LocalizationProvider, MobileDateTimePicker } from "@mui/x-date-pickers";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import moment from "jalali-moment";
|
||||
import dynamic from "next/dynamic";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { GET_FMS_DATA } from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import PatrolDetailInfo from "./PatrolDetailInfo";
|
||||
import PatrolResultCodeErrors from "./PatrolResultCodeErrors";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const PatrolDetail = ({ control, watch, setValue, tabState, setTabState }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const [patrolResultStatus, setPatrolResultStatus] = useState(null);
|
||||
const [readyToRequest, setReadyToRequest] = useState(false);
|
||||
const [patrolData, setPatrolData] = useState(null);
|
||||
|
||||
const requestPatrolInfo = async () => {
|
||||
const formData = new FormData();
|
||||
formData.append("machineCode", watch("road_patrol_machines_id").machine_code);
|
||||
formData.append("startDT", moment(watch("start_time")).format("YYYY-MM-DDTHH:mm"));
|
||||
formData.append("endDT", moment(watch("end_time")).format("YYYY-MM-DDTHH:mm"));
|
||||
await requestServer(GET_FMS_DATA, "post", {
|
||||
data: formData,
|
||||
})
|
||||
.then((response) => {
|
||||
const data = response.data.data;
|
||||
setPatrolData({
|
||||
...data,
|
||||
roadPatrolMachinesId: watch("road_patrol_machines_id"),
|
||||
start_time: watch("start_time"),
|
||||
end_time: watch("end_time"),
|
||||
});
|
||||
setPatrolResultStatus(data.resultCode);
|
||||
})
|
||||
.catch((error) => {});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const roadPatrolMachinesId = watch("road_patrol_machines_id");
|
||||
const startTime = watch("start_time");
|
||||
const endTime = watch("end_time");
|
||||
|
||||
const isReady = roadPatrolMachinesId !== null && startTime !== "" && endTime !== "";
|
||||
setReadyToRequest(isReady);
|
||||
}, [watch("road_patrol_machines_id"), watch("start_time"), watch("end_time")]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
my: 3,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", flexDirection: { xs: "column", lg: "row" }, gap: 2 }}>
|
||||
<Stack sx={{ minWidth: "200px" }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<CarCode
|
||||
carCode={field.value}
|
||||
setCarCode={(value) => field.onChange(value)}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"road_patrol_machines_id"}
|
||||
/>
|
||||
</Stack>
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDateTimePicker
|
||||
value={watch("start_time") ? new Date(watch("start_time")) : null}
|
||||
ampm={false}
|
||||
name="تاریخ و ساعت شروع گشت"
|
||||
closeOnSelect
|
||||
maxDateTime={watch("end_time") ? new Date(watch("end_time")) : null}
|
||||
onChange={(start_time) => {
|
||||
const date = new Date(start_time);
|
||||
const formattedDate = moment(date).locale("en").format("YYYY-MM-DD HH:mm");
|
||||
setValue("start_time", formattedDate);
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: "small",
|
||||
placeholder: "تاریخ و ساعت شروع گشت",
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setValue("start_time", "");
|
||||
}}
|
||||
sx={{
|
||||
color: "#bfbfbf",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||
color: "#363434",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
label="تاریخ و ساعت شروع گشت"
|
||||
/>
|
||||
<MobileDateTimePicker
|
||||
value={watch("end_time") ? new Date(watch("end_time")) : null}
|
||||
ampm={false}
|
||||
name="تاریخ و ساعت پایان گشت"
|
||||
closeOnSelect
|
||||
minDateTime={watch("start_time") ? new Date(watch("start_time")) : null}
|
||||
onChange={(end_time) => {
|
||||
const date = new Date(end_time);
|
||||
const formattedDate = moment(date).locale("en").format("YYYY-MM-DD HH:mm");
|
||||
setValue("end_time", formattedDate);
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: "small",
|
||||
placeholder: "تاریخ و ساعت پایان گشت",
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setValue("end_time", "");
|
||||
}}
|
||||
sx={{
|
||||
color: "#bfbfbf",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||
color: "#363434",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
label="تاریخ و ساعت پایان گشت"
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={requestPatrolInfo}
|
||||
color="success"
|
||||
disabled={!readyToRequest}
|
||||
endIcon={<SearchIcon />}
|
||||
>
|
||||
درخواست اطلاعات
|
||||
</Button>
|
||||
</Box>
|
||||
<Divider sx={{ my: 2, width: "100%" }}>
|
||||
<Chip color="success" label="مشخصات گشت" />
|
||||
</Divider>
|
||||
{patrolResultStatus === 0 ? (
|
||||
<PatrolDetailInfo
|
||||
patrolData={patrolData}
|
||||
tabState={tabState}
|
||||
setTabState={setTabState}
|
||||
setValue={setValue}
|
||||
/>
|
||||
) : (
|
||||
<PatrolResultCodeErrors ResultCode={patrolResultStatus} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PatrolDetail;
|
||||
@@ -0,0 +1,177 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Button, Card, CardActions, CardContent, Chip, Divider, Slide, Stack, Typography } from "@mui/material";
|
||||
import React, { useEffect } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import LocalGasStationIcon from "@mui/icons-material/LocalGasStation";
|
||||
import DirectionsCarFilledIcon from "@mui/icons-material/DirectionsCarFilled";
|
||||
import WatchLaterIcon from "@mui/icons-material/WatchLater";
|
||||
import QueryBuilderIcon from "@mui/icons-material/QueryBuilder";
|
||||
import AddRoadIcon from "@mui/icons-material/AddRoad";
|
||||
import ShareLocationIcon from "@mui/icons-material/ShareLocation";
|
||||
import ElectricCarIcon from "@mui/icons-material/ElectricCar";
|
||||
import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
|
||||
import QrCode2Icon from "@mui/icons-material/QrCode2";
|
||||
import moment from "jalali-moment";
|
||||
import PatrolMapFeatures from "./PatrolMapFeatures";
|
||||
import { useMap } from "react-leaflet";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const MapItemViewBound = ({ patrolData, bound }) => {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (bound.length !== 0) {
|
||||
map.fitBounds(bound, { paddingTopLeft: [16, 16], paddingBottomRight: [16, 130] });
|
||||
}
|
||||
}, [bound]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{patrolData.stopPoints.map((stopPoint, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<PatrolMapFeatures stopPoint={stopPoint} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PatrolDetailInfo = ({ patrolData, tabState, setTabState, setValue }) => {
|
||||
const bound = patrolData.stopPoints.map((point) => [point.latitude, point.longitude]);
|
||||
|
||||
const SendPatrolInfo = () => {
|
||||
setValue("vehicle_runtime", patrolData.accOnDuration);
|
||||
setValue("fuel_consumption", patrolData.fuelConsumption);
|
||||
setValue("distance", patrolData.mileage);
|
||||
setValue("stop_points", patrolData.stopPoints);
|
||||
setTabState(tabState + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Slide in={true} sx={{ width: "100%" }}>
|
||||
<Card>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: { xs: "column", lg: "row" },
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: { xs: 2, lg: 0 },
|
||||
}}
|
||||
>
|
||||
<Stack sx={{ minWidth: { xs: "100%", lg: "300px" }, gap: 2 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="مدت زمان روشن بودن خودرو" icon={<ElectricCarIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{Math.floor(patrolData.accOnDuration / 60)} دقیقه
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="نام خودرو" icon={<DirectionsCarFilledIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{patrolData.roadPatrolMachinesId.car_name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="کد خودرو" icon={<QrCode2Icon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{patrolData.roadPatrolMachinesId.machine_code}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="پلاک خودرو" icon={<DirectionsCarFilledIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{patrolData.roadPatrolMachinesId.plak_number}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="زمان شروع گشت" icon={<QueryBuilderIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{moment(patrolData.start_time).locale("fa").format("HH:mm | YYYY/MM/DD")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="زمان پایان گشت" icon={<WatchLaterIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{moment(patrolData.end_time).locale("fa").format("HH:mm | YYYY/MM/DD")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="مسافت" icon={<AddRoadIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{(patrolData.mileage / 1000).toFixed(1)} کیلومتر
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="میزان مصرف سوخت" icon={<LocalGasStationIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{Math.round(patrolData.fuelConsumption * 10) / 10} لیتر
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Chip size="small" label="تعداد توقف در مسیر" icon={<ShareLocationIcon />} />
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography sx={{ fontSize: "13px", fontWeight: 400 }}>
|
||||
{patrolData.stopPoints.length} مورد
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<Stack spacing={1} sx={{ height: "300px", width: { xs: "100%", lg: "350px" } }}>
|
||||
<Box
|
||||
sx={{
|
||||
p: 1,
|
||||
border: "1px dashed",
|
||||
borderColor: "divider",
|
||||
borderRadius: 1,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MapLayer position={bound}>
|
||||
<MapItemViewBound patrolData={patrolData} bound={bound} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
<CardActions sx={{ alignItems: "center", justifyContent: "center" }}>
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={SendPatrolInfo}
|
||||
endIcon={<KeyboardDoubleArrowLeftIcon />}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
تایید اطلاعات و رفتن به مرحله بعد
|
||||
</Button>
|
||||
</Box>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Slide>
|
||||
);
|
||||
};
|
||||
|
||||
export default PatrolDetailInfo;
|
||||
@@ -0,0 +1,179 @@
|
||||
"use client";
|
||||
|
||||
import { Box, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import TaxiAlertIcon from "@mui/icons-material/TaxiAlert";
|
||||
import EngineeringIcon from "@mui/icons-material/Engineering";
|
||||
import HandymanIcon from "@mui/icons-material/Handyman";
|
||||
import VerifiedIcon from "@mui/icons-material/Verified";
|
||||
import PatrolTimeLine from "./PatrolTimeLine";
|
||||
import PatrolDetail from "./PatrolDetail";
|
||||
import SuperVisorsDetail from "./SuperVisorsDetail";
|
||||
import ActionsDuringPatrol from "./ActionsDurigPatrol";
|
||||
import { useForm } from "react-hook-form";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import CompeleteRequest from "./CompeleteRequest";
|
||||
|
||||
function TabPanel(props) {
|
||||
const { children, value, index } = props;
|
||||
|
||||
return (
|
||||
<div role="tabpanel" hidden={value !== index}>
|
||||
{value === index && <Box>{children}</Box>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const defaultValues = {
|
||||
road_patrol_rahdaran_id: null,
|
||||
road_patrol_machines_id: null,
|
||||
start_time: "",
|
||||
end_time: "",
|
||||
stop_points: null,
|
||||
vehicle_runtime: "",
|
||||
fuel_consumption: "",
|
||||
distance: "",
|
||||
observed_items: null,
|
||||
phone_number: "",
|
||||
otp_token: "",
|
||||
};
|
||||
|
||||
const PatrolForms = ({ mutate, setOpen }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
|
||||
const [tabState, setTabState] = useState(0);
|
||||
const [activeUpTo, setActiveUpTo] = useState(0);
|
||||
|
||||
const handleChangeTab = (event, newValue) => {
|
||||
setTabState(newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeUpTo < tabState) {
|
||||
setActiveUpTo(tabState);
|
||||
}
|
||||
}, [tabState]);
|
||||
|
||||
const {
|
||||
control,
|
||||
watch,
|
||||
getValues,
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
resetField,
|
||||
formState: { isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
mode: "onBlur",
|
||||
});
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
console.log("data", data);
|
||||
// const formData = new FormData();
|
||||
// data.road_patrol_rahdaran_id.forEach((rahdar, index) => formData.append(`road_patrol_rahdaran_id[${index}]`, rahdar.id));
|
||||
// formData.append(`road_patrol_machines_id[0]`, data.road_patrol_machines_id.id)
|
||||
// data.stop_points.forEach((stop_point, index) => formData.append(`stop_points[${index}]`, stop_point))
|
||||
// data.observed_items.forEach((observed_item, index) => {
|
||||
// formData.append(`observed_items[${index}][instant_action]`, 1);
|
||||
// formData.append(`observed_items[${index}][local_name]`, "");
|
||||
// formData.append(`observed_items[${index}][start_point]`, observed_item.start_point);
|
||||
// formData.append(`observed_items[${index}][start_point]`, observed_item.end_point);
|
||||
// formData.append(`observed_items[${index}][amount]`, observed_item.amount);
|
||||
// formData.append(`observed_items[${index}][before_image]`, observed_item.before_image);
|
||||
// formData.append(`observed_items[${index}][after_image]`, observed_item.after_image);
|
||||
// observed_item.cmms_machines.forEach((machine) => formData.append(`observed_items[${index}][cmms_machines][]`, machine.id))
|
||||
// observed_item.rahdaran_id.forEach((rahdar) => formData.append(`observed_items[${index}][rahdaran_id][]`, rahdar.id))
|
||||
// formData.append(`observed_items[${index}][sub_item_id]`, observed_item.sub_item_id);
|
||||
// })
|
||||
// formData.append("start_time", data.start_time);
|
||||
// formData.append("end_time", data.end_time);
|
||||
// formData.append("vehicle_runtime", data.vehicle_runtime);
|
||||
// formData.append("fuel_consumption", +data.fuel_consumption);
|
||||
// formData.append("distance", data.distance);
|
||||
// formData.append("description", "");
|
||||
// formData.append("phone_number", data.phone_number);
|
||||
// formData.append("verification_code", data.verification_code);
|
||||
// if (data.phone_number !== "" && data.verification_code !== "") {
|
||||
// try {
|
||||
// await requestServer(CREATE_PATROL, "post", {
|
||||
// data: formData,
|
||||
// });
|
||||
// mutate();
|
||||
// setOpen(false);
|
||||
// } catch (error) {
|
||||
// }
|
||||
// } else {
|
||||
// console.log("error phone number")
|
||||
// }
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)} id="patrol_form">
|
||||
<Tabs
|
||||
allowScrollButtonsMobile
|
||||
value={tabState}
|
||||
onChange={handleChangeTab}
|
||||
variant={`${isMobile ? "scrollable" : "fullWidth"}`}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-around",
|
||||
width: "100%",
|
||||
backgroundColor: "#efefef",
|
||||
}}
|
||||
>
|
||||
<Tab disabled={!(activeUpTo >= 0)} icon={<TaxiAlertIcon />} label="مشخصات گشت"></Tab>
|
||||
<Tab disabled={!(activeUpTo >= 1)} icon={<EngineeringIcon />} label="مشخصات راهداران"></Tab>
|
||||
<Tab disabled={!(activeUpTo >= 2)} icon={<HandymanIcon />} label="اقدامات حین گشت"></Tab>
|
||||
<Tab disabled={!(activeUpTo >= 3)} icon={<VerifiedIcon />} label="تکمیل درخواست"></Tab>
|
||||
</Tabs>
|
||||
<DialogContent dividers sx={{ display: "flex" }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<TabPanel value={tabState} index={0}>
|
||||
<PatrolDetail
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
watch={watch}
|
||||
tabState={tabState}
|
||||
setTabState={setTabState}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={tabState} index={1}>
|
||||
<SuperVisorsDetail
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
watch={watch}
|
||||
tabState={tabState}
|
||||
setTabState={setTabState}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={tabState} index={2}>
|
||||
<ActionsDuringPatrol
|
||||
watch={watch}
|
||||
setValue={setValue}
|
||||
tabState={tabState}
|
||||
setTabState={setTabState}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel value={tabState} index={3}>
|
||||
<CompeleteRequest
|
||||
register={register}
|
||||
setValue={setValue}
|
||||
watch={watch}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Box>
|
||||
{!isMobile && (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<PatrolTimeLine tabState={tabState} />
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
export default PatrolForms;
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { Typography } from "@mui/material";
|
||||
import React, { useRef } from "react";
|
||||
import { Marker, Popup } from "react-leaflet";
|
||||
import AzmayeshIcon from "@/assets/images/examine_marker.png";
|
||||
|
||||
const PatrolMapFeatures = ({ stopPoint }) => {
|
||||
const position = [stopPoint.latitude, stopPoint.longitude];
|
||||
const mapPatrolMarker = useRef();
|
||||
const createCustomIcon = (size, iconUrl) => {
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Marker
|
||||
ref={mapPatrolMarker}
|
||||
icon={L.icon({
|
||||
iconUrl: AzmayeshIcon.src,
|
||||
iconSize: [35, 35],
|
||||
iconAnchor: [35 / 2, 35],
|
||||
popupAnchor: [0, -35],
|
||||
})}
|
||||
position={position}
|
||||
>
|
||||
<Popup>
|
||||
<Typography variant="button">مدت زمان توقف: {stopPoint.duration} ثانیه</Typography>
|
||||
</Popup>
|
||||
</Marker>
|
||||
);
|
||||
};
|
||||
|
||||
export default PatrolMapFeatures;
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const PatrolResultCodeErrors = ({ ResultCode }) => {
|
||||
const errorMessages = {
|
||||
[-1]: "نام کاربری یا کلمه عبور صحیح نمیباشد",
|
||||
[-2]: "ردیاب به خودرو انتخابی متصل نیست",
|
||||
[-3]: "کاربر دسترسی لازم به اطلاعات خودرو انتخابی را ندارد",
|
||||
[-4]: "خطای درخواست مکرر",
|
||||
};
|
||||
|
||||
const message = errorMessages[ResultCode] || "ابتدا اطلاعات بالا را تکمیل نمایید";
|
||||
|
||||
return (
|
||||
<Box sx={{ my: 5 }}>
|
||||
<Typography variant="h6" sx={{ letterSpacing: "2px", color: ResultCode ? "error.main" : "#606060" }}>
|
||||
{message}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PatrolResultCodeErrors;
|
||||
@@ -0,0 +1,119 @@
|
||||
"use client";
|
||||
|
||||
import { Box, CircularProgress, Typography } from "@mui/material";
|
||||
import TaxiAlertIcon from "@mui/icons-material/TaxiAlert";
|
||||
import EngineeringIcon from "@mui/icons-material/Engineering";
|
||||
import HandymanIcon from "@mui/icons-material/Handyman";
|
||||
import VerifiedIcon from "@mui/icons-material/Verified";
|
||||
import {
|
||||
Timeline,
|
||||
TimelineConnector,
|
||||
TimelineContent,
|
||||
TimelineDot,
|
||||
TimelineItem,
|
||||
timelineItemClasses,
|
||||
TimelineSeparator,
|
||||
} from "@mui/lab";
|
||||
|
||||
const PatrolTimeLine = ({ tabState }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Timeline
|
||||
position="right"
|
||||
sx={{
|
||||
[`& .${timelineItemClasses.root}:before`]: { flex: 0, padding: 0 },
|
||||
}}
|
||||
>
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineConnector />
|
||||
<TimelineDot sx={{ backgroundColor: "#fff" }}>
|
||||
{tabState === 0 ? (
|
||||
<CircularProgress size="23px" />
|
||||
) : (
|
||||
<TaxiAlertIcon
|
||||
color={tabState > 0 ? "success" : "error"}
|
||||
sx={{ width: "1.5rem", height: "1.5rem" }}
|
||||
/>
|
||||
)}
|
||||
</TimelineDot>
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent sx={{ py: "12px", px: 2 }}>
|
||||
<Typography variant="h6">مشخصات گشت</Typography>
|
||||
<Typography variant="caption" sx={{ color: "#606060" }}>
|
||||
تایید اطلاعات اولیه گشت
|
||||
</Typography>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineConnector />
|
||||
<TimelineDot sx={{ backgroundColor: "#fff" }}>
|
||||
{tabState === 1 ? (
|
||||
<CircularProgress size="23px" />
|
||||
) : (
|
||||
<EngineeringIcon
|
||||
color={tabState > 1 ? "success" : "error"}
|
||||
sx={{ width: "1.5rem", height: "1.5rem" }}
|
||||
/>
|
||||
)}
|
||||
</TimelineDot>
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent sx={{ py: "12px", px: 2 }}>
|
||||
<Typography variant="h6">مشخصات راهداران</Typography>
|
||||
<Typography variant="caption" sx={{ color: "#606060" }}>
|
||||
راهدار / راهداران حاضر در گشت
|
||||
</Typography>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineConnector />
|
||||
<TimelineDot sx={{ backgroundColor: "#fff" }}>
|
||||
{tabState === 2 ? (
|
||||
<CircularProgress size="23px" />
|
||||
) : (
|
||||
<HandymanIcon
|
||||
color={tabState > 2 ? "success" : "error"}
|
||||
sx={{ width: "1.5rem", height: "1.5rem" }}
|
||||
/>
|
||||
)}
|
||||
</TimelineDot>
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent sx={{ py: "12px", px: 2 }}>
|
||||
<Typography variant="h6">اقدامات حین گشت</Typography>
|
||||
<Typography variant="caption" sx={{ color: "#606060" }}>
|
||||
ثبت فعالیت های صورت گرفته حین گشت
|
||||
</Typography>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
<TimelineItem>
|
||||
<TimelineSeparator>
|
||||
<TimelineConnector />
|
||||
<TimelineDot sx={{ backgroundColor: "#fff" }}>
|
||||
{tabState === 3 ? (
|
||||
<CircularProgress size="23px" />
|
||||
) : (
|
||||
<VerifiedIcon
|
||||
color={tabState > 3 ? "success" : "error"}
|
||||
sx={{ width: "1.5rem", height: "1.5rem" }}
|
||||
/>
|
||||
)}
|
||||
</TimelineDot>
|
||||
<TimelineConnector />
|
||||
</TimelineSeparator>
|
||||
<TimelineContent sx={{ py: "12px", px: 2 }}>
|
||||
<Typography variant="h6">تکمیل درخواست</Typography>
|
||||
<Typography variant="caption" sx={{ color: "#606060" }}>
|
||||
تکمیل درخواست و ثبت نهایی
|
||||
</Typography>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
</Timeline>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default PatrolTimeLine;
|
||||
@@ -0,0 +1,118 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Button, FormControl, InputLabel, OutlinedInput } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ForwardToInboxIcon from "@mui/icons-material/ForwardToInbox";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { GET_OTP_TOKEN, VERIFY_OTP } from "@/core/utils/routes";
|
||||
import { formatCounter } from "@/core/utils/formatCounter";
|
||||
|
||||
const PhoneValidation = ({ tabState, setTabState }) => {
|
||||
const requestServer = useRequest({ auth: true });
|
||||
const [phoneNumber, setPhoneNumber] = useState("");
|
||||
const [verificationCode, setVerificationCode] = useState("");
|
||||
const [validPhone, setValidPhone] = useState(false);
|
||||
const [delayPerRequest, setDelayPerRequest] = useState(false);
|
||||
const [counter, setCounter] = useState(120);
|
||||
const [otpSended, setOtpSended] = useState(false);
|
||||
const [readyToVerify, setReadyToVerify] = useState(false);
|
||||
|
||||
const handleNumericInput = (e) => {
|
||||
e.target.value = e.target.value.replace(/[^0-9]/g, "");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (phoneNumber.length === 11) {
|
||||
setValidPhone(true);
|
||||
} else {
|
||||
setValidPhone(false);
|
||||
}
|
||||
if (verificationCode.length === 6 && phoneNumber.length === 11) {
|
||||
setReadyToVerify(true);
|
||||
} else {
|
||||
setReadyToVerify(false);
|
||||
}
|
||||
}, [phoneNumber, verificationCode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (counter === 0) {
|
||||
setDelayPerRequest(false);
|
||||
return;
|
||||
}
|
||||
if (delayPerRequest && counter > 0) {
|
||||
const timer = setInterval(() => {
|
||||
setCounter((prevCounter) => prevCounter - 1);
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
}, [counter, delayPerRequest]);
|
||||
|
||||
const sendOtp = () => {
|
||||
if (!validPhone) return false;
|
||||
requestServer(`${GET_OTP_TOKEN}?phone_number=${phoneNumber}`, "get")
|
||||
.then((response) => {})
|
||||
.catch(() => {});
|
||||
//////////**** (mohammad) this part should keep in success but for now we go next level ****////////////
|
||||
setOtpSended(true);
|
||||
setDelayPerRequest(true);
|
||||
};
|
||||
|
||||
const phoneRegister = () => {
|
||||
const formData = new FormData();
|
||||
formData.append("phone_number", phoneNumber);
|
||||
formData.append("verification_code", verificationCode);
|
||||
|
||||
setTabState(tabState + 1);
|
||||
requestServer(VERIFY_OTP, "post", {
|
||||
data: formData,
|
||||
})
|
||||
.then(() => {})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
my: 3,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2, width: { xs: "100%", sm: "70%" } }}>
|
||||
<FormControl size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="phone_number">شماره تلفن مامور گشت</InputLabel>
|
||||
<OutlinedInput
|
||||
id="phone_number"
|
||||
label={"شماره تلفن مامور گشت"}
|
||||
disabled={delayPerRequest}
|
||||
autoComplete="off"
|
||||
size="small"
|
||||
fullWidth
|
||||
value={phoneNumber}
|
||||
onChange={(e) => setPhoneNumber(e.target.value)}
|
||||
onInput={handleNumericInput}
|
||||
type="text"
|
||||
inputProps={{
|
||||
maxLength: 11,
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={sendOtp}
|
||||
disabled={!validPhone || delayPerRequest}
|
||||
variant="contained"
|
||||
color="success"
|
||||
sx={{ width: "200px", height: "37px" }}
|
||||
endIcon={<ForwardToInboxIcon />}
|
||||
>
|
||||
{delayPerRequest ? formatCounter(counter) : "دریافت کد"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PhoneValidation;
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Card, IconButton, Stack, Typography } from "@mui/material";
|
||||
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import React from "react";
|
||||
|
||||
const SuperVisorInfo = ({ superVisor, deleteSuperVisor }) => {
|
||||
return (
|
||||
<Card
|
||||
elevation={3}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
px: 1,
|
||||
py: 1,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AccountCircleIcon color="primary" sx={{ width: "50px", height: "50px" }} />
|
||||
<Stack sx={{ ml: 1, justifyContent: "center" }}>
|
||||
<Typography variant="body1" sx={{ letterSpacing: "1px", fontWeight: 500 }}>
|
||||
{superVisor.name}
|
||||
</Typography>
|
||||
<Typography variant="caption">کد راهدار: {superVisor.code}</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<IconButton color="error" onClick={() => deleteSuperVisor(superVisor.id)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SuperVisorInfo;
|
||||
@@ -0,0 +1,139 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Button, Chip, Divider, Grid, Stack, Typography } from "@mui/material";
|
||||
import SaveAltIcon from "@mui/icons-material/SaveAlt";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import RahdarCode from "@/core/components/RahdarCode";
|
||||
import SuperVisorInfo from "@/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/SuperVisorInfo";
|
||||
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
|
||||
import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const SuperVisorsDetail = ({ control, watch, setValue, tabState, setTabState }) => {
|
||||
const [SuperVisorList, setSuperVisorList] = useState([]);
|
||||
const [readyToRequest, setReadyToRequest] = useState(false);
|
||||
|
||||
const requestPatrolInfo = () => {
|
||||
setSuperVisorList((prev) => [...prev, watch("road_patrol_rahdaran_id")]);
|
||||
setReadyToRequest(false);
|
||||
setValue("road_patrol_rahdaran_id", null);
|
||||
};
|
||||
|
||||
const deleteSuperVisor = (id) => {
|
||||
setSuperVisorList((prev) => prev.filter((superVisor) => superVisor.id !== id));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setReadyToRequest(watch("road_patrol_rahdaran_id") != null);
|
||||
}, [watch("road_patrol_rahdaran_id")]);
|
||||
|
||||
const SendSuperVisor = () => {
|
||||
setValue("road_patrol_rahdaran_id", SuperVisorList);
|
||||
setTabState(tabState + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
my: 3,
|
||||
}}
|
||||
>
|
||||
<Grid container sx={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
<Grid
|
||||
item
|
||||
xs={6}
|
||||
sx={{ display: "flex", alignItems: "center", flexDirection: { xs: "column", lg: "row" }, gap: 2 }}
|
||||
>
|
||||
<Stack sx={{ minWidth: "250px" }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<RahdarCode
|
||||
rahdarsCode={field.value}
|
||||
setRahdarsCode={(value) => field.onChange(value)}
|
||||
error={error}
|
||||
multiple={false}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"road_patrol_rahdaran_id"}
|
||||
/>
|
||||
</Stack>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={requestPatrolInfo}
|
||||
color="success"
|
||||
sx={{ textWrap: "nowrap", px: 3 }}
|
||||
disabled={!readyToRequest}
|
||||
endIcon={<SaveAltIcon />}
|
||||
>
|
||||
ثبت راهدار
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider sx={{ my: 2, width: "100%" }}>
|
||||
<Chip color="success" variant="outlined" label="لیست راهداران انتخاب شده" />
|
||||
</Divider>
|
||||
{SuperVisorList.length !== 0 ? (
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
sx={{
|
||||
p: 1,
|
||||
mt: 1,
|
||||
maxHeight: "300px",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{SuperVisorList.map((superVisor) => (
|
||||
<Grid key={superVisor.id} item xs={12} sm={6} lg={4}>
|
||||
<SuperVisorInfo superVisor={superVisor} deleteSuperVisor={deleteSuperVisor} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: 1,
|
||||
mt: 2,
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="error"
|
||||
startIcon={<KeyboardDoubleArrowRightIcon />}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
بازگشت
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={SendSuperVisor}
|
||||
endIcon={<KeyboardDoubleArrowLeftIcon />}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
تایید اطلاعات و رفتن به مرحله بعد
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Box sx={{ my: 5, width: "100%", textAlign: "center" }}>
|
||||
<Typography variant="h6" sx={{ letterSpacing: "2px", color: "#606060" }}>
|
||||
ابتدا اطلاعات بالا را تکمیل نمایید
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SuperVisorsDetail;
|
||||
@@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { Dialog } from "@mui/material";
|
||||
import PatrolForms from "./PatrolForms";
|
||||
|
||||
const CreatePatrol = ({ open, setOpen, mutate }) => {
|
||||
return (
|
||||
<Dialog open={open} fullWidth maxWidth="lg">
|
||||
<PatrolForms setOpen={setOpen} mutate={mutate} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default CreatePatrol;
|
||||
@@ -1,11 +1,14 @@
|
||||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import Toolbar from "./Toolbar";
|
||||
import { GET_ROAD_PATROL_OPERATOR_LIST } from "@/core/utils/routes";
|
||||
import moment from "jalali-moment";
|
||||
import RowActions from "./RowActions";
|
||||
import ReportForm from "./RowActions/ReportForm";
|
||||
import MachinesCodeDialog from "./RowActions/MachinesCodeForm";
|
||||
import RahdaranDialog from "./RowActions/RahdaranForm";
|
||||
|
||||
const OperatorList = () => {
|
||||
const columns = useMemo(
|
||||
@@ -22,31 +25,64 @@ const OperatorList = () => {
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "operator_name",
|
||||
header: "نام مامور",
|
||||
id: "operator_name",
|
||||
accessorKey: "cmms_machines",
|
||||
header: "خودرو", // Car ID
|
||||
id: "cmmsMachines__machine_code",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
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 }) => {
|
||||
if (renderedCellValue) {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<MachinesCodeDialog machinesLists={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return <Typography variant="body2">کد خودرویی وجود ندارد</Typography>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "officer_phone_number",
|
||||
header: "تلفن همراه",
|
||||
id: "officer_phone_number",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
},
|
||||
{
|
||||
accessorKey: "car_id",
|
||||
header: "کد خودرو",
|
||||
id: "car_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
accessorKey: "rahdaran",
|
||||
header: "راهداران",
|
||||
id: "rahdaran",
|
||||
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 }) => {
|
||||
if (renderedCellValue) {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<RahdaranDialog rahdarLists={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return <Typography variant="body2">بدون شخص</Typography>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => moment(row.start_date).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
@@ -81,6 +117,29 @@ const OperatorList = () => {
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
header: "گزارش مامور",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
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"}>
|
||||
<ReportForm rowId={row.getValue("id")} />
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
@@ -89,7 +148,6 @@ const OperatorList = () => {
|
||||
<>
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
table_title={"عملیات"}
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
import ShowPlak from "@/core/components/ShowPlak";
|
||||
|
||||
const MachinesCodeContent = ({ machinesLists }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
"*::-webkit-scrollbar": {
|
||||
width: "0.5em",
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableContainer sx={{ maxHeight: 600 }}>
|
||||
<Table stickyHeader sx={{ minWidth: 800 }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>کد ماشین</TableCell>
|
||||
<TableCell>نام خودرو</TableCell>
|
||||
<TableCell>پلاک</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{machinesLists.map((machine) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={machine.id}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
>
|
||||
<TableCell>{machine.machine_code}</TableCell>
|
||||
<TableCell>{machine.car_name}</TableCell>
|
||||
<TableCell>
|
||||
{machine.plak_number ? (
|
||||
<ShowPlak plak_number={machine.plak_number} />
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
</DialogContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachinesCodeContent;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import DirectionsCarIcon from "@mui/icons-material/DirectionsCar";
|
||||
import { useState } from "react";
|
||||
import MachinesCodeContent from "./MachinesCodeContent";
|
||||
|
||||
const MachinesCodeDialog = ({ machinesLists }) => {
|
||||
const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="کد خودرو">
|
||||
<IconButton color="primary" onClick={() => setOpenMachinesCodeDialog(true)}>
|
||||
<DirectionsCarIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openMachinesCodeDialog}
|
||||
onClose={() => setOpenMachinesCodeDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>کد خودرو</DialogTitle>
|
||||
<MachinesCodeContent machinesLists={machinesLists} />
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setOpenMachinesCodeDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachinesCodeDialog;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
|
||||
const RahdaranContent = ({ rahdarLists }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
"*::-webkit-scrollbar": {
|
||||
width: "0.5em",
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableContainer sx={{ maxHeight: 600 }}>
|
||||
<Table stickyHeader sx={{ minWidth: 800 }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>کد راهدار</TableCell>
|
||||
<TableCell>نام</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rahdarLists.map((rahdar) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={rahdar.id}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
>
|
||||
<TableCell>{rahdar.code}</TableCell>
|
||||
<TableCell>{rahdar.name}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
</DialogContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RahdaranContent;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import { useState } from "react";
|
||||
import RahdaranContent from "./RahdaranContent";
|
||||
|
||||
const RahdaranDialog = ({ rahdarLists }) => {
|
||||
const [openRahdaranDialog, setOpenRahdaranDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="راهداران">
|
||||
<IconButton color="primary" onClick={() => setOpenRahdaranDialog(true)}>
|
||||
<GroupsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openRahdaranDialog}
|
||||
onClose={() => setOpenRahdaranDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>لیست راهداران</DialogTitle>
|
||||
<RahdaranContent rahdarLists={rahdarLists} />
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenRahdaranDialog(false)} variant="outlined" color="secondary" autoFocus>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RahdaranDialog;
|
||||
@@ -2,8 +2,7 @@ import { Tooltip, IconButton } from "@mui/material";
|
||||
import PrintIcon from "@mui/icons-material/Print";
|
||||
|
||||
const ReportForm = ({ rowId }) => {
|
||||
const reportUrl = "https://rms.rmto.ir/v2/road_patrols/operator/report/96872"; // TODO replace id here
|
||||
|
||||
const reportUrl = `https://rms.rmto.ir/v2/road_patrols/operator/report/${rowId}`;
|
||||
return (
|
||||
<Tooltip title="گزارش" arrow placement="right">
|
||||
<IconButton
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { Box } from "@mui/material";
|
||||
import ReportForm from "./ReportForm";
|
||||
|
||||
const RowActions = ({ row }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<ReportForm rowId={row.getValue("id")} />
|
||||
</Box>
|
||||
);
|
||||
return <Box sx={{ display: "flex", gap: 1 }}></Box>;
|
||||
};
|
||||
export default RowActions;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import PrintExcel from "./ExcelPrint";
|
||||
import OperatorCreate from "./Actions/Create";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const Toolbar = ({ table, filterData }) => {
|
||||
const Toolbar = ({ table, filterData, mutate }) => {
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<OperatorCreate table={table} mutate={mutate} />
|
||||
<PrintExcel table={table} filterData={filterData} />
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default Toolbar;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import PageTitle from "@/core/components/PageTitle";
|
||||
import { Stack } from "@mui/material";
|
||||
import OperatorList from "@/components/dashboard/roadPatrols/operator/OperatorList";
|
||||
import OperatorList from "./OperatorList";
|
||||
|
||||
const OperatorPage = () => {
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,45 @@
|
||||
import { Button, DialogActions, DialogContent, Stack, Typography, List, ListItem } from "@mui/material";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { DELETE_ROAD_PATROL_SUPERVISOR } from "@/core/utils/routes";
|
||||
|
||||
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||
const requestServer = useRequest();
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const [submittingPartial, setSubmittingPartial] = useState(false);
|
||||
const [submittingGeneral, setSubmittingGeneral] = useState(false);
|
||||
const handleGeneralDelete = () => {
|
||||
setSubmittingGeneral(true);
|
||||
requestServer(`${DELETE_ROAD_PATROL_SUPERVISOR}/${rowId}`, "post", {
|
||||
data: {
|
||||
type: 2,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
mutate();
|
||||
setOpenDeleteDialog(false);
|
||||
setSubmittingGeneral(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setSubmittingGeneral(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handlePartialDelete = () => {
|
||||
setSubmittingPartial(true);
|
||||
requestServer(`${DELETE_ROAD_PATROL_SUPERVISOR}/${rowId}`, "post", {
|
||||
data: {
|
||||
type: 1,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
mutate();
|
||||
setOpenDeleteDialog(false);
|
||||
setSubmittingPartial(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setSubmittingPartial(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -42,23 +78,11 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="primary">
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
/* handle partial delete */
|
||||
}}
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
>
|
||||
حذف جزئی
|
||||
<Button onClick={handlePartialDelete} variant="outlined" color="warning" disabled={submittingPartial}>
|
||||
{submittingPartial ? "درحال ارسال اطلاعات..." : "حذف جزئی"}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
/* handle complete delete */
|
||||
}}
|
||||
variant="contained"
|
||||
color="error"
|
||||
>
|
||||
حذف کلی
|
||||
<Button onClick={handleGeneralDelete} variant="contained" color="error" disabled={submittingGeneral}>
|
||||
{submittingGeneral ? "درحال ارسال اطلاعات..." : "حذف کلی"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
import ShowPlak from "@/core/components/ShowPlak";
|
||||
|
||||
const MachinesCodeContent = ({ machinesLists }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
"*::-webkit-scrollbar": {
|
||||
width: "0.5em",
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableContainer sx={{ maxHeight: 600 }}>
|
||||
<Table stickyHeader sx={{ minWidth: 800 }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>کد ماشین</TableCell>
|
||||
<TableCell>نام خودرو</TableCell>
|
||||
<TableCell>پلاک</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{machinesLists.map((machine) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={machine.id}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
>
|
||||
<TableCell>{machine.machine_code}</TableCell>
|
||||
<TableCell>{machine.car_name}</TableCell>
|
||||
<TableCell>
|
||||
{machine.plak_number ? (
|
||||
<ShowPlak plak_number={machine.plak_number} />
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
</DialogContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachinesCodeContent;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import DirectionsCarIcon from "@mui/icons-material/DirectionsCar";
|
||||
import { useState } from "react";
|
||||
import MachinesCodeContent from "./MachinesCodeContent";
|
||||
|
||||
const MachinesCodeDialog = ({ machinesLists }) => {
|
||||
const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="کد خودرو">
|
||||
<IconButton color="primary" onClick={() => setOpenMachinesCodeDialog(true)}>
|
||||
<DirectionsCarIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openMachinesCodeDialog}
|
||||
onClose={() => setOpenMachinesCodeDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>کد خودرو</DialogTitle>
|
||||
<MachinesCodeContent machinesLists={machinesLists} />
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setOpenMachinesCodeDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachinesCodeDialog;
|
||||
@@ -1,26 +1,10 @@
|
||||
import { Dialog, DialogContent, DialogTitle, IconButton, Tooltip, Typography, Card, CardContent } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
const OfficerDescriptionForm = ({ row }) => {
|
||||
const OfficerDescriptionForm = ({ description }) => {
|
||||
const [openOfficerDescriptionDialog, setOpenOfficerDescriptionDialog] = useState(false);
|
||||
const requestServer = useRequest();
|
||||
|
||||
const handleSubmit = () => {
|
||||
// requestServer(`${RESERVE_PASSENGER_BOSS}/${rowId}`, "post", { description })
|
||||
// .then((response) => {
|
||||
// setOpenOfficerDescriptionDialog(false);
|
||||
// mutate();
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// console.error(error);
|
||||
// })
|
||||
// .finally(() => {
|
||||
// setIsSubmitting(false);
|
||||
// });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -55,18 +39,7 @@ const OfficerDescriptionForm = ({ row }) => {
|
||||
<DialogContent>
|
||||
<Card sx={{ padding: 2, boxShadow: 3, borderRadius: 2 }}>
|
||||
<CardContent>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="body1"
|
||||
component="div"
|
||||
sx={{ fontWeight: "bold", mb: 2, color: "primary.main" }}
|
||||
>
|
||||
توضیحات :
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{row.original.description || "هیچ توضیحی موجود نیست"}{" "}
|
||||
{/* should replace row.original.description with the back end*/}
|
||||
</Typography>
|
||||
<Typography variant="body2">{description || "هیچ توضیحی موجود نیست"} </Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</DialogContent>
|
||||
@@ -74,5 +47,4 @@ const OfficerDescriptionForm = ({ row }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default OfficerDescriptionForm;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
|
||||
const RahdaranContent = ({ rahdarLists }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
"*::-webkit-scrollbar": {
|
||||
width: "0.5em",
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableContainer sx={{ maxHeight: 600 }}>
|
||||
<Table stickyHeader sx={{ minWidth: 800 }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>کد راهدار</TableCell>
|
||||
<TableCell>نام</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rahdarLists.map((rahdar) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={rahdar.id}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
>
|
||||
<TableCell>{rahdar.code}</TableCell>
|
||||
<TableCell>{rahdar.name}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
</DialogContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RahdaranContent;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import { useState } from "react";
|
||||
import RahdaranContent from "./RahdaranContent";
|
||||
|
||||
const RahdaranDialog = ({ rahdarLists }) => {
|
||||
const [openRahdaranDialog, setOpenRahdaranDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="راهداران">
|
||||
<IconButton color="primary" onClick={() => setOpenRahdaranDialog(true)}>
|
||||
<GroupsIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openRahdaranDialog}
|
||||
onClose={() => setOpenRahdaranDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>لیست راهداران</DialogTitle>
|
||||
<RahdaranContent rahdarLists={rahdarLists} />
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenRahdaranDialog(false)} variant="outlined" color="secondary" autoFocus>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RahdaranDialog;
|
||||
@@ -2,8 +2,7 @@ import { Tooltip, IconButton } from "@mui/material";
|
||||
import PrintIcon from "@mui/icons-material/Print";
|
||||
|
||||
const ReportForm = ({ rowId }) => {
|
||||
const reportUrl = "https://rms.rmto.ir/v2/road_patrols/operator/report/96872"; // TODO replace id here
|
||||
|
||||
const reportUrl = `https://rms.rmto.ir/v2/road_patrols/operator/report/${rowId}`;
|
||||
return (
|
||||
<Tooltip title="گزارش" arrow placement="right">
|
||||
<IconButton
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { Box } from "@mui/material";
|
||||
import ReportForm from "./ReportForm";
|
||||
import DeleteForm from "./DeleteForm";
|
||||
import OfficerDescriptionForm from "./OfficerDescription";
|
||||
|
||||
const RowActions = ({ row, mutate }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<ReportForm rowId={row.getValue("id")} />
|
||||
<OfficerDescriptionForm row={row} />
|
||||
<DeleteForm rowId={row.getValue("id")} mutate={mutate} />
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
import { useMemo } from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import {Box, Stack, Typography} from "@mui/material";
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import Toolbar from "./Toolbar";
|
||||
import { GET_ROAD_PATROL_SUPERVISOR_LIST } from "@/core/utils/routes";
|
||||
import moment from "jalali-moment";
|
||||
import RowActions from "./RowActions";
|
||||
import useProvinces from "@/lib/hooks/useProvince";
|
||||
import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems";
|
||||
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
||||
import useEdaratLists from "@/lib/hooks/useEdaratLists";
|
||||
import OfficerDescriptionForm from "./RowActions/OfficerDescription";
|
||||
import ReportForm from "./RowActions/ReportForm";
|
||||
import MachinesCodeDialog from "./RowActions/MachinesCodeForm";
|
||||
import RahdaranDialog from "./RowActions/RahdaranForm";
|
||||
|
||||
const OperatorList = () => {
|
||||
const columns = useMemo(
|
||||
@@ -92,31 +95,64 @@ const OperatorList = () => {
|
||||
Cell: ({ renderedCellValue, row }) => <>{row.original.edare_name}</>,
|
||||
},
|
||||
{
|
||||
accessorKey: "operator_name",
|
||||
header: "نام مامور",
|
||||
id: "operator_name",
|
||||
accessorKey: "cmms_machines",
|
||||
header: "خودرو", // Car ID
|
||||
id: "cmmsMachines__machine_code",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
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 }) => {
|
||||
if (renderedCellValue) {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<MachinesCodeDialog machinesLists={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return <Typography variant="body2">کد خودرویی وجود ندارد</Typography>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "officer_phone_number",
|
||||
header: "تلفن همراه",
|
||||
id: "officer_phone_number",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
},
|
||||
{
|
||||
accessorKey: "car_id",
|
||||
header: "کد خودرو",
|
||||
id: "car_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
accessorKey: "rahdaran",
|
||||
header: "راهداران",
|
||||
id: "rahdaran",
|
||||
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 }) => {
|
||||
if (renderedCellValue) {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<RahdaranDialog rahdarLists={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return <Typography variant="body2">بدون شخص</Typography>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => moment(row.start_date).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
@@ -151,6 +187,54 @@ const OperatorList = () => {
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "توضیحات مامور",
|
||||
id: "description",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
grow: false,
|
||||
size: 100,
|
||||
muiTableBodyCellProps: {
|
||||
sx: {
|
||||
borderLeft: "1px solid #e1e1e1",
|
||||
py: 0,
|
||||
"&:first-of-type": {
|
||||
borderLeft: "unset",
|
||||
},
|
||||
},
|
||||
},
|
||||
Cell: ({ renderedCellValue }) => {
|
||||
return (
|
||||
<Stack alignItems={"center"} justifyContent={"center"}>
|
||||
<OfficerDescriptionForm description={renderedCellValue} />
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "گزارش مامور",
|
||||
enableColumnFilter: false,
|
||||
datatype: "text",
|
||||
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"}>
|
||||
<ReportForm rowId={row.getValue("id")} />
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
@@ -159,7 +243,6 @@ const OperatorList = () => {
|
||||
<>
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
table_title={"ارزیابی"}
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
import PageTitle from "@/core/components/PageTitle";
|
||||
import { Stack } from "@mui/material";
|
||||
import SupervisorList from "./SupervisorList";
|
||||
import SuperviserList from "./SupervisorList";
|
||||
|
||||
const SupervisorPage = () => {
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<PageTitle title={"ارزیابی"} />
|
||||
<SupervisorList />
|
||||
<SuperviserList />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
36
src/core/components/ActivityCodeLog.jsx
Normal file
36
src/core/components/ActivityCodeLog.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, memo } from "react";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { ACTIVITY_LOG } from "@/core/utils/routes";
|
||||
|
||||
const ActivityCodeLog = memo(({ activity_code }) => {
|
||||
const requestServer = useRequest({ notificationShow: false });
|
||||
const [hasLogged, setHasLogged] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activity_code || hasLogged) return;
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const fetchActivityLog = async () => {
|
||||
try {
|
||||
await requestServer(ACTIVITY_LOG, "post", {
|
||||
data: { activityCode: activity_code },
|
||||
signal: controller.signal,
|
||||
});
|
||||
setHasLogged(true);
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
fetchActivityLog();
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [activity_code, hasLogged, requestServer]);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
export default ActivityCodeLog;
|
||||
@@ -21,7 +21,9 @@ const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error, multiple
|
||||
requestOptions: { signal: controller.signal },
|
||||
})
|
||||
.then((response) => {
|
||||
setOptions(response.data.data || []);
|
||||
const combinedArray = carCode ? [...carCode, ...response.data.data] : [...response.data.data];
|
||||
const uniqueArray = Array.from(new Map(combinedArray.map((item) => [item.id, item])).values());
|
||||
setOptions(uniqueArray || []);
|
||||
})
|
||||
.catch(() => {
|
||||
setOptions([]);
|
||||
@@ -38,7 +40,6 @@ const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error, multiple
|
||||
fetchCarCodes(inputValue, controller);
|
||||
return () => controller.abort();
|
||||
}, [inputValue]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
multiple={multiple}
|
||||
|
||||
@@ -23,6 +23,7 @@ const DataTable_Main = (props) => {
|
||||
table_title,
|
||||
RowActions,
|
||||
specific_data,
|
||||
specialFilter,
|
||||
} = props;
|
||||
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||
@@ -33,29 +34,34 @@ const DataTable_Main = (props) => {
|
||||
};
|
||||
|
||||
const fetchUrl = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||
params.set("size", pagination.pageSize);
|
||||
const isValueEmpty = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length === 0 || value.every((v) => v === "");
|
||||
}
|
||||
return value === "" || value === null || value === undefined;
|
||||
};
|
||||
if (specialFilter) {
|
||||
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
||||
(filter) => !isValueEmpty(filter.value)
|
||||
);
|
||||
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
||||
} else {
|
||||
const filteredFilterData = Object.values(filterData)
|
||||
.filter((filter) => !isValueEmpty(filter.value))
|
||||
.map(({ filterMode, id, ...rest }) => ({
|
||||
...rest,
|
||||
id: id.replace(/__/g, "."),
|
||||
fn: filterMode,
|
||||
}));
|
||||
|
||||
const filteredFilterData = Object.values(filterData)
|
||||
.filter((filter) => !isValueEmpty(filter.value))
|
||||
.map(({ filterMode, id, ...rest }) => ({
|
||||
...rest,
|
||||
id: id.replace(/__/g, "."),
|
||||
fn: filterMode,
|
||||
}));
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||
params.set("size", pagination.pageSize);
|
||||
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
|
||||
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
|
||||
}
|
||||
params.set("sorting", JSON.stringify(sortData));
|
||||
return `${table_url}?${params}`;
|
||||
}, [table_url, filterData, pagination, columns, sortData]);
|
||||
|
||||
}, [table_url, filterData, pagination, columns, sortData, specialFilter]);
|
||||
const fetcher = async (url) => {
|
||||
try {
|
||||
const response = await request(url);
|
||||
@@ -143,5 +149,4 @@ const DataTable_Main = (props) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable_Main;
|
||||
|
||||
@@ -12,24 +12,19 @@ const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const createCustomIcon = (size, iconUrl, labelText) => {
|
||||
const createCustomIcon = (size, iconUrl, labelText, color) => {
|
||||
if (labelText) {
|
||||
return L.divIcon({
|
||||
className: "custom-marker", // Apply your custom CSS class
|
||||
html: `
|
||||
<div style="position: relative; display: inline-block; text-align: center;">
|
||||
<img
|
||||
src="${iconUrl}"
|
||||
style="width: ${size[0]}px; height: ${size[1]}px;"
|
||||
alt="icon"
|
||||
/>
|
||||
<div style="position: absolute; top: 100%; left: 50%; transform: translate(-50%, 0); color: black; font-size: 12px;">
|
||||
${labelText}
|
||||
<div style="position: relative; text-align: center; width: 50px;">
|
||||
<div style="background-color: ${color}; color: white; border-radius: 20px; padding: 5px;">
|
||||
<span style="font-family: 'IRANSansFaNum', sans-serif;">${labelText}</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
|
||||
</div>`,
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,6 +40,7 @@ const MAX_ZOOM_FOR_MARKER = 13;
|
||||
const MapInteraction = ({ setValue, startLat, startLng }) => {
|
||||
const [isMarkerLocked, setIsMarkerLocked] = useState(!!(startLat && startLng)); // وضعیت قفل مارکر
|
||||
const [enableSend, setEnableSend] = useState(false);
|
||||
const [startIconColor, setStartIconColor] = useState(startLat ? "#1CAC66" : "#003d4f"); // رنگ آیکن شروع
|
||||
const [markerPosition, setMarkerPosition] = useState(
|
||||
startLat && startLng ? { lat: startLat, lng: startLng } : null
|
||||
);
|
||||
@@ -79,6 +75,7 @@ const MapInteraction = ({ setValue, startLat, startLng }) => {
|
||||
setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
|
||||
setMarkerPosition({ lat: center.lat, lng: center.lng }); // بهروزرسانی موقعیت مارکر
|
||||
setIsMarkerLocked(true);
|
||||
setStartIconColor("#1CAC66");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,6 +83,7 @@ const MapInteraction = ({ setValue, startLat, startLng }) => {
|
||||
setValue("start_point", null); // حذف مقدار قبلی
|
||||
setIsMarkerLocked(false); // باز کردن قفل مارکر
|
||||
setMarkerPosition(null); // تنظیم مارکر به مرکز نقشه
|
||||
setStartIconColor("#003d4f");
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -93,7 +91,7 @@ const MapInteraction = ({ setValue, startLat, startLng }) => {
|
||||
<Marker
|
||||
position={markerPosition || map.getCenter()} // استفاده از موقعیت
|
||||
ref={markerRef}
|
||||
icon={createCustomIcon([35, 35], HereIcon.src, "اینجا")}
|
||||
icon={createCustomIcon([35, 35], HereIcon.src, "اینجا", startIconColor)}
|
||||
eventHandlers={{
|
||||
click: handleMarkerClick,
|
||||
}}
|
||||
|
||||
@@ -12,27 +12,21 @@ const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
const createCustomIcon = (size, iconUrl, labelText) => {
|
||||
const createCustomIcon = (size, iconUrl, labelText, color) => {
|
||||
if (labelText) {
|
||||
return L.divIcon({
|
||||
className: "custom-marker", // Apply your custom CSS class
|
||||
html: `
|
||||
<div style="position: relative; display: inline-block; text-align: center;">
|
||||
<img
|
||||
src="${iconUrl}"
|
||||
style="width: ${size[0]}px; height: ${size[1]}px;"
|
||||
alt="icon"
|
||||
/>
|
||||
<div style="position: absolute; top: 100%; left: 50%; transform: translate(-50%, 0); color: black; font-size: 12px;">
|
||||
${labelText}
|
||||
<div style="position: relative; text-align: center; width: 50px;">
|
||||
<div style="background-color: ${color}; color: white; border-radius: 20px; padding: 5px;">
|
||||
<span style="font-family: 'IRANSansFaNum', sans-serif;">${labelText}</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
|
||||
</div>`,
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
});
|
||||
}
|
||||
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
@@ -48,6 +42,8 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
|
||||
const [startPosition, setStartPosition] = useState(startLat && startLng ? { lat: startLat, lng: startLng } : null);
|
||||
const [endPosition, setEndPosition] = useState(endLat && endLng ? { lat: endLat, lng: endLng } : null);
|
||||
const [enableSend, setEnableSend] = useState(false);
|
||||
const [startIconColor, setStartIconColor] = useState(startLat ? "#1CAC66" : "#003d4f"); // رنگ آیکن شروع
|
||||
const [endIconColor, setEndIconColor] = useState(endLat ? "#D13131" : "#003d4f"); // رنگ آیکن پایان
|
||||
const startRef = useRef();
|
||||
const endRef = useRef();
|
||||
|
||||
@@ -86,6 +82,7 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
|
||||
setIsStartLocked(false);
|
||||
setStartPosition(null);
|
||||
setValue("start_point", null);
|
||||
setStartIconColor("#003d4f");
|
||||
};
|
||||
|
||||
const handleUnlockEnd = () => {
|
||||
@@ -95,6 +92,8 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
|
||||
setStartPosition(null);
|
||||
setValue("end_point", "");
|
||||
setValue("start_point", null);
|
||||
setStartIconColor("#003d4f");
|
||||
setEndIconColor("#003d4f");
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -102,7 +101,7 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
|
||||
<Marker
|
||||
position={startPosition || map.getCenter()}
|
||||
ref={startRef}
|
||||
icon={createCustomIcon([35, 35], StartIcon.src, "نقطه شروع")}
|
||||
icon={createCustomIcon([35, 35], StartIcon.src, "شروع", startIconColor)}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
if (!enableSend) return;
|
||||
@@ -111,6 +110,8 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
|
||||
setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
|
||||
setStartPosition({ lat: center.lat, lng: center.lng });
|
||||
setIsStartLocked(true);
|
||||
setStartIconColor("#1CAC66");
|
||||
map.panBy([25, 25])
|
||||
}
|
||||
},
|
||||
}}
|
||||
@@ -172,7 +173,7 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
|
||||
<Marker
|
||||
position={endPosition || map.getCenter()}
|
||||
ref={endRef}
|
||||
icon={createCustomIcon([35, 35], EndIcon.src, "نقطه پایان")}
|
||||
icon={createCustomIcon([35, 35], EndIcon.src, "پایان", endIconColor)}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
if (!enableSend) return;
|
||||
@@ -181,6 +182,14 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
|
||||
setValue("end_point", { lat: center.lat.toString(), lng: center.lng.toString() });
|
||||
setEndPosition({ lat: center.lat, lng: center.lng });
|
||||
setIsEndLocked(true);
|
||||
setEndIconColor("#D13131");
|
||||
map.fitBounds(
|
||||
[
|
||||
startPosition,
|
||||
map.getCenter(),
|
||||
],
|
||||
{ paddingTopLeft: [16, 16], paddingBottomRight: [16, 16] }
|
||||
);
|
||||
}
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -59,9 +59,11 @@ function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperTex
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
|
||||
{helperText ? helperText : ""}
|
||||
</FormHelperText>
|
||||
{helperText ? (
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
|
||||
{helperText}
|
||||
</FormHelperText>
|
||||
) : null}
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -64,9 +64,11 @@ function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperTex
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
|
||||
{helperText ? helperText : ""}
|
||||
</FormHelperText>
|
||||
{helperText ? (
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
|
||||
{helperText}
|
||||
</FormHelperText>
|
||||
) : null}
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -22,7 +22,11 @@ const RahdarCode = ({ rahdarsCode, setRahdarsCode, error, multiple = true }) =>
|
||||
requestOptions: { signal: controller.signal },
|
||||
})
|
||||
.then((response) => {
|
||||
setOptions(response.data.data || []);
|
||||
const combinedArray = rahdarsCode
|
||||
? [...rahdarsCode, ...response.data.data]
|
||||
: [...response.data.data];
|
||||
const uniqueArray = Array.from(new Map(combinedArray.map((item) => [item.id, item])).values());
|
||||
setOptions(uniqueArray || []);
|
||||
})
|
||||
.catch(() => {
|
||||
setOptions([]);
|
||||
|
||||
@@ -2,16 +2,24 @@
|
||||
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function WithPermission({ children, permission_name }) {
|
||||
const { data, error, isLoading } = usePermissions();
|
||||
const [cachedData, setCachedData] = useState(null);
|
||||
|
||||
if (error || isLoading || !data || !permission_name) {
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setCachedData(data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (error || isLoading || !cachedData || !permission_name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasPermission =
|
||||
permission_name.includes("all") || permission_name.some((permission) => data.includes(permission));
|
||||
permission_name.includes("all") || permission_name.some((permission) => cachedData.includes(permission));
|
||||
|
||||
if (!hasPermission) {
|
||||
return (
|
||||
|
||||
7
src/core/utils/formatCounter.js
Normal file
7
src/core/utils/formatCounter.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export const formatCounter = (counter) => {
|
||||
const minutes = Math.floor(counter / 60)
|
||||
.toString()
|
||||
.padStart(2, "0");
|
||||
const seconds = (counter % 60).toString().padStart(2, "0");
|
||||
return `${minutes}:${seconds}`;
|
||||
};
|
||||
@@ -195,13 +195,6 @@ export const pageMenu = [
|
||||
hasSubitems: true,
|
||||
badges: ["road_patrols.operation_cnt"],
|
||||
Subitems: [
|
||||
{
|
||||
id: "roadPatrolManagmentOparationCreate",
|
||||
label: "ثبت اقدام",
|
||||
type: "page",
|
||||
route: "/dashboard/road-patrols/operator",
|
||||
permissions: ["add-road-patrol"],
|
||||
},
|
||||
{
|
||||
id: "roadPatrolManagmentOparationCartable",
|
||||
label: "کارتابل",
|
||||
|
||||
@@ -30,9 +30,13 @@ export const UPDATE_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/update";
|
||||
|
||||
//road patrol
|
||||
export const GET_ROAD_PATROL_OPERATOR_LIST = api + "/api/v3/road_patrols/operator_index";
|
||||
export const EXPORT_ROAD_PATROL_OPERATOR_LIST = "https://rms.rmto.ir/v2/road_patrols/operator/cartable/report";
|
||||
export const EXPORT_ROAD_PATROL_OPERATOR_LIST = "https://rms.witel.ir/v2/road_patrols/operator/cartable/report";
|
||||
export const GET_ROAD_PATROL_SUPERVISOR_LIST = api + "/api/v3/road_patrols/supervisor_index";
|
||||
export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
|
||||
export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.witel.ir/v2/road_patrols/supervisor/cartable/report";
|
||||
export const DELETE_ROAD_PATROL_SUPERVISOR = api + "/api/v3/road_patrols/delete";
|
||||
export const CREATE_PATROL = api + "/api/v3/road_patrols/store";
|
||||
export const GET_FMS_DATA = api + "/api/v3/fms_vehicle/get_activity";
|
||||
export const GET_OTP_TOKEN = api + "/v2/get_otp_token";
|
||||
|
||||
// road items
|
||||
export const GET_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_index";
|
||||
@@ -50,3 +54,7 @@ export const DELETE_BY_SUPERVISOR = api + "/api/v3/road_items/delete";
|
||||
export const RESTORE_BY_SUPERVISOR = api + "/api/v3/road_items/restore";
|
||||
export const GET_CAR_LIST_SEARCH = api + "/api/v3/cmms_machines/search";
|
||||
export const GET_RAHDARANS_LIST_SEARCH = api + "/api/v3/rahdaran/search";
|
||||
export const GET_OBSERVED_GASHT_LIST = api + "/api/v3/observed_items/filter";
|
||||
|
||||
// activity code log
|
||||
export const ACTIVITY_LOG = api + "/api/v3/profile/add_activity";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { createTheme } from "@mui/material";
|
||||
import { grey } from '@mui/material/colors';
|
||||
|
||||
const theme = createTheme({
|
||||
direction: "rtl",
|
||||
@@ -16,6 +17,9 @@ const theme = createTheme({
|
||||
main: "#015688",
|
||||
contrastText: "#fff",
|
||||
},
|
||||
secondary: {
|
||||
main: grey[700],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MuiBackdrop: {
|
||||
|
||||
@@ -14,5 +14,5 @@ export const usePermissions = () => {
|
||||
}
|
||||
};
|
||||
|
||||
return useSWR(GET_PERMISSIONS_ROUTE, fetcher, { keepPreviousData: true });
|
||||
return useSWR(GET_PERMISSIONS_ROUTE, fetcher, { keepPreviousData: true, dedupingInterval: 30000 });
|
||||
};
|
||||
|
||||
@@ -20,7 +20,6 @@ const useRequest = (initOptions) => {
|
||||
|
||||
return async (url = "", method = "get", options) => {
|
||||
const mergedOptions = Object.assign({}, _options, options);
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
url,
|
||||
|
||||
@@ -14,5 +14,5 @@ export const useSidebarBadge = () => {
|
||||
}
|
||||
};
|
||||
|
||||
return useSWR(GET_SIDEBAR_BADGE_ROUTE, fetcher, { keepPreviousData: true });
|
||||
return useSWR(GET_SIDEBAR_BADGE_ROUTE, fetcher, { keepPreviousData: true, dedupingInterval: 10000 });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user