Feat Front Widgets Refactizados y Ajustes Backend
This commit is contained in:
		
							
								
								
									
										217
									
								
								Elecciones-Web/Restaurar/Nuevos/components/MapaBsAs.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								Elecciones-Web/Restaurar/Nuevos/components/MapaBsAs.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | ||||
| // src/components/MapaBsAs.tsx | ||||
| import { useState, useMemo } from 'react'; | ||||
| import { ComposableMap, Geographies, Geography } from 'react-simple-maps'; | ||||
| import { Tooltip } from 'react-tooltip'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import axios from 'axios'; | ||||
| import type { Feature, Geometry } from 'geojson'; | ||||
| import { geoCentroid } from 'd3-geo'; // Para calcular el centro de cada partido | ||||
| import { useSpring, animated } from 'react-spring'; // Para animar el zoom | ||||
|  | ||||
| //import geoUrl from '/partidos-bsas.topojson'; | ||||
| import './MapaBsAs.css'; | ||||
|  | ||||
| // --- Interfaces y Tipos --- | ||||
| interface ResultadoMapa { | ||||
|   partidoId: string; | ||||
|   agrupacionGanadoraId: string; | ||||
|   porcentajeGanador: number; // Nueva propiedad desde el backend | ||||
| } | ||||
|  | ||||
| interface Agrupacion { | ||||
|   id: string; | ||||
|   nombre: string; | ||||
| } | ||||
|  | ||||
| interface PartidoProperties { | ||||
|   id: number; | ||||
|   departamento: string; | ||||
|   cabecera: string; // Asegúrate de que coincida con tu topojson | ||||
|   provincia: string; | ||||
| } | ||||
|  | ||||
| type PartidoGeography = Feature<Geometry, PartidoProperties> & { rsmKey: string }; | ||||
|  | ||||
| const PALETA_COLORES: { [key: string]: [number, number, number] } = { | ||||
|   'default': [214, 214, 218] // RGB para el color por defecto | ||||
| }; | ||||
|  | ||||
| const INITIAL_PROJECTION = { | ||||
|   center: [-59.8, -37.0] as [number, number], | ||||
|   scale: 5400, | ||||
| }; | ||||
|  | ||||
| const MapaBsAs = () => { | ||||
|   const [selectedPartido, setSelectedPartido] = useState<PartidoGeography | null>(null); | ||||
|   const [projectionConfig, setProjectionConfig] = useState(INITIAL_PROJECTION); | ||||
|  | ||||
|   // --- Carga de Datos --- | ||||
|   const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapa[]>({ | ||||
|     queryKey: ['mapaResultados'], | ||||
|     queryFn: async () => { | ||||
|       const { data } = await axios.get('http://localhost:5217/api/Resultados/mapa'); | ||||
|       return data; | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   const { data: geoData, isLoading: isLoadingGeo } = useQuery({ | ||||
|     queryKey: ['mapaGeoData'], | ||||
|     queryFn: async () => { | ||||
|       const { data } = await axios.get('/partidos-bsas.topojson');  | ||||
|       return data; | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery<Agrupacion[]>({ | ||||
|     queryKey: ['catalogoAgrupaciones'], | ||||
|     queryFn: async () => { | ||||
|       const { data } = await axios.get('http://localhost:5217/api/Catalogos/agrupaciones'); | ||||
|       return data; | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   const { nombresAgrupaciones, coloresPartidos } = useMemo(() => { | ||||
|     if (!agrupacionesData) return { nombresAgrupaciones: {}, coloresPartidos: {} }; | ||||
|  | ||||
|     const nombres = agrupacionesData.reduce((acc, agrupacion) => { | ||||
|       acc[agrupacion.id] = agrupacion.nombre; | ||||
|       return acc; | ||||
|     }, {} as { [key: string]: string }); | ||||
|  | ||||
|     const colores = agrupacionesData.reduce((acc, agrupacion, index) => { | ||||
|       const baseColor = [255, 87, 51, 51, 255, 87, 51, 87, 255, 255, 51, 161, 161, 51, 255, 255, 195, 0, 199, 0, 57, 144, 12, 63, 88, 24, 69]; | ||||
|       acc[agrupacion.nombre] = [baseColor[index*3], baseColor[index*3+1], baseColor[index*3+2]]; | ||||
|       return acc; | ||||
|     }, {} as { [key: string]: [number, number, number] }); | ||||
|      | ||||
|     colores['default'] = [214, 214, 218]; | ||||
|     return { nombresAgrupaciones: nombres, coloresPartidos: colores }; | ||||
|   }, [agrupacionesData]); | ||||
|  | ||||
|   const animatedProps = useSpring({ | ||||
|     to: { scale: projectionConfig.scale, cx: projectionConfig.center[0], cy: projectionConfig.center[1] }, | ||||
|     config: { tension: 170, friction: 26 }, | ||||
|   }); | ||||
|  | ||||
|   if (isLoadingResultados || isLoadingGeo || isLoadingAgrupaciones) { | ||||
|     return <div>Cargando datos del mapa...</div>; | ||||
|   } | ||||
|  | ||||
|   const getPartyStyle = (partidoIdGeo: string) => { | ||||
|     const resultado = resultadosData?.find(r => r.partidoId === partidoIdGeo); | ||||
|     if (!resultado) { | ||||
|       return { fill: `rgb(${PALETA_COLORES.default.join(',')})` }; | ||||
|     } | ||||
|     const nombreAgrupacion = nombresAgrupaciones[resultado.agrupacionGanadoraId] || 'Otro'; | ||||
|     const baseColor = coloresPartidos[nombreAgrupacion] || PALETA_COLORES.default; | ||||
|      | ||||
|     // Calcula la opacidad basada en el porcentaje. 0.4 (débil) a 1.0 (fuerte) | ||||
|     const opacity = 0.4 + (resultado.porcentajeGanador / 100) * 0.6; | ||||
|      | ||||
|     return { fill: `rgba(${baseColor.join(',')}, ${opacity})` }; | ||||
|   }; | ||||
|  | ||||
|   const handleGeographyClick = (geo: PartidoGeography) => { | ||||
|     const centroid = geoCentroid(geo); | ||||
|     setSelectedPartido(geo); | ||||
|     setProjectionConfig({ | ||||
|       center: centroid, | ||||
|       scale: 18000, // Zoom más cercano | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   const handleReset = () => { | ||||
|     setSelectedPartido(null); | ||||
|     setProjectionConfig(INITIAL_PROJECTION); | ||||
|   }; | ||||
|    | ||||
|   return ( | ||||
|     <div className="mapa-wrapper"> | ||||
|       <div className="mapa-container"> | ||||
|         <animated.div style={{ | ||||
|           width: '100%', | ||||
|           height: '100%', | ||||
|           transform: animatedProps.scale.to(s => `scale(${s / INITIAL_PROJECTION.scale})`), | ||||
|         }}> | ||||
|           <ComposableMap | ||||
|             projection="geoMercator" | ||||
|             projectionConfig={{ | ||||
|               scale: animatedProps.scale, | ||||
|               center: [animatedProps.cx, animatedProps.cy], | ||||
|             }} | ||||
|             className="rsm-svg" | ||||
|           > | ||||
|             <Geographies geography={geoData}> | ||||
|               {({ geographies }: { geographies: PartidoGeography[] }) => | ||||
|                 geographies.map((geo) => { | ||||
|                   const partidoId = String(geo.properties.id); | ||||
|                   const partidoNombre = geo.properties.departamento; | ||||
|                   const resultado = resultadosData?.find(r => r.partidoId === partidoId); | ||||
|                   const agrupacionNombre = resultado ? (nombresAgrupaciones[resultado.agrupacionGanadoraId] || 'Desconocido') : 'Sin datos'; | ||||
|  | ||||
|                   return ( | ||||
|                     <Geography | ||||
|                       key={geo.rsmKey} | ||||
|                       geography={geo} | ||||
|                       data-tooltip-id="partido-tooltip" | ||||
|                       data-tooltip-content={`${partidoNombre}: ${agrupacionNombre}`} | ||||
|                       fill={getPartyStyle(partidoId).fill} | ||||
|                       stroke="#FFF" | ||||
|                       className="rsm-geography" | ||||
|                       style={{ | ||||
|                         default: { outline: 'none' }, | ||||
|                         hover: { outline: 'none', stroke: '#FF5722', strokeWidth: 2, fill: getPartyStyle(partidoId).fill }, | ||||
|                         pressed: { outline: 'none' }, | ||||
|                       }} | ||||
|                       onClick={() => handleGeographyClick(geo)} | ||||
|                     /> | ||||
|                   ); | ||||
|                 }) | ||||
|               } | ||||
|             </Geographies> | ||||
|           </ComposableMap> | ||||
|         </animated.div> | ||||
|         <Tooltip id="partido-tooltip" /> | ||||
|       </div> | ||||
|  | ||||
|       <div className="info-panel"> | ||||
|         <button onClick={handleReset}>Resetear Vista</button> | ||||
|         {selectedPartido ? ( | ||||
|           <div> | ||||
|             <h3>{selectedPartido.properties.departamento}</h3> | ||||
|             <p><strong>Cabecera:</strong> {selectedPartido.properties.cabecera}</p> | ||||
|             <p><strong>ID:</strong> {selectedPartido.properties.id}</p> | ||||
|             {/* Aquí mostrarías más datos del partido seleccionado */} | ||||
|           </div> | ||||
|         ) : ( | ||||
|           <div> | ||||
|             <h3>Provincia de Buenos Aires</h3> | ||||
|             <p>Selecciona un partido para ver más detalles.</p> | ||||
|           </div> | ||||
|         )} | ||||
|         <Legend colores={coloresPartidos} /> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|  | ||||
| // Componente de Leyenda separado | ||||
| const Legend = ({ colores }: { colores: { [key: string]: [number, number, number] } }) => { | ||||
|     return ( | ||||
|         <div className="legend"> | ||||
|             <h4>Leyenda</h4> | ||||
|             {Object.entries(colores).map(([nombre, color]) => { | ||||
|                 if (nombre === 'default') return null; | ||||
|                 return ( | ||||
|                     <div key={nombre} className="legend-item"> | ||||
|                         <div className="legend-color-box" style={{ backgroundColor: `rgb(${color.join(',')})` }} /> | ||||
|                         <span>{nombre}</span> | ||||
|                     </div> | ||||
|                 ); | ||||
|             })} | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| export default MapaBsAs; | ||||
		Reference in New Issue
	
	Block a user