Merge branch 'develop' into 'feature/gasht_submit'

# Conflicts:
#   src/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo.jsx
This commit is contained in:
AmirHossein Mahmoodi
2025-01-06 09:41:42 +00:00
45 changed files with 1475 additions and 169 deletions

View File

@@ -147,7 +147,7 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
mode: "onBlur",
context: { subItemsList, is_gasht },
});
const onSubmitBase = (data) => {
const onSubmitBase = async (data) => {
let result = { ...data };
if (result.before_image === null) {
@@ -184,7 +184,7 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
});
delete result.cmms_machines;
onSubmit({
await onSubmit({
result,
data: {
...data,
@@ -205,7 +205,8 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
variant={`${isMobile ? "scrollable" : "fullWidth"}`}
sx={{
display: "flex",
justifyContent: "space-around",
justifyContent: "center",
alignItems: "center",
width: "100%",
backgroundColor: "#efefef",
}}
@@ -253,7 +254,7 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => {
<Button
onClick={handlePrev}
variant="outlined"
color="error"
color="secondary"
size="large"
startIcon={tabState === 0 ? <ExitToAppIcon /> : <KeyboardDoubleArrowRightIcon />}
>

View File

@@ -4,13 +4,7 @@ import GashtCreateContent from "./GashtCreateContent";
const GashtCreate = ({ open, setOpen, mutate, rowId }) => {
return (
<Dialog open={open} fullWidth maxWidth="md">
<DialogTitle>جستجوی مورد مشاهده شده در گشت راهداری و ترابری</DialogTitle>
<GashtCreateContent mutate={mutate} setOpen={setOpen} />
<DialogActions>
<Button onClick={() => setOpen(false)} variant="outlined" color="primary" autoFocus>
بستن
</Button>
</DialogActions>
</Dialog>
);
};

View File

@@ -1,28 +1,46 @@
import { Box, Button, DialogActions, DialogContent, Grid, Stack, Tab, Tabs } from "@mui/material";
import { Box, Button, DialogActions, DialogContent, Tab, Tabs } from "@mui/material";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import MuiDatePicker from "@/core/components/MuiDatePicker";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import FileCopyIcon from "@mui/icons-material/FileCopy";
import InfoIcon from "@mui/icons-material/Info";
import GetItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemsForm";
import GetSubItemsForm from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetSubItemsForm";
import GetItemInfo from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/GetItemInfo";
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
import BeenhereIcon from "@mui/icons-material/Beenhere";
import StyledForm from "@/core/components/StyledForm";
import React from "react";
import { TabPanel } from "@mui/lab";
import React, { useState } from "react";
import SearchItemInfo from "./SearchItemInfo";
import moment from "jalali-moment";
import GetItemInfo from "./RowActions/GetItemInfo";
function TabPanel(props) {
const { children, value, index } = props;
return (
<div role="tabpanel" hidden={value !== index}>
{value === index && <Box>{children}</Box>}
</div>
);
}
const defaultValues = {
start_date: moment(new Date()).format("YYYY-MM-DD"),
end_date: moment(new Date()).format("YYYY-MM-DD"),
item_id: "",
road_patrol_id: "",
};
const defaultFilter = [
{ value: `${defaultValues.item_id}`, datatype: "text", id: "item_id", fn: "equals" },
{ value: `${defaultValues.road_patrol_id}`, datatype: "text", id: "road_patrol_id", fn: "equals" },
{ value: `${defaultValues.start_date}`, datatype: "date", id: "roadPatrol.start_time", fn: "equals" },
{ value: `${defaultValues.end_date}`, datatype: "date", id: "roadPatrol.end_time", fn: "equals" },
];
const GashtCreateContent = ({ mutate, setOpen }) => {
const defaultValues = {
start_date: null,
end_date: null,
item_id: null,
road_patrol_id: null,
};
const [tabState, setTabState] = useState(0);
const [itemInfo, setItemInfo] = useState(null);
const [submitCompleteForm, setSubmitCompleteForm] = useState(false);
const [specialFilter, setSpecialFilter] = useState(defaultFilter);
const validationSchema = yup
.object()
@@ -36,6 +54,21 @@ const GashtCreateContent = ({ mutate, setOpen }) => {
return (!!values.start_date && !!values.end_date) || !!values.item_id || !!values.road_patrol_id;
});
const handleChangeTab = (event, newValue) => {
setTabState(newValue);
};
const handleClose = () => {
setOpen(false);
};
const handlePrev = () => {
if (tabState === 0) {
handleClose();
} else {
setTabState(tabState - 1);
}
};
const {
control,
getValues,
@@ -49,41 +82,53 @@ const GashtCreateContent = ({ mutate, setOpen }) => {
resolver: yupResolver(validationSchema),
mode: "onBlur",
});
const onSearchSubmit = async (data) => {
setSpecialFilter([
{ value: `${data.item_id}`, datatype: "text", id: "item_id", fn: "equals" },
{ value: `${data.road_patrol_id}`, datatype: "text", id: "road_patrol_id", fn: "equals" },
{ value: `${data.start_date}`, datatype: "date", id: "roadPatrol.start_time", fn: "equals" },
{ value: `${data.end_date}`, datatype: "date", id: "roadPatrol.end_time", fn: "equals" },
]);
};
return (
<StyledForm onSubmit={handleSubmit(onSubmit)}>
<>
<Tabs
allowScrollButtonsMobile
value={tabState}
onChange={handleChangeTab}
variant={`${isMobile ? "scrollable" : "fullWidth"}`}
variant="fullWidth"
sx={{
display: "flex",
justifyContent: "space-around",
justifyContent: "center",
width: "100%",
backgroundColor: "#efefef",
}}
>
<Tab icon={<InsertDriveFileIcon />} label="انتخاب آیتم" />
<Tab disabled={tabState === 0} icon={<FileCopyIcon />} label="انتخاب موضوع مشاهده شده" />
<Tab icon={<InsertDriveFileIcon />} label="انتخاب مورد مشاهده شده" />
<Tab disabled={tabState === 0} icon={<FileCopyIcon />} label="اطلاعات مورد مشاهده شده" />
</Tabs>
<DialogContent dividers sx={{ overflowY: "auto", maxHeight: "70vh" }}>
<Box sx={{ flex: 1 }}>
<TabPanel value={tabState} index={0}>
<GetItemsForm
setItemsList={setItemsList}
setValue={setValue}
watch={watch}
tabState={tabState}
setTabState={setTabState}
/>
<StyledForm onSubmit={handleSubmit(onSearchSubmit)}>
<SearchItemInfo
isSubmitting={isSubmitting}
specialFilter={specialFilter}
watch={watch}
errors={errors}
setValue={setValue}
register={register}
setTabState={setTabState}
setItemInfo={setItemInfo}
/>
</StyledForm>
</TabPanel>
<TabPanel value={tabState} index={1}>
<GetSubItemsForm
setSubItemsList={setSubItemsList}
setValue={setValue}
watch={watch}
tabState={tabState}
setTabState={setTabState}
<GetItemInfo
mutate={mutate}
setOpen={setOpen}
setSubmitCompleteForm={setSubmitCompleteForm}
itemInfo={itemInfo}
/>
</TabPanel>
</Box>
@@ -98,19 +143,21 @@ const GashtCreateContent = ({ mutate, setOpen }) => {
>
{tabState === 0 ? "بستن" : "مرحله قبل"}
</Button>
{tabState === 2 && (
{tabState === 1 && (
<Button
variant="contained"
size="large"
disabled={isSubmitting}
form={"CompleteItemForm"}
type={"submit"}
variant="contained"
color="primary"
size="large"
disabled={submitCompleteForm}
endIcon={<BeenhereIcon />}
>
{isSubmitting ? "در حال ثبت فعالیت" : "ثبت فعالیت"}
{submitCompleteForm ? "درحال ثبت فعالیت..." : "ثبت فعالیت"}
</Button>
)}
</DialogActions>
</StyledForm>
</>
);
};
export default GashtCreateContent;

View File

@@ -0,0 +1,31 @@
"use client";
import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material";
import React from "react";
import dynamic from "next/dynamic";
import MapLoading from "@/core/components/MapLayer/Loading";
import ShowLocationMarker from "@/core/components/ShowLocationMarker";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => <MapLoading />,
ssr: false,
});
const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng }) => {
return (
<>
<DialogContent>
<Box sx={{ width: "400px", height: "400px" }}>
<MapLayer>
<ShowLocationMarker start_lat={start_lat} start_lng={start_lng} />
</MapLayer>
</Box>
</DialogContent>
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
<Button onClick={() => setOpenLocationDialog(false)} variant="contained" color="error">
بستن
</Button>
</DialogActions>
</>
);
};
export default LocationFormContent;

View File

@@ -0,0 +1,36 @@
import React, { useState } from "react";
import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material";
import ExploreIcon from "@mui/icons-material/Explore";
import LocationFormContent from "./LocationFormContent";
const LocationForm = ({ start_lat, start_lng }) => {
const [openLocationDialog, setOpenLocationDialog] = useState(false);
return (
<>
<Tooltip title="موقعیت">
<IconButton color="primary" onClick={() => setOpenLocationDialog(true)}>
<ExploreIcon />
</IconButton>
</Tooltip>
<Dialog
open={openLocationDialog}
onClose={() => setOpenLocationDialog(false)}
PaperProps={{
sx: {
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
},
}}
dir="rtl"
maxWidth={"md"}
>
<DialogTitle>موقعیت</DialogTitle>
<LocationFormContent
start_lat={start_lat}
start_lng={start_lng}
setOpenLocationDialog={setOpenLocationDialog}
/>
</Dialog>
</>
);
};
export default LocationForm;

View File

@@ -0,0 +1,231 @@
import { Button, DialogActions, Grid, Stack } from "@mui/material";
import PreviousStatesInfo from "./PreviousStatesInfo";
import NumberField from "@/core/components/NumberField";
import { Controller, useForm } from "react-hook-form";
import CarCode from "@/core/components/CarCode";
import RahdarCode from "@/core/components/RahdarCode";
import ImageUpload from "@/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/RowActions/ImageUpload";
import MuiDatePicker from "@/core/components/MuiDatePicker";
import MuiTimePicker from "@/core/components/MuiTimePicker";
import MapInfoTwoMarker from "@/core/components/MapInfoTwoMarker";
import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
import { yupResolver } from "@hookform/resolvers/yup";
import StyledForm from "@/core/components/StyledForm";
import { array, mixed, number, object, string } from "yup";
import useRequest from "@/lib/hooks/useRequest";
import BeenhereIcon from "@mui/icons-material/Beenhere";
import React, { useEffect } from "react";
import { CREATE_ROAD_ITEMS } from "@/core/utils/routes";
const CompleteItem = ({ subItem, itemInfo, setSubmitCompleteForm, mutate, setOpen }) => {
const requestServer = useRequest({ notificationSuccess: true });
const defaultValues = {
item_id: itemInfo?.item_id || null,
sub_item_id: itemInfo?.sub_item_id || null,
amount: "",
activity_time: "",
activity_date: "",
before_image: null,
after_image: null,
cmms_machines: null,
rahdaran_id: null,
start_point: { lat: itemInfo?.start_lat || "", lng: itemInfo?.start_lon || "" },
end_point: { lat: itemInfo?.end_lat || "", lng: itemInfo?.end_lon || "" },
};
const validationSchema = object({
item_id: number().required("نوع آیتم را مشخص کنید!"),
sub_item_id: number().required("موضوع مشاهده شده را مشخص کنید!"),
amount: string().required("وارد کردن مقدار الزامیست!"),
cmms_machines: array().required("وارد کردن کد خودرو الزامیست!"),
rahdaran_id: array().required("وارد کردن کد راهداران الزامیست!").min(1, "حداقل یک کد راهدار باید وارد شود!"), // بررسی حداقل یک آیتم,
activity_time: string().required("لطفا زمان فعالیت را انتخاب کنید!"),
activity_date: string().required("لطفاً تاریخ شروع فعالیت را انتخاب کنید!"),
before_image: mixed()
.nullable()
.test("before-image-required", "لطفا عکس قبل از اقدام را بارگذاری کنید!", function (value) {
const { subItem } = this.options.context;
const needsImage = subItem?.needs_image === 1;
if (needsImage) {
return !!value;
}
return true;
}),
after_image: mixed()
.nullable()
.test("after-image-required", "لطفا عکس بعد از اقدام را بارگذاری کنید!", function (value) {
const { subItem } = this.options.context;
const needsImage = subItem?.needs_image === 1;
if (needsImage) {
return !!value;
}
return true;
}),
start_point: mixed()
.test("start-point-required", "لطفاً نقطه شروع را مشخص کنید!", function (value) {
return !!value; // چک می‌کند که مقدار موجود است
})
.required("لطفاً نقطه شروع را مشخص کنید!"),
end_point: mixed().test("end-point-required", "لطفاً نقطه پایان را مشخص کنید!", function (value) {
const { subItem } = this.options.context;
const needsEndPoint = subItem?.needs_end_point === 1;
if (needsEndPoint) {
return !!value; // چک می‌کند که مقدار موجود است
}
return true;
}),
});
const {
control,
watch,
getValues,
register,
handleSubmit,
setValue,
resetField,
formState: { errors, isSubmitting },
} = useForm({
defaultValues,
resolver: yupResolver(validationSchema),
mode: "onBlur",
context: { subItem },
});
useEffect(() => {
setSubmitCompleteForm(isSubmitting);
}, [isSubmitting]);
const onSubmit = async (data) => {
let endPoint;
let startPoint = `${data.start_point.lat},${data.start_point.lng}`;
if (subItem.needs_end_point === 1) {
endPoint = `${data.end_point.lat},${data.end_point.lng}`;
}
const formData = new FormData();
data.after_image !== null && formData.append("after_image", data.after_image);
data.before_image !== null && formData.append("before_image", data.before_image);
subItem.needs_end_point === 1 && formData.append("end_point", endPoint);
formData.append("start_point", startPoint);
formData.append("activity_time", data.activity_time);
formData.append("activity_date", data.activity_date);
formData.append("amount", data.amount);
formData.append("item_id", data.item_id);
formData.append("sub_item_id", data.sub_item_id);
data.cmms_machines.forEach((machine, index) => formData.append(`machines_id[${index}]`, machine.id));
data.rahdaran_id.forEach((rahdar, index) => formData.append(`rahdaran_id[${index}]`, rahdar.id));
await requestServer(CREATE_ROAD_ITEMS, "post", {
data: formData,
})
.then((response) => {
mutate();
setOpen(false);
})
.catch((error) => {});
};
return (
<StyledForm onSubmit={handleSubmit(onSubmit)} id={"CompleteItemForm"}>
<Stack spacing={2}>
<Stack>
<PreviousStatesInfo itemInfo={itemInfo} />
</Stack>
<Stack>
<NumberField
{...register("amount")}
label={`مقدار (${subItem.unit})`}
error={!!errors.amount}
value={watch("amount")}
type="text"
size={"small"}
unit={itemInfo.unit}
inputProps={{
inputMode: "number",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
if (!isNaN(event.target.value)) {
setValue("amount", event.target.value);
} else {
setValue("amount", watch("amount"));
}
}}
helperText={errors.amount ? errors.amount.message : null}
variant="outlined"
fullWidth
/>
</Stack>
<Stack>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<CarCode
carCode={field.value || []}
setCarCode={(value) => field.onChange(value || [])}
error={error}
multiple={true}
/>
);
}}
name={"cmms_machines"}
/>
</Stack>
<Stack>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<RahdarCode
rahdarsCode={field.value || []}
setRahdarsCode={(value) => field.onChange(value || [])}
error={error}
/>
);
}}
name={"rahdaran_id"}
/>
</Stack>
<Stack>
{subItem.needs_image ? (
<ImageUpload setValue={setValue} control={control} errors={errors} getValues={getValues} />
) : null}
</Stack>
<Stack>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<MuiDatePicker
name="activity_date"
error={errors.activity_date ? errors.activity_date.message : null}
value={watch("activity_date")}
placeholder={"تاریخ شروع فعالیت"}
setFieldValue={setValue}
helperText={errors.activity_date ? errors.activity_date.message : null}
/>
</Grid>
<Grid item xs={12} md={6}>
<MuiTimePicker
value={watch("activity_time")}
name="activity_time"
error={errors.activity_time ? errors.activity_time.message : null}
placeholder={"زمان فعالیت"}
setFieldValue={setValue}
helperText={errors.activity_time ? errors.activity_time.message : null}
/>
</Grid>
</Grid>
</Stack>
<Stack>
{subItem?.needs_end_point === 1 ? (
<MapInfoTwoMarker setValue={setValue} errors={errors} />
) : (
<MapInfoOneMarker setValue={setValue} errors={errors} />
)}
</Stack>
</Stack>
</StyledForm>
);
};
export default CompleteItem;

