Preparación Legislativas Nacionales 2025
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// 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 { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { API_BASE_URL, assetBaseUrl } from '../../../../apiService';
|
||||
import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types';
|
||||
import { MapaProvincial } from './MapaProvincial';
|
||||
|
||||
const DEFAULT_MAP_COLOR = '#E0E0E0';
|
||||
const FADED_BACKGROUND_COLOR = '#F0F0F0';
|
||||
const normalizarTexto = (texto: string = '') => texto.trim().toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
|
||||
type PointTuple = [number, number];
|
||||
|
||||
interface MapaNacionalProps {
|
||||
eleccionId: number;
|
||||
categoriaId: number;
|
||||
nivel: 'pais' | 'provincia' | 'municipio';
|
||||
nombreAmbito: string;
|
||||
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) => {
|
||||
const [position, setPosition] = useState({ zoom: 1, center: [-65, -40] as PointTuple });
|
||||
|
||||
const { data: mapaDataNacional } = useSuspenseQuery<ResultadoMapaDto[]>({
|
||||
queryKey: ['mapaResultados', eleccionId, categoriaId, null],
|
||||
queryFn: async () => {
|
||||
const url = `${API_BASE_URL}/elecciones/${eleccionId}/mapa-resultados?categoriaId=${categoriaId}`;
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
|
||||
const { data: geoDataNacional } = useSuspenseQuery<any>({
|
||||
queryKey: ['geoDataNacional'],
|
||||
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]);
|
||||
|
||||
// **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
|
||||
|
||||
return (
|
||||
<div className="mapa-componente-container">
|
||||
{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)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Geographies>
|
||||
|
||||
{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