82 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			82 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | // src/features/legislativas/nacionales/components/MapaProvincial.tsx
 | ||
|  | import axios from 'axios'; | ||
|  | import { useEffect } from 'react'; | ||
|  | import { useSuspenseQuery } from '@tanstack/react-query'; | ||
|  | import { Geographies, Geography } from 'react-simple-maps'; | ||
|  | import { geoCentroid } from 'd3-geo'; | ||
|  | import { feature } from 'topojson-client'; | ||
|  | import { API_BASE_URL, assetBaseUrl } from '../../../../apiService'; | ||
|  | import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types'; | ||
|  | 
 | ||
|  | const DEFAULT_MAP_COLOR = '#E0E0E0'; | ||
|  | const normalizarTexto = (texto: string = ''): string => texto.trim().toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); | ||
|  | type PointTuple = [number, number]; | ||
|  | 
 | ||
|  | interface MapaProvincialProps { | ||
|  |   eleccionId: number; | ||
|  |   categoriaId: number; | ||
|  |   distritoId: string; | ||
|  |   nombreProvincia: string; | ||
|  |   nombreMunicipioSeleccionado: string | null; | ||
|  |   nivel: 'provincia' | 'municipio'; | ||
|  |   onMunicipioSelect: (ambitoId: string, nombre: string) => void; | ||
|  |   onCalculatedCenter: (center: PointTuple, zoom: number) => void; | ||
|  | } | ||
|  | 
 | ||
|  | export const MapaProvincial = ({ eleccionId, categoriaId, distritoId, nombreProvincia, nombreMunicipioSeleccionado, nivel, onMunicipioSelect, onCalculatedCenter }: MapaProvincialProps) => { | ||
|  |   const { data: mapaData = [] } = useSuspenseQuery<ResultadoMapaDto[]>({ | ||
|  |     queryKey: ['mapaResultados', eleccionId, categoriaId, distritoId], | ||
|  |     queryFn: async () => { | ||
|  |       const url = `${API_BASE_URL}/elecciones/${eleccionId}/mapa-resultados?categoriaId=${categoriaId}&distritoId=${distritoId}`; | ||
|  |       const response = await axios.get(url); | ||
|  |       return response.data; | ||
|  |     }, | ||
|  |   }); | ||
|  |    | ||
|  |   // El nombre del archivo ahora es completamente dinámico
 | ||
|  |   const { data: geoData } = useSuspenseQuery<any>({ | ||
|  |     queryKey: ['geoDataProvincial', nombreProvincia], | ||
|  |     queryFn: async () => { | ||
|  |       const nombreNormalizado = nombreProvincia.toLowerCase().replace(/ /g, '_'); | ||
|  |       const mapFile = `departamentos-${nombreNormalizado}.topojson`; | ||
|  |       return axios.get(`${assetBaseUrl}/maps/${mapFile}`).then(res => res.data); | ||
|  |     }, | ||
|  |   }); | ||
|  | 
 | ||
|  |   // useEffect para calcular y "exportar" la posición del municipio al padre
 | ||
|  |   useEffect(() => { | ||
|  |     if (nivel === 'municipio' && geoData?.objects && nombreMunicipioSeleccionado) { | ||
|  |       const geometries = geoData.objects[Object.keys(geoData.objects)[0]].geometries; | ||
|  |       const municipioGeo = geometries.find((g: any) => normalizarTexto(g.properties.departamento) === normalizarTexto(nombreMunicipioSeleccionado)); | ||
|  |       if (municipioGeo) { | ||
|  |           const municipioFeature = feature(geoData, municipioGeo); | ||
|  |           const centroid = geoCentroid(municipioFeature); | ||
|  |           // Usamos un zoom genérico alto para cualquier municipio
 | ||
|  |           onCalculatedCenter(centroid as PointTuple, 40); | ||
|  |       } | ||
|  |     } | ||
|  |   }, [nivel, nombreMunicipioSeleccionado, geoData, onCalculatedCenter]); | ||
|  | 
 | ||
|  |   const resultadosPorNombre = new Map<string, ResultadoMapaDto>(mapaData.map(d => [normalizarTexto(d.ambitoNombre), d])); | ||
|  | 
 | ||
|  |   return ( | ||
|  |     <Geographies geography={geoData}> | ||
|  |       {({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => { | ||
|  |         const resultado = resultadosPorNombre.get(normalizarTexto(geo.properties.departamento)); | ||
|  |         const esSeleccionado = nombreMunicipioSeleccionado ? normalizarTexto(geo.properties.departamento) === normalizarTexto(nombreMunicipioSeleccionado) : false; | ||
|  | 
 | ||
|  |         return ( | ||
|  |           <Geography | ||
|  |             key={geo.rsmKey} | ||
|  |             geography={geo} | ||
|  |             className={`rsm-geography ${esSeleccionado ? 'selected' : ''} ${nombreMunicipioSeleccionado && !esSeleccionado ? 'rsm-geography-faded-municipality' : ''}`} | ||
|  |             fill={resultado?.colorGanador || DEFAULT_MAP_COLOR} | ||
|  |             onClick={resultado ? () => onMunicipioSelect(resultado.ambitoId.toString(), resultado.ambitoNombre) : undefined} | ||
|  |             data-tooltip-id="mapa-tooltip" | ||
|  |             data-tooltip-content={geo.properties.departamento} | ||
|  |           /> | ||
|  |         ); | ||
|  |       })} | ||
|  |     </Geographies> | ||
|  |   ); | ||
|  | }; |