View File

@@ -0,0 +1,28 @@
import { LinearProgress, Typography } from "@mui/material";
import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems";
import CompleteItem from "./CompleteItem";
const GetItemInfo = ({ itemInfo, setSubmitCompleteForm, mutate, setOpen }) => {
const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetISubtems(itemInfo?.item_id);
const subItem = subItemsList.find((SubItem) => SubItem.sub_item === itemInfo?.sub_item_id);
return (
<>
{loadingSubItemsList ? (
<LinearProgress />
) : errorSubItemsList ? (
<Typography color={"error.main"} textAlign={"center"}>
خطا در دریافت اطلاعات!!!
</Typography>
) : (
<CompleteItem
setOpen={setOpen}
mutate={mutate}
setSubmitCompleteForm={setSubmitCompleteForm}
subItem={subItem}
itemInfo={itemInfo}
/>
)}
</>
);
};
export default GetItemInfo;

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
import React from "react";
import { Tooltip, IconButton } from "@mui/material";
import UndoIcon from "@mui/icons-material/Undo";
const SubItemForm = ({ row, setTabState, setItemInfo }) => {
return (
<>
<Tooltip title="انتخاب این مورد">
<IconButton
color="primary"
onClick={() => {
setTabState(1);
setItemInfo(row.original);
}}
>
<UndoIcon />
</IconButton>
</Tooltip>
</>
);
};
export default SubItemForm;

