137 lines
6.4 KiB
TypeScript
137 lines
6.4 KiB
TypeScript
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';
|
|
|
|
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>
|
|
{/* Fila 3: Sub-cabeceras (Partido, %) */}
|
|
<tr>
|
|
{
|
|
// 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];
|
|
|
|
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>
|
|
);
|
|
}; |