feature/end_km #9

Merged
aminghasempoor merged 2 commits from feature/end_km into develop 2026-06-15 07:49:47 +00:00
16 changed files with 147 additions and 78 deletions

View File

@@ -2,7 +2,7 @@ import { Close } from "@mui/icons-material";
import { Dialog, DialogTitle, IconButton } from "@mui/material"; import { Dialog, DialogTitle, IconButton } from "@mui/material";
import ViolationsList from "./ViolationsList"; import ViolationsList from "./ViolationsList";
const ViolationsDialog = ({ open, setOpen, mutate, row }) => { const ViolationsDialog = ({ open, setOpen, mutate }) => {
return ( return (
<Dialog open={open} fullWidth maxWidth="xs"> <Dialog open={open} fullWidth maxWidth="xs">
<IconButton <IconButton
@@ -19,7 +19,7 @@ const ViolationsDialog = ({ open, setOpen, mutate, row }) => {
<Close /> <Close />
</IconButton> </IconButton>
<DialogTitle>تخلفات</DialogTitle> <DialogTitle>تخلفات</DialogTitle>
{open && <ViolationsList setOpen={setOpen} />} {open && <ViolationsList setOpen={setOpen} mutate={mutate} />}
</Dialog> </Dialog>
); );
}; };

View File

@@ -20,10 +20,7 @@ const MachineAndDriver = ({ setTabState, allData, setAllData, handlePrev }) => {
<> <>
<Typography variant="body2">راننده</Typography> <Typography variant="body2">راننده</Typography>
<Divider sx={{ flex: 1 }} /> <Divider sx={{ flex: 1 }} />
<Chip <Chip label={driver ? `${driver.name}` : "راننده ای انتخاب نشده"} size="small" />
label={driver ? `${driver.code} | ${driver.name}` : "راننده ای انتخاب نشده"}
size="small"
/>
<Divider sx={{ flex: 1 }} /> <Divider sx={{ flex: 1 }} />
<DriversDialog setDriver={setDriver} mode={"edit"} /> <DriversDialog setDriver={setDriver} mode={"edit"} />
</> </>

View File

@@ -25,17 +25,17 @@ const RahdaranList = ({ rahdaran, setRahdaran }) => {
my: 1, my: 1,
}} }}
> >
<Stack direction={"row"} spacing={1}> <Stack direction={"row"} sx={{ display: "flex", alignItems: "center" }} spacing={1}>
<AccountCircle color="primary" sx={{ width: "32px", height: "32px" }} /> <AccountCircle color="primary" sx={{ width: "32px", height: "32px" }} />
<Stack> <Stack>
<Stack direction={"row"} spacing={0.5}> <Stack direction={"row"} spacing={0.5}>
<Typography variant="caption">نام و نام خانوادگی:</Typography> <Typography variant="caption">نام و نام خانوادگی:</Typography>
<Typography variant="body2">{rahdar.name}</Typography> <Typography variant="body2">{rahdar.name}</Typography>
</Stack> </Stack>
<Stack direction={"row"} spacing={0.5}> {/*<Stack direction={"row"} spacing={0.5}>*/}
<Typography variant="caption">کدملی:</Typography> {/* <Typography variant="caption">کدملی:</Typography>*/}
<Typography variant="body2">{rahdar.code}</Typography> {/* <Typography variant="body2">-</Typography> /!*{rahdar.code}*!/*/}
</Stack> {/*</Stack>*/}
</Stack> </Stack>
</Stack> </Stack>
<IconButton color="error" onClick={() => handleRemove(index)}> <IconButton color="error" onClick={() => handleRemove(index)}>

View File

@@ -5,7 +5,7 @@ import { Dialog, IconButton, Tooltip } from "@mui/material";
import { useState } from "react"; import { useState } from "react";
import CreateForm from "./Form"; import CreateForm from "./Form";
const MissionCorrection = ({ row, mutate }) => { const MissionCorrection = ({ row, mutate, baseMutate }) => {
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const requestServer = useRequest({ notificationSuccess: true }); const requestServer = useRequest({ notificationSuccess: true });
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -31,6 +31,7 @@ const MissionCorrection = ({ row, mutate }) => {
}) })
.then((response) => { .then((response) => {
mutate(); mutate();
baseMutate();
setOpen(false); setOpen(false);
}) })
.catch((error) => {}) .catch((error) => {})

