Feat Tabla de Datos Redaccion

- Tabla de Top 2 Conurbano
- Tabla de Top 2 Por Secciones Electorales Bs. As.
This commit is contained in:
2025-10-23 12:31:10 -03:00
parent 3c364ef373
commit 4dbda0da63
18 changed files with 403 additions and 18 deletions

View File

@@ -5,7 +5,7 @@ import type {
ApiResponseTablaDetallada, ProyeccionBancas, MunicipioSimple,
TelegramaData, CatalogoItem, CategoriaResumen, ResultadoTicker,
ApiResponseResultadosPorSeccion, PanelElectoralDto, ResumenProvincia,
CategoriaResumenHome
CategoriaResumenHome, ResultadoFila, ResultadoSeccion
} from './types/types';
/**
@@ -337,4 +337,14 @@ export const getHomeResumenNacional = async (eleccionId: number, categoriaId: nu
const url = `/elecciones/home-resumen-nacional?${queryParams.toString()}`;
const { data } = await apiClient.get(url);
return data;
};
export const getTablaConurbano = async (eleccionId: number): Promise<ResultadoFila[]> => {
const { data } = await apiClient.get(`/elecciones/${eleccionId}/tabla-conurbano`);
return data;
};
export const getTablaSecciones = async (eleccionId: number): Promise<ResultadoSeccion[]> => {
const { data } = await apiClient.get(`/elecciones/${eleccionId}/tabla-secciones`);
return data;
};

View File

@@ -6,6 +6,8 @@ import { PanelNacionalWidget } from './nacionales/PanelNacionalWidget';
import { HomeCarouselWidget } from './nacionales/HomeCarouselWidget';
import './DevAppStyle.css'
import { HomeCarouselNacionalWidget } from './nacionales/HomeCarouselNacionalWidget';
import { TablaConurbanoWidget } from './nacionales/TablaConurbanoWidget';
import { TablaSeccionesWidget } from './nacionales/TablaSeccionesWidget';
// --- NUEVO COMPONENTE REUTILIZABLE PARA CONTENIDO COLAPSABLE ---
const CollapsibleWidgetWrapper = ({ children }: { children: React.ReactNode }) => {
@@ -163,7 +165,15 @@ export const DevAppLegislativas = () => {
Uso: <code style={codeStyle}>&lt;ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={2} cantidadResultados={1} /&gt;</code>
</p>
<ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={2} cantidadResultados={1} />
<div style={sectionStyle}>
<h2>Widget: Tabla de Resultados del Conurbano</h2>
<TablaConurbanoWidget />
</div>
<div style={sectionStyle}>
<h2>Widget: Tabla de Resultados por Sección Electoral</h2>
<TablaSeccionesWidget />
</div>
</div>
</div>
);

View File

@@ -0,0 +1,53 @@
// src/features/legislativas/nacionales/TablaConurbanoWidget.tsx
import { useQuery } from '@tanstack/react-query';
import { getTablaConurbano } from '../../../apiService';
import styles from './TablaResultadosWidget.module.css';
export const TablaConurbanoWidget = () => {
// CORRECCIÓN: Se elimina el estado y el selector de categoría.
const ELECCION_ID = 2; // Exclusivo para elecciones nacionales
const { data, isLoading, error } = useQuery({
// La queryKey ya no necesita la categoría
queryKey: ['tablaConurbano', ELECCION_ID],
// La llamada a la API ya no necesita la categoría
queryFn: () => getTablaConurbano(ELECCION_ID),
refetchInterval: 60000,
});
const formatPercent = (num: number) => `${num.toFixed(2)}%`;
return (
<div className={styles.widgetContainer}>
<div className={styles.header}>
<h3>Resultados Conurbano - Diputados Nacionales</h3>
</div>
{isLoading && <p>Cargando resultados...</p>}
{error && <p>Error al cargar los datos.</p>}
{data && (
<table className={styles.resultsTable}>
<thead>
<tr>
<th>Distrito</th>
<th>1ra Fuerza</th>
<th>%</th>
<th>2da Fuerza</th>
<th>%</th>
</tr>
</thead>
<tbody>
{data.map((fila, index) => (
<tr key={fila.ambitoId}>
<td className={styles.distritoCell}><span className={styles.distritoIndex}>{index + 1}.</span>{fila.nombre}</td>
<td className={styles.fuerzaCell}>{fila.fuerza1Display}</td>
<td className={styles.porcentajeCell}>{formatPercent(fila.fuerza1Porcentaje)}</td>
<td className={styles.fuerzaCell}>{fila.fuerza2Display}</td>
<td className={styles.porcentajeCell}>{formatPercent(fila.fuerza2Porcentaje)}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
};

View File

@@ -0,0 +1,76 @@
/* src/features/legislativas/nacionales/TablaResultadosWidget.module.css */
.widgetContainer {
font-family: 'Roboto', sans-serif;
max-width: 900px;
margin: 2rem auto;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 1rem;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e0e0e0;
}
.header h3 {
margin: 0;
font-size: 1.5rem;
}
.categoriaSelector {
min-width: 250px;
}
.resultsTable {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
.resultsTable th,
.resultsTable td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #f0f0f0;
}
.resultsTable th {
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
color: #6c757d;
}
.distritoCell {
font-weight: 500;
}
.fuerzaCell {
color: #212529;
}
.porcentajeCell {
font-weight: 700;
text-align: right;
min-width: 80px;
}
.seccionHeader td {
background-color: #f8f9fa;
font-weight: 700;
font-size: 1.1rem;
color: #007bff;
border-top: 2px solid #007bff;
border-bottom: 2px solid #007bff;
}
.distritoIndex {
font-weight: 400;
color: #6c757d;
padding-right: 0.5rem;
}

