diff --git a/.env.local.example b/.env.local.example index c93a078..fa80b33 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,2 +1,3 @@ NEXT_PUBLIC_VERSION="0.2.0" -NEXT_PUBLIC_API_URL="https://rms.witel.ir" \ No newline at end of file +NEXT_PUBLIC_API_URL="https://rms.witel.ir" +NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map" \ No newline at end of file diff --git a/package.json b/package.json index 7bec569..36ec3d1 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "next": "^14.2.3", "react": "^18", "react-dom": "^18", + "react-hook-form": "^7.52.1", + "react-leaflet": "^4.2.1", "react-toastify": "^10.0.5", "sass": "^1.75.0", "stylis": "^4.3.1", diff --git a/public/global.scss b/public/global.scss index 764379f..7b73e7e 100644 --- a/public/global.scss +++ b/public/global.scss @@ -1,8 +1,10 @@ @import "fontiran"; +@import "leaflet/dist/leaflet.css"; @import "react-toastify/dist/ReactToastify.css"; .filter-toast { - box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, - rgba(0, 0, 0, 0.3) 0px 8px 16px -8px; - background-color: #d0dfe8; -} \ No newline at end of file + box-shadow: + rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, + rgba(0, 0, 0, 0.3) 0px 8px 16px -8px; + background-color: #d0dfe8; +} diff --git a/public/images/locationMarker.png b/public/images/locationMarker.png new file mode 100644 index 0000000..5e19b95 Binary files /dev/null and b/public/images/locationMarker.png differ diff --git a/src/components/dashboard/inquiryPrivacyFencing/Create/Form/LocationOnMap/index.jsx b/src/components/dashboard/inquiryPrivacyFencing/Create/Form/LocationOnMap/index.jsx new file mode 100644 index 0000000..be24704 --- /dev/null +++ b/src/components/dashboard/inquiryPrivacyFencing/Create/Form/LocationOnMap/index.jsx @@ -0,0 +1,23 @@ +import MapLoading from "@/core/components/MapLayer/Loading"; +import MarkerLocation from "@/core/components/MapLayer/MarkerLocation"; +import { Paper, Stack } from "@mui/material"; +import { useFormikContext } from "formik"; +import dynamic from "next/dynamic"; + +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const LocationOnMap = ({ setValue }) => { + return ( + + + + + + + + ) +} +export default LocationOnMap \ No newline at end of file diff --git a/src/components/dashboard/inquiryPrivacyFencing/Create/Form/index.jsx b/src/components/dashboard/inquiryPrivacyFencing/Create/Form/index.jsx new file mode 100644 index 0000000..9fff4da --- /dev/null +++ b/src/components/dashboard/inquiryPrivacyFencing/Create/Form/index.jsx @@ -0,0 +1,570 @@ +import LtrTextField from "@/core/components/LtrTextField"; +import StyledForm from "@/core/components/StyledForm"; +import { SET_INQUIRE_PRIVACY_FENCING } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { Autocomplete, Box, Button, Chip, DialogActions, DialogContent, Fade, Grid, InputAdornment, Stack, TextField, Typography } from "@mui/material"; +import { DatePicker, LocalizationProvider, faIR } from "@mui/x-date-pickers"; +import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali"; +import moment from "jalali-moment"; +import { Controller, useForm } from "react-hook-form"; +import LocationOnMap from "./LocationOnMap"; + +const CreateForm = ({ handleClose, mutate }) => { + const { control, watch, register, handleSubmit, setValue } = useForm({ + defaultValues: { + dabirkhaneh_number: '', + nameh_date: moment(), + nameh_date_fa: '', + marjae_pasokh: '', + motaghazi_is_legal_id: null, + motaghazi_is_legal: '', + motaghazi_type_id: '', + motaghazi_type: null, + motaghazi_firstname: '', + motaghazi_lastname: '', + national_id: '', + shenase_melli: '', + tel_number: '', + mobile_number: '', + address: '', + edare_kol_id: null, + edare_kol: '', + edare_shahri_id: null, + edare_shahri: '', + rah_type_id: null, + rah_type: '', + name_mehvar_id: '', + name_mehvar_fa: null, + mizan_harim: null, + arze_navar: null, + samt_id: null, + samt: '', + kilometr: '', + lat: '', + lon: '', + zone: null, + karbari_type_id: null, + karbari_type: '', + tarh_title: '', + masahat_zirbana: '', + ehdasat_type_id: [], + ehdasat_type: '', + divarkeshi_distance: '', + mostahadesat_distance: '', + mahale_ejra_id: null, + mahale_ejra: '', + traffic_id: null, + traffic: '', + vaziat_eghtesadi_id: null, + vaziat_eghtesadi: '', + has_access_id: null, + has_access: '', + shomare_estelam_harim: '', + max_month: '', + max_day: '', + shomare_tahaodname: '', + shomare_daftarkhaneh: '', + description: '', + ronevesht: '', + } + }); + const request = useRequest() + + const onSubmit = async (data) => { + const formData = new FormData() + formData.append('dabirkhaneh_number', data.dabirkhaneh_number) + formData.append('nameh_date', data.nameh_date.format('YYYY-MM-DD')) + formData.append('nameh_date_fa', data.nameh_date) + formData.append('marjae_pasokh', data.marjae_pasokh) + formData.append('motaghazi_is_legal_id', data.motaghazi_is_legal_id) + formData.append('motaghazi_is_legal', data.motaghazi_is_legal_id) + formData.append('motaghazi_type_id', 1) + formData.append('motaghazi_type', data.motaghazi_type) + formData.append('motaghazi_firstname', data.motaghazi_firstname) + formData.append('motaghazi_lastname', data.motaghazi_lastname) + formData.append('national_id', data.national_id) + formData.append('shenase_melli', data.national_id) + formData.append('tel_number', data.tel_number) + formData.append('mobile_number', data.mobile_number) + formData.append('address', data.address) + formData.append('edare_kol_id', data.edare_kol_id) + formData.append('edare_kol', data.edare_kol_id) + formData.append('edare_shahri_id', data.edare_shahri_id) + formData.append('edare_shahri', data.edare_shahri_id) + formData.append('rah_type_id', data.rah_type_id) + formData.append('rah_type', data.rah_type_id) + formData.append('name_mehvar_id', 1) + formData.append('name_mehvar_fa', data.name_mehvar_id) + formData.append('mizan_harim', data.mizan_harim) + formData.append('arze_navar', data.arze_navar) + formData.append('samt_id', data.samt_id) + formData.append('samt', data.samt_id) + formData.append('kilometr', data.kilometr) + formData.append('lat', data.lat) + formData.append('lon', data.lon) + formData.append('zone', data.zone) + formData.append('karbari_type_id', data.karbari_type_id) + formData.append('karbari_type', data.karbari_type_id) + formData.append('tarh_title', data.tarh_title) + formData.append('masahat_zirbana', data.masahat_zirbana) + formData.append('ehdasat_type_id', data.ehdasat_type_id.join("|")) + formData.append('ehdasat_type', data.ehdasat_type_id.join("|")) + formData.append('divarkeshi_distance', data.divarkeshi_distance) + formData.append('mostahadesat_distance', data.mostahadesat_distance) + formData.append('mahale_ejra_id', data.mahale_ejra_id) + formData.append('mahale_ejra', data.mahale_ejra) + formData.append('traffic_id', data.traffic_id) + formData.append('traffic', data.traffic_id) + formData.append('vaziat_eghtesadi_id', data.vaziat_eghtesadi_id) + formData.append('vaziat_eghtesadi', data.vaziat_eghtesadi) + formData.append('has_access_id', data.has_access_id) + formData.append('has_access', data.has_access_id) + formData.append('shomare_estelam_harim', data.shomare_estelam_harim) + formData.append('max_month', data.max_month) + formData.append('max_day', data.max_day) + formData.append('shomare_tahaodname', data.shomare_tahaodname) + formData.append('shomare_daftarkhaneh', data.shomare_daftarkhaneh) + formData.append('description', data.description) + formData.append('ronevesht', data.ronevesht) + + try { + await request(SET_INQUIRE_PRIVACY_FENCING, 'post', { data: formData }) + handleClose() + mutate() + } catch (error) { + + } + }; + + return ( + + + + + + + + + ( + onChange(moment(newValue))} + label="تاریخ نامه درخواست *" + slotProps={{ + textField: { fullWidth: true, size: "small", autoComplete: 'off' }, + }} + /> + )} + /> + + + + + + ( + { + onChange(newValue) + }} + options={["حقوقی", "حقیقی"]} + renderInput={(params) => } + /> + )} + /> + + + + + ( + { + onChange(newValue) + }} + options={["دولتی", "خصوصی"]} + renderInput={(params) => } + /> + )} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( + { + onChange(newValue) + }} + options={["تهران"]} + renderInput={(params) => ( + + )} + /> + )} + /> + + + ( + { + onChange(newValue) + }} + options={["تهران"]} + renderInput={(params) => } + /> + )} + /> + + + ( + { + onChange(newValue) + }} + options={["آزاد راه", "بزرگراه", 'راه فرعی درجه 3']} + renderInput={(params) => } + /> + )} + /> + + + ( + { + onChange(newValue) + }} + options={["سایر"]} + renderInput={(params) => } + /> + )} + /> + + + + + + + + ( + { + onChange(newValue) + }} + options={["چپ", "راست"]} + renderInput={(params) => } + /> + )} + /> + + + ( + { + onChange(newValue) + }} + options={["12.5", "17.5"]} + renderInput={(params) => } + /> + )} + /> + + + + + ( + { + onChange(newValue) + }} + options={["15", "30"]} + renderInput={(params) => } + /> + )} + /> + + + + + + + + + + + + + + ( + { + onChange(newValue) + }} + options={["38", "39"]} + renderInput={(params) => } + /> + )} + /> + + + + ( + { + onChange(newValue) + }} + options={["اتاقک نگهبانی", "دولتی"]} + renderInput={(params) => } + /> + )} + /> + + + + + + + مترمربع, + }} /> + + + ( + { + onChange(newValue) + }} + renderTags={(value, getTagProps) => + value.map((option, index) => { + const { key, ...tagProps } = getTagProps({ index }); + return ( + + ); + }) + } + options={["تعمیرات", "احداث بنا"]} + renderInput={(params) => } + /> + )} + /> + + + متر, + }} /> + + + متر, + }} /> + + + ( + { + onChange(newValue) + }} + options={["داخل حریم"]} + renderInput={(params) => } + /> + )} + /> + + + ( + { + onChange(newValue) + }} + options={["سبک (ضریب ۱)"]} + renderInput={(params) => } + /> + )} + /> + + + ( + { + onChange(newValue) + }} + options={["تست"]} + renderInput={(params) => } + /> + )} + /> + + + ( + { + onChange(newValue) + }} + options={["بله", "خیر"]} + renderInput={(params) => } + /> + )} + /> + + + + + + + حداکثر مدت زمان شروع عملیات: * + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; +export default CreateForm; diff --git a/src/components/dashboard/inquiryPrivacyFencing/Create/index.jsx b/src/components/dashboard/inquiryPrivacyFencing/Create/index.jsx new file mode 100644 index 0000000..9010277 --- /dev/null +++ b/src/components/dashboard/inquiryPrivacyFencing/Create/index.jsx @@ -0,0 +1,32 @@ +"use client"; +import DialogTransition from "@/core/components/DialogTransition"; +import { Close } from "@mui/icons-material"; +import { Dialog, DialogTitle, IconButton } from "@mui/material"; +import CreateForm from "./Form"; + +const InquiryPrivacyFencingCreate = ({ open, handleClose, mutate }) => { + + return ( + <> + + + ایجاد پاسخ به استعلام + + theme.palette.grey[500], + }} + > + + + + + + ); +}; +export default InquiryPrivacyFencingCreate; diff --git a/src/core/components/DialogTransition.jsx b/src/core/components/DialogTransition.jsx new file mode 100644 index 0000000..f289140 --- /dev/null +++ b/src/core/components/DialogTransition.jsx @@ -0,0 +1,8 @@ +import { Slide } from "@mui/material"; +import React from "react"; + +const DialogTransition = React.forwardRef(function Transition(props, ref) { + return ; +}); + +export default DialogTransition \ No newline at end of file diff --git a/src/core/components/MapLayer/Loading/index.jsx b/src/core/components/MapLayer/Loading/index.jsx new file mode 100644 index 0000000..5d42012 --- /dev/null +++ b/src/core/components/MapLayer/Loading/index.jsx @@ -0,0 +1,17 @@ +import { Box, LinearProgress, Stack } from "@mui/material"; + +function MapLoading() { + return ( + + + + + + ); +} + +export default MapLoading; \ No newline at end of file diff --git a/src/core/components/MapLayer/MarkerLocation/index.jsx b/src/core/components/MapLayer/MarkerLocation/index.jsx new file mode 100644 index 0000000..a75d191 --- /dev/null +++ b/src/core/components/MapLayer/MarkerLocation/index.jsx @@ -0,0 +1,32 @@ +import { useRef } from "react"; +import { Marker, useMapEvents } from "react-leaflet"; + +const prefix = process.env.NODE_ENV === "production" ? "/v3" : ""; + +const locationMarker = L.icon({ + iconUrl: prefix + "/images/locationMarker.png", + iconSize: [50, 50], + iconAnchor: [25, 50], +}); + +const MarkerLocation = ({ setValue }) => { + const location = useRef(); + const map = useMapEvents({ + move: (e) => { + const latlon = e.target.getCenter() + location.current?.setLatLng(latlon); + setValue('lat', latlon.lat) + setValue('lon', latlon.lng) + }, + zoom: (e) => { + const latlon = e.target.getCenter() + location.current?.setLatLng(latlon); + setValue('lat', latlon.lat) + setValue('lon', latlon.lng) + }, + }); + return ( + + ) +} +export default MarkerLocation \ No newline at end of file diff --git a/src/core/components/MapLayer/index.jsx b/src/core/components/MapLayer/index.jsx new file mode 100644 index 0000000..52209da --- /dev/null +++ b/src/core/components/MapLayer/index.jsx @@ -0,0 +1,43 @@ +"use client"; +import { MapContainer, TileLayer, useMap } from "react-leaflet"; + +export const IRAN_CENTER = L.latLng(32.4279, 53.6880); + +const MapOptions = ({ options }) => { + const map = useMap(); + if (!options) return; + + if (options.mapDrag) { + map.dragging.enable(); + map.scrollWheelZoom.enable(); + map.touchZoom.enable(); + map.doubleClickZoom.enable(); + } else { + map.dragging.disable(); + map.scrollWheelZoom.disable(); + map.touchZoom.disable(); + map.doubleClickZoom.disable(); + } +}; + +function MapLayer({ children, style, otherLayers, options }) { + return ( + <> + + + + {children} + + {otherLayers} + + ); +} + +export default MapLayer; \ No newline at end of file diff --git a/src/core/components/StyledForm.jsx b/src/core/components/StyledForm.jsx new file mode 100644 index 0000000..4513129 --- /dev/null +++ b/src/core/components/StyledForm.jsx @@ -0,0 +1,5 @@ +import { styled } from "@mui/material"; + +const StyledForm = styled('form')``; + +export default StyledForm; diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js index 62273df..7c0f4dc 100644 --- a/src/core/utils/routes.js +++ b/src/core/utils/routes.js @@ -1,6 +1,10 @@ const api = process.env.NEXT_PUBLIC_API_URL; +export const GET_CSRF = api + "/csrf"; + export const GET_USER_ROUTE = api + "/webapi/user/get-permission"; export const GET_LOGIN_ROUTE = api + "/login_dev"; export const GET_INQUIRY_PRIVACY_FENCING_ROUTE = api + "/api/v3/harim/divarkeshi"; export const GET_PERMISSIONS_ROUTE = api + "/webapi/user/get-permission"; + +export const SET_INQUIRE_PRIVACY_FENCING = api + "/api/v3/harim/divarkeshi/store"; diff --git a/src/lib/hooks/useRequest.js b/src/lib/hooks/useRequest.js index 2e35512..8805a56 100644 --- a/src/lib/hooks/useRequest.js +++ b/src/lib/hooks/useRequest.js @@ -1,7 +1,18 @@ import { errorResponse } from "@/core/utils/errorResponse"; +import { GET_CSRF } from "@/core/utils/routes"; import { successRequest } from "@/core/utils/successRequest"; import axios from "axios"; +const getCsrfToken = async () => { + try { + const response = await axios.get(GET_CSRF); + return response.data.data; + } catch (error) { + console.error("Error fetching CSRF token:", error); + throw error; + } +}; + const defaultOptions = { data: {}, requestOptions: { @@ -15,13 +26,27 @@ const defaultOptions = { }; const useRequest = (initOptions) => { + const instance = axios.create(); + instance.interceptors.request.use( + async function (config) { + if (config.method !== 'get') { + const csrfToken = await getCsrfToken(); + config.headers['X-CSRF-TOKEN'] = csrfToken; + } + return config; + }, + function (error) { + return Promise.reject(error); + } + ); + const _options = Object.assign({}, defaultOptions, initOptions); return async (url = "", method = "get", options) => { const mergedOptions = Object.assign({}, _options, options); try { - const response = await axios({ + const response = await instance({ url, method, data: method === "get" ? null : mergedOptions.data,