Preparación Legislativas Nacionales 2025
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
// src/apiService.ts
|
||||
import axios from 'axios';
|
||||
import type { ApiResponseRankingMunicipio, ApiResponseRankingSeccion, ApiResponseTablaDetallada, ProyeccionBancas, MunicipioSimple, TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker, ApiResponseResultadosPorSeccion } from './types/types';
|
||||
import type { ApiResponseRankingMunicipio, ApiResponseRankingSeccion, ApiResponseTablaDetallada, ProyeccionBancas, MunicipioSimple, TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker, ApiResponseResultadosPorSeccion, PanelElectoralDto } from './types/types';
|
||||
|
||||
/**
|
||||
* URL base para las llamadas a la API.
|
||||
@@ -84,14 +84,14 @@ export interface ResultadoDetalleSeccion {
|
||||
color: string | null;
|
||||
}
|
||||
|
||||
export const getResumenProvincial = async (): Promise<CategoriaResumen[]> => {
|
||||
const response = await apiClient.get('/resultados/provincia/02');
|
||||
export const getResumenProvincial = async (eleccionId: number): Promise<CategoriaResumen[]> => {
|
||||
const response = await apiClient.get(`/elecciones/${eleccionId}/provincia/02`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getBancasPorSeccion = async (seccionId: string, camara: 'diputados' | 'senadores'): Promise<ProyeccionBancas> => {
|
||||
const { data } = await apiClient.get(`/resultados/bancas-por-seccion/${seccionId}/${camara}`);
|
||||
return data;
|
||||
export const getBancasPorSeccion = async (eleccionId: number, seccionId: string, camara: 'diputados' | 'senadores'): Promise<ProyeccionBancas> => {
|
||||
const { data } = await apiClient.get(`/elecciones/${eleccionId}/bancas-por-seccion/${seccionId}/${camara}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -140,13 +140,13 @@ export const getMesasPorEstablecimiento = async (establecimientoId: string): Pro
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getComposicionCongreso = async (): Promise<ComposicionData> => {
|
||||
const response = await apiClient.get('/resultados/composicion-congreso');
|
||||
export const getComposicionCongreso = async (eleccionId: number): Promise<ComposicionData> => {
|
||||
const response = await apiClient.get(`/elecciones/${eleccionId}/composicion-congreso`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getBancadasDetalle = async (): Promise<BancadaDetalle[]> => {
|
||||
const response = await apiClient.get('/resultados/bancadas-detalle');
|
||||
export const getBancadasDetalle = async (eleccionId: number): Promise<BancadaDetalle[]> => {
|
||||
const response = await apiClient.get(`/elecciones/${eleccionId}/bancadas-detalle`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -155,24 +155,18 @@ export const getConfiguracionPublica = async (): Promise<ConfiguracionPublica> =
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getResultadosPorSeccion = async (seccionId: string, categoriaId: number): Promise<ApiResponseResultadosPorSeccion> => {
|
||||
const response = await apiClient.get(`/resultados/seccion-resultados/${seccionId}?categoriaId=${categoriaId}`);
|
||||
export const getResultadosPorSeccion = async (eleccionId: number, seccionId: string, categoriaId: number): Promise<ApiResponseResultadosPorSeccion> => {
|
||||
const response = await apiClient.get(`/elecciones/${eleccionId}/seccion-resultados/${seccionId}?categoriaId=${categoriaId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getDetalleSeccion = async (seccionId: string, categoriaId: number): Promise<ResultadoDetalleSeccion[]> => {
|
||||
const response = await apiClient.get(`/resultados/seccion/${seccionId}?categoriaId=${categoriaId}`);
|
||||
export const getDetalleSeccion = async (eleccionId: number, seccionId: string, categoriaId: number): Promise<ResultadoDetalleSeccion[]> => {
|
||||
const response = await apiClient.get(`/elecciones/${eleccionId}/seccion/${seccionId}?categoriaId=${categoriaId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getResultadosPorMunicipioYCategoria = async (municipioId: string, categoriaId: number): Promise<ResultadoTicker[]> => {
|
||||
const response = await apiClient.get(`/resultados/partido/${municipioId}?categoriaId=${categoriaId}`);
|
||||
return response.data.resultados;
|
||||
};
|
||||
|
||||
export const getResultadosPorMunicipio = async (municipioId: string, categoriaId: number): Promise<ResultadoTicker[]> => {
|
||||
const response = await apiClient.get(`/resultados/partido/${municipioId}?categoriaId=${categoriaId}`);
|
||||
// La respuesta es un objeto, nosotros extraemos el array de resultados
|
||||
export const getResultadosPorMunicipio = async (eleccionId: number, municipioId: string, categoriaId: number): Promise<ResultadoTicker[]> => {
|
||||
const response = await apiClient.get(`/elecciones/${eleccionId}/partido/${municipioId}?categoriaId=${categoriaId}`);
|
||||
return response.data.resultados;
|
||||
};
|
||||
|
||||
@@ -213,4 +207,18 @@ export const getRankingMunicipiosPorSeccion = async (seccionId: string): Promise
|
||||
export const getEstablecimientosPorMunicipio = async (municipioId: string): Promise<CatalogoItem[]> => {
|
||||
const response = await apiClient.get(`/catalogos/establecimientos-por-municipio/${municipioId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getPanelElectoral = async (eleccionId: number, ambitoId: string | null, categoriaId: number): Promise<PanelElectoralDto> => {
|
||||
|
||||
// Construimos la URL base
|
||||
let url = ambitoId
|
||||
? `/elecciones/${eleccionId}/panel/${ambitoId}`
|
||||
: `/elecciones/${eleccionId}/panel`;
|
||||
|
||||
// Añadimos categoriaId como un query parameter
|
||||
url += `?categoriaId=${categoriaId}`;
|
||||
|
||||
const { data } = await apiClient.get(url);
|
||||
return data;
|
||||
};
|
||||
@@ -1,23 +1,23 @@
|
||||
// src/components/DevApp.tsx
|
||||
import { BancasWidget } from './BancasWidget'
|
||||
import { CongresoWidget } from './CongresoWidget'
|
||||
import MapaBsAs from './MapaBsAs'
|
||||
import { DipSenTickerWidget } from './DipSenTickerWidget'
|
||||
import { TelegramaWidget } from './TelegramaWidget'
|
||||
import { ConcejalesWidget } from './ConcejalesWidget'
|
||||
import MapaBsAsSecciones from './MapaBsAsSecciones'
|
||||
import { SenadoresWidget } from './SenadoresWidget'
|
||||
import { DiputadosWidget } from './DiputadosWidget'
|
||||
import { ResumenGeneralWidget } from './ResumenGeneralWidget'
|
||||
import { SenadoresTickerWidget } from './SenadoresTickerWidget'
|
||||
import { DiputadosTickerWidget } from './DiputadosTickerWidget'
|
||||
import { ConcejalesTickerWidget } from './ConcejalesTickerWidget'
|
||||
import { DiputadosPorSeccionWidget } from './DiputadosPorSeccionWidget'
|
||||
import { SenadoresPorSeccionWidget } from './SenadoresPorSeccionWidget'
|
||||
import { ConcejalesPorSeccionWidget } from './ConcejalesPorSeccionWidget'
|
||||
import { ResultadosTablaDetalladaWidget } from './ResultadosTablaDetalladaWidget'
|
||||
import { ResultadosRankingMunicipioWidget } from './ResultadosRankingMunicipioWidget'
|
||||
import '../App.css';
|
||||
// src/components/common/DevApp.tsx
|
||||
import { BancasWidget } from '../../features/legislativas/provinciales/BancasWidget'
|
||||
import { CongresoWidget } from '../../features/legislativas/provinciales/CongresoWidget'
|
||||
import MapaBsAs from '../../features/legislativas/provinciales/MapaBsAs'
|
||||
import { DipSenTickerWidget } from '../../features/legislativas/provinciales/DipSenTickerWidget'
|
||||
import { TelegramaWidget } from '../../features/legislativas/provinciales/TelegramaWidget'
|
||||
import { ConcejalesWidget } from '../../features/legislativas/provinciales/ConcejalesWidget'
|
||||
import MapaBsAsSecciones from '../../features/legislativas/provinciales/MapaBsAsSecciones'
|
||||
import { SenadoresWidget } from '../../features/legislativas/provinciales/SenadoresWidget'
|
||||
import { DiputadosWidget } from '../../features/legislativas/provinciales/DiputadosWidget'
|
||||
import { ResumenGeneralWidget } from '../../features/legislativas/provinciales/ResumenGeneralWidget'
|
||||
import { SenadoresTickerWidget } from '../../features/legislativas/provinciales/SenadoresTickerWidget'
|
||||
import { DiputadosTickerWidget } from '../../features/legislativas/provinciales/DiputadosTickerWidget'
|
||||
import { ConcejalesTickerWidget } from '../../features/legislativas/provinciales/ConcejalesTickerWidget'
|
||||
import { DiputadosPorSeccionWidget } from '../../features/legislativas/provinciales/DiputadosPorSeccionWidget'
|
||||
import { SenadoresPorSeccionWidget } from '../../features/legislativas/provinciales/SenadoresPorSeccionWidget'
|
||||
import { ConcejalesPorSeccionWidget } from '../../features/legislativas/provinciales/ConcejalesPorSeccionWidget'
|
||||
import { ResultadosTablaDetalladaWidget } from '../../features/legislativas/provinciales/ResultadosTablaDetalladaWidget'
|
||||
import { ResultadosRankingMunicipioWidget } from '../../features/legislativas/provinciales/ResultadosRankingMunicipioWidget'
|
||||
import '../../App.css';
|
||||
|
||||
|
||||
export const DevApp = () => {
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/components/ImageWithFallback.tsx
|
||||
// src/components/common/ImageWithFallback.tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface Props extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/components/ParliamentLayout.tsx
|
||||
// src/components/common/ParliamentLayout.tsx
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { assetBaseUrl } from '../apiService';
|
||||
import { assetBaseUrl } from '../../apiService';
|
||||
import { handleImageFallback } from './imageFallback';
|
||||
|
||||
// Interfaces (no cambian)
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/components/SenateLayout.tsx
|
||||
// src/components/common/SenateLayout.tsx
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { handleImageFallback } from './imageFallback';
|
||||
import { assetBaseUrl } from '../apiService';
|
||||
import { assetBaseUrl } from '../../apiService';
|
||||
|
||||
// Interfaces
|
||||
interface SeatFillData {
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/components/imageFallback.ts
|
||||
// src/components/common/imageFallback.ts
|
||||
|
||||
export function handleImageFallback(selector: string, fallbackImageUrl: string) {
|
||||
// Le decimos a TypeScript que el resultado será una lista de elementos de imagen HTML
|
||||
@@ -0,0 +1,15 @@
|
||||
// src/features/legislativas/rovinciales/DevAppLegislativas.tsx
|
||||
import { PanelNacionalWidget } from './nacionales/PanelNacionalWidget';
|
||||
import './DevAppStyle.css'
|
||||
|
||||
export const DevAppLegislativas = () => {
|
||||
return (
|
||||
<div className="container">
|
||||
<h1>Il visualizzatore di widget - Elecciones Nacionales 2025</h1>
|
||||
|
||||
{/* Le pasamos el ID de la elección que queremos visualizar.
|
||||
Para tus datos de prueba provinciales, este ID es 1. */}
|
||||
<PanelNacionalWidget eleccionId={2} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
.container{
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/* src/features/legislativas/nacionales/PanelNaciona.css */
|
||||
.panel-nacional-container {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
/* Nuevo contenedor para alinear título y selector */
|
||||
.header-top-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.panel-header h1 {
|
||||
font-size: 1.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.categoria-selector {
|
||||
min-width: 220px;
|
||||
/* Ancho del selector */
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.breadcrumb-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #007bff;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
.panel-main-content {
|
||||
display: flex;
|
||||
height: 75vh;
|
||||
min-height: 500px;
|
||||
transition: all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Columna del mapa */
|
||||
.mapa-column {
|
||||
flex: 2; /* Por defecto, ocupa 2/3 del espacio */
|
||||
position: relative;
|
||||
transition: flex 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Columna de resultados */
|
||||
.resultados-column {
|
||||
flex: 1; /* Por defecto, ocupa 1/3 */
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.5s ease-in-out;
|
||||
min-width: 320px; /* Un ancho mínimo para que no se comprima demasiado */
|
||||
}
|
||||
|
||||
/* --- NUEVOS ESTILOS --- */
|
||||
.mapa-componente-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mapa-volver-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 10;
|
||||
padding: 8px 12px;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.partido-fila {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
gap: 1rem; /* Añade un espacio entre logo, info y stats */
|
||||
}
|
||||
|
||||
.partido-logo {
|
||||
flex-shrink: 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.partido-logo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.partido-info-wrapper {
|
||||
flex-grow: 1; /* Permite que esta sección crezca */
|
||||
flex-shrink: 1; /* Permite que se encoja si es necesario */
|
||||
min-width: 0; /* <-- TRUCO CLAVE DE FLEXBOX para que text-overflow funcione */
|
||||
}
|
||||
|
||||
.partido-nombre {
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
white-space: nowrap; /* <-- No permitir que el texto salte de línea */
|
||||
overflow: hidden; /* <-- Ocultar el texto que se desborda */
|
||||
text-overflow: ellipsis; /* <-- Añadir "..." al final */
|
||||
}
|
||||
|
||||
.candidato-nombre {
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.partido-barra-background {
|
||||
height: 15px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 5px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.partido-barra-foreground {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.partido-stats {
|
||||
flex-shrink: 0; /* <-- MUY IMPORTANTE: Evita que este bloque se encoja */
|
||||
text-align: right;
|
||||
min-width: 100px; /* Asegura que siempre tenga espacio suficiente */
|
||||
}
|
||||
|
||||
.partido-porcentaje {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.partido-votos {
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.panel-estado-recuento {
|
||||
margin-top: auto;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.estado-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.estado-item span {
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.estado-item strong {
|
||||
font-size: 1.2rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.rsm-zoomable-group {
|
||||
transition: transform 0.75s ease-in-out;
|
||||
}
|
||||
|
||||
* Contenedor principal del contenido */
|
||||
.panel-main-content {
|
||||
display: flex;
|
||||
height: 70vh;
|
||||
min-height: 500px;
|
||||
transition: all 0.5s ease-in-out; /* Transición suave para el layout */
|
||||
}
|
||||
|
||||
/* Columna del mapa */
|
||||
.mapa-column {
|
||||
flex: 2; /* Por defecto, ocupa 2/3 del espacio */
|
||||
position: relative;
|
||||
transition: flex 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Columna de resultados */
|
||||
.resultados-column {
|
||||
flex: 1; /* Por defecto, ocupa 1/3 */
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.5s ease-in-out;
|
||||
min-width: 320px; /* Un ancho mínimo para que no se comprima demasiado */
|
||||
}
|
||||
|
||||
/* --- ESTADO COLAPSADO --- */
|
||||
/* Cuando el panel principal tiene la clase 'panel-collapsed' */
|
||||
.panel-main-content.panel-collapsed .mapa-column {
|
||||
flex: 1 1 100%; /* El mapa ocupa todo el ancho */
|
||||
}
|
||||
|
||||
.panel-main-content.panel-collapsed .resultados-column {
|
||||
flex-basis: 0;
|
||||
min-width: 0;
|
||||
max-width: 0;
|
||||
padding: 0;
|
||||
overflow: hidden; /* Oculta el contenido para que no se desborde */
|
||||
}
|
||||
|
||||
/* --- Estilo del botón para colapsar --- */
|
||||
.panel-toggle-btn {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
transform: translateY(-50%);
|
||||
z-index: 10;
|
||||
width: 30px;
|
||||
height: 50px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: white;
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.panel-toggle-btn:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.rsm-geography {
|
||||
cursor: pointer;
|
||||
stroke: #000000;
|
||||
stroke-width: 0.2px;
|
||||
outline: none;
|
||||
transition: filter 0.2s ease-in-out, stroke 0.2s ease-in-out, stroke-width 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* --- ESTADO HOVER (Sutil) --- */
|
||||
/* Se aplica solo si la geografía NO está seleccionada */
|
||||
.rsm-geography:not(.selected):hover {
|
||||
filter: brightness(1.10);
|
||||
stroke: #0000ff;
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
|
||||
/* --- ESTADO SELECCIONADO (Foco) --- */
|
||||
/* Clase que añadiremos desde React para el municipio en foco */
|
||||
.rsm-geography.selected {
|
||||
stroke: #0000ff; /* Borde negro para el seleccionado */
|
||||
stroke-width: 0.5px; /* <-- Borde más grueso para destacar */
|
||||
filter: none; /* Quitamos cualquier otro filtro para que se vea nítido */
|
||||
pointer-events: none; /* Desactivamos eventos para que no interfiera el hover */
|
||||
}
|
||||
|
||||
/* Reglas para los mapas atenuados (sin cambios) */
|
||||
.rsm-geography-faded,
|
||||
.rsm-geography-faded-municipality {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rsm-geography-faded:hover,
|
||||
.rsm-geography-faded-municipality:hover {
|
||||
filter: none;
|
||||
stroke: #FFFFFF;
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
|
||||
.partido-barra-foreground {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Spinner para la transición entre mapas */
|
||||
.transition-spinner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.5); /* Fondo blanco semitransparente */
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* Estilo del spinner en sí mismo */
|
||||
.transition-spinner::after {
|
||||
content: '';
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(0, 0, 0, 0.2);
|
||||
border-top-color: #007bff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// src/features/legislativas/nacionales/PanelNacionalWidget.tsx
|
||||
import { useMemo, useState, Suspense } from 'react';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'; // <-- CAMBIO CLAVE
|
||||
import { getPanelElectoral } from '../../../apiService';
|
||||
import { MapaNacional } from './components/MapaNacional';
|
||||
import { PanelResultados } from './components/PanelResultados';
|
||||
import { Breadcrumbs } from './components/Breadcrumbs';
|
||||
import './PanelNacional.css';
|
||||
import Select from 'react-select';
|
||||
import type { PanelElectoralDto } from '../../../types/types';
|
||||
|
||||
interface PanelNacionalWidgetProps {
|
||||
eleccionId: number;
|
||||
}
|
||||
|
||||
type AmbitoState = {
|
||||
id: string | null;
|
||||
nivel: 'pais' | 'provincia' | 'municipio';
|
||||
nombre: string;
|
||||
provinciaNombre?: string;
|
||||
provinciaDistritoId?: string | null;
|
||||
};
|
||||
|
||||
const CATEGORIAS_NACIONALES = [
|
||||
{ value: 2, label: 'Diputados Nacionales' },
|
||||
{ value: 1, label: 'Senadores Nacionales' },
|
||||
];
|
||||
|
||||
// Creamos un componente interno para poder usar Suspense correctamente
|
||||
const PanelContenido = ({ eleccionId, ambitoActual, categoriaId }: { eleccionId: number, ambitoActual: AmbitoState, categoriaId: number }) => {
|
||||
// Este hook ahora suspenderá el renderizado si los datos no están listos
|
||||
const { data } = useSuspenseQuery<PanelElectoralDto>({
|
||||
queryKey: ['panelElectoral', eleccionId, ambitoActual.id, categoriaId],
|
||||
queryFn: () => getPanelElectoral(eleccionId, ambitoActual.id, categoriaId),
|
||||
});
|
||||
|
||||
return (
|
||||
<PanelResultados
|
||||
resultados={data.resultadosPanel}
|
||||
estadoRecuento={data.estadoRecuento}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const PanelNacionalWidget = ({ eleccionId }: PanelNacionalWidgetProps) => {
|
||||
const [ambitoActual, setAmbitoActual] = useState<AmbitoState>({ id: null, nivel: 'pais', nombre: 'Argentina', provinciaDistritoId: null });
|
||||
const [categoriaId, setCategoriaId] = useState<number>(2);
|
||||
const [isPanelOpen, setIsPanelOpen] = useState(true);
|
||||
|
||||
const handleAmbitoSelect = (nuevoAmbitoId: string, nuevoNivel: 'provincia' | 'municipio', nuevoNombre: string) => {
|
||||
setAmbitoActual(prev => ({
|
||||
id: nuevoAmbitoId,
|
||||
nivel: nuevoNivel,
|
||||
nombre: nuevoNombre,
|
||||
provinciaNombre: nuevoNivel === 'municipio' ? prev.nombre : (nuevoNivel === 'provincia' ? nuevoNombre : undefined),
|
||||
provinciaDistritoId: nuevoNivel === 'provincia' ? nuevoAmbitoId : prev.provinciaDistritoId
|
||||
}));
|
||||
};
|
||||
|
||||
const handleResetToPais = () => {
|
||||
setAmbitoActual({ id: null, nivel: 'pais', nombre: 'Argentina', provinciaDistritoId: null });
|
||||
};
|
||||
|
||||
const handleVolverAProvincia = () => {
|
||||
if (ambitoActual.provinciaDistritoId && ambitoActual.provinciaNombre) {
|
||||
setAmbitoActual({
|
||||
id: ambitoActual.provinciaDistritoId,
|
||||
nivel: 'provincia',
|
||||
nombre: ambitoActual.provinciaNombre,
|
||||
provinciaDistritoId: ambitoActual.provinciaDistritoId
|
||||
});
|
||||
} else {
|
||||
handleResetToPais();
|
||||
}
|
||||
};
|
||||
|
||||
const selectedCategoria = useMemo(() =>
|
||||
CATEGORIAS_NACIONALES.find(c => c.value === categoriaId),
|
||||
[categoriaId]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="panel-nacional-container">
|
||||
<header className="panel-header">
|
||||
<div className="header-top-row">
|
||||
<h1>Resultados elecciones {ambitoActual.nombre}</h1>
|
||||
<Select
|
||||
options={CATEGORIAS_NACIONALES}
|
||||
value={selectedCategoria}
|
||||
onChange={(option) => option && setCategoriaId(option.value)}
|
||||
className="categoria-selector"
|
||||
/>
|
||||
</div>
|
||||
<Breadcrumbs
|
||||
nivel={ambitoActual.nivel}
|
||||
nombreAmbito={ambitoActual.nombre}
|
||||
nombreProvincia={ambitoActual.provinciaNombre}
|
||||
onReset={handleResetToPais}
|
||||
onVolverProvincia={handleVolverAProvincia}
|
||||
/>
|
||||
</header>
|
||||
<main className={`panel-main-content ${!isPanelOpen ? 'panel-collapsed' : ''}`}>
|
||||
<div className="mapa-column">
|
||||
<button
|
||||
className="panel-toggle-btn"
|
||||
onClick={() => setIsPanelOpen(!isPanelOpen)}
|
||||
title={isPanelOpen ? "Ocultar panel" : "Mostrar panel"}
|
||||
>
|
||||
{isPanelOpen ? '›' : '‹'}
|
||||
</button>
|
||||
<Suspense fallback={<div className="spinner" />}>
|
||||
<MapaNacional
|
||||
eleccionId={eleccionId}
|
||||
categoriaId={categoriaId}
|
||||
nivel={ambitoActual.nivel}
|
||||
nombreAmbito={ambitoActual.nombre}
|
||||
provinciaDistritoId={ambitoActual.provinciaDistritoId ?? null}
|
||||
onAmbitoSelect={handleAmbitoSelect}
|
||||
onVolver={ambitoActual.nivel === 'municipio' ? handleVolverAProvincia : handleResetToPais}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className="resultados-column">
|
||||
<Suspense fallback={<div className="spinner" />}>
|
||||
<PanelContenido
|
||||
eleccionId={eleccionId}
|
||||
ambitoActual={ambitoActual}
|
||||
categoriaId={categoriaId}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// src/features/legislativas/nacionales/components/AnimatedNumber.tsx
|
||||
import { useAnimatedNumber } from '../hooks/useAnimatedNumber';
|
||||
|
||||
interface AnimatedNumberProps {
|
||||
value: number;
|
||||
formatter: (value: number) => string;
|
||||
}
|
||||
|
||||
export const AnimatedNumber = ({ value, formatter }: AnimatedNumberProps) => {
|
||||
const animatedValue = useAnimatedNumber(value);
|
||||
return <span>{formatter(animatedValue)}</span>;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
// src/features/legislativas/nacionales/components/Breadcrumbs.tsx
|
||||
interface BreadcrumbsProps {
|
||||
nivel: 'pais' | 'provincia' | 'municipio';
|
||||
nombreAmbito: string;
|
||||
nombreProvincia?: string;
|
||||
onReset: () => void;
|
||||
onVolverProvincia: () => void;
|
||||
}
|
||||
|
||||
export const Breadcrumbs = ({ nivel, nombreAmbito, nombreProvincia, onReset, onVolverProvincia }: BreadcrumbsProps) => {
|
||||
return (
|
||||
<div className="breadcrumbs">
|
||||
{nivel !== 'pais' && (
|
||||
<>
|
||||
<button onClick={onReset} className="breadcrumb-link">Argentina</button>
|
||||
<span className="breadcrumb-separator">{'>'}</span>
|
||||
</>
|
||||
)}
|
||||
{nivel === 'municipio' && nombreProvincia && (
|
||||
<>
|
||||
<button onClick={onVolverProvincia} className="breadcrumb-link">{nombreProvincia}</button>
|
||||
<span className="breadcrumb-separator">{'>'}</span>
|
||||
</>
|
||||
)}
|
||||
<span className="breadcrumb-actual">{nombreAmbito}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
// src/features/legislativas/nacionales/components/MapaNacional.tsx
|
||||
import axios from 'axios';
|
||||
import { Suspense, useState, useEffect, useCallback } from 'react'; // <-- Asegúrate de que useCallback esté importado
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { API_BASE_URL, assetBaseUrl } from '../../../../apiService';
|
||||
import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types';
|
||||
import { MapaProvincial } from './MapaProvincial';
|
||||
|
||||
const DEFAULT_MAP_COLOR = '#E0E0E0';
|
||||
const FADED_BACKGROUND_COLOR = '#F0F0F0';
|
||||
const normalizarTexto = (texto: string = '') => texto.trim().toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
|
||||
type PointTuple = [number, number];
|
||||
|
||||
interface MapaNacionalProps {
|
||||
eleccionId: number;
|
||||
categoriaId: number;
|
||||
nivel: 'pais' | 'provincia' | 'municipio';
|
||||
nombreAmbito: string;
|
||||
provinciaDistritoId: string | null;
|
||||
onAmbitoSelect: (ambitoId: string, nivel: 'provincia' | 'municipio', nombre: string) => void;
|
||||
onVolver: () => void;
|
||||
}
|
||||
|
||||
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, provinciaDistritoId, onAmbitoSelect, onVolver }: MapaNacionalProps) => {
|
||||
const [position, setPosition] = useState({ zoom: 1, center: [-65, -40] as PointTuple });
|
||||
|
||||
const { data: mapaDataNacional } = useSuspenseQuery<ResultadoMapaDto[]>({
|
||||
queryKey: ['mapaResultados', eleccionId, categoriaId, null],
|
||||
queryFn: async () => {
|
||||
const url = `${API_BASE_URL}/elecciones/${eleccionId}/mapa-resultados?categoriaId=${categoriaId}`;
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
|
||||
const { data: geoDataNacional } = useSuspenseQuery<any>({
|
||||
queryKey: ['geoDataNacional'],
|
||||
queryFn: () => axios.get(`${assetBaseUrl}/maps/argentina-provincias.topojson`).then(res => res.data),
|
||||
});
|
||||
|
||||
const resultadosNacionalesPorNombre = new Map<string, ResultadoMapaDto>(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d]));
|
||||
|
||||
const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null;
|
||||
|
||||
// El useEffect para el zoom provincial y nacional sigue siendo correcto.
|
||||
useEffect(() => {
|
||||
if (nivel === 'pais') {
|
||||
setPosition({ zoom: 1, center: [-65, -40] });
|
||||
} else if (nivel === 'provincia') {
|
||||
setPosition({ zoom: 7, center: [-60.5, -37] });
|
||||
}
|
||||
// La lógica de centrado en municipio se delega al hijo, que llamará a `handleCalculatedCenter`
|
||||
}, [nivel]);
|
||||
|
||||
// **LA SOLUCIÓN CLAVE**: Estabilizamos la función que se pasa al hijo.
|
||||
const handleCalculatedCenter = useCallback((center: PointTuple, zoom: number) => {
|
||||
setPosition({ center, zoom });
|
||||
}, []); // El array de dependencias vacío asegura que la función nunca cambie
|
||||
|
||||
return (
|
||||
<div className="mapa-componente-container">
|
||||
{nivel !== 'pais' && <button onClick={onVolver} className="mapa-volver-btn">← Volver</button>}
|
||||
<ComposableMap projection="geoMercator" projectionConfig={{ scale: 700, center: [-65, -40] }} style={{ width: "100%", height: "100%" }}>
|
||||
<ZoomableGroup center={position.center} zoom={position.zoom} filterZoomEvent={() => false}>
|
||||
<Geographies geography={geoDataNacional}>
|
||||
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
|
||||
const resultado = resultadosNacionalesPorNombre.get(normalizarTexto(geo.properties.nombre));
|
||||
const esProvinciaActiva = provinciaDistritoId && resultado?.ambitoId === provinciaDistritoId;
|
||||
|
||||
return (
|
||||
<Geography
|
||||
key={geo.rsmKey} geography={geo}
|
||||
className={`rsm-geography ${nivel !== 'pais' ? 'rsm-geography-faded' : ''}`}
|
||||
style={{ visibility: esProvinciaActiva ? 'hidden' : 'visible' }}
|
||||
fill={nivel === 'pais' ? (resultado?.colorGanador || DEFAULT_MAP_COLOR) : FADED_BACKGROUND_COLOR}
|
||||
onClick={() => resultado && onAmbitoSelect(resultado.ambitoId, 'provincia', resultado.ambitoNombre)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Geographies>
|
||||
|
||||
{provinciaDistritoId && (
|
||||
<Suspense fallback={null}>
|
||||
<MapaProvincial
|
||||
eleccionId={eleccionId}
|
||||
categoriaId={categoriaId}
|
||||
distritoId={provinciaDistritoId}
|
||||
nombreProvincia={"BUENOS AIRES"} // Esto se podría hacer dinámico si fuera necesario
|
||||
nombreMunicipioSeleccionado={nombreMunicipioSeleccionado}
|
||||
onMunicipioSelect={(ambitoId, nombre) => onAmbitoSelect(ambitoId, 'municipio', nombre)}
|
||||
onCalculatedCenter={handleCalculatedCenter} // Pasamos la función estabilizada
|
||||
nivel={nivel as 'provincia' | 'municipio'} // El cast de tipo sigue siendo necesario y correcto
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</ZoomableGroup>
|
||||
</ComposableMap>
|
||||
<Tooltip id="mapa-tooltip" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
// src/features/legislativas/nacionales/components/MapaProvincial.tsx
|
||||
import axios from 'axios';
|
||||
import { useEffect } from 'react';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { Geographies, Geography } from 'react-simple-maps';
|
||||
import { geoCentroid } from 'd3-geo';
|
||||
import { feature } from 'topojson-client';
|
||||
import { API_BASE_URL, assetBaseUrl } from '../../../../apiService';
|
||||
import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types';
|
||||
|
||||
const DEFAULT_MAP_COLOR = '#E0E0E0';
|
||||
const normalizarTexto = (texto: string = ''): string => texto.trim().toUpperCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
type PointTuple = [number, number];
|
||||
|
||||
interface MapaProvincialProps {
|
||||
eleccionId: number;
|
||||
categoriaId: number;
|
||||
distritoId: string;
|
||||
nombreProvincia: string;
|
||||
nombreMunicipioSeleccionado: string | null;
|
||||
nivel: 'provincia' | 'municipio';
|
||||
onMunicipioSelect: (ambitoId: string, nombre: string) => void;
|
||||
onCalculatedCenter: (center: PointTuple, zoom: number) => void;
|
||||
}
|
||||
|
||||
export const MapaProvincial = ({ eleccionId, categoriaId, distritoId, nombreProvincia, nombreMunicipioSeleccionado, nivel, onMunicipioSelect, onCalculatedCenter }: MapaProvincialProps) => {
|
||||
const { data: mapaData = [] } = useSuspenseQuery<ResultadoMapaDto[]>({
|
||||
queryKey: ['mapaResultados', eleccionId, categoriaId, distritoId],
|
||||
queryFn: async () => {
|
||||
const url = `${API_BASE_URL}/elecciones/${eleccionId}/mapa-resultados?categoriaId=${categoriaId}&distritoId=${distritoId}`;
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
|
||||
// El nombre del archivo ahora es completamente dinámico
|
||||
const { data: geoData } = useSuspenseQuery<any>({
|
||||
queryKey: ['geoDataProvincial', nombreProvincia],
|
||||
queryFn: async () => {
|
||||
const nombreNormalizado = nombreProvincia.toLowerCase().replace(/ /g, '_');
|
||||
const mapFile = `departamentos-${nombreNormalizado}.topojson`;
|
||||
return axios.get(`${assetBaseUrl}/maps/${mapFile}`).then(res => res.data);
|
||||
},
|
||||
});
|
||||
|
||||
// useEffect para calcular y "exportar" la posición del municipio al padre
|
||||
useEffect(() => {
|
||||
if (nivel === 'municipio' && geoData?.objects && nombreMunicipioSeleccionado) {
|
||||
const geometries = geoData.objects[Object.keys(geoData.objects)[0]].geometries;
|
||||
const municipioGeo = geometries.find((g: any) => normalizarTexto(g.properties.departamento) === normalizarTexto(nombreMunicipioSeleccionado));
|
||||
if (municipioGeo) {
|
||||
const municipioFeature = feature(geoData, municipioGeo);
|
||||
const centroid = geoCentroid(municipioFeature);
|
||||
// Usamos un zoom genérico alto para cualquier municipio
|
||||
onCalculatedCenter(centroid as PointTuple, 40);
|
||||
}
|
||||
}
|
||||
}, [nivel, nombreMunicipioSeleccionado, geoData, onCalculatedCenter]);
|
||||
|
||||
const resultadosPorNombre = new Map<string, ResultadoMapaDto>(mapaData.map(d => [normalizarTexto(d.ambitoNombre), d]));
|
||||
|
||||
return (
|
||||
<Geographies geography={geoData}>
|
||||
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
|
||||
const resultado = resultadosPorNombre.get(normalizarTexto(geo.properties.departamento));
|
||||
const esSeleccionado = nombreMunicipioSeleccionado ? normalizarTexto(geo.properties.departamento) === normalizarTexto(nombreMunicipioSeleccionado) : false;
|
||||
|
||||
return (
|
||||
<Geography
|
||||
key={geo.rsmKey}
|
||||
geography={geo}
|
||||
className={`rsm-geography ${esSeleccionado ? 'selected' : ''} ${nombreMunicipioSeleccionado && !esSeleccionado ? 'rsm-geography-faded-municipality' : ''}`}
|
||||
fill={resultado?.colorGanador || DEFAULT_MAP_COLOR}
|
||||
onClick={resultado ? () => onMunicipioSelect(resultado.ambitoId.toString(), resultado.ambitoNombre) : undefined}
|
||||
data-tooltip-id="mapa-tooltip"
|
||||
data-tooltip-content={geo.properties.departamento}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Geographies>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// src/features/legislativas/nacionales/components/PanelResultados.tsx
|
||||
import type { ResultadoTicker, EstadoRecuentoTicker } from '../../../../types/types';
|
||||
import { ImageWithFallback } from '../../../../components/common/ImageWithFallback';
|
||||
import { assetBaseUrl } from '../../../../apiService';
|
||||
import { AnimatedNumber } from './AnimatedNumber';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
const formatVotes = (num: number) => Math.round(num).toLocaleString('es-AR');
|
||||
|
||||
interface PanelResultadosProps {
|
||||
resultados: ResultadoTicker[];
|
||||
estadoRecuento: EstadoRecuentoTicker;
|
||||
}
|
||||
|
||||
export const PanelResultados = ({ resultados, estadoRecuento }: PanelResultadosProps) => {
|
||||
return (
|
||||
<div className="panel-resultados">
|
||||
<div className="panel-partidos-container">
|
||||
{resultados.map(partido => (
|
||||
<div key={partido.id} className="partido-fila">
|
||||
<div className="partido-logo">
|
||||
<ImageWithFallback src={partido.logoUrl || undefined} fallbackSrc={`${assetBaseUrl}/default-avatar.png`} alt={partido.nombre} />
|
||||
</div>
|
||||
<div className="partido-info-wrapper">
|
||||
<span className="partido-nombre">{partido.nombreCorto || partido.nombre}</span>
|
||||
{partido.nombreCandidato && <span className="candidato-nombre">{partido.nombreCandidato}</span>}
|
||||
<div className="partido-barra-background">
|
||||
<div className="partido-barra-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: partido.color || '#888' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="partido-stats">
|
||||
<span className="partido-porcentaje">
|
||||
<AnimatedNumber value={partido.porcentaje} formatter={formatPercent} />
|
||||
</span>
|
||||
<span className="partido-votos">
|
||||
<AnimatedNumber value={partido.votos} formatter={formatVotes} /> votos
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="panel-estado-recuento">
|
||||
<div className="estado-item">
|
||||
<span>Participación</span>
|
||||
<strong><AnimatedNumber value={estadoRecuento.participacionPorcentaje} formatter={formatPercent} /></strong>
|
||||
</div>
|
||||
<div className="estado-item">
|
||||
<span>Mesas Escrutadas</span>
|
||||
<strong><AnimatedNumber value={estadoRecuento.mesasTotalizadasPorcentaje} formatter={formatPercent} /></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// src/features/legislativas/nacionales/components/hooks/useAnimatedNumber.ts
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
const easeOutQuad = (t: number) => t * (2 - t);
|
||||
|
||||
export const useAnimatedNumber = (
|
||||
endValue: number,
|
||||
duration: number = 700 // Duración de la animación en milisegundos
|
||||
) => {
|
||||
const [currentValue, setCurrentValue] = useState(endValue);
|
||||
const previousValueRef = useRef(endValue);
|
||||
|
||||
useEffect(() => {
|
||||
const startValue = previousValueRef.current;
|
||||
let animationFrameId: number;
|
||||
const startTime = Date.now();
|
||||
|
||||
const animate = () => {
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const progress = Math.min(elapsedTime / duration, 1);
|
||||
const easedProgress = easeOutQuad(progress);
|
||||
|
||||
const newAnimatedValue = startValue + (endValue - startValue) * easedProgress;
|
||||
setCurrentValue(newAnimatedValue);
|
||||
|
||||
if (progress < 1) {
|
||||
animationFrameId = requestAnimationFrame(animate);
|
||||
} else {
|
||||
// Asegurarse de que el valor final sea exacto
|
||||
setCurrentValue(endValue);
|
||||
previousValueRef.current = endValue;
|
||||
}
|
||||
};
|
||||
|
||||
animationFrameId = requestAnimationFrame(animate);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
previousValueRef.current = endValue;
|
||||
};
|
||||
}, [endValue, duration]);
|
||||
|
||||
return currentValue;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* src/components/BancasWidget.css
|
||||
/* src/features/legislativas/rovinciales/BancasWidget.css
|
||||
|
||||
/* Contenedor principal del widget */
|
||||
.bancas-widget-container {
|
||||
@@ -1,9 +1,9 @@
|
||||
// src/components/BancasWidget.tsx (Corregido)
|
||||
// src/features/legislativas/provinciales/BancasWidget.tsx (Corregido)
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select'; // --- CAMBIO: Importar react-select ---
|
||||
import { getBancasPorSeccion, getSeccionesElectoralesConCargos } from '../apiService';
|
||||
import type { ProyeccionBancas, MunicipioSimple } from '../types/types';
|
||||
import { getBancasPorSeccion, getSeccionesElectoralesConCargos } from '../../../apiService';
|
||||
import type { ProyeccionBancas, MunicipioSimple } from '../../../types/types';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import './BancasWidget.css';
|
||||
import type { Property } from 'csstype';
|
||||
@@ -1,10 +1,10 @@
|
||||
// src/components/ConcejalesPorSeccionWidget.tsx
|
||||
// src/features/legislativas/provinciales/ConcejalesPorSeccionWidget.tsx
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select';
|
||||
import { getSeccionesElectorales, getResultadosPorSeccion, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker, ApiResponseResultadosPorSeccion } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getSeccionesElectorales, getResultadosPorSeccion, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker, ApiResponseResultadosPorSeccion } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css'; // Reutilizamos los estilos del ticker
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,9 +1,9 @@
|
||||
// src/components/ConcejalesTickerWidget.tsx
|
||||
// src/features/legislativas/provinciales/ConcejalesTickerWidget.tsx
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css'; // Reutilizamos los mismos estilos
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,10 +1,10 @@
|
||||
// src/components/ConcejalesWidget.tsx
|
||||
// src/features/legislativas/provinciales/ConcejalesWidget.tsx
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select';
|
||||
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,4 +1,4 @@
|
||||
/* src/components/CongresoWidget.css */
|
||||
/* src/features/legislativas/provinciales/CongresoWidget.css */
|
||||
.congreso-container {
|
||||
display: flex;
|
||||
/* Se reduce ligeramente el espacio entre el gráfico y el panel */
|
||||
@@ -1,28 +1,32 @@
|
||||
// src/components/CongresoWidget.tsx
|
||||
// src/features/legislativas/provinciales/CongresoWidget.tsx
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ParliamentLayout } from './ParliamentLayout';
|
||||
import { SenateLayout } from './SenateLayout';
|
||||
import { getComposicionCongreso, getBancadasDetalle } from '../apiService';
|
||||
import type { ComposicionData, BancadaDetalle } from '../apiService';
|
||||
import { ParliamentLayout } from '../../../components/common/ParliamentLayout';
|
||||
import { SenateLayout } from '../../../components/common/SenateLayout';
|
||||
import { getComposicionCongreso, getBancadasDetalle } from '../../../apiService';
|
||||
import type { ComposicionData, BancadaDetalle } from '../../../apiService';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import './CongresoWidget.css';
|
||||
|
||||
type CamaraType = 'diputados' | 'senadores';
|
||||
const DEFAULT_COLOR = '#808080';
|
||||
|
||||
export const CongresoWidget = () => {
|
||||
interface CongresoWidgetProps {
|
||||
eleccionId: number;
|
||||
}
|
||||
|
||||
export const CongresoWidget = ({ eleccionId }: CongresoWidgetProps) => {
|
||||
const [camaraActiva, setCamaraActiva] = useState<CamaraType>('diputados');
|
||||
|
||||
const { data: composicionData, isLoading: isLoadingComposicion, error: errorComposicion } = useQuery<ComposicionData>({
|
||||
queryKey: ['composicionCongreso'],
|
||||
queryFn: getComposicionCongreso,
|
||||
queryKey: ['composicionCongreso', eleccionId],
|
||||
queryFn: () => getComposicionCongreso(eleccionId),
|
||||
refetchInterval: 180000,
|
||||
});
|
||||
|
||||
const { data: bancadasDetalle = [] } = useQuery<BancadaDetalle[]>({
|
||||
queryKey: ['bancadasDetalle'],
|
||||
queryFn: getBancadasDetalle,
|
||||
queryKey: ['bancadasDetalle', eleccionId],
|
||||
queryFn: () => getBancadasDetalle(eleccionId),
|
||||
enabled: !!composicionData,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// src/components/DipSenTickerWidget.tsx
|
||||
// src/features/legislativas/provinciales/DipSenTickerWidget.tsx
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// src/components/DiputadosPorSeccionWidget.tsx
|
||||
// src/features/legislativas/provinciales/DiputadosPorSeccionWidget.tsx
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select';
|
||||
import { getSeccionesElectorales, getResultadosPorSeccion, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker, ApiResponseResultadosPorSeccion } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getSeccionesElectorales, getResultadosPorSeccion, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker, ApiResponseResultadosPorSeccion } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,9 +1,9 @@
|
||||
// src/components/DiputadosTickerWidget.tsx
|
||||
// src/features/legislativas/provinciales/DiputadosTickerWidget.tsx
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,10 +1,10 @@
|
||||
// src/components/DiputadosWidget.tsx
|
||||
// src/features/legislativas/provinciales/DiputadosWidget.tsx
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select';
|
||||
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,4 +1,4 @@
|
||||
/* src/components/MapaBsAs.css */
|
||||
/* src/features/legislativas/provinciales/MapaBsAs.css */
|
||||
:root {
|
||||
--primary-accent-color: #0073e6;
|
||||
--background-panel-color: #ffffff;
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/components/MapaBsAs.tsx
|
||||
// src/features/legislativas/provinciales/MapaBsAs.tsx
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
@@ -7,7 +7,7 @@ import axios from 'axios';
|
||||
import { feature } from 'topojson-client';
|
||||
import type { Feature, Geometry } from 'geojson';
|
||||
import { geoCentroid } from 'd3-geo';
|
||||
import { API_BASE_URL, assetBaseUrl } from '../apiService';
|
||||
import { API_BASE_URL, assetBaseUrl } from '../../../apiService';
|
||||
import './MapaBsAs.css';
|
||||
|
||||
// --- Interfaces y Tipos ---
|
||||
@@ -1,12 +1,12 @@
|
||||
// src/components/MapaBsAsSecciones.tsx
|
||||
// src/features/legislativas/provinciales/MapaBsAsSecciones.tsx
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import axios from 'axios';
|
||||
import { geoCentroid } from 'd3-geo';
|
||||
import { getDetalleSeccion, API_BASE_URL, assetBaseUrl } from '../apiService';
|
||||
import { type ResultadoDetalleSeccion } from '../apiService';
|
||||
import { getDetalleSeccion, API_BASE_URL, assetBaseUrl } from '../../../apiService';
|
||||
import { type ResultadoDetalleSeccion } from '../../../apiService';
|
||||
import './MapaBsAs.css';
|
||||
|
||||
// --- Interfaces y Tipos ---
|
||||
@@ -1,8 +1,9 @@
|
||||
// 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 { getSeccionesElectorales, getRankingMunicipiosPorSeccion } from '../../../apiService';
|
||||
import type { MunicipioSimple, ApiResponseRankingMunicipio, RankingPartido } from '../../../types/types';
|
||||
import './ResultadosTablaSeccionWidget.css';
|
||||
|
||||
type DisplayMode = 'porcentaje' | 'votos' | 'ambos';
|
||||
@@ -1,8 +1,9 @@
|
||||
// src/features/legislativas/provinciales/ResultadosTablaDetalladaWidget.tsx
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select';
|
||||
import { getSeccionesElectorales, getResultadosTablaDetallada } from '../apiService';
|
||||
import type { MunicipioSimple, ApiResponseTablaDetallada } from '../types/types';
|
||||
import { getSeccionesElectorales, getResultadosTablaDetallada } from '../../../apiService';
|
||||
import type { MunicipioSimple, ApiResponseTablaDetallada } from '../../../types/types';
|
||||
import './ResultadosTablaSeccionWidget.css';
|
||||
|
||||
const customSelectStyles = {
|
||||
@@ -1,6 +1,4 @@
|
||||
/* ==========================================================================
|
||||
ResultadosTablaSeccionWidget.css
|
||||
========================================================================== */
|
||||
/* src/features/legislativas/provinciales/ResultadosTablaSeccionWidget.css */
|
||||
|
||||
.tabla-resultados-widget {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
@@ -1,9 +1,9 @@
|
||||
// src/components/ResumenGeneralWidget.tsx
|
||||
// src/features/legislativas/provinciales/ResumenGeneralWidget.tsx
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,10 +1,10 @@
|
||||
// src/components/SenadoresPorSeccionWidget.tsx
|
||||
// src/features/legislativas/provinciales/SenadoresPorSeccionWidget.tsx
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select';
|
||||
import { getSeccionesElectorales, getResultadosPorSeccion, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker, ApiResponseResultadosPorSeccion } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getSeccionesElectorales, getResultadosPorSeccion, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { MunicipioSimple, ResultadoTicker, ApiResponseResultadosPorSeccion } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,9 +1,9 @@
|
||||
// src/components/SenadoresTickerWidget.tsx
|
||||
// src/features/legislativas/provinciales/SenadoresTickerWidget.tsx
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getResumenProvincial, getConfiguracionPublica, assetBaseUrl } from '../../../apiService';
|
||||
import type { CategoriaResumen, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css'; // Reutilizamos los mismos estilos
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,10 +1,10 @@
|
||||
// src/components/SenadoresWidget.tsx
|
||||
// src/features/legislativas/provinciales/SenadoresWidget.tsx
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Select from 'react-select'; // Importamos react-select
|
||||
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica, assetBaseUrl } from '../apiService'; // Usamos las funciones genéricas
|
||||
import type { MunicipioSimple, ResultadoTicker } from '../types/types';
|
||||
import { ImageWithFallback } from './ImageWithFallback';
|
||||
import { getMunicipios, getResultadosPorMunicipio, getConfiguracionPublica, assetBaseUrl } from '../../../apiService'; // Usamos las funciones genéricas
|
||||
import type { MunicipioSimple, ResultadoTicker } from '../../../types/types';
|
||||
import { ImageWithFallback } from '../../../components/common/ImageWithFallback';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
@@ -1,4 +1,4 @@
|
||||
/* src/components/TelegramaWidget.css */
|
||||
/* src/features/legislativas/provinciales/TelegramaWidget.css */
|
||||
.telegrama-container {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/components/TelegramaWidget.tsx
|
||||
// src/features/legislativas/provinciales/TelegramaWidget.tsx
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import Select, { type FilterOptionOption } from 'react-select'; // <-- Importar react-select
|
||||
import {
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
getMesasPorEstablecimiento,
|
||||
getTelegramaPorId,
|
||||
assetBaseUrl
|
||||
} from '../apiService';
|
||||
import type { TelegramaData, CatalogoItem } from '../types/types';
|
||||
} from '../../../apiService';
|
||||
import type { TelegramaData, CatalogoItem } from '../../../types/types';
|
||||
import './TelegramaWidget.css';
|
||||
|
||||
import { pdfjs, Document, Page } from 'react-pdf';
|
||||
@@ -1,6 +1,4 @@
|
||||
/* ==========================================================================
|
||||
TickerWidget.css (Versión Mejorada y Responsiva)
|
||||
========================================================================== */
|
||||
/* src/features/legislativas/provinciales/TickerWidget.css */
|
||||
|
||||
/* --- Contenedor Principal del Widget --- */
|
||||
.ticker-card {
|
||||
@@ -3,26 +3,27 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import { BancasWidget } from './components/BancasWidget'
|
||||
import { CongresoWidget } from './components/CongresoWidget'
|
||||
import MapaBsAs from './components/MapaBsAs'
|
||||
import { DipSenTickerWidget } from './components/DipSenTickerWidget'
|
||||
import { TelegramaWidget } from './components/TelegramaWidget'
|
||||
import { ConcejalesWidget } from './components/ConcejalesWidget'
|
||||
import MapaBsAsSecciones from './components/MapaBsAsSecciones'
|
||||
import { SenadoresWidget } from './components/SenadoresWidget'
|
||||
import { DiputadosWidget } from './components/DiputadosWidget'
|
||||
import { ResumenGeneralWidget } from './components/ResumenGeneralWidget'
|
||||
import { SenadoresTickerWidget } from './components/SenadoresTickerWidget'
|
||||
import { DiputadosTickerWidget } from './components/DiputadosTickerWidget'
|
||||
import { ConcejalesTickerWidget } from './components/ConcejalesTickerWidget'
|
||||
import { DiputadosPorSeccionWidget } from './components/DiputadosPorSeccionWidget'
|
||||
import { SenadoresPorSeccionWidget } from './components/SenadoresPorSeccionWidget'
|
||||
import { ConcejalesPorSeccionWidget } from './components/ConcejalesPorSeccionWidget'
|
||||
import { ResultadosTablaDetalladaWidget } from './components/ResultadosTablaDetalladaWidget';
|
||||
import { ResultadosRankingMunicipioWidget } from './components/ResultadosRankingMunicipioWidget';
|
||||
import { DevApp } from './components/DevApp';
|
||||
import { BancasWidget } from './features/legislativas/provinciales/BancasWidget'
|
||||
import { CongresoWidget } from './features/legislativas/provinciales/CongresoWidget'
|
||||
import MapaBsAs from './features/legislativas/provinciales/MapaBsAs'
|
||||
import { DipSenTickerWidget } from './features/legislativas/provinciales/DipSenTickerWidget'
|
||||
import { TelegramaWidget } from './features/legislativas/provinciales/TelegramaWidget'
|
||||
import { ConcejalesWidget } from './features/legislativas/provinciales/ConcejalesWidget'
|
||||
import MapaBsAsSecciones from './features/legislativas/provinciales/MapaBsAsSecciones'
|
||||
import { SenadoresWidget } from './features/legislativas/provinciales/SenadoresWidget'
|
||||
import { DiputadosWidget } from './features/legislativas/provinciales/DiputadosWidget'
|
||||
import { ResumenGeneralWidget } from './features/legislativas/provinciales/ResumenGeneralWidget'
|
||||
import { SenadoresTickerWidget } from './features/legislativas/provinciales/SenadoresTickerWidget'
|
||||
import { DiputadosTickerWidget } from './features/legislativas/provinciales/DiputadosTickerWidget'
|
||||
import { ConcejalesTickerWidget } from './features/legislativas/provinciales/ConcejalesTickerWidget'
|
||||
import { DiputadosPorSeccionWidget } from './features/legislativas/provinciales/DiputadosPorSeccionWidget'
|
||||
import { SenadoresPorSeccionWidget } from './features/legislativas/provinciales/SenadoresPorSeccionWidget'
|
||||
import { ConcejalesPorSeccionWidget } from './features/legislativas/provinciales/ConcejalesPorSeccionWidget'
|
||||
import { ResultadosTablaDetalladaWidget } from './features/legislativas/provinciales/ResultadosTablaDetalladaWidget';
|
||||
import { ResultadosRankingMunicipioWidget } from './features/legislativas/provinciales/ResultadosRankingMunicipioWidget';
|
||||
//import { DevApp } from './components/common/DevApp';
|
||||
import './index.css';
|
||||
import { DevAppLegislativas } from './features/legislativas/DevAppLegislativas';
|
||||
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
@@ -56,7 +57,8 @@ if (import.meta.env.DEV) {
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<DevApp />
|
||||
<DevAppLegislativas />
|
||||
{/* <DevApp /> */}
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
// src/types/types.ts
|
||||
import type { Feature as GeoJsonFeature, Geometry } from 'geojson';
|
||||
|
||||
|
||||
// Definimos nuestras propiedades personalizadas
|
||||
export interface GeoProperties {
|
||||
nombre: string;
|
||||
id: string;
|
||||
[key: string]: any; // Permite otras propiedades
|
||||
}
|
||||
|
||||
// Nuestro tipo de geografía ahora se basa en el tipo estándar de GeoJSON
|
||||
// y le añadimos la propiedad 'rsmKey' que 'react-simple-maps' añade.
|
||||
export type AmbitoGeography = GeoJsonFeature<Geometry, GeoProperties> & { rsmKey: string };
|
||||
|
||||
// Tipos para la respuesta de la API de resultados por municipio
|
||||
export interface AgrupacionResultadoDto {
|
||||
@@ -33,13 +46,12 @@ export interface MapaDto {
|
||||
export interface GeographyObject {
|
||||
rsmKey: string;
|
||||
properties: {
|
||||
// CORRECCIÓN: Se cambia 'nombre' por 'NAME_2' para coincidir con el archivo topojson
|
||||
NAME_2: string;
|
||||
[key: string]: any; // Permite otras propiedades que puedan venir
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface MunicipioSimple { id: string; nombre: string; camarasDisponibles?: ('diputados' | 'senadores')[];}
|
||||
export interface MunicipioSimple { id: string; nombre: string; camarasDisponibles?: ('diputados' | 'senadores')[]; }
|
||||
|
||||
export interface ResultadoTicker {
|
||||
id: string;
|
||||
@@ -53,8 +65,8 @@ export interface ResultadoTicker {
|
||||
}
|
||||
|
||||
export interface EstadoRecuentoTicker {
|
||||
mesasTotalizadasPorcentaje: number;
|
||||
participacionPorcentaje: number;
|
||||
mesasTotalizadasPorcentaje: number;
|
||||
participacionPorcentaje: number;
|
||||
}
|
||||
|
||||
export interface CategoriaResumen {
|
||||
@@ -109,8 +121,8 @@ export interface CatalogoItem {
|
||||
}
|
||||
|
||||
export interface ApiResponseResultadosPorSeccion {
|
||||
ultimaActualizacion: string;
|
||||
resultados: ResultadoTicker[];
|
||||
ultimaActualizacion: string;
|
||||
resultados: ResultadoTicker[];
|
||||
}
|
||||
|
||||
export interface ResultadoTablaAgrupacion {
|
||||
@@ -219,4 +231,18 @@ export interface RankingMunicipio {
|
||||
export interface ApiResponseRankingMunicipio {
|
||||
categorias: { id: number; nombre: string }[];
|
||||
resultados: RankingMunicipio[];
|
||||
}
|
||||
|
||||
export interface ResultadoMapaDto {
|
||||
ambitoId: string;
|
||||
ambitoNombre: string;
|
||||
agrupacionGanadoraId: string;
|
||||
colorGanador: string;
|
||||
}
|
||||
|
||||
export interface PanelElectoralDto {
|
||||
ambitoNombre: string;
|
||||
mapaData: ResultadoMapaDto[];
|
||||
resultadosPanel: ResultadoTicker[]; // Reutilizamos el tipo que ya tienes
|
||||
estadoRecuento: EstadoRecuentoTicker; // Reutilizamos el tipo que ya tienes
|
||||
}
|
||||
Reference in New Issue
Block a user