}
+ />
+ );
+};
+export default CityController;
diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CreateTollHouseContent.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CreateTollHouseContent.jsx
new file mode 100644
index 0000000..ef2e51d
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CreateTollHouseContent.jsx
@@ -0,0 +1,192 @@
+import StyledForm from "@/core/components/StyledForm";
+import { Box, Button, DialogActions, DialogContent, Tab, Tabs } from "@mui/material";
+import ExitToAppIcon from "@mui/icons-material/ExitToApp";
+import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
+import BeenhereIcon from "@mui/icons-material/Beenhere";
+import OtherHousesIcon from "@mui/icons-material/OtherHouses";
+import LocalShippingIcon from "@mui/icons-material/LocalShipping";
+import EditLocationAltIcon from "@mui/icons-material/EditLocationAlt";
+import React, { useState } from "react";
+import { useForm } from "react-hook-form";
+import { yupResolver } from "@hookform/resolvers/yup";
+import { mixed, number, object, string } from "yup";
+import TollHouseInfo from "./TollHouseInfo";
+import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
+import MachineInfo from "@/components/infrastructure/tollHouse/Form/CreateTollHouse/MachineInfo";
+import TollHouseLocation from "@/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseLocation";
+
+function TabPanel(props) {
+ const { children, value, index } = props;
+ return (
+
+ {value === index && {children}}
+
+ );
+}
+
+const validationSchema = object({
+ name: string().required("وارد کردن نام راهدارخانه الزامیست!"),
+ status: string().required("وارد کردن وضعیت الزامیست!"),
+ type: string().required("وارد کردن نوع الزامیست!"),
+ phone: string().required("وارد کردن تلفن راهدارخانه الزامیست!"),
+ team_num: string().required("وارد کردن تعداد اکیپ الزامیست!"),
+ staff_num: string().required("وارد کردن تعداد پرسنل الزامیست!"),
+ responsible_name: string().required("وارد کردن نام مسئول الزامیست!"),
+ responsible_mobile: string()
+ .required("وارد کردن تلفن مسئول الزامیست!")
+ .matches(/^09\d{9}$/, "شماره تلفن مسئول باید با ۰۹ شروع شده و ۱۱ رقم باشد!"),
+ successor_name: string().required("وارد کردن نام جانشین الزامیست!"),
+ successor_mobile: string()
+ .required("وارد کردن تلفن جانشین الزامیست!")
+ .matches(/^09\d{9}$/, "شماره تلفن جانشین باید با ۰۹ شروع شده و ۱۱ رقم باشد!"),
+ province_id: number().required("وارد کردن استان الزامیست!"),
+ city_id: number().required("وارد کردن شهرستان الزامیست!"),
+ machines_light: string().required("وارد کردن ماشین آلات سبک الزامیست!"),
+ machines_sheavy: string().required("وارد کردن ماشین آلات نیمه سنگین الزامیست!"),
+ machines_heavy: string().required("وارد کردن ماشین آلات سنگین الزامیست!"),
+ overview_files_1: mixed().nullable().required("لطفا نمای کلی از راهداری را بارگذاری کنید!"),
+ overview_files_2: mixed().nullable().required("لطفا آشیانه ماشین آلات را بارگذاری کنید!"),
+ overview_files_3: mixed().nullable().required("لطفا انباری شن و ماسه را بارگذاری کنید!"),
+ overview_files_4: mixed().nullable().required("لطفا محیط کاری را بارگذاری کنید!"),
+ overview_files_5: mixed().nullable().required("لطفا سایر تصاویر را بارگذاری کنید!"),
+ start_point: mixed()
+ .test("start-point-required", "لطفاً محل پروژه را مشخص کنید!", function (value) {
+ return !!value;
+ })
+ .required("لطفاً محل پروژه را مشخص کنید!"),
+});
+const CreateTollHouseContent = ({ setOpen, SubmitCreateTollHouse, defaultValues }) => {
+ const [tabState, setTabState] = useState(0);
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const handleChangeTab = (event, newValue) => {
+ setTabState(newValue);
+ };
+ const handleNext = async () => {
+ let fieldsToValidate = [];
+ if (tabState === 0) {
+ fieldsToValidate = [
+ "name",
+ "province_id",
+ "city_id",
+ "status",
+ "type",
+ "phone",
+ "team_num",
+ "staff_num",
+ "responsible_name",
+ "responsible_mobile",
+ "successor_name",
+ "successor_mobile",
+ ];
+ } else if (tabState === 1) {
+ fieldsToValidate = [
+ "machines_light",
+ "machines_sheavy",
+ "machines_heavy",
+ "overview_files_1",
+ "overview_files_2",
+ "overview_files_3",
+ "overview_files_4",
+ "overview_files_5",
+ ];
+ } else if (tabState === 2) {
+ fieldsToValidate = ["start_point"];
+ }
+ const isValid = await trigger(fieldsToValidate);
+ if (isValid) {
+ setTabState(tabState + 1);
+ }
+ };
+ const handlePrev = () => {
+ if (tabState === 0) {
+ handleClose();
+ } else {
+ setTabState(tabState - 1);
+ }
+ };
+ const {
+ control,
+ handleSubmit,
+ trigger,
+ setValue,
+ formState: { isSubmitting, errors },
+ } = useForm({
+ defaultValues,
+ resolver: yupResolver(validationSchema),
+ mode: "all",
+ });
+ const onSubmitBase = async (data) => {
+ await SubmitCreateTollHouse(data);
+ };
+ return (
+
+
+ } label="اطلاعات راهدارخانه" />
+ } label="ماشین آلات" />
+ } label="موقعیت" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ : }
+ >
+ {tabState === 0 ? "بستن" : "مرحله قبل"}
+
+ {tabState !== 2 && (
+ }
+ >
+ مرحله بعد
+
+ )}
+ {tabState === 2 && (
+ }
+ >
+ {isSubmitting ? "در حال ثبت راهدارخانه" : "ثبت راهدارخانه"}
+
+ )}
+
+
+ );
+};
+export default CreateTollHouseContent;
diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/ImageUpload.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/ImageUpload.jsx
new file mode 100644
index 0000000..5186a4b
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/ImageUpload.jsx
@@ -0,0 +1,77 @@
+import { FormControl, FormHelperText } from "@mui/material";
+import UploadSystem from "@/core/components/UploadSystem";
+import React, { useEffect, useState } from "react";
+
+const ImageUpload = ({ name, value, onChange, error, title }) => {
+ const [beforeImg, setBeforeImg] = useState(value ? value : null);
+ const [beforeFileType, setBeforeFileType] = useState(value ? "image/" : null);
+ const [beforeFileName, setBeforeFileName] = useState(null);
+ const [showBeforeImage, setShowBeforeImage] = useState(!value);
+
+ useEffect(() => {
+ if (value) {
+ setShowBeforeImage(false);
+ if (typeof value === "string") {
+ setBeforeImg(value);
+ setBeforeFileType("image/");
+ } else if (value instanceof File) {
+ setBeforeImg(URL.createObjectURL(value));
+ setBeforeFileType(value.type);
+ }
+ }
+ }, [value]);
+
+ const handleFileChange = (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);
+ onChange(uploadedFile);
+ setShowBeforeImage(false);
+ }
+ };
+
+ return (
+
+
+ {title}
+
+
+ {error ? error.message : null}
+
+ );
+};
+export default ImageUpload;
diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/LogesticController.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/LogesticController.jsx
new file mode 100644
index 0000000..513abbf
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/LogesticController.jsx
@@ -0,0 +1,42 @@
+import { Controller, useWatch } from "react-hook-form";
+import SelectBox from "@/core/components/SelectBox";
+import useProvinces from "@/lib/hooks/useProvince";
+import { Grid } from "@mui/material";
+import CityController from "./CityController";
+
+const LogesticController = ({ control }) => {
+ const { provinces, errorProvinces, loadingProvinces } = useProvinces();
+ const isProvince = useWatch({ control, name: "is_province" });
+ return (
+
+ {!isProvince ? (
+
+ {
+ return (
+
+ );
+ }}
+ />
+
+ ) : null}
+
+
+
+
+ );
+};
+export default LogesticController;
diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/MachineInfo.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/MachineInfo.jsx
new file mode 100644
index 0000000..44d3fea
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/MachineInfo.jsx
@@ -0,0 +1,199 @@
+import { Grid, Stack, TextField } from "@mui/material";
+import { Controller } from "react-hook-form";
+import PersianTextField from "@/core/components/PersianTextField";
+import ImageUpload from "@/components/dashboard/damages/operator/Actions/create/Forms/ImageUpload";
+
+const MachineInfo = ({ control }) => {
+ return (
+
+
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+
+ (
+
+ )}
+ />
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+
+ (
+
+ )}
+ />
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+
+
+
+ );
+};
+export default MachineInfo;
diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseInfo.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseInfo.jsx
new file mode 100644
index 0000000..d06a823
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseInfo.jsx
@@ -0,0 +1,311 @@
+import { Grid, Stack, TextField } from "@mui/material";
+import { Controller } from "react-hook-form";
+import SelectBox from "@/core/components/SelectBox";
+import useProvince from "@/lib/hooks/useProvince";
+import PersianTextField from "@/core/components/PersianTextField";
+import CityController from "./CityController";
+import LogesticController from "./LogesticController";
+
+const TollHouseInfo = ({ control }) => {
+ const { provinces, loadingProvinces, errorProvinces } = useProvince();
+ return (
+
+
+
+
+ (
+
+ )}
+ />
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+
+
+
+
+
+
+
+ {
+ return (
+
+ );
+ }}
+ />
+
+
+ {
+ return (
+
+ );
+ }}
+ />
+
+
+
+
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+
+ (
+ {
+ const inputValue = event.target.value;
+ if (isNaN(Number(inputValue))) {
+ return;
+ }
+ onChange(inputValue);
+ }}
+ />
+ )}
+ />
+
+
+
+
+ );
+};
+export default TollHouseInfo;
diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseLocation.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseLocation.jsx
new file mode 100644
index 0000000..98d36a7
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/TollHouseLocation.jsx
@@ -0,0 +1,20 @@
+import MapInfoOneMarker from "@/core/components/MapInfoOneMarker";
+import { Stack } from "@mui/material";
+import { useWatch } from "react-hook-form";
+
+const TollHouseLocation = ({ setValue, errors, control }) => {
+ const StartPoint = useWatch({ control, name: "start_point" });
+ return (
+ <>
+
+
+
+ >
+ );
+};
+export default TollHouseLocation;
diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/index.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/index.jsx
new file mode 100644
index 0000000..621f4e6
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/index.jsx
@@ -0,0 +1,140 @@
+import { Button, Dialog, IconButton, useMediaQuery } from "@mui/material";
+import { useState } from "react";
+import CreateTollHouseContent from "./CreateTollHouseContent";
+import { AddCircle } from "@mui/icons-material";
+import { useTheme } from "@emotion/react";
+import { CREATE_TOLL_HOUSE } from "@/core/utils/routes";
+import useRequest from "@/lib/hooks/useRequest";
+import CloseIcon from "@mui/icons-material/Close";
+import { useAuth } from "@/lib/contexts/auth";
+import { usePermissions } from "@/lib/hooks/usePermissions";
+
+const CreateTollHouse = ({ mutate }) => {
+ const requestServer = useRequest({ notificationSuccess: true });
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
+ const [open, setOpen] = useState(false);
+ const { user } = useAuth();
+ const { data: userPermissions } = usePermissions();
+ const hasCreateProvincePermission = userPermissions.some((item) => ["add-tollhouse"].includes(item));
+ const handleOpen = () => {
+ setOpen(true);
+ };
+ const SubmitCreateTollHouse = async (result) => {
+ const fields = [
+ "province_id",
+ "city_id",
+ "name",
+ "status",
+ "type",
+ "team_num",
+ "staff_num",
+ "phone",
+ "responsible_name",
+ "responsible_mobile",
+ "successor_name",
+ "successor_mobile",
+ "machines_light",
+ "machines_sheavy",
+ "machines_heavy",
+ "overview_files_1",
+ "overview_files_2",
+ "overview_files_3",
+ "overview_files_4",
+ "overview_files_5",
+ ];
+ const formData = new FormData();
+ fields.forEach((field) => {
+ if (result[field] !== undefined && result[field] !== null) {
+ formData.append(field, result[field]);
+ }
+ });
+ formData.append("lat", result.start_point.lat);
+ formData.append("lng", result.start_point.lng);
+ await requestServer(`${CREATE_TOLL_HOUSE}`, "post", {
+ data: formData,
+ })
+ .then(() => {
+ mutate();
+ setOpen(false);
+ })
+ .catch(() => {});
+ };
+ const defaultValues = {
+ name: "",
+ is_province: !hasCreateProvincePermission,
+ province_id: !hasCreateProvincePermission ? user.province_id : null,
+ city_id: null,
+ status: "",
+ type: "",
+ phone: "",
+ team_num: "",
+ staff_num: "",
+ responsible_name: "",
+ responsible_mobile: "",
+ successor_name: "",
+ successor_mobile: "",
+ machines_light: "",
+ machines_sheavy: "",
+ machines_heavy: "",
+ overview_files_1: null,
+ overview_files_2: null,
+ overview_files_3: null,
+ overview_files_4: null,
+ overview_files_5: null,
+ start_point: "",
+ };
+ return (
+ <>
+ {isMobile ? (
+
+
+
+ ) : (
+ }
+ onClick={handleOpen}
+ >
+ راهدارخانه جدید
+
+ )}
+
+ >
+ );
+};
+export default CreateTollHouse;
diff --git a/src/components/infrastructure/tollHouse/Form/Edit/EditController.jsx b/src/components/infrastructure/tollHouse/Form/Edit/EditController.jsx
new file mode 100644
index 0000000..774e5cb
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/Edit/EditController.jsx
@@ -0,0 +1,107 @@
+import useRequest from "@/lib/hooks/useRequest";
+import { useEffect, useState } from "react";
+import { GET_TOLL_HOUSE_DETAILS, UPDATE_TOLL_HOUSE_ITEM } from "@/core/utils/routes";
+import DialogLoading from "@/core/components/DialogLoading";
+import CreateTollHouseContent from "../CreateTollHouse/CreateTollHouseContent";
+import { usePermissions } from "@/lib/hooks/usePermissions";
+
+const EditController = ({ rowId, mutate, setOpenEditDialog }) => {
+ const { data: userPermissions } = usePermissions();
+ const hasCreateProvincePermission = userPermissions.some((item) => ["add-receipt"].includes(item));
+ const requestServer = useRequest();
+ const [tollHouseItemDetails, setTollHouseItemDetails] = useState(null);
+ const [tollHouseItemDetailsLoading, setTollHouseItemDetailsLoading] = useState(false);
+
+ useEffect(() => {
+ setTollHouseItemDetailsLoading(true);
+ requestServer(`${GET_TOLL_HOUSE_DETAILS}/${rowId}`, "get")
+ .then((response) => {
+ setTollHouseItemDetails(response.data.data);
+ setTollHouseItemDetailsLoading(false);
+ })
+ .catch((e) => {
+ setTollHouseItemDetailsLoading(false);
+ });
+ }, [rowId]);
+
+ const defaultData = {
+ phone: tollHouseItemDetails?.phone || "",
+ is_province: !hasCreateProvincePermission,
+ province_id: tollHouseItemDetails?.province_id || null,
+ city_id: tollHouseItemDetails?.city_id || null,
+ start_point: { lat: tollHouseItemDetails?.lat || "", lng: tollHouseItemDetails?.lng || "" },
+ name: tollHouseItemDetails?.name || "",
+ status: tollHouseItemDetails?.status || "",
+ type: tollHouseItemDetails?.type || "",
+ team_num: tollHouseItemDetails?.team_num || "",
+ staff_num: tollHouseItemDetails?.staff_num || "",
+ responsible_name: tollHouseItemDetails?.responsible_name || "",
+ responsible_mobile: tollHouseItemDetails?.responsible_mobile || "",
+ successor_name: tollHouseItemDetails?.successor_name || "",
+ successor_mobile: tollHouseItemDetails?.successor_mobile || "",
+ machines_light: tollHouseItemDetails?.machines_light || "",
+ machines_sheavy: tollHouseItemDetails?.machines_sheavy || "",
+ machines_heavy: tollHouseItemDetails?.machines_heavy || "",
+ overview_files_1: tollHouseItemDetails?.files[0]?.path || null,
+ overview_files_2: tollHouseItemDetails?.files[1]?.path || null,
+ overview_files_3: tollHouseItemDetails?.files[2]?.path || null,
+ overview_files_4: tollHouseItemDetails?.files[3]?.path || null,
+ overview_files_5: tollHouseItemDetails?.files[4]?.path || null,
+ };
+ const HandleSubmit = async (result) => {
+ const fields = [
+ "province_id",
+ "city_id",
+ "name",
+ "status",
+ "type",
+ "team_num",
+ "staff_num",
+ "phone",
+ "responsible_name",
+ "responsible_mobile",
+ "successor_name",
+ "successor_mobile",
+ "machines_light",
+ "machines_sheavy",
+ "machines_heavy",
+ ];
+ const formData = new FormData();
+ fields.forEach((field) => {
+ if (result[field] !== undefined && result[field] !== null) {
+ formData.append(field, result[field]);
+ }
+ });
+ for (let i = 1; i <= 5; i++) {
+ if (result[`overview_files_${i}`] !== defaultData[`overview_files_${i}`]) {
+ formData.append(`overview_files_${i}`, result[`overview_files_${i}`]);
+ }
+ }
+ formData.append("lat", result.start_point.lat);
+ formData.append("lng", result.start_point.lng);
+
+ await requestServer(`${UPDATE_TOLL_HOUSE_ITEM}/${rowId}`, "post", {
+ data: formData,
+ notificationSuccess: true,
+ })
+ .then(() => {
+ mutate();
+ setOpenEditDialog(false);
+ })
+ .catch(() => {});
+ };
+ return (
+ <>
+ {tollHouseItemDetailsLoading ? (
+
+ ) : (
+
+ )}
+ >
+ );
+};
+export default EditController;
diff --git a/src/components/infrastructure/tollHouse/Form/Edit/index.jsx b/src/components/infrastructure/tollHouse/Form/Edit/index.jsx
new file mode 100644
index 0000000..53be78c
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Form/Edit/index.jsx
@@ -0,0 +1,59 @@
+import { Dialog, IconButton, Tooltip } from "@mui/material";
+import { useState } from "react";
+import BorderColorIcon from "@mui/icons-material/BorderColor";
+import EditController from "./EditController";
+import CloseIcon from "@mui/icons-material/Close";
+
+const EditForm = ({ row, mutate, rowId }) => {
+ const [openEditDialog, setOpenEditDialog] = useState(false);
+
+ return (
+ <>
+
+ {
+ setOpenEditDialog(true);
+ }}
+ >
+
+
+
+
+ >
+ );
+};
+export default EditForm;
diff --git a/src/components/infrastructure/tollHouse/RowActions/DeleteDialog/DeleteContent.jsx b/src/components/infrastructure/tollHouse/RowActions/DeleteDialog/DeleteContent.jsx
new file mode 100644
index 0000000..3f3095a
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/DeleteDialog/DeleteContent.jsx
@@ -0,0 +1,39 @@
+import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
+import React, { useState } from "react";
+import useRequest from "@/lib/hooks/useRequest";
+import { DELETE_TOLL_HOUSE_ITEM } from "@/core/utils/routes";
+
+const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
+ const [submitting, setSubmitting] = useState(false);
+ const requestServer = useRequest({ notificationSuccess: true });
+ const handleClick = () => {
+ setSubmitting(true);
+ requestServer(`${DELETE_TOLL_HOUSE_ITEM}/${rowId}`, "delete")
+ .then(() => {
+ mutate();
+ setOpenDeleteDialog(false);
+ setSubmitting(false);
+ })
+ .catch(() => {
+ setSubmitting(false);
+ });
+ };
+ return (
+ <>
+
+
+ آیا از حذف راهدارخانه اطمینان دارید؟
+
+
+
+
+
+
+ >
+ );
+};
+export default DeleteContent;
diff --git a/src/components/infrastructure/tollHouse/RowActions/DeleteDialog/index.jsx b/src/components/infrastructure/tollHouse/RowActions/DeleteDialog/index.jsx
new file mode 100644
index 0000000..54278bd
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/DeleteDialog/index.jsx
@@ -0,0 +1,34 @@
+import DeleteIcon from "@mui/icons-material/Delete";
+import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import { useState } from "react";
+import DeleteContent from "./DeleteContent";
+const DeleteDialog = ({ rowId, mutate }) => {
+ const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenDeleteDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default DeleteDialog;
diff --git a/src/components/infrastructure/tollHouse/RowActions/ImageForm/ImageFormContent.jsx b/src/components/infrastructure/tollHouse/RowActions/ImageForm/ImageFormContent.jsx
new file mode 100644
index 0000000..c8f22d0
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/ImageForm/ImageFormContent.jsx
@@ -0,0 +1,45 @@
+import { Box, Stack, Typography, useTheme } from "@mui/material";
+import Image from "next/image";
+
+const ImageFormContent = ({ image, title }) => {
+ const theme = useTheme();
+ return (
+
+
+ {title}
+
+
+
+
+
+ );
+};
+export default ImageFormContent;
diff --git a/src/components/infrastructure/tollHouse/RowActions/ImageForm/ImagesContent.jsx b/src/components/infrastructure/tollHouse/RowActions/ImageForm/ImagesContent.jsx
new file mode 100644
index 0000000..4b66734
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/ImageForm/ImagesContent.jsx
@@ -0,0 +1,38 @@
+import { useEffect, useState } from "react";
+import ImageFormContent from "./ImageFormContent";
+import DialogLoading from "@/core/components/DialogLoading";
+import useRequest from "@/lib/hooks/useRequest";
+import { GET_TOLL_HOUSE_IMAGES } from "@/core/utils/routes";
+import { Typography } from "@mui/material";
+
+const ImagesContent = ({ rowId }) => {
+ const requestServer = useRequest();
+ const [tollHouseImages, setTollHouseImages] = useState(null);
+ const [tollHouseImagesLoading, setTollHouseImagesLoading] = useState(false);
+ useEffect(() => {
+ setTollHouseImagesLoading(true);
+ requestServer(`${GET_TOLL_HOUSE_IMAGES}/${rowId}`, "get")
+ .then((response) => {
+ setTollHouseImages(response.data.data);
+ setTollHouseImagesLoading(false);
+ })
+ .catch((e) => {
+ setTollHouseImagesLoading(false);
+ });
+ }, [rowId]);
+
+ return (
+ <>
+ {tollHouseImagesLoading ? (
+
+ ) : tollHouseImages ? (
+ tollHouseImages.map((image, index) => {
+ return ;
+ })
+ ) : (
+ تصویری در سامانه یافت نشد
+ )}
+ >
+ );
+};
+export default ImagesContent;
diff --git a/src/components/infrastructure/tollHouse/RowActions/ImageForm/index.jsx b/src/components/infrastructure/tollHouse/RowActions/ImageForm/index.jsx
new file mode 100644
index 0000000..6ac8d25
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/ImageForm/index.jsx
@@ -0,0 +1,43 @@
+import PhotoLibraryIcon from "@mui/icons-material/PhotoLibrary";
+import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tooltip } from "@mui/material";
+import { useEffect, useState } from "react";
+import ImagesContent from "./ImagesContent";
+import useRequest from "@/lib/hooks/useRequest";
+import { GET_TOLL_HOUSE_IMAGES } from "@/core/utils/routes";
+import DialogLoading from "@/core/components/DialogLoading";
+
+const ImageDialog = ({ rowId }) => {
+ const [openImageDialog, setOpenImageDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenImageDialog(true)}>
+
+
+
+
+ >
+ );
+};
+
+export default ImageDialog;
diff --git a/src/components/infrastructure/tollHouse/RowActions/LocationForm/LocationFormContent.jsx b/src/components/infrastructure/tollHouse/RowActions/LocationForm/LocationFormContent.jsx
new file mode 100644
index 0000000..14b3db3
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/LocationForm/LocationFormContent.jsx
@@ -0,0 +1,35 @@
+"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: () => ,
+ ssr: false,
+});
+
+const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_lat, end_lng }) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+export default LocationFormContent;
diff --git a/src/components/infrastructure/tollHouse/RowActions/LocationForm/index.jsx b/src/components/infrastructure/tollHouse/RowActions/LocationForm/index.jsx
new file mode 100644
index 0000000..f578ca8
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/LocationForm/index.jsx
@@ -0,0 +1,38 @@
+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, end_lat, end_lng }) => {
+ const [openLocationDialog, setOpenLocationDialog] = useState(false);
+
+ return (
+ <>
+
+ setOpenLocationDialog(true)}>
+
+
+
+
+ >
+ );
+};
+export default LocationForm;
diff --git a/src/components/infrastructure/tollHouse/RowActions/index.jsx b/src/components/infrastructure/tollHouse/RowActions/index.jsx
new file mode 100644
index 0000000..8801bda
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/RowActions/index.jsx
@@ -0,0 +1,21 @@
+import { Box } from "@mui/material";
+import EditForm from "../Form/Edit";
+import DeleteDialog from "./DeleteDialog";
+import { usePermissions } from "@/lib/hooks/usePermissions";
+
+const RowActions = ({ row, mutate }) => {
+ const { data: userPermissions } = usePermissions();
+ const hasEditPermission = userPermissions.some((item) =>
+ ["edit-tollhouse", "edit-tollhouse-province"].includes(item)
+ );
+ const hasDeletePermission = userPermissions.some((item) =>
+ ["delete-tollhouse", "delete-tollhouse-province"].includes(item)
+ );
+ return (
+
+ {hasEditPermission && }
+ {hasDeletePermission && }
+
+ );
+};
+export default RowActions;
diff --git a/src/components/infrastructure/tollHouse/TollHouseList.jsx b/src/components/infrastructure/tollHouse/TollHouseList.jsx
new file mode 100644
index 0000000..2b0a39c
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/TollHouseList.jsx
@@ -0,0 +1,412 @@
+"use client";
+import { useEffect, useMemo, useState } from "react";
+import { Box, Stack, Typography } from "@mui/material";
+import DataTableWithAuth from "@/core/components/DataTableWithAuth";
+import Toolbar from "./Toolbar";
+import { GET_TOLL_HOUSE_LIST } from "@/core/utils/routes";
+import moment from "jalali-moment";
+import RowActions from "./RowActions";
+import useProvinces from "@/lib/hooks/useProvince";
+import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
+import useEdaratLists from "@/lib/hooks/useEdaratLists";
+import { usePermissions } from "@/lib/hooks/usePermissions";
+import LocationForm from "./RowActions/LocationForm";
+import ImageDialog from "./RowActions/ImageForm";
+
+const statusOptions = [
+ { value: "", label: "همه وضعیت ها" },
+ { value: 0, label: "غیرفعال" },
+ { value: 1, label: "فعال" },
+];
+const typeOptions = [
+ { value: "", label: "همه نوع ها" },
+ { value: 0, label: "فصلی" },
+ { value: 1, label: "داعمی" },
+ { value: 2, label: "موقت" },
+];
+const TollHouseList = () => {
+ const { data: userPermissions } = usePermissions();
+ const hasProvincePermission = userPermissions.some((item) => ["add-tollhouse"].includes(item));
+
+ const columns = useMemo(() => {
+ return [
+ {
+ accessorKey: "id",
+ header: "کد یکتا", // Unique Code
+ id: "id",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ sortDescFirst: false,
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 100,
+ },
+
+ {
+ header: "اطلاعات راهدارخانه",
+ id: "tollHouseInfo",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "text",
+ grow: false,
+ size: 50,
+ columns: [
+ {
+ accessorKey: "name",
+ header: "نام راهدارخانه",
+ id: "name",
+ enableColumnFilter: true,
+ datatype: "text",
+ filterMode: "equals",
+ sortDescFirst: false,
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 100,
+ },
+ {
+ accessorKey: "province_id",
+ header: "استان",
+ id: "province_id",
+ enableColumnFilter: true,
+ datatype: "numeric",
+ filterMode: "equals",
+ grow: false,
+ size: 130,
+ ColumnSelectComponent: (props) => {
+ const { provinces, errorProvinces, loadingProvinces } = useProvinces();
+ const getColumnSelectOptions = useMemo(() => {
+ if (loadingProvinces) {
+ return [{ value: "loading", label: "در حال بارگذاری..." }];
+ }
+ if (errorProvinces) {
+ return [{ value: "error", label: "خطا در بارگذاری" }];
+ }
+ return [
+ { value: "", label: "کل کشور" },
+ ...provinces.map((province) => ({
+ value: province.id,
+ label: province.name_fa,
+ })),
+ ];
+ }, [provinces, errorProvinces, loadingProvinces]);
+ return (
+
+ );
+ },
+ Cell: ({ row }) => <>{row.original.province_name}>,
+ },
+ {
+ accessorKey: "city_id",
+ header: "شهرستان", // Office
+ id: "city_id",
+ enableColumnFilter: true,
+ datatype: "numeric",
+ filterMode: "equals",
+ dependencyId: "province_id",
+ grow: false,
+ size: 120,
+ ColumnSelectComponent: (props) => {
+ const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(
+ props.dependencyFieldValue.value
+ );
+ const [prevDependency, setPrevDependency] = useState(props.dependencyFieldValue.value);
+
+ const getColumnSelectOptions = useMemo(() => {
+ if (props.dependencyFieldValue.value === "") {
+ return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }];
+ }
+ if (loadingEdaratList) {
+ return [{ value: "loading", label: "در حال بارگذاری..." }];
+ }
+ if (errorEdaratList) {
+ return [{ value: "error", label: "خطا در بارگذاری" }];
+ }
+ return [
+ { value: "", label: "کل شهر ها" },
+ ...edaratList.map((edare) => ({
+ value: edare.id,
+ label: edare.name_fa,
+ })),
+ ];
+ }, [edaratList, loadingEdaratList, errorEdaratList]);
+ useEffect(() => {
+ if (prevDependency === props.dependencyFieldValue?.value) return;
+ props.handleChange({ ...props.filterParameters, value: "" });
+ setPrevDependency(props.dependencyFieldValue?.value);
+ }, [props.dependencyFieldValue?.value]);
+ return (
+
+ );
+ },
+ Cell: ({ row }) => <>{row.original.city_name}>,
+ },
+ {
+ accessorKey: "status",
+ header: "وضعیت",
+ id: "status",
+ enableColumnFilter: true,
+ datatype: "numeric",
+ filterMode: "equals",
+ grow: false,
+ size: 100,
+ columnSelectOption: () => {
+ return statusOptions.map((status) => ({
+ value: status.value,
+ label: status.label,
+ }));
+ },
+ Cell: ({ row }) => {
+ const status = statusOptions.find((option) => option.value == row.original.status);
+ return {status?.label || ""};
+ },
+ },
+ {
+ accessorKey: "type",
+ header: "نوع",
+ id: "type",
+ enableColumnFilter: true,
+ datatype: "numeric",
+ filterMode: "equals",
+ grow: false,
+ size: 100,
+ columnSelectOption: () => {
+ return typeOptions.map((type) => ({
+ value: type.value,
+ label: type.label,
+ }));
+ },
+ Cell: ({ row }) => {
+ return {row.original.type_fa};
+ },
+ },
+ {
+ accessorKey: "phone",
+ header: "تلفن راهدارخانه",
+ id: "phone",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 120,
+ },
+ {
+ accessorKey: "team_num",
+ header: "تعداد اکیپ",
+ id: "team_num",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 120,
+ },
+ {
+ accessorKey: "staff_num",
+ header: "تعداد پرسنل",
+ id: "staff_num",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 120,
+ },
+ {
+ accessorKey: "responsible_name",
+ header: "مسئول",
+ id: "responsible_name",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 120,
+ },
+ {
+ accessorKey: "responsible_mobile",
+ header: "شماره مسئول",
+ id: "responsible_mobile",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 120,
+ },
+ {
+ accessorKey: "successor_name",
+ header: "جانشین",
+ id: "successor_name",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 120,
+ },
+ {
+ accessorKey: "successor_mobile",
+ header: "شماره جانشین",
+ id: "successor_mobile",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 120,
+ },
+ ],
+ },
+ {
+ header: "ماشین آلات",
+ id: "machines",
+ enableColumnFilter: false,
+ enableSorting: false,
+ datatype: "text",
+ grow: false,
+ size: 50,
+ columns: [
+ {
+ accessorKey: "machines_light",
+ header: "ماشین آلات سبک",
+ id: "machines_light",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 100,
+ },
+ {
+ accessorKey: "machines_sheavy",
+ header: "ماشین آلات نیمه سنگین",
+ id: "machines_sheavy",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 100,
+ },
+ {
+ accessorKey: "machines_heavy",
+ header: "ماشین آلات سنگین",
+ id: "machines_heavy",
+ enableColumnFilter: false,
+ enableSorting: true,
+ datatype: "text",
+ filterMode: "equals",
+ columnFilterModeOptions: ["equals", "contains"],
+ grow: false,
+ size: 100,
+ },
+ {
+ header: "تصاویر",
+ id: "images",
+ 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, row }) => {
+ return (
+
+
+
+ );
+ },
+ },
+ ],
+ },
+ {
+ header: "موقعیت", // Location
+ id: "location",
+ 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, row }) => {
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
+ header: "تاریخ ثبت",
+ id: "created_at",
+ enableColumnFilter: true,
+ datatype: "date",
+ filterMode: "between",
+ grow: false,
+ size: 100,
+ },
+ ];
+ }, []);
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+export default TollHouseList;
diff --git a/src/components/infrastructure/tollHouse/Toolbar.jsx b/src/components/infrastructure/tollHouse/Toolbar.jsx
new file mode 100644
index 0000000..d8ada27
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/Toolbar.jsx
@@ -0,0 +1,10 @@
+import { Box } from "@mui/material";
+import CreateTollHouse from "./Form/CreateTollHouse";
+import { usePermissions } from "@/lib/hooks/usePermissions";
+
+const Toolbar = ({ table, filterData, mutate }) => {
+ const { data: userPermissions } = usePermissions();
+ const hasAddPermission = userPermissions.some((item) => ["add-tollhouse", "add-tollhouse-province"].includes(item));
+ return {hasAddPermission && };
+};
+export default Toolbar;
diff --git a/src/components/infrastructure/tollHouse/index.jsx b/src/components/infrastructure/tollHouse/index.jsx
new file mode 100644
index 0000000..c1d4172
--- /dev/null
+++ b/src/components/infrastructure/tollHouse/index.jsx
@@ -0,0 +1,14 @@
+"use client";
+import PageTitle from "@/core/components/PageTitle";
+import { Stack } from "@mui/material";
+import TollHouseList from "./TollHouseList";
+
+const TollHousePage = () => {
+ return (
+
+
+
+
+ );
+};
+export default TollHousePage;
diff --git a/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarListItems.jsx b/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarListItems.jsx
index fd8e97e..c61dbe3 100644
--- a/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarListItems.jsx
+++ b/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarListItems.jsx
@@ -35,7 +35,11 @@ const SidebarListItems = ({ menuItem, dispatch }) => {
{menuItem.badges && (
{menuItem.badges.map((badge, i) => (
-
+
))}
)}
diff --git a/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarSubitems.jsx b/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarSubitems.jsx
index 5a4f1e5..ba0cd5f 100644
--- a/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarSubitems.jsx
+++ b/src/components/layouts/dashboard/headerWithSidebar/Sidebar/SidebarSubitems.jsx
@@ -33,7 +33,14 @@ const SidebarSubitems = ({ subitem, dispatch }) => {
{subitem.badges && (
{subitem.badges.map((badge, i) => (
-
+
))}
)}
diff --git a/src/core/components/DataTable/Main.js b/src/core/components/DataTable/Main.js
index efad5a0..ef2d81a 100644
--- a/src/core/components/DataTable/Main.js
+++ b/src/core/components/DataTable/Main.js
@@ -113,7 +113,7 @@ const DataTable_Main = (props) => {
...initialStateProps,
},
state: {
- showProgressBars: isValidating,
+ showProgressBars: isValidating || props.loading,
columnVisibility: flattenHideData,
sorting: sortData,
pagination,
@@ -171,6 +171,8 @@ const DataTable_Main = (props) => {
page_name={page_name}
table_name={table_name}
special_data={props.data}
+ special_filter={specialFilter}
+ setFilterData={props.setFilterData}
/>
);
};
diff --git a/src/core/components/DataTable/filter/FilterBody.jsx b/src/core/components/DataTable/filter/FilterBody.jsx
index 461eb01..67b34fd 100644
--- a/src/core/components/DataTable/filter/FilterBody.jsx
+++ b/src/core/components/DataTable/filter/FilterBody.jsx
@@ -58,7 +58,7 @@ function FilterBody({ columns, drawerState, setDrawerState }) {
} = useForm({
defaultValues: filterData,
resolver: yupResolver(validationSchema),
- mode: "onBlur",
+ mode: "all",
});
const onSubmit = (data) => {
diff --git a/src/core/components/DataTable/filter/FilterCustom.jsx b/src/core/components/DataTable/filter/FilterCustom.jsx
new file mode 100644
index 0000000..f8d43f3
--- /dev/null
+++ b/src/core/components/DataTable/filter/FilterCustom.jsx
@@ -0,0 +1,32 @@
+import { useCallback, useState } from "react";
+import FilterButton from "./FilterButton";
+import { Box, Drawer } from "@mui/material";
+import FilterDrawer from "../../FilterDrawer";
+
+const FilterCustom = ({ filterData, setFilterData }) => {
+ const [open, setOpen] = useState(false);
+
+ const closeDrawer = useCallback(() => setOpen(false), []);
+
+ return (
+ <>
+
+ setOpen(false)}
+ sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
+ >
+
+ {open && (
+
+ )}
+
+
+ >
+ );
+};
+export default FilterCustom;
diff --git a/src/core/components/DataTable/table/Paper.js b/src/core/components/DataTable/table/Paper.js
index 14665a3..9ce08fc 100644
--- a/src/core/components/DataTable/table/Paper.js
+++ b/src/core/components/DataTable/table/Paper.js
@@ -14,6 +14,7 @@ const DataTable_Paper = ({
need_filter,
table_url,
table_title,
+ setFilterData,
...rest
}) => {
const {
@@ -86,6 +87,7 @@ const DataTable_Paper = ({
page_name={page_name}
table_name={table_name}
table_title={table_title}
+ setFilterData={setFilterData}
{...paperProps}
/>
))}
diff --git a/src/core/components/DataTable/toolbar/TopToolbar.js b/src/core/components/DataTable/toolbar/TopToolbar.js
index 2c322ff..ea0c6d0 100644
--- a/src/core/components/DataTable/toolbar/TopToolbar.js
+++ b/src/core/components/DataTable/toolbar/TopToolbar.js
@@ -6,6 +6,7 @@ import FilterColumn from "@/core/components/DataTable/filter";
import UpdateTable from "@/core/components/DataTable/update/UpdateTable";
import ResetStorage from "@/core/components/DataTable/reset/ResetStorage";
import HideColumn from "@/core/components/DataTable/hide";
+import FilterCustom from "../filter/FilterCustom";
const DataTable_TopToolbar = ({
mutate,
@@ -18,6 +19,8 @@ const DataTable_TopToolbar = ({
table_name,
table_title,
special_data,
+ special_filter,
+ setFilterData,
}) => {
const {
getState,
@@ -118,6 +121,9 @@ const DataTable_TopToolbar = ({
table_name={table_name}
/>
)}
+ {special_filter && setFilterData && (
+
+ )}
)}
diff --git a/src/core/components/FilterDrawer/FilterController.jsx b/src/core/components/FilterDrawer/FilterController.jsx
new file mode 100644
index 0000000..4a6a125
--- /dev/null
+++ b/src/core/components/FilterDrawer/FilterController.jsx
@@ -0,0 +1,25 @@
+import { Controller } from "react-hook-form";
+import FilterField from "./FilterField";
+
+const FilterController = ({ item, control, reset, errors }) => {
+ return (
+ {
+ return (
+ reset()}
+ errors={errors}
+ />
+ );
+ }}
+ />
+ );
+};
+export default FilterController;
diff --git a/src/core/components/FilterDrawer/FilterControllerWithDependency.jsx b/src/core/components/FilterDrawer/FilterControllerWithDependency.jsx
new file mode 100644
index 0000000..361941f
--- /dev/null
+++ b/src/core/components/FilterDrawer/FilterControllerWithDependency.jsx
@@ -0,0 +1,27 @@
+import { Controller, useWatch } from "react-hook-form";
+import FilterField from "./FilterField";
+
+const FilterControllerWithDependency = ({ item, control, reset, errors }) => {
+ const dependencyField = useWatch({ control, name: item.dependencyId });
+
+ return (
+ {
+ return (
+ reset()}
+ errors={errors}
+ />
+ );
+ }}
+ />
+ );
+};
+export default FilterControllerWithDependency;
diff --git a/src/core/components/FilterDrawer/FilterField.jsx b/src/core/components/FilterDrawer/FilterField.jsx
new file mode 100644
index 0000000..ed86df8
--- /dev/null
+++ b/src/core/components/FilterDrawer/FilterField.jsx
@@ -0,0 +1,103 @@
+import { useState } from "react";
+import CustomTextFieldRange from "./fieldsType/CustomTextFieldRange";
+import CustomSelect from "./fieldsType/CustomSelect";
+import CustomDatePicker from "../CustomDatePicker";
+import CustomDatePickerRange from "./fieldsType/CustomDate/CustomDatePickerRange";
+import CustomSelectMultiple from "./fieldsType/CustomSelectMultiple";
+import CustomTextField from "./fieldsType/CustomTextField";
+import FilterOptionList from "./FilterOptionList";
+
+const filterModeOptionFa = {
+ equals: "برابر",
+ notEquals: "نابرابر",
+ contains: "شامل",
+ lessThan: "کوچکتر",
+ greaterThan: "بزرگتر",
+ fuzzy: "فازی",
+ between: "مابین",
+};
+
+function FilterField({
+ item,
+ filterParameters,
+ handleChange,
+ handleBlur,
+ dependencyFieldValue,
+ setFieldValue,
+ resetForm,
+ errors,
+}) {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const defaultFilterTranslation = filterModeOptionFa[filterParameters.filterMode] || filterParameters.filterMode;
+
+ const handleOpenFilterBox = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const renderField = () => {
+ const commonProps = {
+ item,
+ filterParameters,
+ defaultFilterTranslation,
+ handleOpenFilterBox,
+ dependencyFieldValue,
+ setFieldValue,
+ handleChange,
+ handleBlur,
+ errors,
+ };
+
+ switch (filterParameters.datatype) {
+ case "numeric":
+ if (filterParameters.filterMode === "between") {
+ return ;
+ }
+ if (filterParameters.filterMode === "equals") {
+ return item.SelectComponent ? (
+
+ ) : (
+
+ );
+ }
+ break;
+ case "date":
+ if (filterParameters.filterMode === "equals") {
+ return ;
+ }
+ if (filterParameters.filterMode === "between") {
+ return ;
+ }
+ break;
+
+ case "array":
+ if (filterParameters.filterMode === "equals") {
+ return ;
+ }
+ break;
+
+ default:
+ return ;
+ }
+ };
+
+ return (
+ <>
+ {renderField()}
+ {Array.isArray(item.filterModeOptions) && (
+
+ )}
+ >
+ );
+}
+
+export default FilterField;
diff --git a/src/core/components/FilterDrawer/FilterOptionList.jsx b/src/core/components/FilterDrawer/FilterOptionList.jsx
new file mode 100644
index 0000000..1a797f6
--- /dev/null
+++ b/src/core/components/FilterDrawer/FilterOptionList.jsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { ListItem, Menu } from "@mui/material";
+import { useState } from "react";
+
+function FilterOptionList({
+ filterType,
+ filterOption,
+ filterParameters,
+ anchorEl,
+ filterModeOptionFa,
+ setAnchorEl,
+ handleChange,
+}) {
+ const [selectedFilter, setSelectedFilter] = useState(filterType);
+
+ const handleChangeItem = (event, index) => {
+ handleChange({
+ ...filterParameters,
+ value: filterOption[index] === "between" ? ["", ""] : "",
+ filterMode: filterOption[index],
+ });
+ setSelectedFilter(filterOption[index]);
+ setAnchorEl(null);
+ };
+
+ const handleCloseFilterBox = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+
+ );
+}
+
+export default FilterOptionList;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomDate/CustomDatePicker.jsx b/src/core/components/FilterDrawer/fieldsType/CustomDate/CustomDatePicker.jsx
new file mode 100644
index 0000000..e207cfb
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomDate/CustomDatePicker.jsx
@@ -0,0 +1,23 @@
+import React from "react";
+import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
+import { Typography } from "@mui/material";
+
+function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, handleChange }) {
+ return (
+ {
+ handleChange({ ...filterParameters, value: formattedDate });
+ }}
+ placeholder={column.header}
+ helperText={
+
+ نوع فیلتر: {defaultFilterTranslation} (تاریخ)
+
+ }
+ />
+ );
+}
+
+export default CustomDatePicker;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomDate/CustomDatePickerRange.jsx b/src/core/components/FilterDrawer/fieldsType/CustomDate/CustomDatePickerRange.jsx
new file mode 100644
index 0000000..58c597f
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomDate/CustomDatePickerRange.jsx
@@ -0,0 +1,41 @@
+"use client";
+
+import React from "react";
+import { Box, Typography } from "@mui/material";
+import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
+
+function CustomDatePickerRange({ item, filterParameters, defaultFilterTranslation, handleChange, errors }) {
+ return (
+
+ {
+ handleChange({ ...filterParameters, value: [formattedDate, filterParameters.value[1]] });
+ }}
+ maxDate={filterParameters.value[1]}
+ placeholder={`از تاریخ`}
+ helperText={
+
+ نوع فیلتر: {defaultFilterTranslation} (تاریخ)
+
+ }
+ />
+ {
+ handleChange({ ...filterParameters, value: [filterParameters.value[0], formattedDate] });
+ }}
+ minDate={filterParameters.value[0]}
+ placeholder={`تا تاریخ`}
+ helperText={errors?.[`${item.id}`]?.value ? errors?.[`${item.id}`]?.value.message : null}
+ error={Boolean(errors?.[`${item.id}`]?.value)}
+ />
+
+ );
+}
+
+export default CustomDatePickerRange;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomDate/MuiDatePicker.jsx b/src/core/components/FilterDrawer/fieldsType/CustomDate/MuiDatePicker.jsx
new file mode 100644
index 0000000..d2d9df0
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomDate/MuiDatePicker.jsx
@@ -0,0 +1,72 @@
+import React from "react";
+import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
+import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
+import { faIR } from "@mui/x-date-pickers/locales";
+import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
+import ClearIcon from "@mui/icons-material/Clear";
+import moment from "jalali-moment";
+
+function MuiDatePicker({ label, value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
+ return (
+
+
+ {
+ const date = new Date(value);
+ const formattedDate = moment(date).locale("en").format("YYYY-MM-DD");
+ setFieldValue(name, formattedDate);
+ }}
+ minDate={minDate ? new Date(minDate) : null}
+ maxDate={maxDate ? new Date(maxDate) : null}
+ slotProps={{
+ textField: {
+ size: "small",
+ error: error,
+ placeholder: placeholder,
+ InputProps: {
+ endAdornment: (
+
+ {
+ event.stopPropagation();
+ setFieldValue(name, "");
+ }}
+ sx={{
+ color: "#bfbfbf",
+ "&:hover": {
+ backgroundColor: "rgba(189, 189, 189, 0.1)",
+ color: "#363434",
+ },
+ }}
+ >
+
+
+
+ ),
+ },
+ InputLabelProps: {
+ shrink: true,
+ },
+ },
+ }}
+ />
+ {/*
+ {helperText ? helperText : ""}
+ */}
+
+
+ );
+}
+
+export default MuiDatePicker;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomSelect.jsx b/src/core/components/FilterDrawer/fieldsType/CustomSelect.jsx
new file mode 100644
index 0000000..61172e6
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomSelect.jsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
+function CustomSelect({ item, filterParameters, handleChange }) {
+ return (
+
+
+ {item.header}
+
+ }
+ size="small"
+ onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
+ displayEmpty
+ >
+ {item.selectOption().map((option) => (
+
+ ))}
+
+
+ );
+}
+
+export default CustomSelect;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomSelectByDependency.jsx b/src/core/components/FilterDrawer/fieldsType/CustomSelectByDependency.jsx
new file mode 100644
index 0000000..69536cd
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomSelectByDependency.jsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
+function CustomSelectByDependency({ item, filterParameters, value, handleChange, selectOption }) {
+ return (
+
+
+ {item.header}
+
+ }
+ onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
+ displayEmpty
+ >
+ {selectOption.map((option) => (
+
+ ))}
+
+
+ );
+}
+
+export default CustomSelectByDependency;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomSelectMultiple.jsx b/src/core/components/FilterDrawer/fieldsType/CustomSelectMultiple.jsx
new file mode 100644
index 0000000..c1fd7fa
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomSelectMultiple.jsx
@@ -0,0 +1,55 @@
+"use client";
+
+import {
+ Box,
+ Chip,
+ FormControl,
+ FormHelperText,
+ InputLabel,
+ MenuItem,
+ OutlinedInput,
+ Select,
+ Typography,
+} from "@mui/material";
+
+function CustomSelectMultiple({ item, filterParameters, handleChange }) {
+ const selectOption = item.selectOption;
+
+ const getLabelForValue = (value) => {
+ const option = selectOption.find((opt) => opt.value === value);
+ return option ? option.label : value;
+ };
+
+ return (
+
+
+ {item.header}
+
+
+
+ );
+}
+
+export default CustomSelectMultiple;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomTextField.jsx b/src/core/components/FilterDrawer/fieldsType/CustomTextField.jsx
new file mode 100644
index 0000000..86a9c57
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomTextField.jsx
@@ -0,0 +1,29 @@
+import { InputAdornment, TextField, Typography } from "@mui/material";
+import FilterListIcon from "@mui/icons-material/FilterList";
+
+function CustomTextField({ item, filterParameters, handleOpenFilterBox, handleBlur, handleChange }) {
+ return (
+ handleChange({ ...filterParameters, value: e.target.value })}
+ onBlur={handleBlur}
+ fullWidth
+ variant="outlined"
+ size="small"
+ sx={{ my: 1 }}
+ InputProps={{
+ endAdornment: (
+
+
+
+ ),
+ }}
+ InputLabelProps={{ shrink: true }}
+ />
+ );
+}
+
+export default CustomTextField;
diff --git a/src/core/components/FilterDrawer/fieldsType/CustomTextFieldRange.jsx b/src/core/components/FilterDrawer/fieldsType/CustomTextFieldRange.jsx
new file mode 100644
index 0000000..147ec85
--- /dev/null
+++ b/src/core/components/FilterDrawer/fieldsType/CustomTextFieldRange.jsx
@@ -0,0 +1,62 @@
+import { Box, InputAdornment, TextField, Typography } from "@mui/material";
+import FilterListIcon from "@mui/icons-material/FilterList";
+
+function CustomTextFieldRange({
+ item,
+ defaultFilterTranslation,
+ handleOpenFilterBox,
+ handleChange,
+ filterParameters,
+ handleBlur,
+ errors,
+}) {
+ return (
+
+
+ handleChange({ ...filterParameters, value: [e.target.value, filterParameters.value[1]] })
+ }
+ onBlur={handleBlur}
+ label={از {item.header}}
+ value={filterParameters.value[0]}
+ fullWidth
+ error={touched?.[`${item.id}`]?.value && Boolean(errors?.[`${item.id}`]?.value)}
+ helperText={
+
+ نوع فیلتر: {defaultFilterTranslation}
+
+ }
+ variant="outlined"
+ size="small"
+ sx={{ my: 1, marginRight: 1 }}
+ />
+
+ handleChange({ ...filterParameters, value: [filterParameters.value[0], e.target.value] })
+ }
+ onBlur={handleBlur}
+ error={Boolean(errors?.[`${item.id}`]?.value)}
+ helperText={errors?.[`${item.id}`]?.value ? errors?.[`${item.id}`]?.value.message : null}
+ label={تا {item.header}}
+ value={filterParameters.value[1]}
+ fullWidth
+ variant="outlined"
+ size="small"
+ sx={{ my: 1 }}
+ InputProps={{
+ endAdornment: (
+
+
+
+ ),
+ }}
+ />
+
+ );
+}
+
+export default CustomTextFieldRange;
diff --git a/src/core/components/FilterDrawer/index.jsx b/src/core/components/FilterDrawer/index.jsx
new file mode 100644
index 0000000..a231b71
--- /dev/null
+++ b/src/core/components/FilterDrawer/index.jsx
@@ -0,0 +1,153 @@
+import { yupResolver } from "@hookform/resolvers/yup";
+import { Cancel, FilterAlt } from "@mui/icons-material";
+import { Box, Button, IconButton, Typography } from "@mui/material";
+import { useForm } from "react-hook-form";
+import * as Yup from "yup";
+import ScrollBox from "../ScrollBox";
+import FilterController from "./FilterController";
+import FilterControllerWithDependency from "./FilterControllerWithDependency";
+
+const headerSx = {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "space-between",
+ px: 2,
+ py: 1,
+ backgroundColor: "#155175",
+ boxShadow: "rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px",
+ maxWidth: "450px",
+};
+
+const headerTitleSx = { display: "flex", alignItems: "center" };
+const headerIconSx = { color: "#fff", mr: 1 };
+const iconButtonSx = { color: "#fff" };
+
+const formContainerSx = { px: 2, py: 3 };
+const footerSx = { display: "flex", justifyContent: "center", alignItems: "center", pb: 2 };
+
+const submitButtonSx = {
+ px: 8,
+ boxShadow: "rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
+ backgroundColor: "primary2",
+ ":hover": { backgroundColor: "primary2" },
+};
+
+const validationSchema = Yup.object({
+ activity_date_time: Yup.array()
+ .of(Yup.string().nullable())
+ .test({
+ test(value, ctx) {
+ const [start, end] = value || ["", ""];
+ if ((start && !end) || (!start && end)) {
+ return ctx.createError({ message: "این بخش را تکمیل نمایید" });
+ }
+ return true;
+ },
+ }),
+});
+
+const FilterDrawer = ({ defaultValues, setFilterData, closeDrawer }) => {
+ const {
+ control,
+ errors,
+ reset,
+ handleSubmit,
+ formState: { isDirty },
+ } = useForm({
+ defaultValues,
+ resolver: yupResolver(
+ Yup.object(
+ Object.keys(defaultValues).reduce((acc, key) => {
+ const initialValue = defaultValues[key];
+ if (initialValue.filterMode === "between") {
+ acc[key] = Yup.object().shape({
+ value: Yup.array()
+ .of(Yup.string().nullable())
+ .test({
+ test(value, ctx) {
+ const [start, end] = value || ["", ""];
+ if (
+ initialValue.datatype === "numeric" &&
+ parseInt(end, 10) <= parseInt(start, 10)
+ ) {
+ return ctx.createError({
+ message: `مقدار وارده باید بیشتر از (${start}) باشد`,
+ });
+ } else if ((start && !end) || (!start && end)) {
+ return ctx.createError({
+ message: "این بخش را تکمیل نمایید",
+ });
+ }
+ return true;
+ },
+ }),
+ });
+ }
+ return acc;
+ }, {})
+ )
+ ),
+ mode: "all",
+ });
+
+ const onSubmit = (data) => {
+ setFilterData(data);
+ closeDrawer();
+ };
+
+ return (
+ <>
+
+
+
+
+ فیلتر
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+export default FilterDrawer;
diff --git a/src/core/components/FlyToLocation.jsx b/src/core/components/FlyToLocation.jsx
new file mode 100644
index 0000000..0f63772
--- /dev/null
+++ b/src/core/components/FlyToLocation.jsx
@@ -0,0 +1,32 @@
+import { useEffect, useState } from "react";
+import { useMap } from "react-leaflet";
+
+const FlyToUserLocation = ({ Component }) => {
+ const [userPosition, setUserPosition] = useState(null);
+ const map = useMap();
+
+ const handleLocateUser = () => {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ setUserPosition([position.coords.latitude, position.coords.longitude]);
+ },
+ (error) => {
+ console.error("Location error:", error);
+ }
+ );
+ } else {
+ alert("مرورگر شما از موقعیتیاب پشتیبانی نمیکند.");
+ }
+ };
+
+ useEffect(() => {
+ if (userPosition) {
+ map.flyTo(userPosition, 16);
+ }
+ }, [userPosition, map]);
+
+ return ;
+};
+
+export default FlyToUserLocation;
diff --git a/src/core/components/MapInfoOneMarker.jsx b/src/core/components/MapInfoOneMarker.jsx
index a68d4bf..8a887da 100644
--- a/src/core/components/MapInfoOneMarker.jsx
+++ b/src/core/components/MapInfoOneMarker.jsx
@@ -1,11 +1,13 @@
-import React, { useEffect, useRef, useState } from "react";
-import { Marker, useMapEvents } from "react-leaflet";
-import "leaflet/dist/leaflet.css";
-import L from "leaflet";
-import { Alert, Box, Button, Stack, Typography, Zoom } from "@mui/material";
-import dynamic from "next/dynamic";
-import MapLoading from "@/core/components/MapLayer/Loading";
import HereIcon from "@/assets/images/examine_marker_active.png";
+import MapLoading from "@/core/components/MapLayer/Loading";
+import { MyLocation } from "@mui/icons-material";
+import { Alert, Box, Button, Stack, Typography, Zoom } from "@mui/material";
+import L from "leaflet";
+import "leaflet/dist/leaflet.css";
+import dynamic from "next/dynamic";
+import { useEffect, useRef, useState } from "react";
+import { Marker, useMapEvents } from "react-leaflet";
+import FlyToUserLocation from "./FlyToLocation";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => ,
@@ -113,20 +115,36 @@ const MapInteraction = ({ setValue, startLat, startLng, title }) => {