Fix Arrastre Mapa en Zoom

This commit is contained in:
2025-09-22 09:08:43 -03:00
parent 3750d1a56d
commit 5a8bee52d5
3 changed files with 29 additions and 66 deletions

View File

@@ -296,7 +296,7 @@
} }
/* --- MAPA Y ELEMENTOS ASOCIADOS (sin cambios) --- */ /* --- MAPA Y ELEMENTOS ASOCIADOS --- */
.mapa-componente-container { .mapa-componente-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -329,6 +329,11 @@
transition: transform 0.75s ease-in-out; 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 { .panel-main-content.panel-collapsed .mapa-column {
flex: 1 1 100%; flex: 1 1 100%;
} }
@@ -368,7 +373,6 @@
} }
.rsm-geography { .rsm-geography {
cursor: pointer;
stroke: #000000; stroke: #000000;
stroke-width: 0.25px; stroke-width: 0.25px;
outline: none; outline: none;
@@ -579,18 +583,14 @@
/* --- ESTILOS DE CURSOR PARA EL ARRASTRE DEL MAPA --- */ /* --- ESTILOS DE CURSOR PARA EL ARRASTRE DEL MAPA --- */
.map-locked .rsm-geography { .map-locked .rsm-geography {
cursor: pointer; cursor: pointer;
/* Cursor normal de clic */
} }
.map-pannable .rsm-geography { .map-pannable .rsm-geography {
cursor: grab; cursor: grab;
/* Indica que el mapa se puede arrastrar */
} }
.map-pannable .rsm-geography:active { /* El cursor 'grabbing' se aplica automáticamente por el navegador durante el arrastre */
cursor: grabbing;
/* Indica que se está arrastrando */
}
/* --- MEDIA QUERY PARA RESPONSIVE (ENFOQUE FINAL CON CAPAS) --- */ /* --- MEDIA QUERY PARA RESPONSIVE (ENFOQUE FINAL CON CAPAS) --- */
@media (max-width: 800px) { @media (max-width: 800px) {

View File

@@ -1,3 +1,4 @@
// src/features/legislativas/nacionales/components/MapaNacional.tsx
import axios from 'axios'; import axios from 'axios';
import { Suspense, useState, useEffect, useCallback, useRef } from 'react'; import { Suspense, useState, useEffect, useCallback, useRef } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query'; 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 zoom: isMobileView ? 1.5 : 1.05, // 1.5 para móvil, 1.05 para desktop
center: [-65, -40] as PointTuple center: [-65, -40] as PointTuple
}); });
const [isPanning, setIsPanning] = useState(false);
const initialProvincePositionRef = useRef<{ zoom: number, center: PointTuple } | null>(null); const initialProvincePositionRef = useRef<{ zoom: number, center: PointTuple } | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
const lupaRef = useRef<HTMLDivElement | null>(null); const lupaRef = useRef<HTMLDivElement | null>(null);
const cabaPathRef = useRef<SVGPathElement | null>(null); const cabaPathRef = useRef<SVGPathElement | null>(null);
const isAnimatingRef = useRef(false); 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 }); const [lupaStyle, setLupaStyle] = useState<React.CSSProperties>({ opacity: 0 });
@@ -80,10 +82,9 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
useEffect(() => { useEffect(() => {
if (nivel === 'pais') { if (nivel === 'pais') {
setPosition({ 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] center: [-65, -40]
}); });
// Reseteamos el ref
initialProvincePositionRef.current = null; initialProvincePositionRef.current = null;
} else if (nivel === 'provincia') { } else if (nivel === 'provincia') {
const nombreNormalizado = normalizarTexto(nombreAmbito); const nombreNormalizado = normalizarTexto(nombreAmbito);
@@ -103,11 +104,9 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
} }
setPosition(provinceConfig); setPosition(provinceConfig);
// --- Guardar el objeto de posición completo en el ref ---
initialProvincePositionRef.current = provinceConfig; initialProvincePositionRef.current = provinceConfig;
} }
}, [nivel, nombreAmbito, geoDataNacional]); }, [nivel, nombreAmbito, geoDataNacional, isMobileView]);
const resultadosNacionalesPorNombre = new Map<string, ResultadoMapaDto>(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d])); const resultadosNacionalesPorNombre = new Map<string, ResultadoMapaDto>(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d]));
const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null; const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null;
@@ -142,29 +141,18 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
}; };
isAnimatingRef.current = true; isAnimatingRef.current = true;
const handleResize = () => { if (!isAnimatingRef.current) updateLupaPosition(); };
const handleResize = () => {
if (!isAnimatingRef.current) {
updateLupaPosition();
}
};
const resizeObserver = new ResizeObserver(handleResize); const resizeObserver = new ResizeObserver(handleResize);
if (containerRef.current) { if (containerRef.current) resizeObserver.observe(containerRef.current);
resizeObserver.observe(containerRef.current);
}
let timerId: NodeJS.Timeout; let timerId: NodeJS.Timeout;
if (initialLoadRef.current && nivel === 'pais') { if (initialLoadRef.current && nivel === 'pais') {
// Carga inicial: posicionar inmediatamente
timerId = setTimeout(() => { timerId = setTimeout(() => {
updateLupaPosition(); updateLupaPosition();
isAnimatingRef.current = false; isAnimatingRef.current = false;
}, 0); }, 0);
initialLoadRef.current = false; // Marcar como ya cargado initialLoadRef.current = false;
} else { } else {
// Transición de vuelta: esperar a que termine la animación
timerId = setTimeout(() => { timerId = setTimeout(() => {
updateLupaPosition(); updateLupaPosition();
isAnimatingRef.current = false; isAnimatingRef.current = false;
@@ -172,77 +160,53 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
} }
return () => { return () => {
if (containerRef.current) { if (containerRef.current) resizeObserver.unobserve(containerRef.current);
resizeObserver.unobserve(containerRef.current);
}
clearTimeout(timerId); clearTimeout(timerId);
isAnimatingRef.current = false; isAnimatingRef.current = false;
}; };
}, [position, nivel]); }, [position, nivel]);
// --- HANDLERS PARA EL ZOOM --- const handleZoomIn = () => setPosition(prev => ({ ...prev, zoom: Math.min(prev.zoom * 1.8, 100) }));
const handleZoomIn = () => {
setPosition(prev => ({
...prev,
zoom: Math.min(prev.zoom * 1.8, 100) // Multiplica el zoom actual, con un límite
}));
};
// --- Lógica de reseteo en handleZoomOut ---
const handleZoomOut = () => { const handleZoomOut = () => {
setPosition(prev => { setPosition(prev => {
const newZoom = Math.max(prev.zoom / 1.8, 1); const newZoom = Math.max(prev.zoom / 1.8, 1);
const initialPos = initialProvincePositionRef.current; const initialPos = initialProvincePositionRef.current;
if (initialPos && newZoom <= initialPos.zoom) return initialPos;
// 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.
return { ...prev, zoom: newZoom }; return { ...prev, zoom: newZoom };
}); });
}; };
const handleMoveEnd = (newPosition: { coordinates: PointTuple, zoom: number }) => { const handleMoveEnd = (newPosition: { coordinates: PointTuple, zoom: number }) => {
// Solo actualizamos el centro (coordenadas), no el zoom, al arrastrar
setPosition(prev => ({ ...prev, center: newPosition.coordinates })); setPosition(prev => ({ ...prev, center: newPosition.coordinates }));
setIsPanning(false);
}; };
const panEnabled = const panEnabled =
//isMobileView &&
nivel === 'provincia' && nivel === 'provincia' &&
initialProvincePositionRef.current !== null && initialProvincePositionRef.current !== null &&
position.zoom > initialProvincePositionRef.current.zoom && position.zoom > initialProvincePositionRef.current.zoom &&
!nombreMunicipioSeleccionado; !nombreMunicipioSeleccionado;
const showZoomControls = nivel === 'provincia';
// --- FUNCIÓN DE FILTRO ---
const filterInteractionEvents = (event: any) => { const filterInteractionEvents = (event: any) => {
// La librería pasa un objeto de evento que contiene el evento original del navegador. if (event.sourceEvent && event.sourceEvent.type === 'wheel') return false;
// 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`.
return panEnabled; return panEnabled;
}; };
// --- LÓGICA PARA DESHABILITAR EL BOTÓN --- const showZoomControls = nivel === 'provincia';
const isZoomOutDisabled = const isZoomOutDisabled =
(nivel === 'provincia' && initialProvincePositionRef.current && position.zoom <= initialProvincePositionRef.current.zoom) || (nivel === 'provincia' && initialProvincePositionRef.current && position.zoom <= initialProvincePositionRef.current.zoom) ||
(nivel === 'pais' && position.zoom <= (isMobileView ? 1.4 : 1.05)); (nivel === 'pais' && position.zoom <= (isMobileView ? 1.4 : 1.05));
const mapContainerClasses = panEnabled ? 'mapa-componente-container map-pannable' : 'mapa-componente-container map-locked';
return ( return (
<div className="mapa-componente-container" ref={containerRef}> <div className={mapContainerClasses} ref={containerRef}>
{showZoomControls && ( {showZoomControls && (
<div className="zoom-controls-container"> <div className="zoom-controls-container">
<button onClick={handleZoomIn} className="zoom-btn" title="Acercar"> <button onClick={handleZoomIn} className="zoom-btn" title="Acercar">
<span className="zoom-icon-wrapper"><BiZoomIn /></span> <span className="zoom-icon-wrapper"><BiZoomIn /></span>
</button> </button>
<button <button
onClick={handleZoomOut} onClick={handleZoomOut}
className={`zoom-btn ${isZoomOutDisabled ? 'disabled' : ''}`} className={`zoom-btn ${isZoomOutDisabled ? 'disabled' : ''}`}
@@ -265,8 +229,10 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
<ZoomableGroup <ZoomableGroup
center={position.center} center={position.center}
zoom={position.zoom} zoom={position.zoom}
onMoveStart={() => setIsPanning(true)}
onMoveEnd={handleMoveEnd} onMoveEnd={handleMoveEnd}
filterZoomEvent={filterInteractionEvents} filterZoomEvent={filterInteractionEvents}
className={isPanning ? 'panning' : ''}
> >
<Geographies geography={geoDataNacional}> <Geographies geography={geoDataNacional}>
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => { {({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
@@ -281,9 +247,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
geography={geo} geography={geo}
ref={esCABA ? cabaPathRef : undefined} ref={esCABA ? cabaPathRef : undefined}
className={`rsm-geography ${nivel !== 'pais' ? 'rsm-geography-faded' : ''}`} className={`rsm-geography ${nivel !== 'pais' ? 'rsm-geography-faded' : ''}`}
style={{ style={{ visibility: esCABA ? 'hidden' : (esProvinciaActiva ? 'hidden' : 'visible') }}
visibility: esCABA ? 'hidden' : (esProvinciaActiva ? 'hidden' : 'visible'),
}}
fill={nivel === 'pais' ? (resultado?.colorGanador || DEFAULT_MAP_COLOR) : FADED_BACKGROUND_COLOR} fill={nivel === 'pais' ? (resultado?.colorGanador || DEFAULT_MAP_COLOR) : FADED_BACKGROUND_COLOR}
onClick={() => !esCABA && resultado && onAmbitoSelect(resultado.ambitoId, 'provincia', resultado.ambitoNombre)} onClick={() => !esCABA && resultado && onAmbitoSelect(resultado.ambitoId, 'provincia', resultado.ambitoNombre)}
data-tooltip-id="mapa-tooltip" data-tooltip-id="mapa-tooltip"
@@ -321,7 +285,6 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
onAmbitoSelect(resultadoCABA.ambitoId, 'provincia', resultadoCABA.ambitoNombre); onAmbitoSelect(resultadoCABA.ambitoId, 'provincia', resultadoCABA.ambitoNombre);
} }
}; };
return <CabaLupa fillColor={fillColor} onClick={handleClick} />; return <CabaLupa fillColor={fillColor} onClick={handleClick} />;
})()} })()}
</div> </div>

View File

@@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7d2922aaeb546ad280af958d81394ab1715a3267")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+3750d1a56d3311ec92c79dc6cb564a0b8a68239c")]
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")] [assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]