Fix Arrastre Mapa en Zoom
This commit is contained in:
		| @@ -296,7 +296,7 @@ | ||||
| } | ||||
|  | ||||
|  | ||||
| /* --- MAPA Y ELEMENTOS ASOCIADOS (sin cambios) --- */ | ||||
| /* --- MAPA Y ELEMENTOS ASOCIADOS --- */ | ||||
| .mapa-componente-container { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| @@ -329,6 +329,11 @@ | ||||
|   transition: transform 0.75s ease-in-out; | ||||
| } | ||||
|  | ||||
| /* AÑADIDO: Desactivar la transición durante el arrastre */ | ||||
| .rsm-zoomable-group.panning { | ||||
|   transition: none; | ||||
| } | ||||
|  | ||||
| .panel-main-content.panel-collapsed .mapa-column { | ||||
|   flex: 1 1 100%; | ||||
| } | ||||
| @@ -368,7 +373,6 @@ | ||||
| } | ||||
|  | ||||
| .rsm-geography { | ||||
|   cursor: pointer; | ||||
|   stroke: #000000; | ||||
|   stroke-width: 0.25px; | ||||
|   outline: none; | ||||
| @@ -579,18 +583,14 @@ | ||||
| /* --- ESTILOS DE CURSOR PARA EL ARRASTRE DEL MAPA --- */ | ||||
| .map-locked .rsm-geography { | ||||
|   cursor: pointer; | ||||
|   /* Cursor normal de clic */ | ||||
| } | ||||
|  | ||||
| .map-pannable .rsm-geography { | ||||
|   cursor: grab; | ||||
|   /* Indica que el mapa se puede arrastrar */ | ||||
| } | ||||
|  | ||||
| .map-pannable .rsm-geography:active { | ||||
|   cursor: grabbing; | ||||
|   /* Indica que se está arrastrando */ | ||||
| } | ||||
| /* El cursor 'grabbing' se aplica automáticamente por el navegador durante el arrastre */ | ||||
|  | ||||
|  | ||||
| /* --- MEDIA QUERY PARA RESPONSIVE (ENFOQUE FINAL CON CAPAS) --- */ | ||||
| @media (max-width: 800px) { | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| // src/features/legislativas/nacionales/components/MapaNacional.tsx | ||||
| import axios from 'axios'; | ||||
| import { Suspense, useState, useEffect, useCallback, useRef } from 'react'; | ||||
| import { useSuspenseQuery } from '@tanstack/react-query'; | ||||
| @@ -53,13 +54,14 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|     zoom: isMobileView ? 1.5 : 1.05, // 1.5 para móvil, 1.05 para desktop | ||||
|     center: [-65, -40] as PointTuple | ||||
|   }); | ||||
|   const [isPanning, setIsPanning] = useState(false); | ||||
|   const initialProvincePositionRef = useRef<{ zoom: number, center: PointTuple } | null>(null); | ||||
|  | ||||
|   const containerRef = useRef<HTMLDivElement | null>(null); | ||||
|   const lupaRef = useRef<HTMLDivElement | null>(null); | ||||
|   const cabaPathRef = useRef<SVGPathElement | null>(null); | ||||
|   const isAnimatingRef = useRef(false); | ||||
|   const initialLoadRef = useRef(true); // Ref para controlar la carga inicial | ||||
|   const initialLoadRef = useRef(true); | ||||
|  | ||||
|   const [lupaStyle, setLupaStyle] = useState<React.CSSProperties>({ opacity: 0 }); | ||||
|  | ||||
| @@ -80,10 +82,9 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|   useEffect(() => { | ||||
|     if (nivel === 'pais') { | ||||
|       setPosition({ | ||||
|         zoom: isMobileView ? 1.4 : 1.05, // 1.5 para móvil, 1.05 para desktop | ||||
|         zoom: isMobileView ? 1.4 : 1.05, | ||||
|         center: [-65, -40] | ||||
|       }); | ||||
|       // Reseteamos el ref | ||||
|       initialProvincePositionRef.current = null; | ||||
|     } else if (nivel === 'provincia') { | ||||
|       const nombreNormalizado = normalizarTexto(nombreAmbito); | ||||
| @@ -103,11 +104,9 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|       } | ||||
|  | ||||
|       setPosition(provinceConfig); | ||||
|  | ||||
|       // --- Guardar el objeto de posición completo en el ref --- | ||||
|       initialProvincePositionRef.current = provinceConfig; | ||||
|     } | ||||
|   }, [nivel, nombreAmbito, geoDataNacional]); | ||||
|   }, [nivel, nombreAmbito, geoDataNacional, isMobileView]); | ||||
|  | ||||
|   const resultadosNacionalesPorNombre = new Map<string, ResultadoMapaDto>(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d])); | ||||
|   const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null; | ||||
| @@ -142,29 +141,18 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|     }; | ||||
|  | ||||
|     isAnimatingRef.current = true; | ||||
|  | ||||
|     const handleResize = () => { | ||||
|       if (!isAnimatingRef.current) { | ||||
|         updateLupaPosition(); | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     const handleResize = () => { if (!isAnimatingRef.current) updateLupaPosition(); }; | ||||
|     const resizeObserver = new ResizeObserver(handleResize); | ||||
|     if (containerRef.current) { | ||||
|       resizeObserver.observe(containerRef.current); | ||||
|     } | ||||
|     if (containerRef.current) resizeObserver.observe(containerRef.current); | ||||
|  | ||||
|     let timerId: NodeJS.Timeout; | ||||
|  | ||||
|     if (initialLoadRef.current && nivel === 'pais') { | ||||
|       // Carga inicial: posicionar inmediatamente | ||||
|       timerId = setTimeout(() => { | ||||
|         updateLupaPosition(); | ||||
|         isAnimatingRef.current = false; | ||||
|       }, 0); | ||||
|       initialLoadRef.current = false; // Marcar como ya cargado | ||||
|       initialLoadRef.current = false; | ||||
|     } else { | ||||
|       // Transición de vuelta: esperar a que termine la animación | ||||
|       timerId = setTimeout(() => { | ||||
|         updateLupaPosition(); | ||||
|         isAnimatingRef.current = false; | ||||
| @@ -172,77 +160,53 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|     } | ||||
|  | ||||
|     return () => { | ||||
|       if (containerRef.current) { | ||||
|         resizeObserver.unobserve(containerRef.current); | ||||
|       } | ||||
|       if (containerRef.current) resizeObserver.unobserve(containerRef.current); | ||||
|       clearTimeout(timerId); | ||||
|       isAnimatingRef.current = false; | ||||
|     }; | ||||
|   }, [position, nivel]); | ||||
|  | ||||
|   // --- HANDLERS PARA EL ZOOM --- | ||||
|   const handleZoomIn = () => { | ||||
|     setPosition(prev => ({ | ||||
|       ...prev, | ||||
|       zoom: Math.min(prev.zoom * 1.8, 100) // Multiplica el zoom actual, con un límite | ||||
|     })); | ||||
|   }; | ||||
|   const handleZoomIn = () => setPosition(prev => ({ ...prev, zoom: Math.min(prev.zoom * 1.8, 100) })); | ||||
|  | ||||
|   // --- Lógica de reseteo en handleZoomOut --- | ||||
|   const handleZoomOut = () => { | ||||
|     setPosition(prev => { | ||||
|       const newZoom = Math.max(prev.zoom / 1.8, 1); | ||||
|       const initialPos = initialProvincePositionRef.current; | ||||
|  | ||||
|       // Si estamos en una provincia Y el nuevo zoom es igual o menor que el inicial... | ||||
|       if (initialPos && newZoom <= initialPos.zoom) { | ||||
|         // ...reseteamos a la posición inicial guardada (zoom Y centro). | ||||
|         return initialPos; | ||||
|       } | ||||
|  | ||||
|       // Si no, solo actualizamos el zoom. | ||||
|       if (initialPos && newZoom <= initialPos.zoom) return initialPos; | ||||
|       return { ...prev, zoom: newZoom }; | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   const handleMoveEnd = (newPosition: { coordinates: PointTuple, zoom: number }) => { | ||||
|     // Solo actualizamos el centro (coordenadas), no el zoom, al arrastrar | ||||
|     setPosition(prev => ({ ...prev, center: newPosition.coordinates })); | ||||
|     setIsPanning(false); | ||||
|   }; | ||||
|  | ||||
|   const panEnabled = | ||||
|     //isMobileView && | ||||
|     nivel === 'provincia' && | ||||
|     initialProvincePositionRef.current !== null && | ||||
|     position.zoom > initialProvincePositionRef.current.zoom && | ||||
|     !nombreMunicipioSeleccionado; | ||||
|  | ||||
|   const showZoomControls = nivel === 'provincia'; | ||||
|  | ||||
|   // --- FUNCIÓN DE FILTRO --- | ||||
|   const filterInteractionEvents = (event: any) => { | ||||
|     // La librería pasa un objeto de evento que contiene el evento original del navegador. | ||||
|     // Si el evento original es de la rueda del ratón ('wheel'), siempre lo bloqueamos. | ||||
|     if (event.sourceEvent && event.sourceEvent.type === 'wheel') { | ||||
|       return false; | ||||
|     } | ||||
|     // Para cualquier otro evento (arrastre, etc.), la decisión depende de nuestra lógica `panEnabled`. | ||||
|     if (event.sourceEvent && event.sourceEvent.type === 'wheel') return false; | ||||
|     return panEnabled; | ||||
|   }; | ||||
|  | ||||
|   // --- LÓGICA PARA DESHABILITAR EL BOTÓN --- | ||||
|   const showZoomControls = nivel === 'provincia'; | ||||
|   const isZoomOutDisabled = | ||||
|     (nivel === 'provincia' && initialProvincePositionRef.current && position.zoom <= initialProvincePositionRef.current.zoom) || | ||||
|     (nivel === 'pais' && position.zoom <= (isMobileView ? 1.4 : 1.05)); | ||||
|  | ||||
|   const mapContainerClasses = panEnabled ? 'mapa-componente-container map-pannable' : 'mapa-componente-container map-locked'; | ||||
|  | ||||
|   return ( | ||||
|     <div className="mapa-componente-container" ref={containerRef}> | ||||
|     <div className={mapContainerClasses} ref={containerRef}> | ||||
|       {showZoomControls && ( | ||||
|         <div className="zoom-controls-container"> | ||||
|           <button onClick={handleZoomIn} className="zoom-btn" title="Acercar"> | ||||
|             <span className="zoom-icon-wrapper"><BiZoomIn /></span> | ||||
|           </button> | ||||
|            | ||||
|           <button | ||||
|             onClick={handleZoomOut} | ||||
|             className={`zoom-btn ${isZoomOutDisabled ? 'disabled' : ''}`} | ||||
| @@ -265,8 +229,10 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|           <ZoomableGroup | ||||
|             center={position.center} | ||||
|             zoom={position.zoom} | ||||
|             onMoveStart={() => setIsPanning(true)} | ||||
|             onMoveEnd={handleMoveEnd} | ||||
|             filterZoomEvent={filterInteractionEvents} | ||||
|             className={isPanning ? 'panning' : ''} | ||||
|           > | ||||
|             <Geographies geography={geoDataNacional}> | ||||
|               {({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => { | ||||
| @@ -281,9 +247,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|                     geography={geo} | ||||
|                     ref={esCABA ? cabaPathRef : undefined} | ||||
|                     className={`rsm-geography ${nivel !== 'pais' ? 'rsm-geography-faded' : ''}`} | ||||
|                     style={{ | ||||
|                       visibility: esCABA ? 'hidden' : (esProvinciaActiva ? 'hidden' : 'visible'), | ||||
|                     }} | ||||
|                     style={{ visibility: esCABA ? 'hidden' : (esProvinciaActiva ? 'hidden' : 'visible') }} | ||||
|                     fill={nivel === 'pais' ? (resultado?.colorGanador || DEFAULT_MAP_COLOR) : FADED_BACKGROUND_COLOR} | ||||
|                     onClick={() => !esCABA && resultado && onAmbitoSelect(resultado.ambitoId, 'provincia', resultado.ambitoNombre)} | ||||
|                     data-tooltip-id="mapa-tooltip" | ||||
| @@ -321,7 +285,6 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|                 onAmbitoSelect(resultadoCABA.ambitoId, 'provincia', resultadoCABA.ambitoNombre); | ||||
|               } | ||||
|             }; | ||||
|  | ||||
|             return <CabaLupa fillColor={fillColor} onClick={handleClick} />; | ||||
|           })()} | ||||
|         </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user