View File

@@ -0,0 +1,11 @@
import { Box } from "@mui/material";
import SubItemForm from "./SubItemForm";
const RowActions = ({ row, mutate, setTabState, setItemInfo }) => {
return (
<Box sx={{ display: "flex", gap: 1 }}>
<SubItemForm row={row} setTabState={setTabState} setItemInfo={setItemInfo} />
</Box>
);
};
export default RowActions;

View File

@@ -0,0 +1,42 @@
import { LinearProgress, Typography } from "@mui/material";
import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems";
import React from "react";
import SearchItems from "./SearchItems";
import TableInfo from "./TableInfo";
const SearchItemInfo = ({
watch,
errors,
setValue,
register,
specialFilter,
setTabState,
setItemInfo,
isSubmitting,
}) => {
const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems();
return (
<>
{loadingItemsList ? (
<LinearProgress />
) : errorItemsList ? (
<Typography textAlign={"center"} color={"error.main"}>
خطا در دریافت اطلاعات!!!
</Typography>
) : (
<>
<SearchItems
isSubmitting={isSubmitting}
register={register}
setValue={setValue}
errors={errors}
watch={watch}
itemsList={itemsList}
/>
<TableInfo specialFilter={specialFilter} setTabState={setTabState} setItemInfo={setItemInfo} />
</>
)}
</>
);
};
export default SearchItemInfo;

