From 8fb907ba0a96710ad30180f98590559169fccf0d Mon Sep 17 00:00:00 2001 From: 666mxvbee Date: Wed, 27 May 2026 20:24:56 +0300 Subject: [PATCH 1/2] fix: avoid zone map refit after dragging --- src/components/ZoneMapSelector.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/ZoneMapSelector.tsx b/src/components/ZoneMapSelector.tsx index 276f7f1..35ca85f 100644 --- a/src/components/ZoneMapSelector.tsx +++ b/src/components/ZoneMapSelector.tsx @@ -270,12 +270,15 @@ export default function ZoneMapSelector() { const [loading, setLoading] = useState(false); const [error, setError] = useState(); const suppressMapClickUntilRef = useRef(0); + const lastFittedZoneIdRef = useRef(); useEffect(() => { if (!zone) { setPoints([]); + lastFittedZoneIdRef.current = undefined; return; } + const zoneId = String(zone.id); const existing = zone.points .filter(point => typeof point.latitude === 'number' && typeof point.longitude === 'number') .slice(0, 4); @@ -283,9 +286,13 @@ export default function ZoneMapSelector() { if (existing.length === 4 && uniqueCoords.size > 1) { setPoints(existing.map(point => ({ lat: point.latitude!, lng: point.longitude! }))); - setFitVersion(version => version + 1); + if (lastFittedZoneIdRef.current !== zoneId) { + lastFittedZoneIdRef.current = zoneId; + setFitVersion(version => version + 1); + } } else { setPoints([]); + lastFittedZoneIdRef.current = zoneId; } }, [zone]); From 23622c671a4ec3b4a7c5cb418d128bf36d065707 Mon Sep 17 00:00:00 2001 From: 666mxvbee Date: Thu, 28 May 2026 03:24:46 +0300 Subject: [PATCH 2/2] fix: stabilize zone map editing overlays --- src/components/ZoneMapSelector.tsx | 59 +++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/components/ZoneMapSelector.tsx b/src/components/ZoneMapSelector.tsx index 35ca85f..089ffb3 100644 --- a/src/components/ZoneMapSelector.tsx +++ b/src/components/ZoneMapSelector.tsx @@ -4,12 +4,19 @@ import { Button } from './UiKit'; import { useFeedbackStore } from '@/feedback/feedbackStore'; import { fitYandexMap, yandexPoint, type YandexPoint } from '@/maps/yandex'; import { useYandexMap } from '@/maps/useYandexMap'; +import type { ParkingZone } from '@/types'; type MapPoint = { lat: number; lng: number; }; +type ZoneMapOverlay = { + id: ParkingZone['id']; + points: MapPoint[]; + isActive?: boolean; +}; + function hasCoordinates(latitude?: number | null, longitude?: number | null): latitude is number { return typeof latitude === 'number' && Number.isFinite(latitude) @@ -25,6 +32,15 @@ function fromYandexPoint(point: YandexPoint): MapPoint { return { lat: point[0], lng: point[1] }; } +function zoneGeoPoints(zone: ParkingZone): MapPoint[] | null { + const points = zone.points + .filter(point => hasCoordinates(point.latitude, point.longitude)) + .slice(0, 4) + .map(point => ({ lat: point.latitude!, lng: point.longitude! })); + const uniqueCoords = new Set(points.map(point => `${point.lat},${point.lng}`)); + return points.length === 4 && uniqueCoords.size > 1 ? points : null; +} + function stopYandexEventPropagation(event: any) { if (typeof event?.stopPropagation === 'function') event.stopPropagation(); } @@ -32,6 +48,7 @@ function stopYandexEventPropagation(event: any) { function YandexZoneGeometryMap({ center, points, + otherZones, fitVersion, onMapClick, onPointsCommit, @@ -39,6 +56,7 @@ function YandexZoneGeometryMap({ }: { center: YandexPoint; points: MapPoint[]; + otherZones: ZoneMapOverlay[]; fitVersion: number; onMapClick: (point: MapPoint) => void; onPointsCommit: (points: MapPoint[]) => void; @@ -110,6 +128,26 @@ function YandexZoneGeometryMap({ }); }; + otherZones.forEach(otherZone => { + const coordinates = otherZone.points.map(toYandexPoint); + const polygon = new ymaps.Polygon( + [coordinates], + { + hintContent: `Зона #${String(otherZone.id)}` + }, + { + strokeColor: otherZone.isActive === false ? '#9ca3af' : '#0f766e', + strokeOpacity: 0.72, + strokeWidth: 2, + fillColor: otherZone.isActive === false ? '#9ca3af' : '#0f766e', + fillOpacity: otherZone.isActive === false ? 0.08 : 0.14, + zIndex: 80, + interactivityModel: 'default#transparent' + } + ); + collection.add(polygon); + }); + const pointsFromDraggedGeometry = (geoObject: any) => { const coordinates = geoObject.geometry.getCoordinates(); const line = Array.isArray(coordinates?.[0]?.[0]) @@ -243,7 +281,7 @@ function YandexZoneGeometryMap({ map.behaviors.enable(['drag']); map.geoObjects.remove(collection); }; - }, [ymaps, map, points, onInteractionStart, onMapClick, onPointsCommit]); + }, [ymaps, map, points, otherZones, onInteractionStart, onMapClick, onPointsCommit]); return (
@@ -308,11 +346,27 @@ export default function ZoneMapSelector() { return yandexPoint(59.9386, 30.3141); }, [points, cameraMeta]); + const otherZoneOverlays = useMemo(() => { + if (!zone) return []; + return zones + .filter(item => String(item.id) !== String(zone.id) && item.camera_id === zone.camera_id) + .reduce((acc, item) => { + const itemPoints = zoneGeoPoints(item); + if (itemPoints) { + acc.push({ + id: item.id, + points: itemPoints, + isActive: item.is_active + }); + } + return acc; + }, []); + }, [zones, zone?.id, zone?.camera_id]); + function onMapClick(pos: MapPoint) { if (Date.now() < suppressMapClickUntilRef.current) return; if (points.length >= 4) return; setPoints(prev => [...prev, pos]); - setFitVersion(version => version + 1); } function suppressMapClick() { @@ -421,6 +475,7 @@ export default function ZoneMapSelector() {