diff --git a/Elecciones-Web/frontend/src/App.tsx b/Elecciones-Web/frontend/src/App.tsx index 6d33218..5273caf 100644 --- a/Elecciones-Web/frontend/src/App.tsx +++ b/Elecciones-Web/frontend/src/App.tsx @@ -17,6 +17,7 @@ import { DiputadosPorSeccionWidget } from './components/DiputadosPorSeccionWidge import { SenadoresPorSeccionWidget } from './components/SenadoresPorSeccionWidget' import { ConcejalesPorSeccionWidget } from './components/ConcejalesPorSeccionWidget' import { ResultadosTablaDetalladaWidget } from './components/ResultadosTablaDetalladaWidget' +import { ResultadosRankingMunicipioWidget } from './components/ResultadosRankingMunicipioWidget' function App() { return ( @@ -56,6 +57,8 @@ function App() {
+
+ ) diff --git a/Elecciones-Web/frontend/src/apiService.ts b/Elecciones-Web/frontend/src/apiService.ts index 325b021..3622f5f 100644 --- a/Elecciones-Web/frontend/src/apiService.ts +++ b/Elecciones-Web/frontend/src/apiService.ts @@ -1,6 +1,6 @@ // src/apiService.ts import axios from 'axios'; -import type { ApiResponseRankingSeccion, ApiResponseTablaDetallada, ProyeccionBancas, MunicipioSimple, TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker, ApiResponseResultadosPorSeccion } from './types/types'; +import type { ApiResponseRankingMunicipio, ApiResponseRankingSeccion, ApiResponseTablaDetallada, ProyeccionBancas, MunicipioSimple, TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker, ApiResponseResultadosPorSeccion } from './types/types'; /** * URL base para las llamadas a la API. @@ -200,4 +200,9 @@ export const getResultadosTablaDetallada = async (seccionId: string): Promise => { const { data } = await apiClient.get(`/resultados/ranking-por-seccion/${seccionId}`); return data; +}; + +export const getRankingMunicipiosPorSeccion = async (seccionId: string): Promise => { + const { data } = await apiClient.get(`/resultados/ranking-municipios-por-seccion/${seccionId}`); + return data; }; \ No newline at end of file diff --git a/Elecciones-Web/frontend/src/components/DevApp.tsx b/Elecciones-Web/frontend/src/components/DevApp.tsx index b7b4664..f84b65e 100644 --- a/Elecciones-Web/frontend/src/components/DevApp.tsx +++ b/Elecciones-Web/frontend/src/components/DevApp.tsx @@ -16,6 +16,7 @@ import { DiputadosPorSeccionWidget } from './DiputadosPorSeccionWidget' import { SenadoresPorSeccionWidget } from './SenadoresPorSeccionWidget' import { ConcejalesPorSeccionWidget } from './ConcejalesPorSeccionWidget' import { ResultadosTablaDetalladaWidget } from './ResultadosTablaDetalladaWidget' +import { ResultadosRankingMunicipioWidget } from './ResultadosRankingMunicipioWidget' import '../App.css'; @@ -42,7 +43,8 @@ export const DevApp = () => { - + + ); diff --git a/Elecciones-Web/frontend/src/components/ResultadosRankingMunicipioWidget.tsx b/Elecciones-Web/frontend/src/components/ResultadosRankingMunicipioWidget.tsx new file mode 100644 index 0000000..c6c5d43 --- /dev/null +++ b/Elecciones-Web/frontend/src/components/ResultadosRankingMunicipioWidget.tsx @@ -0,0 +1,129 @@ +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 + +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([]); + 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({ + queryKey: ['rankingMunicipiosPorSeccion', selectedSeccion?.value], + queryFn: () => getRankingMunicipiosPorSeccion(selectedSeccion!.value), + enabled: !!selectedSeccion, + }); + + return ( +
+
+

Resultados por Municipio

+