diff --git a/src/App.tsx b/src/App.tsx
index 57f432b..05cb4ef 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -82,7 +82,7 @@ const queryClient = new QueryClient({
// Clear tokens and redirect to login
await removeToken();
await removeRefreshToken();
- window.location.href = '/auth/login';
+ // window.location.href = '/auth/login';
}
}
}
diff --git a/src/config/axios.ts b/src/config/axios.ts
index 7751219..24f2c04 100644
--- a/src/config/axios.ts
+++ b/src/config/axios.ts
@@ -64,7 +64,7 @@ axiosInstance.interceptors.response.use(
} else {
await removeRefreshToken();
await removeToken();
- window.location.href = `/`;
+ // window.location.href = `/`;
return;
}
} catch (refreshError) {
diff --git a/src/pages/settings/GeneralSettings.tsx b/src/pages/settings/GeneralSettings.tsx
index 28674e1..2beb2e5 100644
--- a/src/pages/settings/GeneralSettings.tsx
+++ b/src/pages/settings/GeneralSettings.tsx
@@ -16,6 +16,7 @@ import { useFormik } from 'formik'
import * as yup from 'yup'
import { toast } from 'react-toastify'
import { extractErrorMessage } from '@/config/func'
+import ServiceAreaPicker from './components/ServiceAreaPicker'
const validationSchema = yup.object({
name: yup.string().required('نام رستوران الزامی است'),
@@ -46,6 +47,7 @@ const GeneralSettings: FC = () => {
longitude: 0,
description: '',
logo: '',
+ serviceArea: undefined,
},
validationSchema,
onSubmit: (values) => {
@@ -91,13 +93,14 @@ const GeneralSettings: FC = () => {
formik.setValues({
name: restaurantData.name || '',
menuColor: restaurantData.menuColor || '#000000',
- phone: restaurantData.phoneNumber || '',
+ phone: restaurantData.phone || '',
instagram: restaurantData.instagram || '',
address: restaurantData.address || '',
latitude: restaurantData.latitude || 0,
longitude: restaurantData.longitude || 0,
description: restaurantData.description || '',
logo: restaurantData.logo || '',
+ serviceArea: restaurantData.serviceArea || undefined,
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -207,6 +210,14 @@ const GeneralSettings: FC = () => {
/>
+
+ formik.setFieldValue('serviceArea', polygon)}
+ />
+
+ {/* */}
+
-
-
+
+
+
+
void
+ error_text?: string
+}
+
+const ServiceAreaInput: FC = ({
+ label,
+ polygon,
+ onClick,
+ error_text,
+}) => {
+ const getDisplayText = () => {
+ if (polygon?.coordinates?.[0] && polygon.coordinates[0].length > 0) {
+ // در GeoJSON Polygon، اولین نقطه در انتها تکرار میشود تا بسته شود
+ // پس باید 1 را کم کنیم تا تعداد واقعی نقاط را نشان دهیم
+ const pointCount = polygon.coordinates[0].length
+ const actualPointCount = pointCount > 0 ? pointCount - 1 : 0
+ return `ناحیه انتخاب شده (${actualPointCount} نقطه)`
+ }
+ return 'روی نقشه کلیک کنید تا ناحیه را انتخاب کنید'
+ }
+
+ return (
+
+ {label &&
{label}
}
+
+
+ {getDisplayText()}
+
+ {error_text && (
+
+ )}
+
+ )
+}
+
+export default ServiceAreaInput
+
diff --git a/src/pages/settings/components/ServiceAreaPicker.tsx b/src/pages/settings/components/ServiceAreaPicker.tsx
new file mode 100644
index 0000000..ec3248a
--- /dev/null
+++ b/src/pages/settings/components/ServiceAreaPicker.tsx
@@ -0,0 +1,235 @@
+import { type FC, useState, useEffect } from 'react'
+import { MapContainer, TileLayer, Polygon, Polyline, useMapEvents, useMap } from 'react-leaflet'
+import type { LatLngTuple } from 'leaflet'
+import type { LatLng } from 'leaflet'
+import type { GeoJSONPolygon, GeoJSONPoint } from '../types/Types'
+import { Trash } from 'iconsax-react'
+
+interface ServiceAreaPickerProps {
+ initialPolygon?: GeoJSONPolygon;
+ onPolygonSelect: (polygon: GeoJSONPolygon | null) => void
+}
+
+const ChangeView = ({ center, zoom }: { center: LatLngTuple; zoom: number }) => {
+ const map = useMap()
+ useEffect(() => {
+ map.setView(center, zoom)
+ }, [map, center, zoom])
+ return null
+}
+
+const ServiceAreaPicker: FC = ({ initialPolygon, onPolygonSelect }) => {
+ const [points, setPoints] = useState([])
+ const [isDrawing, setIsDrawing] = useState(false)
+ const [isPolygonClosed, setIsPolygonClosed] = useState(false)
+ const mapCenter: LatLngTuple = [35.6892, 51.3890] // تهران به عنوان پیشفرض
+
+ const handleClear = () => {
+ setPoints([])
+ setIsDrawing(false)
+ setIsPolygonClosed(false)
+ onPolygonSelect(null)
+ }
+
+ // تبدیل GeoJSON Polygon به LatLngTuple[]
+ const convertGeoJSONToPoints = (polygon: GeoJSONPolygon): LatLngTuple[] => {
+ if (!polygon.coordinates || polygon.coordinates.length === 0) {
+ return []
+ }
+ // اولین ring از coordinates را بگیر (outer ring)
+ const ring = polygon.coordinates[0]
+ // تبدیل [lng, lat] به [lat, lng] و حذف آخرین نقطه اگر تکراری باشد
+ const convertedPoints: LatLngTuple[] = ring.map((coord) => [coord[1], coord[0]])
+ // اگر اولین و آخرین نقطه یکسان هستند، آخرین را حذف کن
+ if (convertedPoints.length > 0 &&
+ convertedPoints[0][0] === convertedPoints[convertedPoints.length - 1][0] &&
+ convertedPoints[0][1] === convertedPoints[convertedPoints.length - 1][1]) {
+ convertedPoints.pop()
+ }
+ return convertedPoints
+ }
+
+ // مقداردهی اولیه با initialPolygon
+ useEffect(() => {
+ if (initialPolygon) {
+ const initialPoints = convertGeoJSONToPoints(initialPolygon)
+ if (initialPoints.length >= 3) {
+ setPoints(initialPoints)
+ setIsPolygonClosed(true)
+ onPolygonSelect(initialPolygon)
+ } else {
+ handleClear()
+ }
+ } else {
+ handleClear()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [initialPolygon])
+
+ const convertToGeoJSON = (pts: LatLngTuple[]): GeoJSONPolygon | null => {
+ if (pts.length < 3) return null
+
+ // تبدیل [lat, lng] به [lng, lat] و بستن Polygon با تکرار اولین نقطه
+ const coords = pts.map((pt) => [pt[1], pt[0]] as GeoJSONPoint)
+
+ // در GeoJSON Polygon، اولین نقطه باید در انتها تکرار شود تا بسته شود
+ if (coords.length > 0 &&
+ (coords[0][0] !== coords[coords.length - 1][0] ||
+ coords[0][1] !== coords[coords.length - 1][1])) {
+ coords.push(coords[0])
+ }
+
+ const coordinates: GeoJSONPoint[][] = [coords]
+
+ return {
+ type: 'Polygon',
+ coordinates,
+ }
+ }
+
+ // بررسی فاصله بین دو نقطه (به درجه)
+ const getDistance = (pt1: LatLngTuple, pt2: LatLngTuple): number => {
+ const latDiff = pt1[0] - pt2[0]
+ const lngDiff = pt1[1] - pt2[1]
+ return Math.sqrt(latDiff * latDiff + lngDiff * lngDiff)
+ }
+
+ const handleMapClick = (e: { latlng: LatLng }) => {
+ if (!isDrawing && points.length === 0) {
+ setIsDrawing(true)
+ }
+
+ const newPoint: LatLngTuple = [e.latlng.lat, e.latlng.lng]
+
+ // اگر حداقل 3 نقطه داریم و کاربر روی نقطه اول کلیک کرد (یا نزدیک به آن)، Polygon را ببند
+ if (points.length >= 3) {
+ const firstPoint = points[0]
+ const distance = getDistance(newPoint, firstPoint)
+ // اگر فاصله کمتر از 0.002 درجه باشد (تقریباً 200 متر)، به عنوان کلیک روی نقطه اول در نظر بگیریم
+ if (distance < 0.002) {
+ // Polygon را ببند و کامل کن (convertToGeoJSON خودش نقطه اول را اضافه میکند)
+ const geoJsonPolygon = convertToGeoJSON(points)
+ if (geoJsonPolygon) {
+ onPolygonSelect(geoJsonPolygon)
+ setIsDrawing(false)
+ setIsPolygonClosed(true)
+ // نقاط را نگه دار تا Polygon کامل نمایش داده شود
+ return
+ }
+ }
+ }
+
+ // اگر Polygon قبلاً بسته شده بود، دوباره شروع کن
+ if (isPolygonClosed) {
+ setPoints([])
+ setIsPolygonClosed(false)
+ setIsDrawing(true)
+ onPolygonSelect(null)
+ }
+
+ // نقطه جدید را اضافه کن
+ const updatedPoints = [...points, newPoint]
+ setPoints(updatedPoints)
+
+ // تا زمانی که Polygon بسته نشده، null بفرست
+ onPolygonSelect(null)
+ }
+
+ const MapClickHandler = () => {
+ useMapEvents({
+ click: handleMapClick,
+ })
+ return null
+ }
+
+ const polygonOptions = {
+ color: '#3B82F6',
+ fillColor: '#3B82F6',
+ fillOpacity: 0.2,
+ weight: 2,
+ }
+
+ const polylineOptions = {
+ color: '#3B82F6',
+ weight: 2,
+ dashArray: '5, 10',
+ }
+
+ // فقط وقتی حداقل 2 نقطه داریم، center را تغییر میدهیم
+ const centerPoint = points.length >= 2
+ ? (() => {
+ const sum = points.reduce(
+ (acc, pt) => [acc[0] + pt[0], acc[1] + pt[1]],
+ [0, 0] as [number, number]
+ )
+ return [sum[0] / points.length, sum[1] / points.length] as LatLngTuple
+ })()
+ : points.length === 1
+ ? points[0] // با یک نقطه، center همان نقطه است
+ : mapCenter
+
+ return (
+
+
+
+
+ {points.length >= 2 && (
+
+ )}
+ {points.length === 0 && (
+
+ )}
+
+ {points.length >= 2 && !isPolygonClosed && (
+
+ )}
+ {/* فقط وقتی Polygon بسته شده، نمایش بده */}
+ {points.length >= 3 && isPolygonClosed && (
+
+ )}
+
+
+
+
+
+ {points.length === 0
+ ? 'روی نقشه کلیک کنید تا نقاط ناحیه را انتخاب کنید'
+ : isPolygonClosed
+ ? `ناحیه کامل شده (${points.length} نقطه)`
+ : points.length < 3
+ ? `نقاط انتخاب شده: ${points.length} (حداقل 3 نقطه لازم است)`
+ : `نقاط انتخاب شده: ${points.length} - برای بستن ناحیه روی نقطه اول کلیک کنید`}
+
+
+
+ {points.length > 0 && (
+
+ )}
+
+
+
+ )
+}
+
+export default ServiceAreaPicker
\ No newline at end of file
diff --git a/src/pages/settings/types/Types.ts b/src/pages/settings/types/Types.ts
index c3259fa..8037047 100644
--- a/src/pages/settings/types/Types.ts
+++ b/src/pages/settings/types/Types.ts
@@ -1,5 +1,12 @@
import type { IResponse } from "@/types/response.types";
+export type GeoJSONPoint = [number, number];
+
+export type GeoJSONPolygon = {
+ type: "Polygon";
+ coordinates: GeoJSONPoint[][];
+};
+
export type UpdateRestaurantType = {
name: string;
menuColor: string;
@@ -10,6 +17,7 @@ export type UpdateRestaurantType = {
latitude: number;
longitude: number;
description: string;
+ serviceArea?: GeoJSONPolygon;
};
export type Restaurant = {
@@ -19,19 +27,20 @@ export type Restaurant = {
deletedAt: string | null;
name: string;
slug: string;
- logo: string | null;
- address: string | null;
- menuColor: string | null;
- latitude: number | null;
- longitude: number | null;
- serviceArea: unknown | null;
+ logo: string;
+ address: string;
+ menuColor: string;
+ latitude: number;
+ longitude: number;
+ serviceArea: GeoJSONPolygon;
isActive: boolean;
establishedYear: number | null;
phoneNumber: string | null;
- instagram: string | null;
+ phone: string;
+ instagram: string;
telegram: string | null;
whatsapp: string | null;
- description: string | null;
+ description: string;
seoTitle: string | null;
seoDescription: string | null;
tagNames: unknown | null;