Merge branch 'feature/amiriis_road_patrol_see_item' into 'develop'

add observed item in road potrol

See merge request witel-front-end/rms!63
This commit is contained in:
AmirHossein Mahmoodi
2025-02-15 11:30:26 +00:00
10 changed files with 431 additions and 29 deletions

View File

@@ -249,7 +249,6 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
<GetItemsForm
setItemsList={setItemsList}
setValue={setValue}
watch={watch}
tabState={tabState}
setTabState={setTabState}
/>

View File

@@ -2,7 +2,7 @@
import DeleteIcon from "@mui/icons-material/Delete";
import HandymanIcon from "@mui/icons-material/Handyman";
import { Box, Card, CardActions, CardHeader, IconButton, Typography } from "@mui/material";
import { Box, Card, CardActions, CardHeader, Chip, IconButton, Typography } from "@mui/material";
import EditActionDuringPatrol from "./EditActionDuringPatrol";
const ActionInfo = ({ action, setActionsList, index, deleteAction }) => {
@@ -17,7 +17,11 @@ const ActionInfo = ({ action, setActionsList, index, deleteAction }) => {
>
<CardHeader
avatar={<HandymanIcon color="primary" sx={{ width: "30px", height: "30px" }} />}
title={<Typography sx={{ fontSize: "14px", fontWeight: 500 }}>{action.data.item_name}</Typography>}
title={
<Typography component={"div"} sx={{ fontSize: "14px", fontWeight: 500 }}>
{action.data.item_name} <Chip size="small" label={"انجام شده"} />
</Typography>
}
subheader={action.data.sub_item_name}
/>
<CardActions disableSpacing>

View File

@@ -6,12 +6,20 @@ import { Box, Button, Chip, DialogActions, DialogContent, Divider, Grid, Stack,
import { useState } from "react";
import ActionInfo from "./ActionInfo";
import ModalActionsDuringPatrol from "./ModalActionsDuringPatrol";
import ModalObservedItemPatrol from "./ModalObservedItemPatrol";
import ObservedInfo from "./ObservedInfo";
const ActionsDuringPatrol = ({ allData, setAllData, handlePrev, setTabState }) => {
const [actionsList, setActionsList] = useState(allData.observed_items);
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
const [openAction, setOpenAction] = useState(false);
const [openObserved, setOpenObseved] = useState(false);
const handleOpenAction = () => {
setOpenAction(true);
};
const handleOpenObserved = () => {
setOpenObseved(true);
};
const deleteAction = (indexToRemove) => {
@@ -28,17 +36,30 @@ const ActionsDuringPatrol = ({ allData, setAllData, handlePrev, setTabState }) =
<DialogContent dividers sx={{ display: "flex" }}>
<Box sx={{ flex: 1 }}>
<Stack
direction={"row"}
direction={{ xs: "column", sm: "row" }}
justifyContent={"center"}
spacing={2}
sx={{
mt: 1,
mb: 2,
}}
>
<Button onClick={handleOpen} startIcon={<AddCircleIcon />} variant="outlined" sx={{ mb: 1 }}>
<Button onClick={handleOpenAction} startIcon={<AddCircleIcon />} variant="contained">
ثبت اقدام انجام شده
</Button>
<ModalActionsDuringPatrol open={open} setOpen={setOpen} setActionsList={setActionsList} />
<ModalActionsDuringPatrol
open={openAction}
setOpen={setOpenAction}
setActionsList={setActionsList}
/>
<Button onClick={handleOpenObserved} startIcon={<AddCircleIcon />} variant="outlined">
ثبت اقدام مشاهده شده
</Button>
<ModalObservedItemPatrol
open={openObserved}
setOpen={setOpenObseved}
setActionsList={setActionsList}
/>
</Stack>
<Divider sx={{ my: 2, width: "100%" }}>
<Chip variant="outlined" label="لیست اقدامات حین گشت" />
@@ -56,12 +77,21 @@ const ActionsDuringPatrol = ({ allData, setAllData, handlePrev, setTabState }) =
>
{actionsList.map((action, index) => (
<Grid key={index} item xs={12} sm={6} lg={4}>
<ActionInfo
action={action}
setActionsList={setActionsList}
index={index}
deleteAction={() => deleteAction(index)}
/>
{action.instant_action == 1 ? (
<ActionInfo
action={action}
setActionsList={setActionsList}
index={index}
deleteAction={() => deleteAction(index)}
/>
) : (
<ObservedInfo
action={action}
setActionsList={setActionsList}
index={index}
deleteAction={() => deleteAction(index)}
/>
)}
</Grid>
))}
</Grid>

View File

@@ -109,19 +109,34 @@ const CompeleteRequest = ({ allData, handlePrev, mutate, setOpen }) => {
formData.append(`stop_points[${index}][startDT]`, stop_point.startDT);
});
allData.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.result.start_point);
formData.append(`observed_items[${index}][end_point]`, observed_item.result.end_point);
formData.append(`observed_items[${index}][amount]`, observed_item.result.amount);
formData.append(`observed_items[${index}][before_image]`, observed_item.result.before_image);
formData.append(`observed_items[${index}][after_image]`, observed_item.result.after_image);
formData.append(`observed_items[${index}][item_id]`, observed_item.result.item_id);
formData.append(`observed_items[${index}][sub_item_id]`, observed_item.result.sub_item_id);
formData.append(`observed_items[${index}][road_item_machines_id][]`, allData.roadPatrolMachinesId.id);
allData.road_patrol_rahdaran_id.forEach((rahdar, i) =>
formData.append(`observed_items[${index}][road_item_rahdaran_id][]`, rahdar.id)
);
if (observed_item.instant_action == 1) {
formData.append(`observed_items[${index}][instant_action]`, observed_item.instant_action);
formData.append(`observed_items[${index}][local_name]`, "");
formData.append(`observed_items[${index}][start_point]`, observed_item.result.start_point);
if (observed_item.result.end_point)
formData.append(`observed_items[${index}][end_point]`, observed_item.result.end_point);
formData.append(`observed_items[${index}][amount]`, observed_item.result.amount);
formData.append(`observed_items[${index}][before_image]`, observed_item.result.before_image);
formData.append(`observed_items[${index}][after_image]`, observed_item.result.after_image);
formData.append(`observed_items[${index}][item_id]`, observed_item.result.item_id);
formData.append(`observed_items[${index}][sub_item_id]`, observed_item.result.sub_item_id);
formData.append(
`observed_items[${index}][road_item_machines_id][]`,
allData.roadPatrolMachinesId.id
);
allData.road_patrol_rahdaran_id.forEach((rahdar, i) =>
formData.append(`observed_items[${index}][road_item_rahdaran_id][]`, rahdar.id)
);
} else {
formData.append(`observed_items[${index}][instant_action]`, observed_item.instant_action);
formData.append(`observed_items[${index}][local_name]`, observed_item.result.local_name);
formData.append(`observed_items[${index}][priority]`, observed_item.result.priority);
formData.append(`observed_items[${index}][start_point]`, observed_item.result.start_point);
if (observed_item.result.end_point)
formData.append(`observed_items[${index}][end_point]`, observed_item.result.end_point);
formData.append(`observed_items[${index}][item_id]`, observed_item.result.item_id);
formData.append(`observed_items[${index}][sub_item_id]`, observed_item.result.sub_item_id);
}
});
formData.append("start_time", allData.start_time);
formData.append("end_time", allData.end_time);

View File

@@ -0,0 +1,203 @@
import GetItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm";
import GetSubItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm";
import StyledForm from "@/core/components/StyledForm";
import { useTheme } from "@emotion/react";
import { yupResolver } from "@hookform/resolvers/yup";
import BeenhereIcon from "@mui/icons-material/Beenhere";
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
import FileCopyIcon from "@mui/icons-material/FileCopy";
import InfoIcon from "@mui/icons-material/Info";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
import { Box, Button, DialogActions, DialogContent, Tab, Tabs, useMediaQuery } from "@mui/material";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { mixed, number, object, string } from "yup";
import GetObservedItemInfo from "./GetObservedItemInfo";
function TabPanel(props) {
const { children, value, index } = props;
return (
<div role="tabpanel" hidden={value !== index}>
{value === index && <Box>{children}</Box>}
</div>
);
}
const validationSchema = object({
item_id: number().required("نوع آیتم را مشخص کنید!"),
sub_item_id: number().required("اقدام انجام شده را مشخص کنید!"),
local_name: string().required("نام محلی را مشخص کنید!"),
priority: string().required("اولویت را مشخص کنید!"),
start_point: mixed()
.test("start-point-required", "لطفاً نقطه شروع را مشخص کنید!", function (value) {
return !!value; // چک می‌کند که مقدار موجود است
})
.required("لطفاً نقطه شروع را مشخص کنید!"),
end_point: mixed().test("end-point-required", "لطفاً نقطه پایان را مشخص کنید!", function (value) {
const { subItemsList } = this.options.context;
const needsEndPoint = subItemsList?.needs_end_point === 1;
if (needsEndPoint) {
return !!value; // چک می‌کند که مقدار موجود است
}
return true;
}),
});
const CreateFormContentObservedItem = ({ setOpen, onSubmit }) => {
const defaultValues = {
item_id: null,
sub_item_id: null,
local_name: "",
priority: "1",
start_point: "",
end_point: "",
};
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);
};
const handleChangeTab = (event, newValue) => {
setTabState(newValue);
};
const handlePrev = () => {
if (tabState === 2) {
const fieldsToReset = ["local_name", "priority", "start_point", "end_point"];
fieldsToReset.forEach((field) => resetField(field));
}
if (tabState === 0) {
handleClose();
} else {
setTabState(tabState - 1);
}
};
const {
control,
watch,
getValues,
register,
handleSubmit,
setValue,
resetField,
formState: { errors, isSubmitting },
} = useForm({
defaultValues,
resolver: yupResolver(validationSchema),
mode: "onBlur",
context: { subItemsList },
});
const onSubmitBase = async (data) => {
let result = { ...data };
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}`;
}
await onSubmit({
result,
data: {
...data,
item_name: itemsList.name,
sub_item_name: subItemsList.name,
unit_fa: subItemsList.unit,
},
});
};
return (
<StyledForm onSubmit={handleSubmit(onSubmitBase)} id="road_items">
<Tabs
allowScrollButtonsMobile
value={tabState}
onChange={handleChangeTab}
variant={`${isMobile ? "scrollable" : "fullWidth"}`}
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
backgroundColor: "#efefef",
}}
>
<Tab icon={<InsertDriveFileIcon />} label="انتخاب آیتم" />
<Tab disabled={tabState === 0} icon={<FileCopyIcon />} label="انتخاب اقدام مشاهده شده" />
<Tab disabled={tabState === 0 || tabState === 1} icon={<InfoIcon />} label="اطلاعات فعالیت" />
</Tabs>
<DialogContent dividers sx={{ overflowY: "auto", maxHeight: "70vh" }}>
<Box sx={{ flex: 1 }}>
<TabPanel value={tabState} index={0}>
<GetItemsForm
setItemsList={setItemsList}
setValue={setValue}
tabState={tabState}
setTabState={setTabState}
/>
</TabPanel>
<TabPanel value={tabState} index={1}>
<GetSubItemsForm
setSubItemsList={setSubItemsList}
setValue={setValue}
watch={watch}
tabState={tabState}
setTabState={setTabState}
/>
</TabPanel>
<TabPanel value={tabState} index={2}>
<GetObservedItemInfo
control={control}
setValue={setValue}
errors={errors}
itemsList={itemsList}
subItemsList={subItemsList}
register={register}
watch={watch}
getValues={getValues}
/>
</TabPanel>
</Box>
</DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button
onClick={handlePrev}
variant="outlined"
color="secondary"
size="large"
startIcon={tabState === 0 ? <ExitToAppIcon /> : <KeyboardDoubleArrowRightIcon />}
>
{tabState === 0 ? "بستن" : "مرحله قبل"}
</Button>
{tabState === 2 && (
<Button
variant="contained"
size="large"
disabled={isSubmitting}
type={"submit"}
form="road_items"
endIcon={<BeenhereIcon />}
>
{isSubmitting ? "در حال ثبت فعالیت" : "ثبت فعالیت"}
</Button>
)}
</DialogActions>
</StyledForm>
);
};
export default CreateFormContentObservedItem;

View File

@@ -0,0 +1,55 @@
import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select, Stack, TextField } from "@mui/material";
import PreviousStatesInfoInObservedItem from "./PreviousStatesInfoInObservedItem";
const GetObservedItemInfo = ({ register, itemsList, subItemsList, control, setValue, errors, watch, getValues }) => {
return (
<Stack spacing={3}>
<Stack>
<PreviousStatesInfoInObservedItem itemsList={itemsList} subItemsList={subItemsList} />
</Stack>
<Stack>
<TextField
{...register("local_name")}
label="نام محلی"
error={!!errors.local_name}
type="text"
size={"small"}
placeholder={`نام محلی را وارد کنید`}
helperText={errors.local_name ? errors.local_name.message : null}
variant="outlined"
fullWidth
/>
</Stack>
<Stack>
<FormControl fullWidth size="small">
<InputLabel id={`label_priority`} shrink>
اولویت
</InputLabel>
<Select
{...register("priority")}
labelId={`label_priority`}
name={`priority`}
value={watch("priority")}
input={<OutlinedInput label={"اولویت"} />}
size="small"
displayEmpty
>
<MenuItem value={"1"}>عادی</MenuItem>
<MenuItem value={"2"}>فوری</MenuItem>
<MenuItem value={"3"}>آنی</MenuItem>
</Select>
</FormControl>
</Stack>
<Stack>
{subItemsList?.needs_end_point === 1 ? (
<MapInfoTwoMarker setValue={setValue} errors={errors} />
) : (
<MapInfoOneMarker setValue={setValue} errors={errors} />
)}
</Stack>
</Stack>
);
};
export default GetObservedItemInfo;

View File

@@ -3,7 +3,7 @@ import { Dialog } from "@mui/material";
const ModalActionsDuringPatrol = ({ open, setOpen, setActionsList }) => {
const HandleSubmit = async (data) => {
setActionsList((prev) => [...prev, data]);
setActionsList((prev) => [...prev, { ...data, instant_action: 1 }]);
setOpen(false);
};

View File

@@ -0,0 +1,16 @@
import { Dialog } from "@mui/material";
import CreateFormContentObservedItem from "./CreateFormContentObservedItem";
const ModalObservedItemPatrol = ({ open, setOpen, setActionsList }) => {
const HandleSubmit = async (data) => {
setActionsList((prev) => [...prev, { ...data, instant_action: 0 }]);
setOpen(false);
};
return (
<Dialog open={open} fullWidth maxWidth="sm">
<CreateFormContentObservedItem setOpen={setOpen} onSubmit={HandleSubmit} is_gasht={true} />
</Dialog>
);
};
export default ModalObservedItemPatrol;

View File

@@ -0,0 +1,37 @@
"use client";
import DeleteIcon from "@mui/icons-material/Delete";
import VisibilityIcon from "@mui/icons-material/Visibility";
import { Box, Card, CardActions, CardHeader, Chip, IconButton, Typography } from "@mui/material";
const ObservedInfo = ({ action, deleteAction }) => {
return (
<Card
elevation={0}
sx={{
maxWidth: 300,
border: 1,
borderColor: "divider",
}}
>
<CardHeader
avatar={<VisibilityIcon color="primary" sx={{ width: "30px", height: "30px" }} />}
title={
<Typography component={"div"} sx={{ fontSize: "14px", fontWeight: 500 }}>
{action.data.item_name} <Chip size="small" label={"مشاهده شده"} />
</Typography>
}
subheader={action.data.sub_item_name}
/>
<CardActions disableSpacing>
<Box>
<IconButton aria-label="حذف فعالیت" color="error" onClick={deleteAction}>
<DeleteIcon />
</IconButton>
</Box>
</CardActions>
</Card>
);
};
export default ObservedInfo;

View File

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