diff --git a/src/app/(withAuth)/infrastructure/layout.js b/src/app/(withAuth)/infrastructure/layout.js new file mode 100644 index 0000000..219cee2 --- /dev/null +++ b/src/app/(withAuth)/infrastructure/layout.js @@ -0,0 +1,13 @@ +import HeaderWithLogo from "@/components/layouts/dashboard/headerWithLogo"; +import HeaderWithSidebar from "@/components/layouts/dashboard/headerWithSidebar"; +import { Stack } from "@mui/material"; + +const Layout = ({ children }) => { + return ( + + + {children} + + ); +}; +export default Layout; diff --git a/src/app/(withAuth)/infrastructure/toll-house/loading.jsx b/src/app/(withAuth)/infrastructure/toll-house/loading.jsx new file mode 100644 index 0000000..4535a8d --- /dev/null +++ b/src/app/(withAuth)/infrastructure/toll-house/loading.jsx @@ -0,0 +1,7 @@ +"use client"; +import PageLoading from "@/core/components/PageLoading"; + +const Loading = () => { + return ; +}; +export default Loading; diff --git a/src/app/(withAuth)/infrastructure/toll-house/page.js b/src/app/(withAuth)/infrastructure/toll-house/page.js new file mode 100644 index 0000000..dec7c50 --- /dev/null +++ b/src/app/(withAuth)/infrastructure/toll-house/page.js @@ -0,0 +1,16 @@ +import WithPermission from "@/core/middlewares/withPermission"; +import TollHousePage from "@/components/infrastructure/tollHouse"; + +export const metadata = { + title: "فهرست راهدارخانه ها", +}; + +const Page = () => { + return ( + + + + ); +}; + +export default Page; diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CityContent.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CityContent.jsx new file mode 100644 index 0000000..3069208 --- /dev/null +++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CityContent.jsx @@ -0,0 +1,33 @@ +import SelectBox from "@/core/components/SelectBox"; +import { useEffect, useState } from "react"; +import useCities from "@/lib/hooks/useCities"; + +const CityContent = ({ provinceID, field, fieldState: { error } }) => { + const { cityList, loadingCityList, errorCityList } = useCities(provinceID); + const [prevDependency, setPrevDependency] = useState(provinceID); + + useEffect(() => { + if (prevDependency === provinceID) return; + field.onChange(null); + setPrevDependency(provinceID); + }, [provinceID]); + + return ( + + ); +}; +export default CityContent; diff --git a/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CityController.jsx b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CityController.jsx new file mode 100644 index 0000000..72de542 --- /dev/null +++ b/src/components/infrastructure/tollHouse/Form/CreateTollHouse/CityController.jsx @@ -0,0 +1,14 @@ +import { Controller, useWatch } from "react-hook-form"; +import CityContent from "./CityContent"; + +const CityController = ({ control }) => { + const provinceID = useWatch({ control, name: "province_id" }); + return ( + } + /> + ); +}; +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..f56c8ca --- /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 ( + + ); +} + +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: "onBlur", + }); + const onSubmitBase = async (data) => { + await SubmitCreateTollHouse(data); + }; + return ( + + + } label="اطلاعات راهدارخانه" /> + } label="ماشین آلات" /> + } label="موقعیت" /> + + + + + + + + + + + + + + + + + {tabState !== 2 && ( + + )} + {tabState === 2 && ( + + )} + + + ); +}; +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 ? ( + + + + ) : ( + + )} + setOpen(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"md"} + scroll="paper" + > + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {open && ( + + )} + + + ); +}; +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); + }} + > + + + + + setOpenEditDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openEditDialog && ( + + )} + + + ); +}; +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)}> + + + + setOpenDeleteDialog(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"} + > + حذف راهدارخانه + {openDeleteDialog && ( + + )} + + + ); +}; +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} + + + Image + + + ); +}; +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)}> + + + + setOpenImageDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"sm"} + scroll="paper" + > + تصاویر + {openImageDialog && } + + + + + + ); +}; + +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)}> + + + + 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"} + > + موقعیت + + + + ); +}; +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/core/utils/headerMenu.js b/src/core/utils/headerMenu.js index a3ac3c9..dc58a18 100644 --- a/src/core/utils/headerMenu.js +++ b/src/core/utils/headerMenu.js @@ -37,7 +37,7 @@ export const headerMenu = [ }, { title: "راهدارخانه ها", - href: api + "/v2/rahdari_points", + href: "/infrastructure/toll-house", }, ], [ diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js index b91186b..d97dba7 100644 --- a/src/core/utils/routes.js +++ b/src/core/utils/routes.js @@ -9,7 +9,7 @@ export const GET_PERMISSIONS_ROUTE = api + "/webapi/user/get-permission"; export const GET_SIDEBAR_BADGE_ROUTE = api + "/api/v3/notifications"; export const REFER_ADMIN_PROVINCE = "/v3/api/fake-submit"; export const REFER_ADMIN_CITY = "/v3/api/fake-submit"; -export const GET_CITY_LISTS = "/v3/api/fake-cities"; +export const GET_CITY_LISTS = api + "/public/contents/provinces"; export const GET_PROVINCE_LISTS = api + "/public/contents/provinces"; export const GET_PREV_STATE_OPINION = "/v3/api/fake-prev-state-opinion"; export const SUBMIT_ROAD_SAFETY_FORM = "/v3/api/fake-submit"; @@ -154,3 +154,11 @@ export const GET_USER_ACTIVITIES_LIST = api + "/api/v3/log_list"; export const CREATE_USER_ACTIVITY = api + "/api/v3/log_list"; export const UPDATE_USER_ACTIVITY = api + "/api/v3/log_list"; export const DELETE_USER_ACTIVITIES = api + "/api/v3/log_list"; + +// toll-house +export const GET_TOLL_HOUSE_LIST = api + "/api/v3/road_maintenance_station"; +export const CREATE_TOLL_HOUSE = api + "/api/v3/road_maintenance_station"; +export const GET_TOLL_HOUSE_DETAILS = api + "/api/v3/road_maintenance_station"; +export const DELETE_TOLL_HOUSE_ITEM = api + "/api/v3/road_maintenance_station"; +export const UPDATE_TOLL_HOUSE_ITEM = api + "/api/v3/road_maintenance_station"; +export const GET_TOLL_HOUSE_IMAGES = api + "/api/v3/road_maintenance_station/images"; diff --git a/src/lib/hooks/useCities.js b/src/lib/hooks/useCities.js index 6ac535d..dfaadbb 100644 --- a/src/lib/hooks/useCities.js +++ b/src/lib/hooks/useCities.js @@ -1,30 +1,59 @@ -import { useState, useEffect } from "react"; +import { useEffect, useReducer } from "react"; import { GET_CITY_LISTS } from "@/core/utils/routes"; import useRequest from "@/lib/hooks/useRequest"; +const ACTIONS = { + LOADING: "loading", + SUCCESS: "success", + ERROR: "error", + RESET: "reset", +}; -const useCities = () => { +const initialState = { + data: [], + loading: true, + error: null, +}; +function reducer(state, action) { + switch (action.type) { + case ACTIONS.LOADING: + return { ...state, loading: true, error: null }; + case ACTIONS.SUCCESS: + return { ...state, loading: false, data: action.payload }; + case ACTIONS.ERROR: + return { ...state, loading: false, error: action.payload }; + case ACTIONS.RESET: + return { ...initialState, loading: false }; + default: + return state; + } +} +const useCities = (id) => { const requestServer = useRequest(); - const [cities, setCities] = useState([]); - const [loadingCities, setLoadingCities] = useState(true); - const [errorCities, setErrorCities] = useState(null); + const [state, dispatch] = useReducer(reducer, initialState); useEffect(() => { - const fetchCities = async () => { + if (!id) { + dispatch({ type: ACTIONS.RESET }); + return; + } + const fetchCityLists = async () => { + dispatch({ type: ACTIONS.LOADING }); try { - const response = await requestServer(`${GET_CITY_LISTS}`); - setCities(response.data.data); - setLoadingCities(false); - } catch (e) { - console.error("Error fetching cities:", e); - setErrorCities(e); - setLoadingCities(false); + const response = await requestServer(`${GET_CITY_LISTS}/${id}`); + dispatch({ type: ACTIONS.SUCCESS, payload: response.data.data }); + } catch (error) { + dispatch({ type: ACTIONS.ERROR, payload: error }); } }; - fetchCities(); - }, []); + fetchCityLists(); + }, [id]); - return { cities, loadingCities, errorCities }; + return { + cityList: state.data, + loadingCityList: state.loading, + errorCityList: state.error, + }; }; export default useCities;