Feat Selector Modo Tabla
This commit is contained in:
		| @@ -2,9 +2,20 @@ 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 | ||||
| import React from 'react'; | ||||
| import type { MunicipioSimple, ApiResponseRankingMunicipio, RankingPartido } from '../types/types'; | ||||
| import './ResultadosTablaSeccionWidget.css';  | ||||
|  | ||||
| type DisplayMode = 'porcentaje' | 'votos' | 'ambos'; | ||||
| type DisplayOption = { | ||||
|     value: DisplayMode; | ||||
|     label: string; | ||||
| }; | ||||
|  | ||||
| const displayModeOptions: readonly DisplayOption[] = [ | ||||
|     { value: 'porcentaje', label: 'Ver Porcentajes' }, | ||||
|     { value: 'votos', label: 'Ver Votos' }, | ||||
|     { value: 'ambos', label: 'Ver Ambos' }, | ||||
| ]; | ||||
|  | ||||
| const customSelectStyles = { | ||||
|   control: (base: any) => ({ ...base, minWidth: '200px', border: '1px solid #ced4da', boxShadow: 'none', '&:hover': { borderColor: '#86b7fe' } }), | ||||
| @@ -12,10 +23,36 @@ const customSelectStyles = { | ||||
| }; | ||||
|  | ||||
| const formatPercent = (porcentaje: number) => `${porcentaje.toFixed(2).replace('.', ',')}%`; | ||||
| // Nueva función para formatear votos con separador de miles | ||||
| const formatVotos = (votos: number) => votos.toLocaleString('es-AR'); | ||||
|  | ||||
| // --- NUEVO COMPONENTE HELPER PARA RENDERIZAR CELDAS --- | ||||
| const CellRenderer = ({ partido, mode }: { partido?: RankingPartido, mode: DisplayMode }) => { | ||||
|     if (!partido) { | ||||
|         return <span>-</span>; | ||||
|     } | ||||
|  | ||||
|     switch (mode) { | ||||
|         case 'votos': | ||||
|             return <span>{formatVotos(partido.votos)}</span>; | ||||
|         case 'ambos': | ||||
|             return ( | ||||
|                 <div className="cell-ambos"> | ||||
|                     <span>{formatVotos(partido.votos)}</span> | ||||
|                     <small>{formatPercent(partido.porcentaje)}</small> | ||||
|                 </div> | ||||
|             ); | ||||
|         case 'porcentaje': | ||||
|         default: | ||||
|             return <span>{formatPercent(partido.porcentaje)}</span>; | ||||
|     } | ||||
| }; | ||||
|  | ||||
|  | ||||
| export const ResultadosRankingMunicipioWidget = () => { | ||||
|   const [secciones, setSecciones] = useState<MunicipioSimple[]>([]); | ||||
|   const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null); | ||||
|   const [displayMode, setDisplayMode] = useState<DisplayOption>(displayModeOptions[0]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const fetchSecciones = async () => { | ||||
| @@ -48,91 +85,79 @@ export const ResultadosRankingMunicipioWidget = () => { | ||||
|   }); | ||||
|  | ||||
|   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-resultados-widget"> | ||||
|             <div className="tabla-header"> | ||||
|                 <h3>Resultados por Municipio</h3> | ||||
|                 <div className="header-filters"> | ||||
|                     <Select | ||||
|                         options={displayModeOptions} | ||||
|                         value={displayMode} | ||||
|                          | ||||
|                         // --- CORRECCIÓN EN ONCHANGE --- | ||||
|                         // 'option' ahora es del tipo correcto, por lo que no necesitamos aserción. | ||||
|                         onChange={(option) => setDisplayMode(option as DisplayOption)} | ||||
|                          | ||||
|                         styles={customSelectStyles} | ||||
|                         isSearchable={false} | ||||
|                     /> | ||||
|                     <Select | ||||
|                         options={seccionOptions} | ||||
|                         value={selectedSeccion} | ||||
|                         onChange={(option) => setSelectedSeccion(option)} | ||||
|                         isLoading={secciones.length === 0} | ||||
|                         styles={customSelectStyles} | ||||
|                         isSearchable={false} | ||||
|                     /> | ||||
|                 </div> | ||||
|             </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> | ||||
|               {/* Fila 3: Sub-cabeceras (Partido, %) */} | ||||
|               <tr> | ||||
|                 <th  className="sub-header-init" /> | ||||
|                 { | ||||
|                   // 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> | ||||
|             </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]; | ||||
|             <div className="tabla-container"> | ||||
|                 {isLoading ? <p>Cargando...</p> : !rankingData || rankingData.categorias.length === 0 ? <p>No hay datos.</p> : ( | ||||
|                     <table> | ||||
|                         <thead> | ||||
|                             <tr> | ||||
|                                 <th rowSpan={2} className="sticky-col municipio-header">Municipio</th> | ||||
|                                 {rankingData.categorias.map(cat => ( | ||||
|                                     <th key={cat.id} colSpan={2} className="categoria-header">{cat.nombre}</th> | ||||
|                                 ))} | ||||
|                             </tr> | ||||
|                             <tr> | ||||
|                                 {rankingData.categorias.flatMap(cat => [ | ||||
|                                     <th key={`${cat.id}-1`} className="partido-header">1° Puesto</th>, | ||||
|                                     <th key={`${cat.id}-2`} className="partido-header category-divider-header">2° Puesto</th> | ||||
|                                 ])} | ||||
|                             </tr> | ||||
|                         </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> | ||||
|   ); | ||||
|                                         return [ | ||||
|                                             <td key={`${municipio.municipioId}-${cat.id}-1`} className="category-divider"> | ||||
|                                                 <div className="cell-content"> | ||||
|                                                     <span className="cell-partido-nombre">{primerPuesto?.nombreCorto || '-'}</span> | ||||
|                                                     <CellRenderer partido={primerPuesto} mode={displayMode.value} /> | ||||
|                                                 </div> | ||||
|                                             </td>, | ||||
|                                             <td key={`${municipio.municipioId}-${cat.id}-2`}> | ||||
|                                                 <div className="cell-content"> | ||||
|                                                     <span className="cell-partido-nombre">{segundoPuesto?.nombreCorto || '-'}</span> | ||||
|                                                     <CellRenderer partido={segundoPuesto} mode={displayMode.value} /> | ||||
|                                                 </div> | ||||
|                                             </td> | ||||
|                                         ]; | ||||
|                                     })} | ||||
|                                 </tr> | ||||
|                             ))} | ||||
|                         </tbody> | ||||
|                     </table> | ||||
|                 )} | ||||
|             </div> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user