View File

@@ -0,0 +1,96 @@
import { Button, FormControl, FormHelperText, InputLabel, MenuItem, Select, Stack, TextField } from "@mui/material";
import MuiDatePicker from "@/core/components/MuiDatePicker";
import SearchIcon from "@mui/icons-material/Search";
import React from "react";
const SearchItems = ({ setValue, itemsList, watch, errors, register, isSubmitting }) => {
const disabledButton =
(!!watch("start_date") && !!watch("end_date")) || !!watch("item_id") || !!watch("road_patrol_id");
return (
<Stack display={"flex"} sx={{ pb: 2 }} direction={"row"} spacing={1} alignItems={"center"}>
<Stack sx={{ flex: 2 }}>
<MuiDatePicker
name="start_date"
error={errors.start_date ? errors.start_date.message : null}
value={watch("start_date")}
placeholder={"از تاریخ"}
setFieldValue={setValue}
helperText={errors.start_date ? errors.start_date.message : null}
/>
</Stack>
<Stack sx={{ flex: 2 }}>
<MuiDatePicker
name="end_date"
minDate={watch("start_date")}
error={errors.end_date ? errors.end_date.message : null}
value={watch("end_date")}
placeholder={"تا تاریخ"}
setFieldValue={setValue}
helperText={errors.end_date ? errors.end_date.message : null}
/>
</Stack>
<Stack sx={{ flex: 1.5 }}>
<FormControl error={!!errors.item_id} size="small" fullWidth>
<InputLabel id="demo-error-label">آیتم فعالیت</InputLabel>
<Select
labelId="demo-error-label"
id="item_id"
name="item_id"
value={watch("item_id") || ""}
label="آیتم فعالیت"
onChange={(event) => setValue("item_id", event.target.value)}
>
<MenuItem value={""}>حذف</MenuItem>
{itemsList.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.name}
</MenuItem>
))}
</Select>
{/*{errors.item_id ? (*/}
{/* <FormHelperText>*/}
{/* errors.item_id.message*/}
{/* </FormHelperText>*/}
{/*) : null}*/}
</FormControl>
</Stack>
<Stack sx={{ flex: 1 }}>
<TextField
{...register("road_patrol_id")}
label="کد یکتا گشت"
error={!!errors.road_patrol_id}
value={watch("road_patrol_id")}
type="tel"
size={"small"}
inputProps={{
inputMode: "number",
min: 0,
pattern: "[0-9]*",
}}
onChange={(event) => {
if (!isNaN(event.target.value)) {
setValue("road_patrol_id", event.target.value);
} else {
setValue("road_patrol_id", watch("road_patrol_id"));
}
}}
helperText={errors.road_patrol_id ? errors.road_patrol_id.message : null}
variant="outlined"
/>
</Stack>
<Stack>
<Button
variant="contained"
size="medium"
type={"submit"}
disabled={!disabledButton || isSubmitting}
endIcon={<SearchIcon />}
>
{isSubmitting ? "درحال دریافت اطلاعات" : "جستجو"}
</Button>
</Stack>
</Stack>
);
};
export default SearchItems;

View File

@@ -0,0 +1,115 @@
import { useMemo } from "react";
import { Box, Stack } from "@mui/material";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import RowActions from "./RowActions";
import { GET_OBSERVED_GASHT_LIST } from "@/core/utils/routes";
import LocationForm from "./LocationForm";
const TableInfo = ({ specialFilter, setTabState, setItemInfo }) => {
const columns = useMemo(
() => [
{
accessorKey: "road_patrol_id",
header: "کد یکتا گشت",
id: "road_patrol_id",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 50,
},
{
accessorKey: "priority_fa",
header: "اولویت",
id: "priority_fa",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 80,
},
{
accessorKey: "local_name",
header: "نام محلی",
id: "local_name",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "item_name",
header: "نام ایتم",
id: "item_name",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "sub_item_name",
header: "موضوع مشاهده شده",
id: "sub_item_name",
enableColumnFilter: false,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "location",
header: "موقعیت", // Location
id: "location",
enableColumnFilter: false,
enableSorting: false,
datatype: "array",
grow: false,
size: 50,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue, row }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<LocationForm start_lat={row.original.start_lat} start_lng={row.original.start_lon} />
</Stack>
);
},
},
],
[]
);
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
table_title={"لیست موارد مشاهده شده"}
columns={columns}
specialFilter={specialFilter}
table_url={GET_OBSERVED_GASHT_LIST}
page_name={"operator"}
table_name={"Observed_Gasht_List"}
enableRowActions
positionActionsColumn={"last"}
RowActions={(props) => (
<RowActions {...props} setTabState={setTabState} setItemInfo={setItemInfo} />
)}
/>
</Box>
</>
);
};
export default TableInfo;

View File