View File

@@ -0,0 +1,58 @@
// src/features/legislativas/nacionales/TablaSeccionesWidget.tsx
import React from 'react'; // Importar React para React.Fragment
import { useQuery } from '@tanstack/react-query';
import { getTablaSecciones } from '../../../apiService';
import styles from './TablaResultadosWidget.module.css';
export const TablaSeccionesWidget = () => {
const ELECCION_ID = 2;
const { data, isLoading, error } = useQuery({
queryKey: ['tablaSecciones', ELECCION_ID],
queryFn: () => getTablaSecciones(ELECCION_ID),
refetchInterval: 60000,
});
const formatPercent = (num: number) => `${num.toFixed(2)}%`;
return (
<div className={styles.widgetContainer}>
<div className={styles.header}>
<h3>Resultados por Sección - Diputados Nacionales</h3>
</div>
{isLoading && <p>Cargando resultados...</p>}
{error && <p>Error al cargar los datos.</p>}
{data && (
<table className={styles.resultsTable}>
<thead>
<tr>
<th>Municipio</th>
<th>1ra Fuerza</th>
<th>%</th>
<th>2da Fuerza</th>
<th>%</th>
</tr>
</thead>
<tbody>
{data.map((seccion) => (
<React.Fragment key={seccion.seccionId}>
<tr className={styles.seccionHeader}>
<td colSpan={5}>{seccion.nombre}</td>
</tr>
{seccion.municipios.map((fila, index) => (
<tr key={fila.ambitoId}>
<td className={styles.distritoCell}><span className={styles.distritoIndex}>{index + 1}.</span>{fila.nombre}</td>
<td className={styles.fuerzaCell}>{fila.fuerza1Display}</td>
<td className={styles.porcentajeCell}>{formatPercent(fila.fuerza1Porcentaje)}</td>
<td className={styles.fuerzaCell}>{fila.fuerza2Display}</td>
<td className={styles.porcentajeCell}>{formatPercent(fila.fuerza2Porcentaje)}</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
)}
</div>
);
};

View File

@@ -152,7 +152,7 @@ export interface ResultadoCandidato {
color: string | null;
porcentaje: number;
votos: number;
bancasObtenidas?: number;
bancasObtenidas?: number;
}
// Definición para una categoría.
@@ -171,12 +171,29 @@ export interface ResumenProvincia {
}
export interface CategoriaResumenHome {
categoriaId: number;
categoriaNombre: string;
estadoRecuento: EstadoRecuentoDto | null;
resultados: ResultadoCandidato[];
votosEnBlanco: number;
votosEnBlancoPorcentaje: number;
votosTotales: number;
ultimaActualizacion: string;
categoriaId: number;
categoriaNombre: string;
estadoRecuento: EstadoRecuentoDto | null;
resultados: ResultadoCandidato[];
votosEnBlanco: number;
votosEnBlancoPorcentaje: number;
votosTotales: number;
ultimaActualizacion: string;
}
// --- TIPOS PARA WIDGETS DE TABLAS ---
export interface ResultadoFila {
ambitoId: number;
nombre: string;
orden: number;
fuerza1Display: string;
fuerza1Porcentaje: number;
fuerza2Display: string;
fuerza2Porcentaje: number;
}
export interface ResultadoSeccion {
seccionId: string;
nombre: string;
municipios: ResultadoFila[];
}