Compare commits
24 Commits
e7e8f3e5cc
...
v1.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 42a0f3ad1c | |||
| 0689fb307a | |||
| 09c7385faa | |||
| 3745206604 | |||
| f42a4ff63b | |||
| 18674a5b75 | |||
| 68e7fd8cd1 | |||
| e66af994af | |||
| 1e0d3dcf0e | |||
| 92e3505bd9 | |||
| 241f583572 | |||
| 3ea5c940c0 | |||
| 806854327a | |||
| a80e35a510 | |||
| cb85157cf7 | |||
| caaff22c66 | |||
| a9a996e1e8 | |||
| ca20435807 | |||
| 61ed1980f1 | |||
| 93478c8c80 | |||
| a9a6d90281 | |||
| 376ea3a3e8 | |||
| a540d79715 | |||
| c6283ae130 |
@@ -1,5 +1,5 @@
|
||||
HOST="rms.witel.ir"
|
||||
HOST_RMTO="rms.rmto.ir"
|
||||
NEXT_PUBLIC_VERSION="1.8.8"
|
||||
NEXT_PUBLIC_VERSION="1.9.0"
|
||||
NEXT_PUBLIC_API_URL="https://rms.witel.ir"
|
||||
NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map"
|
||||
@@ -0,0 +1,18 @@
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import DriverReportsPage from "@/components/dashboard/roadMissions/reports/diverReports";
|
||||
|
||||
export const metadata = {
|
||||
title: "کارتابل گزارش راننده",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<WithPermission
|
||||
permission_name={[
|
||||
"all", // TODO : change permission
|
||||
]}
|
||||
>
|
||||
<DriverReportsPage />
|
||||
</WithPermission>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
import MachinesTypeReportsPage from "@/components/dashboard/roadMissions/reports/machinesTypeReports";
|
||||
|
||||
export const metadata = {
|
||||
title: "کارتابل گزارش نوع ماشینها",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<WithPermission
|
||||
permission_name={[
|
||||
"all", // TODO : change permission
|
||||
]}
|
||||
>
|
||||
<MachinesTypeReportsPage />
|
||||
</WithPermission>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import MachinesReportsPage from "@/components/dashboard/roadMissions/reports/machinesReports";
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
|
||||
export const metadata = {
|
||||
title: "کارتابل گزارش ماشینها",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<WithPermission
|
||||
permission_name={[
|
||||
"all", // TODO : change permission
|
||||
]}
|
||||
>
|
||||
<MachinesReportsPage />
|
||||
</WithPermission>
|
||||
);
|
||||
}
|
||||
@@ -38,7 +38,6 @@ const StartMissionContent = ({ rowId, mutate, setOpenStartMissionDialog }) => {
|
||||
const onSubmit = (values) => {
|
||||
const formattedDate = values.start_date;
|
||||
const formattedTime = format(values.start_time, "HH:mm");
|
||||
console.log(formattedDate, formattedTime);
|
||||
|
||||
requestServer(`${START_MISSION}/${rowId}`, "post", {
|
||||
data: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import polyline from "@mapbox/polyline";
|
||||
@@ -18,14 +18,24 @@ const ShowRoute = ({ area }) => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (coords.length > 0) {
|
||||
map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 });
|
||||
}
|
||||
}, [area]);
|
||||
const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src);
|
||||
const destIcon = createCustomIcon(defaultIconSize, DestIcon.src);
|
||||
const [showRoute, setShowRoute] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!coords.length) return;
|
||||
|
||||
setShowRoute(false);
|
||||
|
||||
map.flyToBounds(coords, {
|
||||
padding: [50, 50],
|
||||
duration: 1.5,
|
||||
});
|
||||
|
||||
map.once("moveend", () => {
|
||||
setShowRoute(true);
|
||||
});
|
||||
}, [area]);
|
||||
if (!area) return null;
|
||||
const coords = polyline.decode(area, 6);
|
||||
|
||||
@@ -36,7 +46,8 @@ const ShowRoute = ({ area }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
{showRoute && <Polyline positions={coords} color="#015688" weight={7} />}
|
||||
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
|
||||
@@ -6,107 +6,107 @@ import { Controller, useWatch } from "react-hook-form";
|
||||
|
||||
const MissionDates = ({ control }) => {
|
||||
const type = useWatch({ control, name: "type" });
|
||||
|
||||
const start_date = useWatch({ control, name: "start_date" });
|
||||
const end_date = useWatch({ control, name: "end_date" });
|
||||
|
||||
const start_time = useWatch({ control, name: "start_time" });
|
||||
const end_time = useWatch({ control, name: "end_time" });
|
||||
|
||||
const startDateTime = makeDateTime(start_date, start_time);
|
||||
const endDateTime = makeDateTime(end_date, end_time);
|
||||
const now = new Date();
|
||||
|
||||
const startDateTime = start_date && start_time ? makeDateTime(start_date, start_time) : null;
|
||||
|
||||
const endDateTime = end_date && end_time ? makeDateTime(end_date, end_time) : null;
|
||||
|
||||
const isSameDay =
|
||||
start_date && end_date && new Date(start_date).toDateString() === new Date(end_date).toDateString();
|
||||
|
||||
const isToday = start_date && new Date(start_date).toDateString() === now.toDateString();
|
||||
|
||||
return (
|
||||
type && (
|
||||
<>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"start_date"}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<MuiDatePicker
|
||||
name="start_date"
|
||||
error={error}
|
||||
value={field.value}
|
||||
disableFuture={false}
|
||||
minDate={now}
|
||||
maxDate={end_date ?? null}
|
||||
placeholder={"تاریخ شروع ماموریت را وارد کنید"}
|
||||
label={"تاریخ شروع ماموریت"}
|
||||
setFieldValue={(name, value) => field.onChange(value || [])}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name="start_date"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="start_date"
|
||||
value={field.value}
|
||||
error={error}
|
||||
disableFuture={false}
|
||||
minDate={now}
|
||||
maxDate={end_date ?? null}
|
||||
label="تاریخ شروع ماموریت"
|
||||
placeholder="تاریخ شروع ماموریت را وارد کنید"
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{type == 1 && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"start_time"}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<MuiTimePicker
|
||||
name="start_time"
|
||||
error={error}
|
||||
value={field.value}
|
||||
minTime={
|
||||
start_date && new Date(start_date).toDateString() === now.toDateString()
|
||||
? now
|
||||
: null
|
||||
}
|
||||
maxTime={endDateTime ?? null}
|
||||
placeholder={"زمان شروع ماموریت را وارد کنید"}
|
||||
label={"زمان شروع ماموریت"}
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name="start_time"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<MuiTimePicker
|
||||
name="start_time"
|
||||
value={field.value}
|
||||
error={error}
|
||||
minTime={isToday ? now : null}
|
||||
maxTime={isSameDay && endDateTime ? endDateTime : null}
|
||||
label="زمان شروع ماموریت"
|
||||
placeholder="زمان شروع ماموریت را وارد کنید"
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"end_date"}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<MuiDatePicker
|
||||
name="end_date"
|
||||
error={error}
|
||||
value={field.value}
|
||||
disableFuture={false}
|
||||
minDate={start_date ? start_date : now}
|
||||
placeholder={"تاریخ پایان ماموریت را وارد کنید"}
|
||||
label={"تاریخ پایان ماموریت"}
|
||||
setFieldValue={(name, value) => field.onChange(value || [])}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name="end_date"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="end_date"
|
||||
value={field.value}
|
||||
error={error}
|
||||
disableFuture={false}
|
||||
minDate={start_date || now}
|
||||
label="تاریخ پایان ماموریت"
|
||||
placeholder="تاریخ پایان ماموریت را وارد کنید"
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{type == 1 && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"end_time"}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<MuiTimePicker
|
||||
name="end_time"
|
||||
error={error}
|
||||
value={field.value}
|
||||
minTime={startDateTime ?? null}
|
||||
placeholder={"زمان پایان ماموریت را وارد کنید"}
|
||||
label={"زمان پایان ماموریت"}
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name="end_time"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<MuiTimePicker
|
||||
name="end_time"
|
||||
value={field.value}
|
||||
error={error}
|
||||
minTime={isSameDay && startDateTime ? startDateTime : null}
|
||||
label="زمان پایان ماموریت"
|
||||
placeholder="زمان پایان ماموریت را وارد کنید"
|
||||
setFieldValue={(name, value) => field.onChange(value || null)}
|
||||
helperText={error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
@@ -114,4 +114,5 @@ const MissionDates = ({ control }) => {
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default MissionDates;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Close } from "@mui/icons-material";
|
||||
import { Dialog, DialogTitle, IconButton } from "@mui/material";
|
||||
import ViolationsList from "./ViolationsList";
|
||||
|
||||
const ViolationsDialog = ({ open, setOpen, mutate, row }) => {
|
||||
const ViolationsDialog = ({ open, setOpen, mutate }) => {
|
||||
return (
|
||||
<Dialog open={open} fullWidth maxWidth="xs">
|
||||
<IconButton
|
||||
@@ -19,7 +19,7 @@ const ViolationsDialog = ({ open, setOpen, mutate, row }) => {
|
||||
<Close />
|
||||
</IconButton>
|
||||
<DialogTitle>تخلفات</DialogTitle>
|
||||
{open && <ViolationsList setOpen={setOpen} />}
|
||||
{open && <ViolationsList setOpen={setOpen} mutate={mutate} />}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,10 +20,7 @@ const MachineAndDriver = ({ setTabState, allData, setAllData, handlePrev }) => {
|
||||
<>
|
||||
<Typography variant="body2">راننده</Typography>
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
<Chip
|
||||
label={driver ? `${driver.code} | ${driver.name}` : "راننده ای انتخاب نشده"}
|
||||
size="small"
|
||||
/>
|
||||
<Chip label={driver ? `${driver.name}` : "راننده ای انتخاب نشده"} size="small" />
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
<DriversDialog setDriver={setDriver} mode={"edit"} />
|
||||
</>
|
||||
|
||||
@@ -25,17 +25,17 @@ const RahdaranList = ({ rahdaran, setRahdaran }) => {
|
||||
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" }} />
|
||||
<Stack>
|
||||
<Stack direction={"row"} spacing={0.5}>
|
||||
<Typography variant="caption">نام و نام خانوادگی:</Typography>
|
||||
<Typography variant="body2">{rahdar.name}</Typography>
|
||||
</Stack>
|
||||
<Stack direction={"row"} spacing={0.5}>
|
||||
<Typography variant="caption">کدملی:</Typography>
|
||||
<Typography variant="body2">{rahdar.code}</Typography>
|
||||
</Stack>
|
||||
{/*<Stack direction={"row"} spacing={0.5}>*/}
|
||||
{/* <Typography variant="caption">کدملی:</Typography>*/}
|
||||
{/* <Typography variant="body2">-</Typography> /!*{rahdar.code}*!/*/}
|
||||
{/*</Stack>*/}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<IconButton color="error" onClick={() => handleRemove(index)}>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Dialog, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import CreateForm from "./Form";
|
||||
|
||||
const MissionCorrection = ({ row, mutate }) => {
|
||||
const MissionCorrection = ({ row, mutate, baseMutate }) => {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -31,6 +31,7 @@ const MissionCorrection = ({ row, mutate }) => {
|
||||
})
|
||||
.then((response) => {
|
||||
mutate();
|
||||
baseMutate();
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((error) => {})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import MissionCorrection from "./MissionCorrection";
|
||||
|
||||
export default function RowActions({ row, mutate }) {
|
||||
export default function RowActions({ row, mutate, baseMutate }) {
|
||||
return (
|
||||
<>
|
||||
<MissionCorrection row={row} mutate={mutate} />
|
||||
<MissionCorrection row={row} mutate={mutate} baseMutate={baseMutate} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import useCities from "@/lib/hooks/useCities";
|
||||
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
||||
import useProvinces from "@/lib/hooks/useProvince";
|
||||
|
||||
const ViolationsList = ({ setOpen }) => {
|
||||
const ViolationsList = ({ setOpen, mutate }) => {
|
||||
const columns = useMemo(() => {
|
||||
const dynamicColumns = {
|
||||
header: "استان",
|
||||
@@ -132,7 +132,7 @@ const ViolationsList = ({ setOpen }) => {
|
||||
table_name={"operatorViolationsList"}
|
||||
enableRowActions
|
||||
positionActionsColumn={"first"}
|
||||
RowActions={(props) => <RowActions {...props} setOpen={setOpen} />}
|
||||
RowActions={(props) => <RowActions {...props} setOpen={setOpen} baseMutate={mutate} />}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -4,7 +4,7 @@ import ViolationsDialog from "./Dialog";
|
||||
import theme from "@/core/utils/theme";
|
||||
import RemoveRoadIcon from "@mui/icons-material/RemoveRoad";
|
||||
|
||||
const OperatorViolations = ({ row, mutate }) => {
|
||||
const OperatorViolations = ({ mutate }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
return (
|
||||
@@ -26,7 +26,7 @@ const OperatorViolations = ({ row, mutate }) => {
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<ViolationsDialog open={open} setOpen={setOpen} mutate={mutate} row={row} />
|
||||
<ViolationsDialog open={open} setOpen={setOpen} mutate={mutate} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
const AlarmText = ({ isSource }) => {
|
||||
if (isSource) return null;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 5,
|
||||
left: 5,
|
||||
zIndex: 1000,
|
||||
border: "1px solid #ed0f02",
|
||||
backgroundColor: "#f19898",
|
||||
py: 0.5,
|
||||
px: 1,
|
||||
borderRadius: 2,
|
||||
opacity: 0.8,
|
||||
boxShadow: "rgba(0, 0, 0, 0.35) 0px 5px 15px",
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ fontWeight: "bold" }} color="#ed0f02">
|
||||
با کلیک بر روی مارکر موقعیت را انتخاب کنید
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlarmText;
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Box, Button, TextField } from "@mui/material";
|
||||
|
||||
const DataBox = ({ isSource, isDest, sourcePosition, destPosition, editingTarget, onEditSource, onEditDest }) => {
|
||||
if (!isSource) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 5,
|
||||
left: 5,
|
||||
zIndex: 1000,
|
||||
backgroundColor: "white",
|
||||
border: "1px solid #e1e1e1",
|
||||
padding: 1,
|
||||
borderRadius: 2,
|
||||
opacity: 0.8,
|
||||
gap: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "end", gap: 4 }}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
size="small"
|
||||
readOnly
|
||||
value={sourcePosition ? `${sourcePosition.lat.toFixed(6)}, ${sourcePosition.lng.toFixed(6)}` : ""}
|
||||
/>
|
||||
{isSource && isDest && (
|
||||
<Button variant="contained" color="success" size="small" onClick={onEditSource}>
|
||||
ویرایش مبدا
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isDest && (
|
||||
<div style={{ display: "flex", alignItems: "end", gap: 4 }}>
|
||||
<TextField
|
||||
size="small"
|
||||
variant="outlined"
|
||||
readOnly
|
||||
value={destPosition ? `${destPosition.lat.toFixed(6)}, ${destPosition.lng.toFixed(6)}` : ""}
|
||||
/>
|
||||
<Button variant="contained" color="error" size="small" onClick={onEditDest}>
|
||||
ویرایش مقصد
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataBox;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import polyline from "@mapbox/polyline";
|
||||
import SourceIcon from "@/assets/images/source-icon.svg";
|
||||
import DestIcon from "@/assets/images/destination-icon.svg";
|
||||
|
||||
const ShowRoute = ({ area }) => {
|
||||
const map = useMap();
|
||||
const defaultIconSize = [35, 35];
|
||||
|
||||
const createCustomIcon = (size, iconUrl) => {
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (coords.length > 0) {
|
||||
map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 });
|
||||
}
|
||||
}, [area]);
|
||||
const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src);
|
||||
const destIcon = createCustomIcon(defaultIconSize, DestIcon.src);
|
||||
|
||||
if (!area) return null;
|
||||
const coords = polyline.decode(area, 6);
|
||||
|
||||
if (coords.length === 0) return null;
|
||||
|
||||
const sourcePosition = coords[0];
|
||||
const destPosition = coords[coords.length - 1];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShowRoute;
|
||||
@@ -0,0 +1,219 @@
|
||||
import { Marker, Polyline, useMapEvents } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import polyline from "@mapbox/polyline";
|
||||
import SourceIcon from "@/assets/images/source-icon.svg";
|
||||
import PrevSourceIcon from "@/assets/images/prev-source-icon.svg";
|
||||
import PrevDestIcon from "@/assets/images/prev-destination-icon.svg";
|
||||
import DestIcon from "@/assets/images/destination-icon.svg";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import DataBox from "./DataBox";
|
||||
import AlarmText from "./AlarmText";
|
||||
|
||||
const Routing = ({ setArea, setLocations, locations, area }) => {
|
||||
const mapPrevSourceMarker = useRef();
|
||||
const mapPrevDestMarker = useRef();
|
||||
const mapRef = useRef(null);
|
||||
const defaultIconSize = [35, 35];
|
||||
const [isSource, setIsSource] = useState(false);
|
||||
const [sourcePosition, setSourcePosition] = useState(null);
|
||||
const [isDest, setIsDest] = useState(false);
|
||||
const [destPosition, setDestPosition] = useState(null);
|
||||
const [routeCoords, setRouteCoords] = useState([]);
|
||||
const [alternateCoords, setAlternateCoords] = useState([]);
|
||||
const [editingTarget, setEditingTarget] = useState(null);
|
||||
const [selectedRoute, setSelectedRoute] = useState(null);
|
||||
|
||||
const createCustomIcon = (size, iconUrl) => {
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
|
||||
const defaultIcon = createCustomIcon(
|
||||
defaultIconSize,
|
||||
isSource && editingTarget !== "source" ? SourceIcon.src : PrevSourceIcon.src
|
||||
);
|
||||
|
||||
const prevDestIcon = createCustomIcon(
|
||||
defaultIconSize,
|
||||
isDest && editingTarget !== "dest" ? DestIcon.src : PrevDestIcon.src
|
||||
);
|
||||
|
||||
const isMovingSource = !isSource || editingTarget === "source";
|
||||
const isMovingDest = isSource && (!isDest || editingTarget === "dest");
|
||||
|
||||
const map = useMapEvents({
|
||||
move(e) {
|
||||
if (isMovingSource) mapPrevSourceMarker.current.setLatLng(e.target.getCenter());
|
||||
if (isMovingDest && mapPrevDestMarker.current) mapPrevDestMarker.current.setLatLng(e.target.getCenter());
|
||||
},
|
||||
movestart() {
|
||||
if (isMovingSource) mapPrevSourceMarker.current.setIcon(createCustomIcon([45, 45], PrevSourceIcon.src));
|
||||
if (isMovingDest && mapPrevDestMarker.current)
|
||||
mapPrevDestMarker.current.setIcon(createCustomIcon([45, 45], PrevDestIcon.src));
|
||||
},
|
||||
moveend() {
|
||||
if (isMovingSource)
|
||||
mapPrevSourceMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevSourceIcon.src));
|
||||
if (isMovingDest && mapPrevDestMarker.current)
|
||||
mapPrevDestMarker.current.setIcon(createCustomIcon(defaultIconSize, PrevDestIcon.src));
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
mapRef.current = map;
|
||||
}, [map]);
|
||||
|
||||
useEffect(() => {
|
||||
if (locations.length < 2) return;
|
||||
const restoredSource = { lat: locations[0].lat, lng: locations[0].lon };
|
||||
const restoredDest = { lat: locations[1].lat, lng: locations[1].lon };
|
||||
setSourcePosition(restoredSource);
|
||||
setDestPosition(restoredDest);
|
||||
setIsSource(true);
|
||||
setIsDest(true);
|
||||
if (area) {
|
||||
const decoded = polyline.decode(area, 6);
|
||||
setRouteCoords(decoded);
|
||||
setSelectedRoute({ index: "main", coords: decoded });
|
||||
setTimeout(() => {
|
||||
mapRef.current?.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 });
|
||||
}, 100);
|
||||
} else {
|
||||
fetchRoute(restoredSource, restoredDest);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchRoute = async (source, dest) => {
|
||||
const locs = [
|
||||
{ lat: source.lat, lon: source.lng },
|
||||
{ lat: dest.lat, lon: dest.lng },
|
||||
];
|
||||
try {
|
||||
const response = await fetch("https://jouya.141.ir/api/router/drive", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ locations: locs }),
|
||||
});
|
||||
const data = await response.json();
|
||||
const shape = data.trip.legs[0].shape;
|
||||
setLocations(locs);
|
||||
setArea(shape);
|
||||
const decoded = polyline.decode(shape, 6);
|
||||
setRouteCoords(decoded);
|
||||
setSelectedRoute({ index: "main", coords: decoded });
|
||||
|
||||
if (data.alternates?.length > 0) {
|
||||
const decodedAlternates = data.alternates.map((alt) => polyline.decode(alt.trip.legs[0].shape, 6));
|
||||
setAlternateCoords(decodedAlternates);
|
||||
} else {
|
||||
setAlternateCoords([]);
|
||||
}
|
||||
map.flyToBounds(decoded, { padding: [50, 50], duration: 1.5 });
|
||||
} catch (error) {
|
||||
console.error("Routing error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkerClick = async () => {
|
||||
if (!isSource || editingTarget === "source") {
|
||||
const clickedPosition = mapPrevSourceMarker.current.getLatLng();
|
||||
setSourcePosition(clickedPosition);
|
||||
setIsSource(true);
|
||||
setEditingTarget(null);
|
||||
setRouteCoords([]);
|
||||
setAlternateCoords([]);
|
||||
setSelectedRoute(null);
|
||||
|
||||
if (editingTarget === "source" && destPosition) {
|
||||
await fetchRoute(clickedPosition, destPosition);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDestMarkerClick = async () => {
|
||||
if (!isDest || editingTarget === "dest") {
|
||||
const clickedPosition = mapPrevDestMarker.current.getLatLng();
|
||||
setDestPosition(clickedPosition);
|
||||
setIsDest(true);
|
||||
setEditingTarget(null);
|
||||
|
||||
await fetchRoute(sourcePosition, clickedPosition);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSource = () => {
|
||||
setEditingTarget("source");
|
||||
setRouteCoords([]);
|
||||
setAlternateCoords([]);
|
||||
setSelectedRoute(null);
|
||||
};
|
||||
|
||||
const handleEditDest = () => {
|
||||
setEditingTarget("dest");
|
||||
setRouteCoords([]);
|
||||
setAlternateCoords([]);
|
||||
setSelectedRoute(null);
|
||||
};
|
||||
|
||||
const handleSelectRoute = (index, coords) => {
|
||||
setSelectedRoute({ index, coords });
|
||||
const encoded = polyline.encode(coords, 6);
|
||||
setArea(encoded);
|
||||
map.flyToBounds(coords, { padding: [50, 50], duration: 0.3 });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataBox
|
||||
isSource={isSource}
|
||||
isDest={isDest}
|
||||
sourcePosition={sourcePosition}
|
||||
destPosition={destPosition}
|
||||
editingTarget={editingTarget}
|
||||
onEditSource={handleEditSource}
|
||||
onEditDest={handleEditDest}
|
||||
/>
|
||||
<AlarmText isSource={isSource} />
|
||||
<Marker
|
||||
position={sourcePosition || map.getCenter()}
|
||||
ref={mapPrevSourceMarker}
|
||||
eventHandlers={{ click: handleMarkerClick }}
|
||||
icon={defaultIcon}
|
||||
/>
|
||||
{isSource && (
|
||||
<Marker
|
||||
position={destPosition || map.getCenter()}
|
||||
ref={mapPrevDestMarker}
|
||||
eventHandlers={{ click: handleDestMarkerClick }}
|
||||
icon={prevDestIcon}
|
||||
/>
|
||||
)}
|
||||
{alternateCoords.map((coords, index) => (
|
||||
<Polyline
|
||||
key={`alternate-${index}-${selectedRoute?.index === index}`}
|
||||
positions={coords}
|
||||
color={selectedRoute?.index === index ? "#015688" : "#91B2C6"}
|
||||
weight={selectedRoute?.index === index ? 7 : 6}
|
||||
opacity={selectedRoute?.index === index ? 1 : 0.7}
|
||||
eventHandlers={{ click: () => handleSelectRoute(index, coords) }}
|
||||
/>
|
||||
))}
|
||||
{routeCoords.length > 0 && (
|
||||
<Polyline
|
||||
key={`main-${selectedRoute?.index === "main"}`}
|
||||
positions={routeCoords}
|
||||
color={selectedRoute?.index === "main" ? "#015688" : "#91B2C6"}
|
||||
weight={selectedRoute?.index === "main" ? 7 : 6}
|
||||
opacity={selectedRoute?.index === "main" ? 1 : 0.7}
|
||||
eventHandlers={{ click: () => handleSelectRoute("main", routeCoords) }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Routing;
|
||||
@@ -2,41 +2,29 @@ import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCallback, useState } from "react";
|
||||
import MapControlPolygon from "./MapControlPolygon";
|
||||
import MapControlPolyline from "./MapControlPolyline";
|
||||
import SelectBoundType from "./SelectBoundType";
|
||||
import OldBoundLayer from "./OldBoundLayer";
|
||||
import Routing from "./Routing";
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const Area = ({ allData, setAllData, handlePrev, setTabState, oldBound }) => {
|
||||
const [bound, setBound] = useState(allData.bound);
|
||||
const [boundType, setBoundType] = useState(allData.bound_type);
|
||||
const Area = ({ allData, setAllData, handlePrev, setTabState }) => {
|
||||
const [area, setArea] = useState(allData.area);
|
||||
const [locations, setLocations] = useState(allData.locations);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
setAllData({ bound: bound });
|
||||
setAllData({ area: area });
|
||||
setAllData({ locations: locations });
|
||||
setTabState((s) => s + 1);
|
||||
}, [bound]);
|
||||
}, [area]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Stack spacing={2}>
|
||||
<SelectBoundType
|
||||
boundType={boundType}
|
||||
setBoundType={setBoundType}
|
||||
setBound={setBound}
|
||||
setAllData={setAllData}
|
||||
/>
|
||||
<Box sx={{ width: "100%", height: "400px" }}>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
{boundType == "polygon" ? (
|
||||
<MapControlPolygon bound={bound} setBound={setBound} boundType={boundType} />
|
||||
) : (
|
||||
<MapControlPolyline bound={bound} setBound={setBound} boundType={boundType} />
|
||||
)}
|
||||
<OldBoundLayer bound={oldBound} />
|
||||
<Routing area={area} setArea={setArea} locations={locations} setLocations={setLocations} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -45,8 +33,8 @@ const Area = ({ allData, setAllData, handlePrev, setTabState, oldBound }) => {
|
||||
<Button onClick={handlePrev} variant="outlined" color="secondary" size="large">
|
||||
{"مرحله قبلی"}
|
||||
</Button>
|
||||
<Button variant="contained" size="large" disabled={!bound} onClick={handleNext}>
|
||||
{!bound ? "در انتظار ترسیم" : "مرحله بعد"}
|
||||
<Button variant="contained" size="large" disabled={!area} onClick={handleNext}>
|
||||
{!area ? "در انتظار ترسیم" : "مرحله بعد"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ExitToApp } from "@mui/icons-material";
|
||||
import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { object, string } from "yup";
|
||||
import LtrTextField from "@/core/components/LtrTextField";
|
||||
|
||||
const validationSchema = object({
|
||||
explanation: string().required("موضوع را مشخص کنید!"),
|
||||
@@ -125,6 +126,29 @@ const GetItemInfo = ({ allData, setAllData, handlePrev, setTabState }) => {
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Box, Button, Chip, DialogActions, DialogContent, Divider, Stack, Typogr
|
||||
import moment from "jalali-moment";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCallback } from "react";
|
||||
import ShowBound from "../../../../../../Actions/showBound";
|
||||
import ShowRoute from "../Area/Routing/ShowRoute";
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
@@ -23,7 +23,7 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
|
||||
<Stack spacing={2}>
|
||||
<Box sx={{ width: "100%", height: "200px" }}>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowBound bound={allData.bound} />
|
||||
<ShowRoute area={allData.area} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
<Stack spacing={1}>
|
||||
@@ -60,6 +60,11 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
<Chip size="small" label={allData.end_point} />
|
||||
</Stack>
|
||||
<Stack direction={"row"} alignItems={"center"} spacing={1}>
|
||||
<Typography variant="body2">کیلومتر خودرو / ساعت کار</Typography>
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
<Chip size="small" label={allData.end_km} />
|
||||
</Stack>
|
||||
<Stack direction={"row"} alignItems={"center"} spacing={1}>
|
||||
<Typography variant="body2">تاریخ شروع ماموریت</Typography>
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
@@ -93,11 +98,11 @@ const Verify = ({ allData, handlePrev, submitForm, submitting }) => {
|
||||
label={`${allData.machine.machine_code} | ${allData.machine.car_name}`}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack direction={"row"} alignItems={"center"} spacing={1}>
|
||||
{/* <Stack direction={"row"} alignItems={"center"} spacing={1}>
|
||||
<Typography variant="body2">راننده</Typography>
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
<Chip size="small" label={`${allData.driver.code} | ${allData.driver.name}`} />
|
||||
</Stack>
|
||||
</Stack> */}
|
||||
</Stack>
|
||||
<Stack spacing={1}>
|
||||
<Divider>
|
||||
|
||||
@@ -84,7 +84,6 @@ const CreateForm = ({ defaultValues, submitForm, setOpen, submitting, oldBound }
|
||||
</TabPanel>
|
||||
<TabPanel value={tabState} index={2}>
|
||||
<Area
|
||||
oldBound={oldBound}
|
||||
allData={allData}
|
||||
setAllData={(data) => {
|
||||
dispatch({ type: "changeData", data });
|
||||
|
||||
@@ -10,24 +10,15 @@ const DialogAdd = ({ mutate, setOpen, oldBound, rahdaran, machine, row }) => {
|
||||
|
||||
const submitForm = async (result) => {
|
||||
setSubmitting(true);
|
||||
const bound = result.bound.getLatLngs();
|
||||
let area =
|
||||
result.bound_type === "polygon"
|
||||
? bound.map((ring) => ring.map((latlng) => [latlng.lng, latlng.lat]))[0]
|
||||
: bound.map((latlng) => [latlng.lng, latlng.lat]);
|
||||
|
||||
// بستن polygon
|
||||
if (result.bound_type === "polygon" && area.length > 0) {
|
||||
const firstPoint = area[0];
|
||||
area.push({ ...firstPoint });
|
||||
}
|
||||
await requestServer(`${REQUEST_MISSION_CONTINUE_MISSION}/${row.original.id}`, "post", {
|
||||
data: {
|
||||
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}),
|
||||
machine_id: result.machine.id,
|
||||
driver: result.driver.id,
|
||||
encoded_route: result.area,
|
||||
driver: row.original.driver_id,
|
||||
zone: result.region,
|
||||
type: result.type,
|
||||
end_km: result.end_km,
|
||||
...(result.type == 1
|
||||
? {
|
||||
end_date: `${result.end_date} ${moment(result.end_time).format("HH:mm")}`,
|
||||
@@ -36,10 +27,6 @@ const DialogAdd = ({ mutate, setOpen, oldBound, rahdaran, machine, row }) => {
|
||||
end_date: result.end_date,
|
||||
}),
|
||||
end_point: result.end_point,
|
||||
area: {
|
||||
type: result.bound_type,
|
||||
coordinates: area,
|
||||
},
|
||||
category_id: result.category_id,
|
||||
explanation: result.explanation,
|
||||
...(result.category_id == 3
|
||||
@@ -65,16 +52,18 @@ const DialogAdd = ({ mutate, setOpen, oldBound, rahdaran, machine, row }) => {
|
||||
defaultValues={{
|
||||
explanation: "",
|
||||
category_id: "",
|
||||
bound: null,
|
||||
rahdaran: rahdaran.filter((r) => !r.is_driver),
|
||||
bound_type: "polyline",
|
||||
type: "",
|
||||
end_km: "",
|
||||
end_date: "",
|
||||
end_time: null,
|
||||
end_point: "",
|
||||
region: "",
|
||||
machine: machine,
|
||||
driver: rahdaran.find((r) => r.is_driver),
|
||||
driver: row.original.driver_id,
|
||||
area: null,
|
||||
locations: [],
|
||||
}}
|
||||
oldBound={oldBound}
|
||||
submitForm={submitForm}
|
||||
|
||||
@@ -3,61 +3,75 @@ import useRequest from "@/lib/hooks/useRequest";
|
||||
import DialogLoading from "@/core/components/DialogLoading";
|
||||
import CreateForm from "../../../Actions/Create/Form";
|
||||
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";
|
||||
|
||||
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 [rahdaran, setRahdaran] = useState(null);
|
||||
const [rahdaranLoading, setRahdaranLoading] = useState(true);
|
||||
|
||||
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(() => {
|
||||
setRahdaranLoading(true);
|
||||
|
||||
requestServer(`${GET_RAHDARAN_BY_ID}/${row.original.id}`, "get")
|
||||
.then((response) => {
|
||||
setRahdaran(response.data.data);
|
||||
setRahdaranLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setRahdaranLoading(false);
|
||||
});
|
||||
}, [row.original.id]);
|
||||
|
||||
const submitForm = async (result) => {
|
||||
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", {
|
||||
data: {
|
||||
explanation: result.explanation,
|
||||
category_id: result.category_id,
|
||||
|
||||
...(result.category_id == 3
|
||||
? {
|
||||
road_observed_id: result.road_observed_id,
|
||||
}
|
||||
: {}),
|
||||
area: {
|
||||
type: result.bound_type,
|
||||
coordinates: area[0],
|
||||
},
|
||||
...(result.rahdaran.length != 0 ? { rahdaran: result.rahdaran.map((r) => r.id) } : {}),
|
||||
|
||||
encoded_route: result.area,
|
||||
|
||||
...(result.rahdaran.length !== 0
|
||||
? {
|
||||
rahdaran: result.rahdaran.map((r) => r.id),
|
||||
}
|
||||
: {}),
|
||||
|
||||
requested_machines: result.requested_machines,
|
||||
type: result.type,
|
||||
zone: result.region,
|
||||
end_point: result.end_point,
|
||||
|
||||
...(result.type == 1
|
||||
? {
|
||||
start_date: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`,
|
||||
@@ -71,46 +85,51 @@ const EditController = ({ row, mutate, openEditDialog, setOpenEditDialog }) => {
|
||||
hasSidebarUpdate: true,
|
||||
notificationSuccess: true,
|
||||
})
|
||||
.then((response) => {
|
||||
.then(() => {
|
||||
mutate();
|
||||
setOpenEditDialog(false);
|
||||
})
|
||||
.catch((error) => {})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{rahdaranLoading ? (
|
||||
<DialogLoading />
|
||||
) : (
|
||||
<CreateForm
|
||||
defaultValues={{
|
||||
explanation: row.original.explanation,
|
||||
category_id: row.original.category_id,
|
||||
road_observed_id: row.original.category_id == 3 ? row.original.road_observed[0].id : "",
|
||||
rahdaran: rahdaran.filter((r) => !r.isDriver),
|
||||
requested_machines: row.original.requested_machines,
|
||||
bound: bound,
|
||||
bound_type: row.original.area.type,
|
||||
type: row.original.type,
|
||||
start_date: moment(row.original.start_date).format("YYYY-MM-DD"),
|
||||
start_time: moment(row.original.start_date).toDate(),
|
||||
end_date: moment(row.original.end_date).format("YYYY-MM-DD"),
|
||||
end_time: moment(row.original.end_date).toDate(),
|
||||
end_point: row.original.end_point,
|
||||
region: row.original.zone,
|
||||
}}
|
||||
submitForm={submitForm}
|
||||
submitting={submitting}
|
||||
open={openEditDialog}
|
||||
setOpen={setOpenEditDialog}
|
||||
mutate={mutate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
return rahdaranLoading ? (
|
||||
<DialogLoading />
|
||||
) : (
|
||||
<CreateForm
|
||||
defaultValues={{
|
||||
explanation: row.original.explanation,
|
||||
category_id: row.original.category_id,
|
||||
|
||||
road_observed_id: row.original.category_id == 3 ? (row.original.road_observed?.[0]?.id ?? "") : "",
|
||||
|
||||
rahdaran: rahdaran.filter((r) => !r.isDriver),
|
||||
|
||||
requested_machines: row.original.requested_machines,
|
||||
|
||||
area: row.original.encoded_route,
|
||||
locations,
|
||||
|
||||
type: row.original.type,
|
||||
|
||||
start_date: moment(row.original.start_date).format("YYYY-MM-DD"),
|
||||
start_time: moment(row.original.start_date).toDate(),
|
||||
|
||||
end_date: moment(row.original.end_date).format("YYYY-MM-DD"),
|
||||
end_time: moment(row.original.end_date).toDate(),
|
||||
|
||||
end_point: row.original.end_point,
|
||||
region: row.original.zone,
|
||||
}}
|
||||
submitForm={submitForm}
|
||||
submitting={submitting}
|
||||
open={openEditDialog}
|
||||
setOpen={setOpenEditDialog}
|
||||
mutate={mutate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditController;
|
||||
|
||||
@@ -7,7 +7,7 @@ const Toolbar = ({ table, filterData, mutate }) => {
|
||||
return (
|
||||
<Stack direction={"row"} spacing={1}>
|
||||
<Create mutate={mutate} />
|
||||
<OperatorViolations />
|
||||
<OperatorViolations mutate={mutate} />
|
||||
{/*<CreateWithoutProcess mutate={mutate} />*/}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import { Box } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
import Toolbar from "./Toolbar";
|
||||
import RowActions from "./RowActions";
|
||||
|
||||
const DriverReportsList = ({ data, specialFilter }) => {
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessorKey: "driver_id",
|
||||
header: "کد یکتا",
|
||||
id: "driver_id",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "numeric",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "driver_name",
|
||||
header: "نام راننده",
|
||||
id: "driver_name",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "text",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "func",
|
||||
header: "کارکرد",
|
||||
id: "func",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "number",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "missions",
|
||||
header: "تعداد ماموریت",
|
||||
id: "missions",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "number",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
const cleanedData = specialFilter.filter((item) => !(item.id === "province_id" && item.value === "-1"));
|
||||
const RowActionsWithRowData = (props) => <RowActions specialFilter={cleanedData} {...props} />;
|
||||
return (
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
TableToolbar={Toolbar}
|
||||
columns={columns}
|
||||
data={data.data}
|
||||
page_name={"driverReport"}
|
||||
table_name={"driverReportList"}
|
||||
enablePagination={false}
|
||||
specialFilter={specialFilter}
|
||||
enableRowActions
|
||||
RowActions={RowActionsWithRowData}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DriverReportsList;
|
||||
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
import {
|
||||
EXPORT_GET_DRIVER_COUNTRY_ACTIVITY_REPORT,
|
||||
EXPORT_GET_DRIVER_PROVINCE_ACTIVITY_REPORT,
|
||||
} from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import DescriptionIcon from "@mui/icons-material/Description";
|
||||
import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material";
|
||||
import FileSaver from "file-saver";
|
||||
import moment from "jalali-moment";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
const PrintExcel = ({ table, filterData }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [loading, setLoading] = useState(false);
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => {
|
||||
if (filter.value) {
|
||||
acc.push({ id: filter.id, value: filter.value });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
activeFilters.length > 0 &&
|
||||
activeFilters.map((filter, index) => {
|
||||
params.set(`${filter.id}`, filter.value);
|
||||
});
|
||||
return params;
|
||||
}, [activeFilters]);
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true);
|
||||
const CountryOrProvince = activeFilters.find((item) => item.id === "province_id");
|
||||
const requestUrl =
|
||||
CountryOrProvince.value === "-1"
|
||||
? EXPORT_GET_DRIVER_COUNTRY_ACTIVITY_REPORT
|
||||
: EXPORT_GET_DRIVER_PROVINCE_ACTIVITY_REPORT;
|
||||
requestServer(`${requestUrl}?${filterParams}`, "get", {
|
||||
requestOptions: { responseType: "blob" },
|
||||
})
|
||||
.then((response) => {
|
||||
const filename = `خروجی کارتابل گزارشات راننده تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton aria-label="خروجی اکسل" color="success" onClick={clickHandler}>
|
||||
<DescriptionIcon sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button
|
||||
color="success"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit" /> : <DescriptionIcon />}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
خروجی اکسل
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default PrintExcel;
|
||||
@@ -0,0 +1,107 @@
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import { GET_DRIVER_DETAILS } from "@/core/utils/routes";
|
||||
import { Box } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const DriverList = ({ row, specialFilter }) => {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "کد یکتا",
|
||||
id: "id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 80,
|
||||
},
|
||||
{
|
||||
accessorKey: "province_name",
|
||||
header: "استان",
|
||||
id: "province_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "city_name",
|
||||
header: "شهر",
|
||||
id: "city_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "station_name",
|
||||
header: "راهدارخانه",
|
||||
id: "station_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "driver_name",
|
||||
header: "راننده",
|
||||
id: "driver_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "km",
|
||||
header: "کیلومتر شروع",
|
||||
id: "km",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "end_km",
|
||||
header: "کیلومتر پایان",
|
||||
id: "end_km",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<DataTableWithAuth
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
specialFilter={specialFilter}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
table_url={`${GET_DRIVER_DETAILS}/${row.driver_id}`}
|
||||
page_name={"driverListReport"}
|
||||
table_name={"driverList"}
|
||||
positionActionsColumn={"first"}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DriverList;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Box, Button, DialogActions, DialogContent } from "@mui/material";
|
||||
import { Stack } from "@mui/system";
|
||||
import DriverList from "./DriverDetailsDialog/DriverList";
|
||||
|
||||
const DriverDetailsTable = ({ row, setOpenDriverDetails, specialFilter }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Stack spacing={3}>
|
||||
<Stack spacing={2}>
|
||||
<DriverList specialFilter={specialFilter} row={row} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Stack direction={"row"} spacing={1}>
|
||||
<Button onClick={() => setOpenDriverDetails(false)} variant="outlined" color="primary">
|
||||
بستن
|
||||
</Button>
|
||||
</Stack>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DriverDetailsTable;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Close, RemoveRedEye } from "@mui/icons-material";
|
||||
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import DriverDetailsTable from "./Form";
|
||||
|
||||
const DriverDetails = ({ row, specialFilter }) => {
|
||||
const [openDriverDetails, setOpenDriverDetails] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="ریزگزارشات" arrow placement="right">
|
||||
<IconButton
|
||||
color="primary"
|
||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||
onClick={() => {
|
||||
setOpenDriverDetails(true);
|
||||
}}
|
||||
>
|
||||
<RemoveRedEye />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog open={openDriverDetails} fullWidth maxWidth="md">
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={() => setOpenDriverDetails(false)}
|
||||
sx={(theme) => ({
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
zIndex: 50,
|
||||
color: theme.palette.grey[500],
|
||||
})}
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
<DialogTitle>ریز گزارشات راننده</DialogTitle>
|
||||
{openDriverDetails && (
|
||||
<DriverDetailsTable
|
||||
specialFilter={specialFilter}
|
||||
row={row.original}
|
||||
setOpenDriverDetails={setOpenDriverDetails}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DriverDetails;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import polyline from "@mapbox/polyline";
|
||||
import SourceIcon from "@/assets/images/source-icon.svg";
|
||||
import DestIcon from "@/assets/images/destination-icon.svg";
|
||||
|
||||
const ShowRoute = ({ area }) => {
|
||||
const map = useMap();
|
||||
const defaultIconSize = [35, 35];
|
||||
|
||||
const createCustomIcon = (size, iconUrl) => {
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (coords.length > 0) {
|
||||
map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 });
|
||||
}
|
||||
}, [area]);
|
||||
const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src);
|
||||
const destIcon = createCustomIcon(defaultIconSize, DestIcon.src);
|
||||
|
||||
if (!area) return null;
|
||||
const coords = polyline.decode(area, 6);
|
||||
|
||||
if (coords.length === 0) return null;
|
||||
|
||||
const sourcePosition = coords[0];
|
||||
const destPosition = coords[coords.length - 1];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShowRoute;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Box, Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import MapIcon from "@mui/icons-material/Map";
|
||||
import MapLayer from "@/core/components/MapLayer";
|
||||
import ShowRoute from "./ShowRoute";
|
||||
|
||||
const RouteDetail = ({ encoded_route }) => {
|
||||
const [openRouteDetailDialog, setOpenRouteDetailDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="مسیر انتخابی">
|
||||
<IconButton color="primary" onClick={() => setOpenRouteDetailDialog(true)}>
|
||||
<MapIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openRouteDetailDialog}
|
||||
onClose={() => setOpenRouteDetailDialog(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={"sm"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>مسیر انتخابی</DialogTitle>
|
||||
<Box sx={{ mx: 1, borderRadius: "5px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "300px",
|
||||
border: "1px solid #e1e1e1",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowRoute area={encoded_route} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setOpenRouteDetailDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RouteDetail;
|
||||
@@ -0,0 +1,6 @@
|
||||
import DriverDetails from "./DriverDetails";
|
||||
|
||||
const RowActions = ({ row, specialFilter }) => {
|
||||
return <DriverDetails specialFilter={specialFilter} row={row} />;
|
||||
};
|
||||
export default RowActions;
|
||||
@@ -0,0 +1,27 @@
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const FromDateController = ({ control }) => {
|
||||
return (
|
||||
<Controller
|
||||
name="from_date"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="from_date"
|
||||
error={!!error}
|
||||
value={value}
|
||||
label={"تاریخ شروع (از تاریخ)"}
|
||||
placeholder={"از تاریخ"}
|
||||
setFieldValue={(name, newValue) => {
|
||||
const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD");
|
||||
onChange(formattedDate); // Update the field value in react-hook-form
|
||||
}}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FromDateController;
|
||||
@@ -0,0 +1,44 @@
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { Button, Grid } from "@mui/material";
|
||||
import { Controller } from "react-hook-form";
|
||||
import FromDateController from "./FromDateController";
|
||||
import SelectProvince from "./SelectProvince";
|
||||
import ToDateController from "./ToDateController";
|
||||
|
||||
const SearchReportField = ({ control, hasProvincesPermission }) => {
|
||||
return (
|
||||
<Grid container spacing={2} sx={{ py: 2 }}>
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<FromDateController control={control} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<ToDateController control={control} />
|
||||
</Grid>
|
||||
{hasProvincesPermission && (
|
||||
<Grid item xs={12} md={3}>
|
||||
<SelectProvince control={control} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<Controller
|
||||
name="submitButton"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, formState: { isSubmitting } }) => (
|
||||
<Button
|
||||
variant="contained"
|
||||
size="medium"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
endIcon={<SearchIcon />}
|
||||
fullWidth
|
||||
>
|
||||
{isSubmitting ? "درحال دریافت اطلاعات" : "جستجو"}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchReportField;
|
||||
@@ -0,0 +1,45 @@
|
||||
import useProvinces from "@/lib/hooks/useProvince";
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const SelectProvince = ({ control }) => {
|
||||
const { provinces, loadingProvinces, errorProvinces } = useProvinces();
|
||||
return (
|
||||
<Controller
|
||||
name="province_id"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl error={!!error} size="small" fullWidth>
|
||||
<InputLabel id="demo-error-label">استان</InputLabel>
|
||||
<Select
|
||||
{...field}
|
||||
labelId="demo-error-label"
|
||||
id="province_id"
|
||||
value={loadingProvinces ? "loading" : field.value || ""}
|
||||
label="استان"
|
||||
onChange={field.onChange}
|
||||
>
|
||||
{loadingProvinces ? (
|
||||
<MenuItem value={"loading"}>در حال بارگذاری...</MenuItem>
|
||||
) : errorProvinces ? (
|
||||
<MenuItem value={"error"}>خطا در بارگذاری</MenuItem>
|
||||
) : (
|
||||
[
|
||||
<MenuItem key="-1" value={"-1"}>
|
||||
کل کشور
|
||||
</MenuItem>,
|
||||
...provinces.map((province) => (
|
||||
<MenuItem key={province.id} value={province.id}>
|
||||
{province.name_fa}
|
||||
</MenuItem>
|
||||
)),
|
||||
]
|
||||
)}
|
||||
</Select>
|
||||
{error && <FormHelperText>{error.message}</FormHelperText>}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default SelectProvince;
|
||||
@@ -0,0 +1,30 @@
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
|
||||
const ToDateController = ({ control }) => {
|
||||
const minDate = useWatch({ control, name: "from_date" });
|
||||
return (
|
||||
<Controller
|
||||
name="date_to"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="date_to"
|
||||
minDate={minDate}
|
||||
maxDate={new Date()}
|
||||
error={!!error}
|
||||
value={value}
|
||||
label={"تاریخ پایان (تا تاریخ)"}
|
||||
placeholder={"تا تاریخ"}
|
||||
setFieldValue={(name, newValue) => {
|
||||
const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD");
|
||||
onChange(formattedDate); // Update the field value in react-hook-form
|
||||
}}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ToDateController;
|
||||
@@ -0,0 +1,13 @@
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import SearchReportField from "./SearchReportField";
|
||||
|
||||
const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => {
|
||||
return (
|
||||
<>
|
||||
<StyledForm onSubmit={handleSubmit(onSearchSubmit)}>
|
||||
<SearchReportField control={control} hasProvincesPermission={hasProvincesPermission} />
|
||||
</StyledForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SearchReportList;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Box } from "@mui/material";
|
||||
import PrintExcel from "./ExcelPrint";
|
||||
|
||||
const Toolbar = ({ table, filterData, mutate }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<PrintExcel table={table} filterData={filterData} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default Toolbar;
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
import PageTitle from "@/core/components/PageTitle";
|
||||
import { GET_DRIVER_COUNTRY_ACTIVITY_REPORT, GET_DRIVER_PROVINCE_ACTIVITY_REPORT } from "@/core/utils/routes";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { LinearProgress, Stack } from "@mui/material";
|
||||
import moment from "jalali-moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as yup from "yup";
|
||||
import DriverReportsList from "./DriverReportsList";
|
||||
import SearchReportList from "./Search";
|
||||
|
||||
const DriverReportsPage = () => {
|
||||
const [data, setData] = useState(null);
|
||||
const { data: userPermissions } = usePermissions();
|
||||
const { user } = useAuth();
|
||||
const requestServer = useRequest();
|
||||
const hasProvincesPermission = userPermissions?.includes("mission-report-country");
|
||||
|
||||
const defaultValues = {
|
||||
from_date: moment(new Date()).format("YYYY-MM-DD"),
|
||||
date_to: moment(new Date()).format("YYYY-MM-DD"),
|
||||
province_id: user.province_id || "-1",
|
||||
};
|
||||
const defaultFilter = [
|
||||
{ value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" },
|
||||
{ value: `${defaultValues.from_date} 00:00:00`, datatype: "date", id: "start_time", fn: "greaterThan" },
|
||||
{ value: `${defaultValues.date_to} 23:59:59`, datatype: "date", id: "finish_time", fn: "lessThan" },
|
||||
];
|
||||
const [specialFilter, setSpecialFilter] = useState(defaultFilter);
|
||||
|
||||
const onSearchSubmit = async (data) => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("from_date", `${data.from_date} 00:00:00`);
|
||||
params.set("date_to", `${data.date_to} 23:59:59`);
|
||||
setSpecialFilter([
|
||||
{ value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" },
|
||||
{ value: `${data.from_date} 00:00:00`, datatype: "date", id: "start_time", fn: "greaterThan" },
|
||||
{ value: `${data.date_to} 23:59:59`, datatype: "date", id: "finish_time", fn: "lessThan" },
|
||||
]);
|
||||
if (data.province_id === "-1") {
|
||||
try {
|
||||
const response = await requestServer(`${GET_DRIVER_COUNTRY_ACTIVITY_REPORT}?${params}`);
|
||||
const result = response.data.data;
|
||||
|
||||
setData({
|
||||
data: result.activities,
|
||||
filters: data,
|
||||
});
|
||||
} catch (e) {}
|
||||
} else {
|
||||
try {
|
||||
params.set("province_id", `${data.province_id}`);
|
||||
const response = await requestServer(`${GET_DRIVER_PROVINCE_ACTIVITY_REPORT}?${params}`);
|
||||
|
||||
const result = response.data.data;
|
||||
|
||||
setData({
|
||||
data: result.activities,
|
||||
filters: data,
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!userPermissions) return;
|
||||
onSearchSubmit(defaultValues);
|
||||
}, [userPermissions]);
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
from_date: yup
|
||||
.string()
|
||||
.required("تاریخ شروع الزامی است!")
|
||||
.matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!")
|
||||
.test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()),
|
||||
date_to: yup
|
||||
.string()
|
||||
.required("تاریخ پایان الزامی است!")
|
||||
.matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!")
|
||||
.test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()),
|
||||
province_id: yup.string().nullable(),
|
||||
});
|
||||
|
||||
const { control, handleSubmit } = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<PageTitle title={"کارتابل گزارش راننده"} />
|
||||
<SearchReportList
|
||||
control={control}
|
||||
hasProvincesPermission={hasProvincesPermission}
|
||||
handleSubmit={handleSubmit}
|
||||
onSearchSubmit={onSearchSubmit}
|
||||
/>
|
||||
{!data ? <LinearProgress /> : <DriverReportsList specialFilter={specialFilter} data={data} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default DriverReportsPage;
|
||||
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
import {
|
||||
EXPORT_GET_MACHINES_COUNTRY_ACTIVITY_REPORT,
|
||||
EXPORT_GET_MACHINES_PROVINCE_ACTIVITY_REPORT,
|
||||
} from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import DescriptionIcon from "@mui/icons-material/Description";
|
||||
import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material";
|
||||
import FileSaver from "file-saver";
|
||||
import moment from "jalali-moment";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
const PrintExcel = ({ table, filterData }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [loading, setLoading] = useState(false);
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => {
|
||||
if (filter.value) {
|
||||
acc.push({ id: filter.id, value: filter.value });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
activeFilters.length > 0 &&
|
||||
activeFilters.map((filter, index) => {
|
||||
params.set(`${filter.id}`, filter.value);
|
||||
});
|
||||
return params;
|
||||
}, [activeFilters]);
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true);
|
||||
const CountryOrProvince = activeFilters.find((item) => item.id === "province_id");
|
||||
const requestUrl =
|
||||
CountryOrProvince.value === "-1"
|
||||
? EXPORT_GET_MACHINES_COUNTRY_ACTIVITY_REPORT
|
||||
: EXPORT_GET_MACHINES_PROVINCE_ACTIVITY_REPORT;
|
||||
requestServer(`${requestUrl}?${filterParams}`, "get", {
|
||||
requestOptions: { responseType: "blob" },
|
||||
})
|
||||
.then((response) => {
|
||||
const filename = `خروجی کارتابل گزارشات گشت راهداری تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton aria-label="خروجی اکسل" color="success" onClick={clickHandler}>
|
||||
<DescriptionIcon sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button
|
||||
color="success"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit" /> : <DescriptionIcon />}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
خروجی اکسل
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default PrintExcel;
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import { Box } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
import Toolbar from "./Toolbar";
|
||||
import RowActions from "./RowActions";
|
||||
|
||||
const MachinesReportsList = ({ data, specialFilter }) => {
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessorKey: "machine_id",
|
||||
header: "کد یکتا",
|
||||
id: "machine_id",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "numeric",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "car_name",
|
||||
header: "مدل ماشین",
|
||||
id: "car_name",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "text",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "machine_code",
|
||||
header: "کد ماشین",
|
||||
id: "machine_code",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "text",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "func",
|
||||
header: "کارکرد",
|
||||
id: "func",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "number",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "missions",
|
||||
header: "تعداد ماموریت",
|
||||
id: "missions",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "number",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
const cleanedData = specialFilter.filter((item) => !(item.id === "province_id" && item.value === "-1"));
|
||||
const RowActionsWithRowData = (props) => <RowActions specialFilter={cleanedData} {...props} />;
|
||||
return (
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
TableToolbar={Toolbar}
|
||||
columns={columns}
|
||||
data={data.data}
|
||||
page_name={"roadMissionReport"}
|
||||
table_name={"roadMissionReportList"}
|
||||
enablePagination={false}
|
||||
specialFilter={specialFilter}
|
||||
enableRowActions
|
||||
RowActions={RowActionsWithRowData}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MachinesReportsList;
|
||||
@@ -0,0 +1,107 @@
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import { GET_MACHINE_MISSIONS_DETAILS } from "@/core/utils/routes";
|
||||
import { Box } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const MachineMissionsList = ({ row, specialFilter }) => {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "کد یکتا",
|
||||
id: "id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 80,
|
||||
},
|
||||
{
|
||||
accessorKey: "province_name",
|
||||
header: "استان",
|
||||
id: "province_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "city_name",
|
||||
header: "شهر",
|
||||
id: "city_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "station_name",
|
||||
header: "راهدارخانه",
|
||||
id: "station_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "driver_name",
|
||||
header: "راننده",
|
||||
id: "driver_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "km",
|
||||
header: "کیلومتر شروع",
|
||||
id: "km",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "end_km",
|
||||
header: "کیلومتر پایان",
|
||||
id: "end_km",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<DataTableWithAuth
|
||||
specialFilter={specialFilter}
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
table_url={`${GET_MACHINE_MISSIONS_DETAILS}/${row.machine_id}`}
|
||||
page_name={"roadMissionsTransportation"}
|
||||
table_name={"roadMissionsTransportationMachinesList"}
|
||||
positionActionsColumn={"first"}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachineMissionsList;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Box, Button, DialogActions, DialogContent } from "@mui/material";
|
||||
import { Stack } from "@mui/system";
|
||||
import MachineMissionsList from "./MachineMissionsDetailsDialog/MachineMissionsList";
|
||||
|
||||
const MachineMissionsDetailsTable = ({ row, setOpenMachineMissionsDetails, specialFilter }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Stack spacing={3}>
|
||||
<Stack spacing={2}>
|
||||
<MachineMissionsList specialFilter={specialFilter} row={row} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Stack direction={"row"} spacing={1}>
|
||||
<Button onClick={() => setOpenMachineMissionsDetails(false)} variant="outlined" color="primary">
|
||||
بستن
|
||||
</Button>
|
||||
</Stack>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachineMissionsDetailsTable;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Close, RemoveRedEye } from "@mui/icons-material";
|
||||
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import MachineMissionsDetailsTable from "./Form";
|
||||
|
||||
const MissionDetails = ({ row, specialFilter }) => {
|
||||
const [openMachineMissionsDetails, setOpenMachineMissionsDetails] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="ریزگزارشات" arrow placement="right">
|
||||
<IconButton
|
||||
color="primary"
|
||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||
onClick={() => {
|
||||
setOpenMachineMissionsDetails(true);
|
||||
}}
|
||||
>
|
||||
<RemoveRedEye />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog open={openMachineMissionsDetails} fullWidth maxWidth="md">
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={() => setOpenMachineMissionsDetails(false)}
|
||||
sx={(theme) => ({
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
zIndex: 50,
|
||||
color: theme.palette.grey[500],
|
||||
})}
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
<DialogTitle>ریز گزارشات خودرو</DialogTitle>
|
||||
{openMachineMissionsDetails && (
|
||||
<MachineMissionsDetailsTable
|
||||
specialFilter={specialFilter}
|
||||
row={row.original}
|
||||
setOpenMachineMissionsDetails={setOpenMachineMissionsDetails}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MissionDetails;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import polyline from "@mapbox/polyline";
|
||||
import SourceIcon from "@/assets/images/source-icon.svg";
|
||||
import DestIcon from "@/assets/images/destination-icon.svg";
|
||||
|
||||
const ShowRoute = ({ area }) => {
|
||||
const map = useMap();
|
||||
const defaultIconSize = [35, 35];
|
||||
|
||||
const createCustomIcon = (size, iconUrl) => {
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (coords.length > 0) {
|
||||
map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 });
|
||||
}
|
||||
}, [area]);
|
||||
const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src);
|
||||
const destIcon = createCustomIcon(defaultIconSize, DestIcon.src);
|
||||
|
||||
if (!area) return null;
|
||||
const coords = polyline.decode(area, 6);
|
||||
|
||||
if (coords.length === 0) return null;
|
||||
|
||||
const sourcePosition = coords[0];
|
||||
const destPosition = coords[coords.length - 1];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShowRoute;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Box, Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import MapIcon from "@mui/icons-material/Map";
|
||||
import MapLayer from "@/core/components/MapLayer";
|
||||
import ShowRoute from "./ShowRoute";
|
||||
|
||||
const RouteDetail = ({ encoded_route }) => {
|
||||
const [openRouteDetailDialog, setOpenRouteDetailDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="مسیر انتخابی">
|
||||
<IconButton color="primary" onClick={() => setOpenRouteDetailDialog(true)}>
|
||||
<MapIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openRouteDetailDialog}
|
||||
onClose={() => setOpenRouteDetailDialog(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={"sm"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>مسیر انتخابی</DialogTitle>
|
||||
<Box sx={{ mx: 1, borderRadius: "5px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "300px",
|
||||
border: "1px solid #e1e1e1",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowRoute area={encoded_route} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setOpenRouteDetailDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RouteDetail;
|
||||
@@ -0,0 +1,6 @@
|
||||
import MissionDetails from "./MissionDitails";
|
||||
|
||||
const RowActions = ({ row, specialFilter }) => {
|
||||
return <MissionDetails specialFilter={specialFilter} row={row} />;
|
||||
};
|
||||
export default RowActions;
|
||||
@@ -0,0 +1,27 @@
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const FromDateController = ({ control }) => {
|
||||
return (
|
||||
<Controller
|
||||
name="from_date"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="from_date"
|
||||
error={!!error}
|
||||
value={value}
|
||||
label={"تاریخ شروع (از تاریخ)"}
|
||||
placeholder={"از تاریخ"}
|
||||
setFieldValue={(name, newValue) => {
|
||||
const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD");
|
||||
onChange(formattedDate); // Update the field value in react-hook-form
|
||||
}}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FromDateController;
|
||||
@@ -0,0 +1,44 @@
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { Button, Grid } from "@mui/material";
|
||||
import { Controller } from "react-hook-form";
|
||||
import FromDateController from "./FromDateController";
|
||||
import SelectProvince from "./SelectProvince";
|
||||
import ToDateController from "./ToDateController";
|
||||
|
||||
const SearchReportField = ({ control, hasProvincesPermission }) => {
|
||||
return (
|
||||
<Grid container spacing={2} sx={{ py: 2 }}>
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<FromDateController control={control} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<ToDateController control={control} />
|
||||
</Grid>
|
||||
{hasProvincesPermission && (
|
||||
<Grid item xs={12} md={3}>
|
||||
<SelectProvince control={control} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<Controller
|
||||
name="submitButton"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, formState: { isSubmitting } }) => (
|
||||
<Button
|
||||
variant="contained"
|
||||
size="medium"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
endIcon={<SearchIcon />}
|
||||
fullWidth
|
||||
>
|
||||
{isSubmitting ? "درحال دریافت اطلاعات" : "جستجو"}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchReportField;
|
||||
@@ -0,0 +1,45 @@
|
||||
import useProvinces from "@/lib/hooks/useProvince";
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const SelectProvince = ({ control }) => {
|
||||
const { provinces, loadingProvinces, errorProvinces } = useProvinces();
|
||||
return (
|
||||
<Controller
|
||||
name="province_id"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl error={!!error} size="small" fullWidth>
|
||||
<InputLabel id="demo-error-label">استان</InputLabel>
|
||||
<Select
|
||||
{...field}
|
||||
labelId="demo-error-label"
|
||||
id="province_id"
|
||||
value={loadingProvinces ? "loading" : field.value || ""}
|
||||
label="استان"
|
||||
onChange={field.onChange}
|
||||
>
|
||||
{loadingProvinces ? (
|
||||
<MenuItem value={"loading"}>در حال بارگذاری...</MenuItem>
|
||||
) : errorProvinces ? (
|
||||
<MenuItem value={"error"}>خطا در بارگذاری</MenuItem>
|
||||
) : (
|
||||
[
|
||||
<MenuItem key="-1" value={"-1"}>
|
||||
کل کشور
|
||||
</MenuItem>,
|
||||
...provinces.map((province) => (
|
||||
<MenuItem key={province.id} value={province.id}>
|
||||
{province.name_fa}
|
||||
</MenuItem>
|
||||
)),
|
||||
]
|
||||
)}
|
||||
</Select>
|
||||
{error && <FormHelperText>{error.message}</FormHelperText>}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default SelectProvince;
|
||||
@@ -0,0 +1,30 @@
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
|
||||
const ToDateController = ({ control }) => {
|
||||
const minDate = useWatch({ control, name: "from_date" });
|
||||
return (
|
||||
<Controller
|
||||
name="date_to"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="date_to"
|
||||
minDate={minDate}
|
||||
maxDate={new Date()}
|
||||
error={!!error}
|
||||
value={value}
|
||||
label={"تاریخ پایان (تا تاریخ)"}
|
||||
placeholder={"تا تاریخ"}
|
||||
setFieldValue={(name, newValue) => {
|
||||
const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD");
|
||||
onChange(formattedDate); // Update the field value in react-hook-form
|
||||
}}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ToDateController;
|
||||
@@ -0,0 +1,13 @@
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import SearchReportField from "./SearchReportField";
|
||||
|
||||
const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => {
|
||||
return (
|
||||
<>
|
||||
<StyledForm onSubmit={handleSubmit(onSearchSubmit)}>
|
||||
<SearchReportField control={control} hasProvincesPermission={hasProvincesPermission} />
|
||||
</StyledForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SearchReportList;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Box } from "@mui/material";
|
||||
import PrintExcel from "./ExcelPrint";
|
||||
|
||||
const Toolbar = ({ table, filterData, mutate }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<PrintExcel table={table} filterData={filterData} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default Toolbar;
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
import PageTitle from "@/core/components/PageTitle";
|
||||
import { GET_MACHINES_COUNTRY_ACTIVITY_REPORT, GET_MACHINES_PROVINCE_ACTIVITY_REPORT } from "@/core/utils/routes";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { LinearProgress, Stack } from "@mui/material";
|
||||
import moment from "jalali-moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as yup from "yup";
|
||||
import MachinesReportsList from "./MachinesReportsList";
|
||||
import SearchReportList from "./Search";
|
||||
|
||||
const MachinesReportsPage = () => {
|
||||
const [data, setData] = useState(null);
|
||||
const { data: userPermissions } = usePermissions();
|
||||
const { user } = useAuth();
|
||||
const requestServer = useRequest();
|
||||
const hasProvincesPermission = userPermissions?.includes("mission-report-country");
|
||||
|
||||
const defaultValues = {
|
||||
from_date: moment(new Date()).format("YYYY-MM-DD"),
|
||||
date_to: moment(new Date()).format("YYYY-MM-DD"),
|
||||
province_id: user.province_id || "-1",
|
||||
};
|
||||
const defaultFilter = [
|
||||
{ value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" },
|
||||
{ value: `${defaultValues.from_date} 00:00:00`, datatype: "date", id: "start_time", fn: "greaterThan" },
|
||||
{ value: `${defaultValues.date_to} 23:59:59`, datatype: "date", id: "finish_time", fn: "lessThan" },
|
||||
];
|
||||
const [specialFilter, setSpecialFilter] = useState(defaultFilter);
|
||||
|
||||
const onSearchSubmit = async (data) => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("from_date", `${data.from_date} 00:00:00`);
|
||||
params.set("date_to", `${data.date_to} 23:59:59`);
|
||||
setSpecialFilter([
|
||||
{ value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" },
|
||||
{ value: `${data.from_date} 00:00:00`, datatype: "date", id: "start_time", fn: "greaterThan" },
|
||||
{ value: `${data.date_to} 23:59:59`, datatype: "date", id: "finish_time", fn: "lessThan" },
|
||||
]);
|
||||
if (data.province_id === "-1") {
|
||||
try {
|
||||
const response = await requestServer(`${GET_MACHINES_COUNTRY_ACTIVITY_REPORT}?${params}`);
|
||||
const result = response.data.data;
|
||||
|
||||
setData({
|
||||
data: result.activities,
|
||||
filters: data,
|
||||
});
|
||||
} catch (e) {}
|
||||
} else {
|
||||
try {
|
||||
params.set("province_id", `${data.province_id}`);
|
||||
const response = await requestServer(`${GET_MACHINES_PROVINCE_ACTIVITY_REPORT}?${params}`);
|
||||
|
||||
const result = response.data.data;
|
||||
|
||||
setData({
|
||||
data: result.activities,
|
||||
filters: data,
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!userPermissions) return;
|
||||
onSearchSubmit(defaultValues);
|
||||
}, [userPermissions]);
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
from_date: yup
|
||||
.string()
|
||||
.required("تاریخ شروع الزامی است!")
|
||||
.matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!")
|
||||
.test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()),
|
||||
date_to: yup
|
||||
.string()
|
||||
.required("تاریخ پایان الزامی است!")
|
||||
.matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!")
|
||||
.test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()),
|
||||
province_id: yup.string().nullable(),
|
||||
});
|
||||
|
||||
const { control, handleSubmit } = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<PageTitle title={"کارتابل گزارش ماشینها"} />
|
||||
<SearchReportList
|
||||
control={control}
|
||||
hasProvincesPermission={hasProvincesPermission}
|
||||
handleSubmit={handleSubmit}
|
||||
onSearchSubmit={onSearchSubmit}
|
||||
/>
|
||||
{!data ? <LinearProgress /> : <MachinesReportsList specialFilter={specialFilter} data={data} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default MachinesReportsPage;
|
||||
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
import {
|
||||
EXPORT_GET_MACHINES_COUNTRY_ACTIVITY_REPORT,
|
||||
EXPORT_GET_MACHINES_PROVINCE_ACTIVITY_REPORT,
|
||||
EXPORT_GET_MACHINES_TYPE_COUNTRY_ACTIVITY_REPORT,
|
||||
EXPORT_GET_MACHINES_TYPE_PROVINCE_ACTIVITY_REPORT,
|
||||
} from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import DescriptionIcon from "@mui/icons-material/Description";
|
||||
import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material";
|
||||
import FileSaver from "file-saver";
|
||||
import moment from "jalali-moment";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
const PrintExcel = ({ table, filterData }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [loading, setLoading] = useState(false);
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => {
|
||||
if (filter.value) {
|
||||
acc.push({ id: filter.id, value: filter.value });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
activeFilters.length > 0 &&
|
||||
activeFilters.map((filter, index) => {
|
||||
params.set(`${filter.id}`, filter.value);
|
||||
});
|
||||
return params;
|
||||
}, [activeFilters]);
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true);
|
||||
const CountryOrProvince = activeFilters.find((item) => item.id === "province_id");
|
||||
const requestUrl =
|
||||
CountryOrProvince.value === "-1"
|
||||
? EXPORT_GET_MACHINES_TYPE_COUNTRY_ACTIVITY_REPORT
|
||||
: EXPORT_GET_MACHINES_TYPE_PROVINCE_ACTIVITY_REPORT;
|
||||
requestServer(`${requestUrl}?${filterParams}`, "get", {
|
||||
requestOptions: { responseType: "blob" },
|
||||
})
|
||||
.then((response) => {
|
||||
const filename = `خروجی کارتابل گزارشات گشت راهداری تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton aria-label="خروجی اکسل" color="success" onClick={clickHandler}>
|
||||
<DescriptionIcon sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button
|
||||
color="success"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit" /> : <DescriptionIcon />}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
خروجی اکسل
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default PrintExcel;
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import { Box } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
import Toolbar from "./Toolbar";
|
||||
// import RowActions from "./RowActions";
|
||||
|
||||
const MachinesReportsList = ({ data, specialFilter }) => {
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessorKey: "car_type",
|
||||
header: "نوع ماشین",
|
||||
id: "car_type",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "text",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "func",
|
||||
header: "کارکرد",
|
||||
id: "func",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "number",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
{
|
||||
accessorKey: "missions",
|
||||
header: "تعداد ماموریت",
|
||||
id: "missions",
|
||||
enableColumnFilter: false,
|
||||
enableSorting: false,
|
||||
datatype: "number",
|
||||
grow: false,
|
||||
size: 50,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
TableToolbar={Toolbar}
|
||||
columns={columns}
|
||||
data={data.data}
|
||||
page_name={"roadMissionReportMachineTypeReport"}
|
||||
table_name={"roadMissionReportMachineTypeReportList"}
|
||||
enablePagination={false}
|
||||
specialFilter={specialFilter}
|
||||
// enableRowActions
|
||||
// RowActions={RowActions}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MachinesReportsList;
|
||||
@@ -0,0 +1,106 @@
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import { GET_MACHINE_MISSIONS_DETAILS, GET_MACHINE_TYPE_MISSIONS_DETAILS } from "@/core/utils/routes";
|
||||
import { Box } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const MachineMissionsList = ({ row }) => {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "کد یکتا",
|
||||
id: "id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 80,
|
||||
},
|
||||
{
|
||||
accessorKey: "province_name",
|
||||
header: "استان",
|
||||
id: "province_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "city_name",
|
||||
header: "شهر",
|
||||
id: "city_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "station_name",
|
||||
header: "راهدارخانه",
|
||||
id: "station_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "driver_name",
|
||||
header: "راننده",
|
||||
id: "driver_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "km",
|
||||
header: "کیلومتر شروع",
|
||||
id: "km",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "end_km",
|
||||
header: "کیلومتر پایان",
|
||||
id: "end_km",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<DataTableWithAuth
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
table_url={`${GET_MACHINE_TYPE_MISSIONS_DETAILS}?car_type=${row.car_type}`}
|
||||
page_name={"roadMissionReportMachineType"}
|
||||
table_name={"roadMissionReportMachineTypeList"}
|
||||
positionActionsColumn={"first"}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachineMissionsList;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Box, Button, DialogActions, DialogContent } from "@mui/material";
|
||||
import { Stack } from "@mui/system";
|
||||
import MachineMissionsList from "./MachineMissionsDetailsDialog/MachineMissionsList";
|
||||
|
||||
const MachineMissionsDetailsTable = ({ row, setOpenMachineMissionsDetails }) => {
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Stack spacing={3}>
|
||||
<Stack spacing={2}>
|
||||
<MachineMissionsList row={row} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Stack direction={"row"} spacing={1}>
|
||||
<Button onClick={() => setOpenMachineMissionsDetails(false)} variant="outlined" color="primary">
|
||||
بستن
|
||||
</Button>
|
||||
</Stack>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MachineMissionsDetailsTable;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Close, RemoveRedEye } from "@mui/icons-material";
|
||||
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import MachineMissionsDetailsTable from "./Form";
|
||||
|
||||
const MissionDetails = ({ row }) => {
|
||||
const [openMachineMissionsDetails, setOpenMachineMissionsDetails] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="ریزگزارشات" arrow placement="right">
|
||||
<IconButton
|
||||
color="primary"
|
||||
sx={{ textTransform: "unset", alignSelf: "center" }}
|
||||
onClick={() => {
|
||||
setOpenMachineMissionsDetails(true);
|
||||
}}
|
||||
>
|
||||
<RemoveRedEye />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog open={openMachineMissionsDetails} fullWidth maxWidth="md">
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={() => setOpenMachineMissionsDetails(false)}
|
||||
sx={(theme) => ({
|
||||
position: "absolute",
|
||||
right: 8,
|
||||
top: 8,
|
||||
zIndex: 50,
|
||||
color: theme.palette.grey[500],
|
||||
})}
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
<DialogTitle>ریز گزارشات نوع خودرو</DialogTitle>
|
||||
{openMachineMissionsDetails && (
|
||||
<MachineMissionsDetailsTable
|
||||
row={row.original}
|
||||
setOpenMachineMissionsDetails={setOpenMachineMissionsDetails}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MissionDetails;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import polyline from "@mapbox/polyline";
|
||||
import SourceIcon from "@/assets/images/source-icon.svg";
|
||||
import DestIcon from "@/assets/images/destination-icon.svg";
|
||||
|
||||
const ShowRoute = ({ area }) => {
|
||||
const map = useMap();
|
||||
const defaultIconSize = [35, 35];
|
||||
|
||||
const createCustomIcon = (size, iconUrl) => {
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (coords.length > 0) {
|
||||
map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 });
|
||||
}
|
||||
}, [area]);
|
||||
const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src);
|
||||
const destIcon = createCustomIcon(defaultIconSize, DestIcon.src);
|
||||
|
||||
if (!area) return null;
|
||||
const coords = polyline.decode(area, 6);
|
||||
|
||||
if (coords.length === 0) return null;
|
||||
|
||||
const sourcePosition = coords[0];
|
||||
const destPosition = coords[coords.length - 1];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShowRoute;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Box, Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import MapIcon from "@mui/icons-material/Map";
|
||||
import MapLayer from "@/core/components/MapLayer";
|
||||
import ShowRoute from "./ShowRoute";
|
||||
|
||||
const RouteDetail = ({ encoded_route }) => {
|
||||
const [openRouteDetailDialog, setOpenRouteDetailDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="مسیر انتخابی">
|
||||
<IconButton color="primary" onClick={() => setOpenRouteDetailDialog(true)}>
|
||||
<MapIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={openRouteDetailDialog}
|
||||
onClose={() => setOpenRouteDetailDialog(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={"sm"}
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: "large" }}>مسیر انتخابی</DialogTitle>
|
||||
<Box sx={{ mx: 1, borderRadius: "5px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "300px",
|
||||
border: "1px solid #e1e1e1",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
>
|
||||
<MapLayer style={{ borderRadius: "4px" }}>
|
||||
<ShowRoute area={encoded_route} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => setOpenRouteDetailDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RouteDetail;
|
||||
@@ -0,0 +1,6 @@
|
||||
// import MissionDetails from "./MissionDitails";
|
||||
|
||||
const RowActions = ({ row }) => {
|
||||
return <>{/*<MissionDetails row={row} />*/}</>;
|
||||
};
|
||||
export default RowActions;
|
||||
@@ -0,0 +1,27 @@
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const FromDateController = ({ control }) => {
|
||||
return (
|
||||
<Controller
|
||||
name="from_date"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="from_date"
|
||||
error={!!error}
|
||||
value={value}
|
||||
label={"تاریخ شروع (از تاریخ)"}
|
||||
placeholder={"از تاریخ"}
|
||||
setFieldValue={(name, newValue) => {
|
||||
const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD");
|
||||
onChange(formattedDate); // Update the field value in react-hook-form
|
||||
}}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FromDateController;
|
||||
@@ -0,0 +1,44 @@
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { Button, Grid } from "@mui/material";
|
||||
import { Controller } from "react-hook-form";
|
||||
import FromDateController from "./FromDateController";
|
||||
import SelectProvince from "./SelectProvince";
|
||||
import ToDateController from "./ToDateController";
|
||||
|
||||
const SearchReportField = ({ control, hasProvincesPermission }) => {
|
||||
return (
|
||||
<Grid container spacing={2} sx={{ py: 2 }}>
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<FromDateController control={control} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<ToDateController control={control} />
|
||||
</Grid>
|
||||
{hasProvincesPermission && (
|
||||
<Grid item xs={12} md={3}>
|
||||
<SelectProvince control={control} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} md={hasProvincesPermission ? 3 : 4}>
|
||||
<Controller
|
||||
name="submitButton"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, formState: { isSubmitting } }) => (
|
||||
<Button
|
||||
variant="contained"
|
||||
size="medium"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
endIcon={<SearchIcon />}
|
||||
fullWidth
|
||||
>
|
||||
{isSubmitting ? "درحال دریافت اطلاعات" : "جستجو"}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchReportField;
|
||||
@@ -0,0 +1,45 @@
|
||||
import useProvinces from "@/lib/hooks/useProvince";
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const SelectProvince = ({ control }) => {
|
||||
const { provinces, loadingProvinces, errorProvinces } = useProvinces();
|
||||
return (
|
||||
<Controller
|
||||
name="province_id"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl error={!!error} size="small" fullWidth>
|
||||
<InputLabel id="demo-error-label">استان</InputLabel>
|
||||
<Select
|
||||
{...field}
|
||||
labelId="demo-error-label"
|
||||
id="province_id"
|
||||
value={loadingProvinces ? "loading" : field.value || ""}
|
||||
label="استان"
|
||||
onChange={field.onChange}
|
||||
>
|
||||
{loadingProvinces ? (
|
||||
<MenuItem value={"loading"}>در حال بارگذاری...</MenuItem>
|
||||
) : errorProvinces ? (
|
||||
<MenuItem value={"error"}>خطا در بارگذاری</MenuItem>
|
||||
) : (
|
||||
[
|
||||
<MenuItem key="-1" value={"-1"}>
|
||||
کل کشور
|
||||
</MenuItem>,
|
||||
...provinces.map((province) => (
|
||||
<MenuItem key={province.id} value={province.id}>
|
||||
{province.name_fa}
|
||||
</MenuItem>
|
||||
)),
|
||||
]
|
||||
)}
|
||||
</Select>
|
||||
{error && <FormHelperText>{error.message}</FormHelperText>}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default SelectProvince;
|
||||
@@ -0,0 +1,30 @@
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
|
||||
const ToDateController = ({ control }) => {
|
||||
const minDate = useWatch({ control, name: "from_date" });
|
||||
return (
|
||||
<Controller
|
||||
name="date_to"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<MuiDatePicker
|
||||
name="date_to"
|
||||
minDate={minDate}
|
||||
maxDate={new Date()}
|
||||
error={!!error}
|
||||
value={value}
|
||||
label={"تاریخ پایان (تا تاریخ)"}
|
||||
placeholder={"تا تاریخ"}
|
||||
setFieldValue={(name, newValue) => {
|
||||
const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD");
|
||||
onChange(formattedDate); // Update the field value in react-hook-form
|
||||
}}
|
||||
helperText={error ? error.message : null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default ToDateController;
|
||||
@@ -0,0 +1,13 @@
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import SearchReportField from "./SearchReportField";
|
||||
|
||||
const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => {
|
||||
return (
|
||||
<>
|
||||
<StyledForm onSubmit={handleSubmit(onSearchSubmit)}>
|
||||
<SearchReportField control={control} hasProvincesPermission={hasProvincesPermission} />
|
||||
</StyledForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SearchReportList;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Box } from "@mui/material";
|
||||
import PrintExcel from "./ExcelPrint";
|
||||
|
||||
const Toolbar = ({ table, filterData, mutate }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<PrintExcel table={table} filterData={filterData} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default Toolbar;
|
||||
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
import PageTitle from "@/core/components/PageTitle";
|
||||
import {
|
||||
GET_MACHINES_TYPE_COUNTRY_ACTIVITY_REPORT,
|
||||
GET_MACHINES_TYPE_PROVINCE_ACTIVITY_REPORT,
|
||||
} from "@/core/utils/routes";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { LinearProgress, Stack } from "@mui/material";
|
||||
import moment from "jalali-moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as yup from "yup";
|
||||
import MachinesReportsList from "./MachinesReportsList";
|
||||
import SearchReportList from "./Search";
|
||||
|
||||
const MachinesTypeReportsPage = () => {
|
||||
const [data, setData] = useState(null);
|
||||
const { data: userPermissions } = usePermissions();
|
||||
const { user } = useAuth();
|
||||
const requestServer = useRequest();
|
||||
const hasProvincesPermission = userPermissions?.includes("mission-report-country");
|
||||
|
||||
const defaultValues = {
|
||||
from_date: moment(new Date()).format("YYYY-MM-DD"),
|
||||
date_to: moment(new Date()).format("YYYY-MM-DD"),
|
||||
province_id: user.province_id || "-1",
|
||||
};
|
||||
const defaultFilter = [
|
||||
{ value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" },
|
||||
{ value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" },
|
||||
{ value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" },
|
||||
];
|
||||
const [specialFilter, setSpecialFilter] = useState(defaultFilter);
|
||||
|
||||
const onSearchSubmit = async (data) => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("from_date", `${data.from_date} 00:00:00`);
|
||||
params.set("date_to", `${data.date_to} 23:59:59`);
|
||||
setSpecialFilter([
|
||||
{ value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" },
|
||||
{ value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" },
|
||||
{ value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" },
|
||||
]);
|
||||
if (data.province_id === "-1") {
|
||||
try {
|
||||
const response = await requestServer(`${GET_MACHINES_TYPE_COUNTRY_ACTIVITY_REPORT}?${params}`);
|
||||
const result = response.data.data;
|
||||
|
||||
setData({
|
||||
data: result.activities,
|
||||
filters: data,
|
||||
});
|
||||
} catch (e) {}
|
||||
} else {
|
||||
try {
|
||||
params.set("province_id", `${data.province_id}`);
|
||||
const response = await requestServer(`${GET_MACHINES_TYPE_PROVINCE_ACTIVITY_REPORT}?${params}`);
|
||||
|
||||
const result = response.data.data;
|
||||
|
||||
setData({
|
||||
data: result.activities,
|
||||
filters: data,
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!userPermissions) return;
|
||||
onSearchSubmit(defaultValues);
|
||||
}, [userPermissions]);
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
from_date: yup
|
||||
.string()
|
||||
.required("تاریخ شروع الزامی است!")
|
||||
.matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!")
|
||||
.test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()),
|
||||
date_to: yup
|
||||
.string()
|
||||
.required("تاریخ پایان الزامی است!")
|
||||
.matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!")
|
||||
.test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()),
|
||||
province_id: yup.string().nullable(),
|
||||
});
|
||||
|
||||
const { control, handleSubmit } = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<PageTitle title={"کارتابل گزارش نوع ماشینها"} />
|
||||
<SearchReportList
|
||||
control={control}
|
||||
hasProvincesPermission={hasProvincesPermission}
|
||||
handleSubmit={handleSubmit}
|
||||
onSearchSubmit={onSearchSubmit}
|
||||
/>
|
||||
{!data ? <LinearProgress /> : <MachinesReportsList specialFilter={specialFilter} data={data} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default MachinesTypeReportsPage;
|
||||
@@ -78,7 +78,6 @@ const MachinesList = ({ machineType, setMachine, setOpenMachinesDialog }) => {
|
||||
columns={columns}
|
||||
specialFilter={[
|
||||
{ value: machineType, datatype: "text", id: "car_type", fn: "equals" },
|
||||
{ value: 1, datatype: "numeric", id: "status", fn: "equals" },
|
||||
{
|
||||
value: city_id ? city_id : province_id,
|
||||
datatype: "numeric",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Marker, Polyline, useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import polyline from "@mapbox/polyline";
|
||||
@@ -18,14 +18,24 @@ const ShowRoute = ({ area }) => {
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (coords.length > 0) {
|
||||
map.flyToBounds(coords, { padding: [50, 50], duration: 1.5 });
|
||||
}
|
||||
}, [area]);
|
||||
const sourceIcon = createCustomIcon(defaultIconSize, SourceIcon.src);
|
||||
const destIcon = createCustomIcon(defaultIconSize, DestIcon.src);
|
||||
const [showRoute, setShowRoute] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!coords.length) return;
|
||||
|
||||
setShowRoute(false);
|
||||
|
||||
map.flyToBounds(coords, {
|
||||
padding: [50, 50],
|
||||
duration: 1.5,
|
||||
});
|
||||
|
||||
map.once("moveend", () => {
|
||||
setShowRoute(true);
|
||||
});
|
||||
}, [area]);
|
||||
if (!area) return null;
|
||||
const coords = polyline.decode(area, 6);
|
||||
|
||||
@@ -36,7 +46,8 @@ const ShowRoute = ({ area }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Polyline positions={coords} color="#015688" weight={7} opacity={1} />
|
||||
{showRoute && <Polyline positions={coords} color="#015688" weight={7} />}
|
||||
|
||||
<Marker position={sourcePosition} icon={sourceIcon} />
|
||||
<Marker position={destPosition} icon={destIcon} />
|
||||
</>
|
||||
|
||||
@@ -20,7 +20,7 @@ const ViolationForm = ({ setOpenMachinesDialog, mutate, openMachinesDialog }) =>
|
||||
data: {
|
||||
machine_code: result.machinesId.machine_code,
|
||||
km: result.km,
|
||||
exit_station: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`,
|
||||
exit_time: `${result.start_date} ${moment(result.start_time).format("HH:mm")}`,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
|
||||
@@ -11,14 +11,16 @@ const EditController = ({ rowId, mutate, setOpen, row }) => {
|
||||
|
||||
const defaultData = {
|
||||
end_date: endDate,
|
||||
end_time: row?.original?.enter_station ? new Date(row.original.enter_station) : null,
|
||||
end_km: "",
|
||||
end_time: row?.original?.enter_time ? new Date(row.original.enter_time) : null,
|
||||
};
|
||||
const handleSubmit = async (result) => {
|
||||
const payload = {};
|
||||
|
||||
if (result.end_date && result.end_time) {
|
||||
payload.enter_station = `${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", {
|
||||
data: payload,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import { Box, Button, DialogActions, DialogContent, Grid, Stack } from "@mui/material";
|
||||
import MissionDates from "./MissionDates";
|
||||
import * as yup from "yup";
|
||||
import LtrTextField from "@/core/components/LtrTextField";
|
||||
|
||||
export const schema = yup
|
||||
.object({
|
||||
@@ -55,7 +56,7 @@ const EditViolationForm = ({ setOpenMachinesDialog, defaultValues, onSubmitBase
|
||||
<>
|
||||
<StyledForm onSubmit={handleSubmit(handleOnSubmit)}>
|
||||
<DialogContent dividers>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Stack spacing={2} sx={{ flex: 1 }}>
|
||||
<Stack>
|
||||
<Grid container spacing={2}>
|
||||
<MissionDates control={control} />
|
||||
@@ -73,7 +74,28 @@ const EditViolationForm = ({ setOpenMachinesDialog, defaultValues, onSubmitBase
|
||||
</Box>
|
||||
)}
|
||||
</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>
|
||||
<DialogActions sx={{ alignItems: "center", justifyContent: "center" }}>
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReportProblem, RvHookup } from "@mui/icons-material";
|
||||
import { DriveEtaOutlined, ReportProblem, RvHookup } from "@mui/icons-material";
|
||||
import AccountTreeIcon from "@mui/icons-material/AccountTree";
|
||||
import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings";
|
||||
import AssessmentIcon from "@mui/icons-material/Assessment";
|
||||
@@ -34,6 +34,8 @@ import SecurityIcon from "@mui/icons-material/Security";
|
||||
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
|
||||
import SpeedIcon from "@mui/icons-material/Speed";
|
||||
import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle";
|
||||
import TaxiAlertIcon from "@mui/icons-material/TaxiAlert";
|
||||
import AirlineSeatReclineNormalIcon from "@mui/icons-material/AirlineSeatReclineNormal";
|
||||
|
||||
export const pageMenu = [
|
||||
{
|
||||
@@ -192,6 +194,33 @@ export const pageMenu = [
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
{
|
||||
id: "machinesReports",
|
||||
label: "ماشینها",
|
||||
icon: <DriveEtaIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
type: "page",
|
||||
route: "/dashboard/road-missions/reports/machinesReports",
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
{
|
||||
id: "machinesTypeReports",
|
||||
label: "نوع ماشینها",
|
||||
icon: <TaxiAlertIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
type: "page",
|
||||
route: "/dashboard/road-missions/reports/machineTypeReports",
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
{
|
||||
id: "driverReports",
|
||||
label: "راننده",
|
||||
icon: <AirlineSeatReclineNormalIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
type: "page",
|
||||
route: "/dashboard/road-missions/reports/driverReports",
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Mediation, ReportProblem, RvHookup } from "@mui/icons-material";
|
||||
import { DriveEtaOutlined, Mediation, ReportProblem, RvHookup } from "@mui/icons-material";
|
||||
import AccountTreeIcon from "@mui/icons-material/AccountTree";
|
||||
import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings";
|
||||
import AssessmentIcon from "@mui/icons-material/Assessment";
|
||||
@@ -31,7 +31,9 @@ import ScienceIcon from "@mui/icons-material/Science";
|
||||
import SecurityIcon from "@mui/icons-material/Security";
|
||||
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
|
||||
import SpeedIcon from "@mui/icons-material/Speed";
|
||||
import TaxiAlertIcon from "@mui/icons-material/TaxiAlert";
|
||||
import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle";
|
||||
import AirlineSeatReclineNormalIcon from "@mui/icons-material/AirlineSeatReclineNormal";
|
||||
|
||||
export const pageMenuDev = [
|
||||
{
|
||||
@@ -190,6 +192,33 @@ export const pageMenuDev = [
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
{
|
||||
id: "machinesReports",
|
||||
label: "ماشینها",
|
||||
icon: <DriveEtaIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
type: "page",
|
||||
route: "/dashboard/road-missions/reports/machinesReports",
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
{
|
||||
id: "machinesTypeReports",
|
||||
label: "نوع ماشینها",
|
||||
icon: <TaxiAlertIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
type: "page",
|
||||
route: "/dashboard/road-missions/reports/machineTypeReports",
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
{
|
||||
id: "driverReports",
|
||||
label: "راننده",
|
||||
icon: <AirlineSeatReclineNormalIcon sx={{ width: "inherit", height: "inherit" }} />,
|
||||
type: "page",
|
||||
route: "/dashboard/road-missions/reports/driverReports",
|
||||
badges: [{ key: "mission.request_portal" }],
|
||||
permissions: ["all"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -249,6 +249,30 @@ export const FINISH_MISSION = api + "/api/v3/missions/control_unit/finish";
|
||||
export const GET_ROAD_MISSIONS_VIOLATIONS_LIST = api + "/api/v3/missions/violation_management";
|
||||
export const GET_MISSION_PROVINCE_ACTIVITY_REPORT = api + "/api/v3/missions/report/country_activity";
|
||||
export const GET_MISSION_CITY_ACTIVITY_REPORT = api + "/api/v3/missions/report/country_activity";
|
||||
export const GET_MACHINES_COUNTRY_ACTIVITY_REPORT = api + "/api/v3/missions/report_machine/country_machines_activity";
|
||||
export const GET_MACHINES_PROVINCE_ACTIVITY_REPORT = api + "/api/v3/missions/report_machine/province_machines_activity";
|
||||
export const EXPORT_GET_MACHINES_COUNTRY_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/country_machines_activity_excel";
|
||||
export const EXPORT_GET_MACHINES_PROVINCE_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/province_machines_activity_excel";
|
||||
|
||||
export const GET_DRIVER_COUNTRY_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/country_machine_drivers_activity";
|
||||
export const GET_DRIVER_PROVINCE_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/province_machine_drivers_activity";
|
||||
export const EXPORT_GET_DRIVER_COUNTRY_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/country_machine_drivers_activity_excel";
|
||||
export const EXPORT_GET_DRIVER_PROVINCE_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/province_machine_drivers_activity_excel";
|
||||
|
||||
export const GET_MACHINES_TYPE_COUNTRY_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/country_machine_types_activity";
|
||||
export const GET_MACHINES_TYPE_PROVINCE_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/province_machine_types_activity";
|
||||
export const EXPORT_GET_MACHINES_TYPE_COUNTRY_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/country_machine_types_activity_excel";
|
||||
export const EXPORT_GET_MACHINES_TYPE_PROVINCE_ACTIVITY_REPORT =
|
||||
api + "/api/v3/missions/report_machine/province_machine_types_activity_excel";
|
||||
export const CREATE_VIOLATION = api + "/api/v3/missions/violation_management";
|
||||
export const EDIT_VIOLATION = api + "/api/v3/missions/violation_management";
|
||||
export const EXPORT_COUNTRY_ROAD_MISSIONS_REPORTS = api + "/api/v3/missions/report/country_excel_activity";
|
||||
@@ -288,3 +312,6 @@ export const GET_PROVINCES_LIST = api + "/api/v3/provinces/list";
|
||||
export const GET_CITIES_LIST = api + "/api/v3/cities/list";
|
||||
export const GET_STATIONS_LIST = api + "/api/v3/road_maintenance_station";
|
||||
export const UPDATE_MACHINE_STATUS_AND_LOCATION = api + "/api/v3/cmms_machines";
|
||||
export const GET_MACHINE_MISSIONS_DETAILS = api + "/api/v3/missions/report_machine/machine_details_report";
|
||||
export const GET_MACHINE_TYPE_MISSIONS_DETAILS = api + "/api/v3/missions/report_machine/machine_type_details_report";
|
||||
export const GET_DRIVER_DETAILS = api + "/api/v3/missions/report_machine/machine_driver_details_report";
|
||||
|
||||
Reference in New Issue
Block a user