@@ -17,7 +17,11 @@ const ObservedGashtCreate = ({ mutate }) => {
return (
<>
{isMobile ? (
<IconButton aria-label="ثبت فعالیت" color="primary" onClick={handleOpen}>
<IconButton
aria-label="ثبت فعالیت برای موارد مشاهده شده در گشت راهداری"
color="primary"
onClick={handleOpen}
>
<AddRoadIcon sx={{ fontSize: "25px" }} />
</IconButton>
) : (

View File

@@ -193,7 +193,7 @@ const OperatorList = () => {
},
{
accessorKey: "cmms_machines",
header: "کد خودرو", // Car ID
header: "خودرو ها", // Car ID
id: "cmmsMachines__machine_code",
enableColumnFilter: true,
datatype: "text",
@@ -241,20 +241,17 @@ const OperatorList = () => {
},
},
Cell: ({ renderedCellValue }) => {
if (renderedCellValue) {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<RahdaranDialog rahdarLists={renderedCellValue} />
</Stack>
);
}
return <Typography variant="body2">بدون شخص</Typography>;
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<RahdaranDialog rahdarLists={renderedCellValue} />
</Stack>
);
},
},
{
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ ثبت", // Register Date
id: "created_at",
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ فعالیت", // Start Date
id: "activity_date_time",
enableColumnFilter: true,
datatype: "date",
filterMode: "equals",
@@ -263,9 +260,9 @@ const OperatorList = () => {
size: 100,
},
{
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ فعالیت", // Start Date
id: "activity_date_time",
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ ثبت", // Register Date
id: "created_at",
enableColumnFilter: true,
datatype: "date",
filterMode: "equals",
@@ -296,6 +293,7 @@ const OperatorList = () => {
header: "توضیحات کارشناس", // Description
id: "supervisor_description",
enableColumnFilter: false,
enableSorting: false,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
@@ -311,11 +309,14 @@ const OperatorList = () => {
},
},
Cell: ({ renderedCellValue }) => {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<DescriptionForm description={renderedCellValue} />
</Stack>
);
if (renderedCellValue) {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<DescriptionForm description={renderedCellValue} />
</Stack>
);
}
return <Typography variant="body2">بدون توضیحات</Typography>;
},
},
],

View File

