Pre Refinamiento Movil
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
// src/features/legislativas/nacionales/components/MapaNacional.tsx
|
||||
import axios from 'axios';
|
||||
import { Suspense, useState, useEffect, useCallback } from 'react'; // <-- Asegúrate de que useCallback esté importado
|
||||
import { Suspense, useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { geoCentroid } from 'd3-geo';
|
||||
import { feature } from 'topojson-client';
|
||||
import { API_BASE_URL, assetBaseUrl } from '../../../../apiService';
|
||||
import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types';
|
||||
import { MapaProvincial } from './MapaProvincial';
|
||||
import { CabaLupa } from './CabaLupa';
|
||||
|
||||
const DEFAULT_MAP_COLOR = '#E0E0E0';
|
||||
const FADED_BACKGROUND_COLOR = '#F0F0F0';
|
||||
@@ -14,19 +16,44 @@ const normalizarTexto = (texto: string = '') => texto.trim().toUpperCase().norma
|
||||
|
||||
type PointTuple = [number, number];
|
||||
|
||||
const PROVINCE_VIEW_CONFIG: Record<string, { center: PointTuple; zoom: number }> = {
|
||||
"BUENOS AIRES": { center: [-60.5, -37.3], zoom: 5.5 },
|
||||
"SANTA CRUZ": { center: [-69.5, -48.8], zoom: 5 },
|
||||
"CIUDAD AUTONOMA DE BUENOS AIRES": { center: [-58.45, -34.6], zoom: 85 },
|
||||
"CHUBUT": { center: [-68.5, -44.5], zoom: 5.5 },
|
||||
"SANTA FE": { center: [-61, -31.2], zoom: 6 },
|
||||
"CORRIENTES": { center: [-58, -29], zoom: 7 },
|
||||
"RIO NEGRO": { center: [-67.5, -40], zoom: 5.5 },
|
||||
"TIERRA DEL FUEGO": { center: [-66.5, -54.2], zoom: 7 },
|
||||
};
|
||||
|
||||
const LUPA_SIZE_RATIO = 0.2;
|
||||
const MIN_LUPA_SIZE_PX = 100;
|
||||
const MAX_LUPA_SIZE_PX = 180;
|
||||
|
||||
|
||||
interface MapaNacionalProps {
|
||||
eleccionId: number;
|
||||
categoriaId: number;
|
||||
nivel: 'pais' | 'provincia' | 'municipio';
|
||||
nombreAmbito: string;
|
||||
nombreProvinciaActiva: string | undefined | null;
|
||||
provinciaDistritoId: string | null;
|
||||
onAmbitoSelect: (ambitoId: string, nivel: 'provincia' | 'municipio', nombre: string) => void;
|
||||
onVolver: () => void;
|
||||
}
|
||||
|
||||
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, provinciaDistritoId, onAmbitoSelect, onVolver }: MapaNacionalProps) => {
|
||||
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nombreProvinciaActiva, provinciaDistritoId, onAmbitoSelect, onVolver }: MapaNacionalProps) => {
|
||||
const [position, setPosition] = useState({ zoom: 1, center: [-65, -40] as PointTuple });
|
||||
|
||||
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 [lupaStyle, setLupaStyle] = useState<React.CSSProperties>({ opacity: 0 });
|
||||
|
||||
const { data: mapaDataNacional } = useSuspenseQuery<ResultadoMapaDto[]>({
|
||||
queryKey: ['mapaResultados', eleccionId, categoriaId, null],
|
||||
queryFn: async () => {
|
||||
@@ -41,63 +68,171 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, pro
|
||||
queryFn: () => axios.get(`${assetBaseUrl}/maps/argentina-provincias.topojson`).then(res => res.data),
|
||||
});
|
||||
|
||||
const resultadosNacionalesPorNombre = new Map<string, ResultadoMapaDto>(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d]));
|
||||
|
||||
const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null;
|
||||
|
||||
// El useEffect para el zoom provincial y nacional sigue siendo correcto.
|
||||
useEffect(() => {
|
||||
if (nivel === 'pais') {
|
||||
setPosition({ zoom: 1, center: [-65, -40] });
|
||||
} else if (nivel === 'provincia') {
|
||||
setPosition({ zoom: 7, center: [-60.5, -37] });
|
||||
}
|
||||
// La lógica de centrado en municipio se delega al hijo, que llamará a `handleCalculatedCenter`
|
||||
}, [nivel]);
|
||||
const nombreNormalizado = normalizarTexto(nombreAmbito);
|
||||
const manualConfig = PROVINCE_VIEW_CONFIG[nombreNormalizado];
|
||||
|
||||
// **LA SOLUCIÓN CLAVE**: Estabilizamos la función que se pasa al hijo.
|
||||
const handleCalculatedCenter = useCallback((center: PointTuple, zoom: number) => {
|
||||
setPosition({ center, zoom });
|
||||
}, []); // El array de dependencias vacío asegura que la función nunca cambie
|
||||
if (manualConfig) {
|
||||
setPosition(manualConfig);
|
||||
} else {
|
||||
const provinciaGeo = geoDataNacional.objects.provincias.geometries.find((g: any) => normalizarTexto(g.properties.nombre) === nombreNormalizado);
|
||||
if (provinciaGeo) {
|
||||
const provinciaFeature = feature(geoDataNacional, provinciaGeo);
|
||||
const centroid = geoCentroid(provinciaFeature);
|
||||
setPosition({ zoom: 7, center: centroid as PointTuple });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [nivel, nombreAmbito, geoDataNacional]);
|
||||
|
||||
const resultadosNacionalesPorNombre = new Map<string, ResultadoMapaDto>(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d]));
|
||||
const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null;
|
||||
const handleCalculatedCenter = useCallback((center: PointTuple, zoom: number) => { setPosition({ center, zoom }); }, []);
|
||||
|
||||
useEffect(() => {
|
||||
const updateLupaPosition = () => {
|
||||
if (nivel === 'pais' && cabaPathRef.current && containerRef.current) {
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
if (containerRect.width === 0) return;
|
||||
|
||||
const cabaRect = cabaPathRef.current.getBoundingClientRect();
|
||||
const cabaCenterX = (cabaRect.left - containerRect.left) + cabaRect.width / 2;
|
||||
const cabaCenterY = (cabaRect.top - containerRect.top) + cabaRect.height / 2;
|
||||
|
||||
const calculatedSize = containerRect.width * LUPA_SIZE_RATIO;
|
||||
const newLupaSize = Math.max(MIN_LUPA_SIZE_PX, Math.min(calculatedSize, MAX_LUPA_SIZE_PX));
|
||||
|
||||
const horizontalOffset = newLupaSize * 0.5;
|
||||
const verticalOffset = newLupaSize * 0.2;
|
||||
|
||||
setLupaStyle({
|
||||
position: 'absolute',
|
||||
top: `${cabaCenterY - verticalOffset}px`,
|
||||
left: `${cabaCenterX + horizontalOffset}px`,
|
||||
width: `${newLupaSize}px`,
|
||||
opacity: 1,
|
||||
});
|
||||
} else {
|
||||
setLupaStyle({ opacity: 0, pointerEvents: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
isAnimatingRef.current = true;
|
||||
|
||||
const handleResize = () => {
|
||||
if (!isAnimatingRef.current) {
|
||||
updateLupaPosition();
|
||||
}
|
||||
};
|
||||
|
||||
const resizeObserver = new ResizeObserver(handleResize);
|
||||
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
|
||||
} else {
|
||||
// Transición de vuelta: esperar a que termine la animación
|
||||
timerId = setTimeout(() => {
|
||||
updateLupaPosition();
|
||||
isAnimatingRef.current = false;
|
||||
}, 800);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (containerRef.current) {
|
||||
resizeObserver.unobserve(containerRef.current);
|
||||
}
|
||||
clearTimeout(timerId);
|
||||
isAnimatingRef.current = false;
|
||||
};
|
||||
}, [position, nivel]);
|
||||
|
||||
return (
|
||||
<div className="mapa-componente-container">
|
||||
<div className="mapa-componente-container" ref={containerRef}>
|
||||
{nivel !== 'pais' && <button onClick={onVolver} className="mapa-volver-btn">← Volver</button>}
|
||||
<ComposableMap projection="geoMercator" projectionConfig={{ scale: 700, center: [-65, -40] }} style={{ width: "100%", height: "100%" }}>
|
||||
<ZoomableGroup center={position.center} zoom={position.zoom} filterZoomEvent={() => false}>
|
||||
<Geographies geography={geoDataNacional}>
|
||||
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
|
||||
const resultado = resultadosNacionalesPorNombre.get(normalizarTexto(geo.properties.nombre));
|
||||
const esProvinciaActiva = provinciaDistritoId && resultado?.ambitoId === provinciaDistritoId;
|
||||
|
||||
return (
|
||||
<Geography
|
||||
key={geo.rsmKey} geography={geo}
|
||||
className={`rsm-geography ${nivel !== 'pais' ? 'rsm-geography-faded' : ''}`}
|
||||
style={{ visibility: esProvinciaActiva ? 'hidden' : 'visible' }}
|
||||
fill={nivel === 'pais' ? (resultado?.colorGanador || DEFAULT_MAP_COLOR) : FADED_BACKGROUND_COLOR}
|
||||
onClick={() => resultado && onAmbitoSelect(resultado.ambitoId, 'provincia', resultado.ambitoNombre)}
|
||||
<div className="mapa-render-area">
|
||||
<ComposableMap
|
||||
projection="geoMercator"
|
||||
projectionConfig={{ scale: 700, center: [-65, -40] }}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<ZoomableGroup
|
||||
center={position.center}
|
||||
zoom={position.zoom}
|
||||
filterZoomEvent={() => false}
|
||||
>
|
||||
<Geographies geography={geoDataNacional}>
|
||||
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
|
||||
const nombreNormalizado = normalizarTexto(geo.properties.nombre);
|
||||
const esCABA = nombreNormalizado === 'CIUDAD AUTONOMA DE BUENOS AIRES';
|
||||
const resultado = resultadosNacionalesPorNombre.get(nombreNormalizado);
|
||||
const esProvinciaActiva = provinciaDistritoId && resultado?.ambitoId === provinciaDistritoId;
|
||||
|
||||
return (
|
||||
<Geography
|
||||
key={geo.rsmKey}
|
||||
geography={geo}
|
||||
ref={esCABA ? cabaPathRef : undefined}
|
||||
className={`rsm-geography ${nivel !== 'pais' ? 'rsm-geography-faded' : ''}`}
|
||||
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"
|
||||
data-tooltip-content={geo.properties.nombre}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Geographies>
|
||||
|
||||
{provinciaDistritoId && nombreProvinciaActiva && (
|
||||
<Suspense fallback={null}>
|
||||
<MapaProvincial
|
||||
eleccionId={eleccionId}
|
||||
categoriaId={categoriaId}
|
||||
distritoId={provinciaDistritoId}
|
||||
nombreProvincia={nombreProvinciaActiva}
|
||||
nombreMunicipioSeleccionado={nombreMunicipioSeleccionado}
|
||||
onMunicipioSelect={(ambitoId, nombre) => onAmbitoSelect(ambitoId, 'municipio', nombre)}
|
||||
onCalculatedCenter={handleCalculatedCenter}
|
||||
nivel={nivel as 'provincia' | 'municipio'}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Geographies>
|
||||
</Suspense>
|
||||
)}
|
||||
</ZoomableGroup>
|
||||
</ComposableMap>
|
||||
</div>
|
||||
|
||||
{nivel === 'pais' && (
|
||||
<div id="caba-lupa-anchor" className="caba-magnifier-container" style={lupaStyle} ref={lupaRef}>
|
||||
{(() => {
|
||||
const resultadoCABA = resultadosNacionalesPorNombre.get("CIUDAD AUTONOMA DE BUENOS AIRES");
|
||||
const fillColor = resultadoCABA?.colorGanador || DEFAULT_MAP_COLOR;
|
||||
const handleClick = () => {
|
||||
if (resultadoCABA) {
|
||||
onAmbitoSelect(resultadoCABA.ambitoId, 'provincia', resultadoCABA.ambitoNombre);
|
||||
}
|
||||
};
|
||||
|
||||
return <CabaLupa fillColor={fillColor} onClick={handleClick} />;
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{provinciaDistritoId && (
|
||||
<Suspense fallback={null}>
|
||||
<MapaProvincial
|
||||
eleccionId={eleccionId}
|
||||
categoriaId={categoriaId}
|
||||
distritoId={provinciaDistritoId}
|
||||
nombreProvincia={"BUENOS AIRES"} // Esto se podría hacer dinámico si fuera necesario
|
||||
nombreMunicipioSeleccionado={nombreMunicipioSeleccionado}
|
||||
onMunicipioSelect={(ambitoId, nombre) => onAmbitoSelect(ambitoId, 'municipio', nombre)}
|
||||
onCalculatedCenter={handleCalculatedCenter} // Pasamos la función estabilizada
|
||||
nivel={nivel as 'provincia' | 'municipio'} // El cast de tipo sigue siendo necesario y correcto
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</ZoomableGroup>
|
||||
</ComposableMap>
|
||||
<Tooltip id="mapa-tooltip" />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user