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 {
|
.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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
Reference in New Issue
Block a user