192 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // src/features/legislativas/provinciales/ResultaosRankingMunicipioWidget.tsx
 | |
| 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, 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' } }),
 | |
|     menu: (base: any) => ({ ...base, zIndex: 10 }),
 | |
| };
 | |
| 
 | |
| 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 () => {
 | |
|             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>
 | |
|                 <div className="header-filters">
 | |
|                     <Select
 | |
|                         options={displayModeOptions}
 | |
|                         value={displayMode}
 | |
|                         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={3} className="sticky-col municipio-header">Municipio</th>
 | |
|                                 {rankingData.categorias.map(cat => (
 | |
|                                     <th key={cat.id} colSpan={4} className="categoria-header">
 | |
|                                         {cat.nombre}
 | |
|                                     </th>
 | |
|                                 ))}
 | |
|                             </tr>
 | |
|                             {/* --- Fila 2: Puestos --- */}
 | |
|                             <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 y %) --- */}
 | |
|                             <tr>
 | |
|                                 {rankingData.categorias.flatMap(cat => [
 | |
|                                     <th key={`${cat.id}-p1-partido`} className="sub-header">Partido</th>,
 | |
|                                     <th key={`${cat.id}-p1-porc`} className="sub-header">%</th>,
 | |
|                                     <th key={`${cat.id}-p2-partido`} className="sub-header category-divider-header">Partido</th>,
 | |
|                                     <th key={`${cat.id}-p2-porc`} className="sub-header">%</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 primerPuestoRaw = resCategoria?.ranking[0];
 | |
|                                         const segundoPuestoRaw = resCategoria?.ranking[1];
 | |
| 
 | |
|                                         // Asegurarse que los objetos tengan la propiedad 'votos'
 | |
|                                         const primerPuesto = primerPuestoRaw
 | |
|                                             ? {
 | |
|                                                 nombreCorto: primerPuestoRaw.nombreCorto,
 | |
|                                                 porcentaje: primerPuestoRaw.porcentaje,
 | |
|                                                 votos: 'votos' in primerPuestoRaw ? (primerPuestoRaw as any).votos : 0
 | |
|                                             }
 | |
|                                             : undefined;
 | |
|                                         const segundoPuesto = segundoPuestoRaw
 | |
|                                             ? {
 | |
|                                                 nombreCorto: segundoPuestoRaw.nombreCorto,
 | |
|                                                 porcentaje: segundoPuestoRaw.porcentaje,
 | |
|                                                 votos: 'votos' in segundoPuestoRaw ? (segundoPuestoRaw as any).votos : 0
 | |
|                                             }
 | |
|                                             : undefined;
 | |
| 
 | |
|                                         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 ? <CellRenderer partido={primerPuesto} mode={displayMode.value} /> : '-'}
 | |
|                                             </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 ? <CellRenderer partido={segundoPuesto} mode={displayMode.value} /> : '-'}
 | |
|                                             </td>
 | |
|                                         ];
 | |
|                                     })}
 | |
|                                 </tr>
 | |
|                             ))}
 | |
|                         </tbody>
 | |
|                     </table>
 | |
|                 )}
 | |
|             </div>
 | |
|         </div>
 | |
|     );
 | |
| }; |