| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  | import { useState, useMemo, useEffect } from 'react'; | 
					
						
							|  |  |  | import { useQuery } from '@tanstack/react-query'; | 
					
						
							|  |  |  | import Select from 'react-select'; | 
					
						
							|  |  |  | import { getSeccionesElectorales, getRankingMunicipiosPorSeccion } from '../apiService'; | 
					
						
							|  |  |  | import type { MunicipioSimple, ApiResponseRankingMunicipio } from '../types/types'; | 
					
						
							|  |  |  | import './ResultadosTablaSeccionWidget.css'; // Reutilizamos el mismo estilo
 | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  | import React from 'react'; | 
					
						
							| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | const customSelectStyles = { | 
					
						
							|  |  |  |   control: (base: any) => ({ ...base, minWidth: '200px', border: '1px solid #ced4da', boxShadow: 'none', '&:hover': { borderColor: '#86b7fe' } }), | 
					
						
							|  |  |  |   menu: (base: any) => ({ ...base, zIndex: 10 }), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const formatPercent = (porcentaje: number) => `${porcentaje.toFixed(2).replace('.', ',')}%`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const ResultadosRankingMunicipioWidget = () => { | 
					
						
							|  |  |  |   const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | 
					
						
							|  |  |  |   const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     const fetchSecciones = async () => { | 
					
						
							|  |  |  |       const seccionesData = await getSeccionesElectorales(); | 
					
						
							|  |  |  |       if (seccionesData && seccionesData.length > 0) { | 
					
						
							|  |  |  |         const orden = new Map([ | 
					
						
							|  |  |  |           ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3], | 
					
						
							|  |  |  |           ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7] | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |         const getOrden = (nombre: string) => { | 
					
						
							|  |  |  |           const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/); | 
					
						
							|  |  |  |           return match ? orden.get(match[0]) ?? 99 : 99; | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre)); | 
					
						
							|  |  |  |         setSecciones(seccionesData); | 
					
						
							|  |  |  |         if (!selectedSeccion) { | 
					
						
							|  |  |  |           setSelectedSeccion({ value: seccionesData[0].id, label: seccionesData[0].nombre }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     fetchSecciones(); | 
					
						
							|  |  |  |   }, [selectedSeccion]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const seccionOptions = useMemo(() => secciones.map(s => ({ value: s.id, label: s.nombre })), [secciones]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const { data: rankingData, isLoading } = useQuery<ApiResponseRankingMunicipio>({ | 
					
						
							|  |  |  |     queryKey: ['rankingMunicipiosPorSeccion', selectedSeccion?.value], | 
					
						
							|  |  |  |     queryFn: () => getRankingMunicipiosPorSeccion(selectedSeccion!.value), | 
					
						
							|  |  |  |     enabled: !!selectedSeccion, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <div className="tabla-resultados-widget"> | 
					
						
							|  |  |  |       <div className="tabla-header"> | 
					
						
							|  |  |  |         <h3>Resultados por Municipio</h3> | 
					
						
							|  |  |  |         <Select | 
					
						
							|  |  |  |           options={seccionOptions} | 
					
						
							|  |  |  |           value={selectedSeccion} | 
					
						
							|  |  |  |           onChange={(option) => setSelectedSeccion(option)} | 
					
						
							|  |  |  |           isLoading={secciones.length === 0} | 
					
						
							|  |  |  |           styles={customSelectStyles} | 
					
						
							|  |  |  |           isSearchable={false} | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <div className="tabla-container"> | 
					
						
							|  |  |  |         {isLoading ? <p>Cargando...</p> : !rankingData || rankingData.categorias.length === 0 ? <p>No hay datos.</p> : ( | 
					
						
							|  |  |  |           <table> | 
					
						
							|  |  |  |             <thead> | 
					
						
							|  |  |  |               {/* --- Fila 1: Nombres de Categorías --- */} | 
					
						
							|  |  |  |               <tr> | 
					
						
							|  |  |  |                 <th rowSpan={2} className="sticky-col municipio-header">Municipio</th> | 
					
						
							|  |  |  |                 {rankingData.categorias.map(cat => ( | 
					
						
							|  |  |  |                   // Cada categoría ahora ocupa 4 columnas (1° Partido, 1° %, 2° Partido, 2° %)
 | 
					
						
							|  |  |  |                   <th key={cat.id} colSpan={4} className="categoria-header"> | 
					
						
							|  |  |  |                     {cat.nombre} | 
					
						
							|  |  |  |                   </th> | 
					
						
							|  |  |  |                 ))} | 
					
						
							|  |  |  |               </tr> | 
					
						
							|  |  |  |               {/* --- Fila 2: Puestos y % --- */} | 
					
						
							|  |  |  |               <tr> | 
					
						
							|  |  |  |                 {rankingData.categorias.flatMap(cat => [ | 
					
						
							|  |  |  |                   <th key={`${cat.id}-p1`} colSpan={2} className="puesto-header">1° Puesto</th>, | 
					
						
							|  |  |  |                   <th key={`${cat.id}-p2`} colSpan={2} className="puesto-header category-divider-header">2° Puesto</th> | 
					
						
							|  |  |  |                 ])} | 
					
						
							|  |  |  |               </tr> | 
					
						
							| 
									
										
										
										
											2025-09-04 17:19:54 -03:00
										 |  |  |               {/* Fila 3: Sub-cabeceras (Partido, %) */} | 
					
						
							| 
									
										
										
										
											2025-09-05 14:53:14 -03:00
										 |  |  |               <tr> | 
					
						
							|  |  |  |                 <th /> | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                   // Usamos un bucle map simple en lugar de flatMap.
 | 
					
						
							|  |  |  |                   // React manejará la creación de un array de nodos sin problemas.
 | 
					
						
							|  |  |  |                   rankingData.categorias.map(cat => ( | 
					
						
							|  |  |  |                     // Usamos un Fragmento (<>) para agrupar los 4 <th> de cada categoría
 | 
					
						
							|  |  |  |                     // sin añadir un nodo extra al DOM.
 | 
					
						
							|  |  |  |                     <React.Fragment key={`subheaders-${cat.id}`}> | 
					
						
							|  |  |  |                       <th className="sub-header">Partido</th> | 
					
						
							|  |  |  |                       <th className="sub-header">%</th> | 
					
						
							|  |  |  |                       <th className="sub-header category-divider-header">Partido</th> | 
					
						
							|  |  |  |                       <th className="sub-header">%</th> | 
					
						
							|  |  |  |                     </React.Fragment> | 
					
						
							|  |  |  |                   )) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               </tr> | 
					
						
							| 
									
										
										
										
											2025-09-04 15:54:00 -03:00
										 |  |  |             </thead> | 
					
						
							|  |  |  |             <tbody> | 
					
						
							|  |  |  |               {rankingData.resultados.map(municipio => ( | 
					
						
							|  |  |  |                 <tr key={municipio.municipioId}> | 
					
						
							|  |  |  |                   <td className="sticky-col">{municipio.municipioNombre}</td> | 
					
						
							|  |  |  |                   {rankingData.categorias.flatMap(cat => { | 
					
						
							|  |  |  |                     const resCategoria = municipio.resultadosPorCategoria[cat.id]; | 
					
						
							|  |  |  |                     const primerPuesto = resCategoria?.ranking[0]; | 
					
						
							|  |  |  |                     const segundoPuesto = resCategoria?.ranking[1]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     return [ | 
					
						
							|  |  |  |                       // --- Celdas para el 1° Puesto ---
 | 
					
						
							|  |  |  |                       <td key={`${municipio.municipioId}-${cat.id}-p1-partido`} className="cell-partido"> | 
					
						
							|  |  |  |                         {primerPuesto?.nombreCorto || '-'} | 
					
						
							|  |  |  |                       </td>, | 
					
						
							|  |  |  |                       <td key={`${municipio.municipioId}-${cat.id}-p1-porc`} className="cell-porcentaje"> | 
					
						
							|  |  |  |                         {primerPuesto ? formatPercent(primerPuesto.porcentaje) : '-'} | 
					
						
							|  |  |  |                       </td>, | 
					
						
							|  |  |  |                       // --- Celdas para el 2° Puesto ---
 | 
					
						
							|  |  |  |                       <td key={`${municipio.municipioId}-${cat.id}-p2-partido`} className="cell-partido category-divider"> | 
					
						
							|  |  |  |                         {segundoPuesto?.nombreCorto || '-'} | 
					
						
							|  |  |  |                       </td>, | 
					
						
							|  |  |  |                       <td key={`${municipio.municipioId}-${cat.id}-p2-porc`} className="cell-porcentaje"> | 
					
						
							|  |  |  |                         {segundoPuesto ? formatPercent(segundoPuesto.porcentaje) : '-'} | 
					
						
							|  |  |  |                       </td> | 
					
						
							|  |  |  |                     ]; | 
					
						
							|  |  |  |                   })} | 
					
						
							|  |  |  |                 </tr> | 
					
						
							|  |  |  |               ))} | 
					
						
							|  |  |  |             </tbody> | 
					
						
							|  |  |  |           </table> | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; |