@@ -37,7 +37,7 @@ const DescriptionForm = ({ description }) => {
</IconButton>
</DialogTitle>
<DialogContent>
<Card sx={{ padding: 2, boxShadow: 3, borderRadius: 2 }}>
<Card elevation={0}>
<CardContent>
<Typography variant="body2">{description || "هیچ توضیحی موجود نیست"} </Typography>
</CardContent>

View File

@@ -17,7 +17,7 @@ const EditFormContent = ({ setOpenEditDialog, onSubmit, defaultData }) => {
rahdaran_id: defaultData?.rahdaran_id || null,
};
const onSubmitBase = (data) => {
const onSubmitBase = async (data) => {
let result = { ...data };
if (result.before_image === null) {
delete result.before_image;
@@ -53,7 +53,7 @@ const EditFormContent = ({ setOpenEditDialog, onSubmit, defaultData }) => {
});
delete result.cmms_machines;
onSubmit({
await onSubmit({
result,
data: {
...data,

View File

@@ -159,7 +159,7 @@ const EditFormCreate = ({ defaultData, subItem, onSubmitBase, defaultValues, set
</Stack>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenEditDialog(false)} variant="outlined" color="primary" autoFocus>
<Button onClick={() => setOpenEditDialog(false)} variant="outlined" color="secondary" autoFocus>
بستن
</Button>
<Button

View File

@@ -25,7 +25,7 @@ const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_
</Box>
</DialogContent>
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
<Button onClick={() => setOpenLocationDialog(false)} variant="contained" color="error">
<Button onClick={() => setOpenLocationDialog(false)} variant="outlined" color="secondary">
بستن
</Button>
</DialogActions>

View File

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

View File

@@ -28,10 +28,10 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
</Stack>
</DialogContent>
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
<Button onClick={() => setOpenDeleteDialog(false)} variant="contained" color="error">
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
خیر !
</Button>
<Button onClick={handleClick} variant="contained" color="primary" disabled={submitting}>
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
{submitting ? "درحال ارسال اطلاعات..." : "بله اطمینان دارم"}
</Button>
</DialogActions>

View File

@@ -25,7 +25,7 @@ const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_
</Box>
</DialogContent>
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
<Button onClick={() => setOpenLocationDialog(false)} variant="contained" color="error">
<Button onClick={() => setOpenLocationDialog(false)} variant="outlined" color="secondary">
بستن
</Button>
</DialogActions>

View File

@@ -257,7 +257,7 @@ const SupervisorList = () => {
},
{
accessorKey: "cmms_machines",
header: "کد خودرو", // Car ID
header: "خودرو ها", // Car ID
id: "cmmsMachines__machine_code",
enableColumnFilter: true,
datatype: "text",
@@ -316,9 +316,9 @@ const SupervisorList = () => {
},
},
{
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ ثبت", // Register Date
id: "created_at",
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ فعالیت", // Start Date
id: "activity_date_time",
enableColumnFilter: true,
datatype: "date",
filterMode: "equals",
@@ -327,9 +327,9 @@ const SupervisorList = () => {
size: 100,
},
{
accessorFn: (row) => moment(row.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ فعالیت", // Start Date
id: "activity_date_time",
accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
header: "تاریخ ثبت", // Register Date
id: "created_at",
enableColumnFilter: true,
datatype: "date",
filterMode: "equals",

View File

@@ -7,6 +7,8 @@ import { GET_ROAD_PATROL_OPERATOR_LIST } from "@/core/utils/routes";
import moment from "jalali-moment";
import RowActions from "./RowActions";
import ReportForm from "./RowActions/ReportForm";
import MachinesCodeDialog from "./RowActions/MachinesCodeForm";
import RahdaranDialog from "./RowActions/RahdaranForm";
const OperatorList = () => {
const columns = useMemo(
@@ -23,31 +25,64 @@ const OperatorList = () => {
size: 100,
},
{
accessorKey: "operator_name",
header: "نام مامور",
id: "operator_name",
accessorKey: "cmms_machines",
header: "خودرو", // Car ID
id: "cmmsMachines__machine_code",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
filterMode: "contains",
enableSorting: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue }) => {
if (renderedCellValue) {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<MachinesCodeDialog machinesLists={renderedCellValue} />
</Stack>
);
}
return <Typography variant="body2">کد خودرویی وجود ندارد</Typography>;
},
},
{
accessorKey: "officer_phone_number",
header: "تلفن همراه",
id: "officer_phone_number",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
},
{
accessorKey: "car_id",
header: "کد خودرو",
id: "car_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
accessorKey: "rahdaran",
header: "راهداران",
id: "rahdaran",
enableColumnFilter: false,
enableSorting: false,
datatype: "array",
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue }) => {
if (renderedCellValue) {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<RahdaranDialog rahdarLists={renderedCellValue} />
</Stack>
);
}
return <Typography variant="body2">بدون شخص</Typography>;
},
},
{
accessorFn: (row) => moment(row.start_date).locale("fa").format("HH:mm | yyyy/MM/DD"),

View File

@@ -0,0 +1,55 @@
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
import ShowPlak from "@/core/components/ShowPlak";
const MachinesCodeContent = ({ machinesLists }) => {
return (
<>
<DialogContent>
<Paper
elevation={0}
sx={{
width: "100%",
overflow: "hidden",
"*::-webkit-scrollbar": {
width: "0.5em",
},
"*::-webkit-scrollbar-thumb": {
backgroundColor: "primary.main",
},
}}
>
<TableContainer sx={{ maxHeight: 600 }}>
<Table stickyHeader sx={{ minWidth: 800 }}>
<TableHead>
<TableRow>
<TableCell>کد ماشین</TableCell>
<TableCell>نام خودرو</TableCell>
<TableCell>پلاک</TableCell>
</TableRow>
</TableHead>
<TableBody>
{machinesLists.map((machine) => {
return (
<TableRow
key={machine.id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>{machine.machine_code}</TableCell>
<TableCell>{machine.car_name}</TableCell>
<TableCell>
{machine.plak_number ? (
<ShowPlak plak_number={machine.plak_number} />
) : null}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
</DialogContent>
</>
);
};
export default MachinesCodeContent;

View File

@@ -0,0 +1,43 @@
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
import DirectionsCarIcon from "@mui/icons-material/DirectionsCar";
import { useState } from "react";
import MachinesCodeContent from "./MachinesCodeContent";
const MachinesCodeDialog = ({ machinesLists }) => {
const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false);
return (
<>
<Tooltip title="کد خودرو">
<IconButton color="primary" onClick={() => setOpenMachinesCodeDialog(true)}>
<DirectionsCarIcon />
</IconButton>
</Tooltip>
<Dialog
fullWidth
open={openMachinesCodeDialog}
onClose={() => setOpenMachinesCodeDialog(false)}
PaperProps={{
sx: {
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
},
}}
dir="rtl"
maxWidth={"md"}
>
<DialogTitle sx={{ fontSize: "large" }}>کد خودرو</DialogTitle>
<MachinesCodeContent machinesLists={machinesLists} />
<DialogActions>
<Button
onClick={() => setOpenMachinesCodeDialog(false)}
variant="outlined"
color="secondary"
autoFocus
>
بستن
</Button>
</DialogActions>
</Dialog>
</>
);
};
export default MachinesCodeDialog;

View File

@@ -0,0 +1,48 @@
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
const RahdaranContent = ({ rahdarLists }) => {
return (
<>
<DialogContent>
<Paper
elevation={0}
sx={{
width: "100%",
overflow: "hidden",
"*::-webkit-scrollbar": {
width: "0.5em",
},
"*::-webkit-scrollbar-thumb": {
backgroundColor: "primary.main",
},
}}
>
<TableContainer sx={{ maxHeight: 600 }}>
<Table stickyHeader sx={{ minWidth: 800 }}>
<TableHead>
<TableRow>
<TableCell>کد راهدار</TableCell>
<TableCell>نام</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rahdarLists.map((rahdar) => {
return (
<TableRow
key={rahdar.id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>{rahdar.code}</TableCell>
<TableCell>{rahdar.name}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
</DialogContent>
</>
);
};
export default RahdaranContent;

View File

@@ -0,0 +1,38 @@
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
import GroupsIcon from "@mui/icons-material/Groups";
import { useState } from "react";
import RahdaranContent from "./RahdaranContent";
const RahdaranDialog = ({ rahdarLists }) => {
const [openRahdaranDialog, setOpenRahdaranDialog] = useState(false);
return (
<>
<Tooltip title="راهداران">
<IconButton color="primary" onClick={() => setOpenRahdaranDialog(true)}>
<GroupsIcon />
</IconButton>
</Tooltip>
<Dialog
fullWidth
open={openRahdaranDialog}
onClose={() => setOpenRahdaranDialog(false)}
PaperProps={{
sx: {
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
},
}}
dir="rtl"
maxWidth={"md"}
>
<DialogTitle sx={{ fontSize: "large" }}>لیست راهداران</DialogTitle>
<RahdaranContent rahdarLists={rahdarLists} />
<DialogActions>
<Button onClick={() => setOpenRahdaranDialog(false)} variant="outlined" color="secondary" autoFocus>
بستن
</Button>
</DialogActions>
</Dialog>
</>
);
};
export default RahdaranDialog;

View File

@@ -0,0 +1,55 @@
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
import ShowPlak from "@/core/components/ShowPlak";
const MachinesCodeContent = ({ machinesLists }) => {
return (
<>
<DialogContent>
<Paper
elevation={0}
sx={{
width: "100%",
overflow: "hidden",
"*::-webkit-scrollbar": {
width: "0.5em",
},
"*::-webkit-scrollbar-thumb": {
backgroundColor: "primary.main",
},
}}
>
<TableContainer sx={{ maxHeight: 600 }}>
<Table stickyHeader sx={{ minWidth: 800 }}>
<TableHead>
<TableRow>
<TableCell>کد ماشین</TableCell>
<TableCell>نام خودرو</TableCell>
<TableCell>پلاک</TableCell>
</TableRow>
</TableHead>
<TableBody>
{machinesLists.map((machine) => {
return (
<TableRow
key={machine.id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>{machine.machine_code}</TableCell>
<TableCell>{machine.car_name}</TableCell>
<TableCell>
{machine.plak_number ? (
<ShowPlak plak_number={machine.plak_number} />
) : null}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
</DialogContent>
</>
);
};
export default MachinesCodeContent;

View File

@@ -0,0 +1,43 @@
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
import DirectionsCarIcon from "@mui/icons-material/DirectionsCar";
import { useState } from "react";
import MachinesCodeContent from "./MachinesCodeContent";
const MachinesCodeDialog = ({ machinesLists }) => {
const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false);
return (
<>
<Tooltip title="کد خودرو">
<IconButton color="primary" onClick={() => setOpenMachinesCodeDialog(true)}>
<DirectionsCarIcon />
</IconButton>
</Tooltip>
<Dialog
fullWidth
open={openMachinesCodeDialog}
onClose={() => setOpenMachinesCodeDialog(false)}
PaperProps={{
sx: {
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
},
}}
dir="rtl"
maxWidth={"md"}
>
<DialogTitle sx={{ fontSize: "large" }}>کد خودرو</DialogTitle>
<MachinesCodeContent machinesLists={machinesLists} />
<DialogActions>
<Button
onClick={() => setOpenMachinesCodeDialog(false)}
variant="outlined"
color="secondary"
autoFocus
>
بستن
</Button>
</DialogActions>
</Dialog>
</>
);
};
export default MachinesCodeDialog;

View File

@@ -0,0 +1,48 @@
import { DialogContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
const RahdaranContent = ({ rahdarLists }) => {
return (
<>
<DialogContent>
<Paper
elevation={0}
sx={{
width: "100%",
overflow: "hidden",
"*::-webkit-scrollbar": {
width: "0.5em",
},
"*::-webkit-scrollbar-thumb": {
backgroundColor: "primary.main",
},
}}
>
<TableContainer sx={{ maxHeight: 600 }}>
<Table stickyHeader sx={{ minWidth: 800 }}>
<TableHead>
<TableRow>
<TableCell>کد راهدار</TableCell>
<TableCell>نام</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rahdarLists.map((rahdar) => {
return (
<TableRow
key={rahdar.id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>{rahdar.code}</TableCell>
<TableCell>{rahdar.name}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
</DialogContent>
</>
);
};
export default RahdaranContent;

View File

@@ -0,0 +1,38 @@
import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material";
import GroupsIcon from "@mui/icons-material/Groups";
import { useState } from "react";
import RahdaranContent from "./RahdaranContent";
const RahdaranDialog = ({ rahdarLists }) => {
const [openRahdaranDialog, setOpenRahdaranDialog] = useState(false);
return (
<>
<Tooltip title="راهداران">
<IconButton color="primary" onClick={() => setOpenRahdaranDialog(true)}>
<GroupsIcon />
</IconButton>
</Tooltip>
<Dialog
fullWidth
open={openRahdaranDialog}
onClose={() => setOpenRahdaranDialog(false)}
PaperProps={{
sx: {
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
},
}}
dir="rtl"
maxWidth={"md"}
>
<DialogTitle sx={{ fontSize: "large" }}>لیست راهداران</DialogTitle>
<RahdaranContent rahdarLists={rahdarLists} />
<DialogActions>
<Button onClick={() => setOpenRahdaranDialog(false)} variant="outlined" color="secondary" autoFocus>
بستن
</Button>
</DialogActions>
</Dialog>
</>
);
};
export default RahdaranDialog;

View File

@@ -1,6 +1,6 @@
"use client";
import { useMemo } from "react";
import { Box, Stack } from "@mui/material";
import {Box, Stack, Typography} from "@mui/material";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import Toolbar from "./Toolbar";
import { GET_ROAD_PATROL_SUPERVISOR_LIST } from "@/core/utils/routes";
@@ -11,6 +11,8 @@ import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsT
import useEdaratLists from "@/lib/hooks/useEdaratLists";
import OfficerDescriptionForm from "./RowActions/OfficerDescription";
import ReportForm from "./RowActions/ReportForm";
import MachinesCodeDialog from "./RowActions/MachinesCodeForm";
import RahdaranDialog from "./RowActions/RahdaranForm";
const OperatorList = () => {
const columns = useMemo(
@@ -93,31 +95,64 @@ const OperatorList = () => {
Cell: ({ renderedCellValue, row }) => <>{row.original.edare_name}</>,
},
{
accessorKey: "operator_name",
header: "نام مامور",
id: "operator_name",
accessorKey: "cmms_machines",
header: "خودرو", // Car ID
id: "cmmsMachines__machine_code",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
filterMode: "contains",
enableSorting: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue }) => {
if (renderedCellValue) {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<MachinesCodeDialog machinesLists={renderedCellValue} />
</Stack>
);
}
return <Typography variant="body2">کد خودرویی وجود ندارد</Typography>;
},
},
{
accessorKey: "officer_phone_number",
header: "تلفن همراه",
id: "officer_phone_number",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
},
{
accessorKey: "car_id",
header: "کد خودرو",
id: "car_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
columnFilterModeOptions: ["equals", "contains"],
accessorKey: "rahdaran",
header: "راهداران",
id: "rahdaran",
enableColumnFilter: false,
enableSorting: false,
datatype: "array",
grow: false,
size: 100,
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
py: 0,
"&:first-of-type": {
borderLeft: "unset",
},
},
},
Cell: ({ renderedCellValue }) => {
if (renderedCellValue) {
return (
<Stack alignItems={"center"} justifyContent={"center"}>
<RahdaranDialog rahdarLists={renderedCellValue} />
</Stack>
);
}
return <Typography variant="body2">بدون شخص</Typography>;
},
},
{
accessorFn: (row) => moment(row.start_date).locale("fa").format("HH:mm | yyyy/MM/DD"),

View File

@@ -1,30 +1,36 @@
"use client";
import { useEffect, useState, memo } from "react";
import useRequest from "@/lib/hooks/useRequest";
import { useEffect } from "react";
import { ACTIVITY_LOG } from "@/core/utils/routes";
const ActivityCodeLog = ({ activity_code }) => {
const ActivityCodeLog = memo(({ activity_code }) => {
const requestServer = useRequest({ notificationShow: false });
const [hasLogged, setHasLogged] = useState(false);
useEffect(() => {
if (!activity_code) return;
if (!activity_code || hasLogged) return;
const fetchSubItems = async () => {
const controller = new AbortController();
const fetchActivityLog = async () => {
try {
await requestServer(ACTIVITY_LOG, "post", {
data: {
activityCode: activity_code,
},
data: { activityCode: activity_code },
signal: controller.signal,
});
} catch (error) {
console.error(error); // Always helpful to log the error for debugging.
}
setHasLogged(true);
} catch (error) {}
};
fetchSubItems();
}, [activity_code, requestServer]);
fetchActivityLog();
return null; // Ensure the component still renders something.
};
return () => {
controller.abort();
};
}, [activity_code, hasLogged, requestServer]);
return null;
});
export default ActivityCodeLog;

View File

@@ -23,6 +23,7 @@ const DataTable_Main = (props) => {
table_title,
RowActions,
specific_data,
specialFilter,
} = props;
const flatColumns = flattenArrayOfObjects(columns, "columns");
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
@@ -33,29 +34,34 @@ const DataTable_Main = (props) => {
};
const fetchUrl = useMemo(() => {
const params = new URLSearchParams();
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
params.set("size", pagination.pageSize);
const isValueEmpty = (value) => {
if (Array.isArray(value)) {
return value.length === 0 || value.every((v) => v === "");
}
return value === "" || value === null || value === undefined;
};
if (specialFilter) {
const filteredSpecialFilterData = Object.values(specialFilter).filter(
(filter) => !isValueEmpty(filter.value)
);
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
} else {
const filteredFilterData = Object.values(filterData)
.filter((filter) => !isValueEmpty(filter.value))
.map(({ filterMode, id, ...rest }) => ({
...rest,
id: id.replace(/__/g, "."),
fn: filterMode,
}));
const filteredFilterData = Object.values(filterData)
.filter((filter) => !isValueEmpty(filter.value))
.map(({ filterMode, id, ...rest }) => ({
...rest,
id: id.replace(/__/g, "."),
fn: filterMode,
}));
const params = new URLSearchParams();
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
params.set("size", pagination.pageSize);
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
}
params.set("sorting", JSON.stringify(sortData));
return `${table_url}?${params}`;
}, [table_url, filterData, pagination, columns, sortData]);
}, [table_url, filterData, pagination, columns, sortData, specialFilter]);
const fetcher = async (url) => {
try {
const response = await request(url);
@@ -143,5 +149,4 @@ const DataTable_Main = (props) => {
/>
);
};
export default DataTable_Main;

View File

@@ -23,8 +23,8 @@ const createCustomIcon = (size, iconUrl, labelText, color) => {
</div>
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
</div>`,
iconSize: [100, 50], // Adjust icon size to fit your content
iconAnchor: [50, 25], // Adjust to position the marker correctly
iconSize: [50, 50],
iconAnchor: [25, 50],
});
}

View File

@@ -23,8 +23,8 @@ const createCustomIcon = (size, iconUrl, labelText, color) => {
</div>
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
</div>`,
iconSize: [100, 50], // Adjust icon size to fit your content
iconAnchor: [50, 25], // Adjust to position the marker correctly
iconSize: [50, 50],
iconAnchor: [25, 50],
});
}
return L.icon({
@@ -111,6 +111,7 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
setStartPosition({ lat: center.lat, lng: center.lng });
setIsStartLocked(true);
setStartIconColor("#1CAC66");
map.panBy([25, 25])
}
},
}}
@@ -182,6 +183,13 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
setEndPosition({ lat: center.lat, lng: center.lng });
setIsEndLocked(true);
setEndIconColor("#D13131");
map.fitBounds(
[
startPosition,
map.getCenter(),
],
{ paddingTopLeft: [16, 16], paddingBottomRight: [16, 16] }
);
}
},
}}

View File

@@ -59,9 +59,11 @@ function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperTex
},
}}
/>
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
{helperText ? helperText : ""}
</FormHelperText>
{helperText ? (
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
{helperText}
</FormHelperText>
) : null}
</LocalizationProvider>
</Box>
);

View File

@@ -64,9 +64,11 @@ function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperTex
},
}}
/>
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
{helperText ? helperText : ""}
</FormHelperText>
{helperText ? (
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
{helperText}
</FormHelperText>
) : null}
</LocalizationProvider>
</Box>
);

View File

@@ -2,16 +2,24 @@
import { Box, Typography } from "@mui/material";
import { usePermissions } from "@/lib/hooks/usePermissions";
import { useEffect, useState } from "react";
function WithPermission({ children, permission_name }) {
const { data, error, isLoading } = usePermissions();
const [cachedData, setCachedData] = useState(null);
if (error || isLoading || !data || !permission_name) {
useEffect(() => {
if (data) {
setCachedData(data);
}
}, [data]);
if (error || isLoading || !cachedData || !permission_name) {
return null;
}
const hasPermission =
permission_name.includes("all") || permission_name.some((permission) => data.includes(permission));
permission_name.includes("all") || permission_name.some((permission) => cachedData.includes(permission));
if (!hasPermission) {
return (

View File

@@ -54,6 +54,7 @@ export const DELETE_BY_SUPERVISOR = api + "/api/v3/road_items/delete";
export const RESTORE_BY_SUPERVISOR = api + "/api/v3/road_items/restore";
export const GET_CAR_LIST_SEARCH = api + "/api/v3/cmms_machines/search";
export const GET_RAHDARANS_LIST_SEARCH = api + "/api/v3/rahdaran/search";
export const GET_OBSERVED_GASHT_LIST = api + "/api/v3/observed_items/filter";
// activity code log
export const ACTIVITY_LOG = api + "/api/v3/profile/add_activity";

View File

@@ -1,5 +1,6 @@
"use client";
import { createTheme } from "@mui/material";
import { grey } from '@mui/material/colors';
const theme = createTheme({
direction: "rtl",
@@ -16,6 +17,9 @@ const theme = createTheme({
main: "#015688",
contrastText: "#fff",
},
secondary: {
main: grey[700],
}
},
components: {
MuiBackdrop: {

View File

@@ -14,5 +14,5 @@ export const usePermissions = () => {
}
};
return useSWR(GET_PERMISSIONS_ROUTE, fetcher, { keepPreviousData: true });
return useSWR(GET_PERMISSIONS_ROUTE, fetcher, { keepPreviousData: true, dedupingInterval: 30000 });
};

View File

@@ -14,5 +14,5 @@ export const useSidebarBadge = () => {
}
};
return useSWR(GET_SIDEBAR_BADGE_ROUTE, fetcher, { keepPreviousData: true });
return useSWR(GET_SIDEBAR_BADGE_ROUTE, fetcher, { keepPreviousData: true, dedupingInterval: 10000 });
};