View File

@@ -1,9 +1,9 @@
import MissionCorrection from "./MissionCorrection"; import MissionCorrection from "./MissionCorrection";
export default function RowActions({ row, mutate }) { export default function RowActions({ row, mutate, baseMutate }) {
return ( return (
<> <>
<MissionCorrection row={row} mutate={mutate} /> <MissionCorrection row={row} mutate={mutate} baseMutate={baseMutate} />
</> </>
); );
} }

View File

@@ -7,7 +7,7 @@ import useCities from "@/lib/hooks/useCities";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import useProvinces from "@/lib/hooks/useProvince"; import useProvinces from "@/lib/hooks/useProvince";
const ViolationsList = ({ setOpen }) => { const ViolationsList = ({ setOpen, mutate }) => {
const columns = useMemo(() => { const columns = useMemo(() => {
const dynamicColumns = { const dynamicColumns = {
header: "استان", header: "استان",
@@ -132,7 +132,7 @@ const ViolationsList = ({ setOpen }) => {
table_name={"operatorViolationsList"} table_name={"operatorViolationsList"}
enableRowActions enableRowActions
positionActionsColumn={"first"} positionActionsColumn={"first"}
RowActions={(props) => <RowActions {...props} setOpen={setOpen} />} RowActions={(props) => <RowActions {...props} setOpen={setOpen} baseMutate={mutate} />}
/> />
</Box> </Box>
</> </>

View File

@@ -4,7 +4,7 @@ import ViolationsDialog from "./Dialog";
import theme from "@/core/utils/theme"; import theme from "@/core/utils/theme";
import RemoveRoadIcon from "@mui/icons-material/RemoveRoad"; import RemoveRoadIcon from "@mui/icons-material/RemoveRoad";
const OperatorViolations = ({ row, mutate }) => { const OperatorViolations = ({ mutate }) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
return ( return (
@@ -26,7 +26,7 @@ const OperatorViolations = ({ row, mutate }) => {
</Button> </Button>
)} )}
</Tooltip> </Tooltip>
<ViolationsDialog open={open} setOpen={setOpen} mutate={mutate} row={row} /> <ViolationsDialog open={open} setOpen={setOpen} mutate={mutate} />
</> </>
); );
}; };

View File

@@ -8,6 +8,7 @@ import { ExitToApp } from "@mui/icons-material";
import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material"; import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup"; import { object, string } from "yup";
import LtrTextField from "@/core/components/LtrTextField";
const validationSchema = object({ const validationSchema = object({
explanation: string().required("موضوع را مشخص کنید!"), explanation: string().required("موضوع را مشخص کنید!"),
@@ -125,6 +126,29 @@ const GetItemInfo = ({ allData, setAllData, handlePrev, setTabState }) => {
}} }}
/> />
</Grid> </Grid>
<Grid item xs={12} sm={12}>
<Stack>
<Controller
control={control}
name={"end_km"}
render={({ field, fieldState: { error } }) => (
<LtrTextField
autoComplete="off"
type="tel"
value={field.value}
onChange={(e) => {
if (isNaN(Number(e.target.value))) return;
field.onChange(e.target.value);
}}
size="small"
fullWidth
label="کیلومتر خودرو / ساعت کار"
InputLabelProps={{ shrink: true }}
/>
)}
/>
</Stack>
</Grid>
</Grid> </Grid>
</Stack> </Stack>
</Box> </Box>

View File

@@ -60,6 +60,11 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
<Divider sx={{ flex: 1 }} /> <Divider sx={{ flex: 1 }} />
<Chip size="small" label={allData.end_point} /> <Chip size="small" label={allData.end_point} />
</Stack> </Stack>
<Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">کیلومتر خودرو / ساعت کار</Typography>
<Divider sx={{ flex: 1 }} />
<Chip size="small" label={allData.end_km} />
</Stack>
<Stack direction={"row"} alignItems={"center"} spacing={1}> <Stack direction={"row"} alignItems={"center"} spacing={1}>
<Typography variant="body2">تاریخ شروع ماموریت</Typography> <Typography variant="body2">تاریخ شروع ماموریت</Typography>
<Divider sx={{ flex: 1 }} /> <Divider sx={{ flex: 1 }} />

View File

@@ -25,8 +25,6 @@ const reducer = (state, action) => {
}; };
const CreateForm = ({ defaultValues, submitForm, setOpen, submitting, oldBound }) => { const CreateForm = ({ defaultValues, submitForm, setOpen, submitting, oldBound }) => {
console.log("defaultValues", defaultValues);
const [allData, dispatch] = useReducer(reducer, defaultValues); const [allData, dispatch] = useReducer(reducer, defaultValues);
const [tabState, setTabState] = useState(0); const [tabState, setTabState] = useState(0);
const handleClose = () => { const handleClose = () => {

View File

@@ -18,6 +18,7 @@ const DialogAdd = ({ mutate, setOpen, oldBound, rahdaran, machine, row }) => {
driver: row.original.driver_id, driver: row.original.driver_id,
zone: result.region, zone: result.region,
type: result.type, type: result.type,
end_km: result.end_km,
...(result.type == 1 ...(result.type == 1
? { ? {
end_date: `${result.end_date} ${moment(result.end_time).format("HH:mm")}`, end_date: `${result.end_date} ${moment(result.end_time).format("HH:mm")}`,
@@ -54,6 +55,7 @@ const DialogAdd = ({ mutate, setOpen, oldBound, rahdaran, machine, row }) => {
rahdaran: rahdaran.filter((r) => !r.is_driver), rahdaran: rahdaran.filter((r) => !r.is_driver),
bound_type: "polyline", bound_type: "polyline",
type: "", type: "",
end_km: "",
end_date: "", end_date: "",
end_time: null, end_time: null,
end_point: "", end_point: "",

View File

@@ -3,61 +3,75 @@ import useRequest from "@/lib/hooks/useRequest";
import DialogLoading from "@/core/components/DialogLoading"; import DialogLoading from "@/core/components/DialogLoading";
import CreateForm from "../../../Actions/Create/Form"; import CreateForm from "../../../Actions/Create/Form";
import moment from "jalali-moment"; import moment from "jalali-moment";
import L from "leaflet"; import polyline from "@mapbox/polyline";
import { GET_RAHDARAN_BY_ID, UPDATE_REQUEST_MISSION } from "@/core/utils/routes"; import { GET_RAHDARAN_BY_ID, UPDATE_REQUEST_MISSION } from "@/core/utils/routes";
const EditController = ({ row, mutate, openEditDialog, setOpenEditDialog }) => { const EditController = ({ row, mutate, openEditDialog, setOpenEditDialog }) => {
const latLngs = useMemo(() => {
return row.original.area.coordinates.map(([lng, lat]) => [lat, lng]);
}, [row.original.area.coordinates]);
const bound = useMemo(
() => (row.original.area.type === "polygon" ? L.polygon(latLngs) : L.polyline(latLngs)),
[row.original.area.type, latLngs]
);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [rahdaran, setRahdaran] = useState(null); const [rahdaran, setRahdaran] = useState(null);
const [rahdaranLoading, setRahdaranLoading] = useState(true); const [rahdaranLoading, setRahdaranLoading] = useState(true);
const requestServer = useRequest(); const requestServer = useRequest();
const locations = useMemo(() => {
if (!row.original.encoded_route) return [];
const decoded = polyline.decode(row.original.encoded_route, 6);
if (decoded.length === 0) return [];
return [
{
lat: decoded[0][0],
lon: decoded[0][1],
},
{
lat: decoded[decoded.length - 1][0],
lon: decoded[decoded.length - 1][1],
},
];
}, [row.original.encoded_route]);
useEffect(() => { useEffect(() => {
setRahdaranLoading(true); setRahdaranLoading(true);
requestServer(`${GET_RAHDARAN_BY_ID}/${row.original.id}`, "get") requestServer(`${GET_RAHDARAN_BY_ID}/${row.original.id}`, "get")
.then((response) => { .then((response) => {
setRahdaran(response.data.data); setRahdaran(response.data.data);
setRahdaranLoading(false);
}) })
.catch((e) => { .catch(() => {})
.finally(() => {
setRahdaranLoading(false); setRahdaranLoading(false);
}); });
}, [row.original.id]); }, [row.original.id]);
const submitForm = async (result) => { const submitForm = async (result) => {
setSubmitting(true); setSubmitting(true);
const latlngs = result.bound.getLatLngs();
const normalized = result.bound_type === "polygon" ? latlngs : [latlngs];
const area = normalized.map((ring) => ring.map(({ lat, lng }) => [lat, lng]));
await requestServer(`${UPDATE_REQUEST_MISSION}/${row.original.id}`, "post", { await requestServer(`${UPDATE_REQUEST_MISSION}/${row.original.id}`, "post", {
data: { data: {
explanation: result.explanation, explanation: result.explanation,
category_id: result.category_id, category_id: result.category_id,
...(result.category_id == 3 ...(result.category_id == 3
? { ? {
road_observed_id: result.road_observed_id, road_observed_id: result.road_observed_id,
} }
: {}), : {}),
area: {
type: result.bound_type, encoded_route: result.area,
coordinates: area[0],
}, ...(result.rahdaran.length !== 0
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}), ? {
rahdaran: result.rahdaran.map((r) => r.id),
}
: {}),
requested_machines: result.requested_machines, requested_machines: result.requested_machines,
type: result.type, type: result.type,
zone: result.region, zone: result.region,
end_point: result.end_point, end_point: result.end_point,
...(result.type == 1 ...(result.type == 1
? { ? {
start_date: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`, start_date: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`,
@@ -71,35 +85,41 @@ const EditController = ({ row, mutate, openEditDialog, setOpenEditDialog }) => {
hasSidebarUpdate: true, hasSidebarUpdate: true,
notificationSuccess: true, notificationSuccess: true,
}) })
.then((response) => { .then(() => {
mutate(); mutate();
setOpenEditDialog(false); setOpenEditDialog(false);
}) })
.catch((error) => {}) .catch(() => {})
.finally(() => { .finally(() => {
setSubmitting(false); setSubmitting(false);
}); });
}; };
return ( return rahdaranLoading ? (
<>
{rahdaranLoading ? (
<DialogLoading /> <DialogLoading />
) : ( ) : (
<CreateForm <CreateForm
defaultValues={{ defaultValues={{
explanation: row.original.explanation, explanation: row.original.explanation,
category_id: row.original.category_id, category_id: row.original.category_id,
road_observed_id: row.original.category_id == 3 ? row.original.road_observed[0].id : "",
road_observed_id: row.original.category_id == 3 ? (row.original.road_observed?.[0]?.id ?? "") : "",
rahdaran: rahdaran.filter((r) => !r.isDriver), rahdaran: rahdaran.filter((r) => !r.isDriver),
requested_machines: row.original.requested_machines, requested_machines: row.original.requested_machines,
bound: bound,
bound_type: row.original.area.type, area: row.original.encoded_route,
locations,
type: row.original.type, type: row.original.type,
start_date: moment(row.original.start_date).format("YYYY-MM-DD"), start_date: moment(row.original.start_date).format("YYYY-MM-DD"),
start_time: moment(row.original.start_date).toDate(), start_time: moment(row.original.start_date).toDate(),
end_date: moment(row.original.end_date).format("YYYY-MM-DD"), end_date: moment(row.original.end_date).format("YYYY-MM-DD"),
end_time: moment(row.original.end_date).toDate(), end_time: moment(row.original.end_date).toDate(),
end_point: row.original.end_point, end_point: row.original.end_point,
region: row.original.zone, region: row.original.zone,
}} }}
@@ -109,8 +129,7 @@ const EditController = ({ row, mutate, openEditDialog, setOpenEditDialog }) => {
setOpen={setOpenEditDialog} setOpen={setOpenEditDialog}
mutate={mutate} mutate={mutate}
/> />
)}
</>
); );
}; };
export default EditController; export default EditController;

View File

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

View File

@@ -78,7 +78,6 @@ const MachinesList = ({ machineType, setMachine, setOpenMachinesDialog }) => {
columns={columns} columns={columns}
specialFilter={[ specialFilter={[
{ value: machineType, datatype: "text", id: "car_type", fn: "equals" }, { value: machineType, datatype: "text", id: "car_type", fn: "equals" },
{ value: 1, datatype: "numeric", id: "status", fn: "equals" },
{ {
value: city_id ? city_id : province_id, value: city_id ? city_id : province_id,
datatype: "numeric", datatype: "numeric",

View File

@@ -11,6 +11,7 @@ const EditController = ({ rowId, mutate, setOpen, row }) => {
const defaultData = { const defaultData = {
end_date: endDate, end_date: endDate,
end_km: "",
end_time: row?.original?.enter_time ? new Date(row.original.enter_time) : null, end_time: row?.original?.enter_time ? new Date(row.original.enter_time) : null,
}; };
const handleSubmit = async (result) => { const handleSubmit = async (result) => {
@@ -19,6 +20,7 @@ const EditController = ({ rowId, mutate, setOpen, row }) => {
if (result.end_date && result.end_time) { if (result.end_date && result.end_time) {
payload.enter_time = `${result.end_date} ${moment(result.end_time).format("HH:mm")}`; payload.enter_time = `${result.end_date} ${moment(result.end_time).format("HH:mm")}`;
} }
payload.end_km = result.end_km;
await requestServer(`${EDIT_VIOLATION}/${rowId}`, "post", { await requestServer(`${EDIT_VIOLATION}/${rowId}`, "post", {
data: payload, data: payload,

View File

@@ -1,9 +1,10 @@
import { useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import StyledForm from "@/core/components/StyledForm"; import StyledForm from "@/core/components/StyledForm";
import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material"; import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
import MissionDates from "./MissionDates"; import MissionDates from "./MissionDates";
import * as yup from "yup"; import * as yup from "yup";
import LtrTextField from "@/core/components/LtrTextField";
export const schema = yup export const schema = yup
.object({ .object({
@@ -55,7 +56,7 @@ const EditViolationForm = ({ setOpenMachinesDialog, defaultValues, onSubmitBase
<> <>
<StyledForm onSubmit={handleSubmit(handleOnSubmit)}> <StyledForm onSubmit={handleSubmit(handleOnSubmit)}>
<DialogContent dividers> <DialogContent dividers>
<Box sx={{ flex: 1 }}> <Stack spacing={2} sx={{ flex: 1 }}>
<Stack> <Stack>
<Grid container spacing={2}> <Grid container spacing={2}>
<MissionDates control={control} /> <MissionDates control={control} />
@@ -73,7 +74,28 @@ const EditViolationForm = ({ setOpenMachinesDialog, defaultValues, onSubmitBase
</Box> </Box>
)} )}
</Stack> </Stack>
</Box> <Stack>
<Controller
control={control}
name={"end_km"}
render={({ field, fieldState: { error } }) => (
<LtrTextField
autoComplete="off"
type="tel"
value={field.value}
onChange={(e) => {
if (isNaN(Number(e.target.value))) return;
field.onChange(e.target.value);
}}
size="small"
fullWidth
label="کیلومتر خودرو / ساعت کار"
InputLabelProps={{ shrink: true }}
/>
)}
/>
</Stack>
</Stack>
</DialogContent> </DialogContent>
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}> <DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
<Button <Button