Feat Front Widgets Refactizados y Ajustes Backend
This commit is contained in:
35
Elecciones-Web/Restaurar/components/BancasWidget.css
Normal file
35
Elecciones-Web/Restaurar/components/BancasWidget.css
Normal file
@@ -0,0 +1,35 @@
|
||||
/* src/components/BancasWidget.css */
|
||||
.bancas-widget-container {
|
||||
background-color: #2a2a2e;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.bancas-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bancas-header h4 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.bancas-header select {
|
||||
background-color: #3a3a3a;
|
||||
color: white;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.waffle-chart-container {
|
||||
height: 300px;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
91
Elecciones-Web/Restaurar/components/BancasWidget.tsx
Normal file
91
Elecciones-Web/Restaurar/components/BancasWidget.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
// src/components/BancasWidget.tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { ResponsiveWaffle } from '@nivo/waffle';
|
||||
import { getBancasPorSeccion } from '../apiService';
|
||||
import type { ProyeccionBancas } from '../types';
|
||||
import './BancasWidget.css';
|
||||
|
||||
// Paleta de colores consistente
|
||||
const NIVO_COLORS = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
|
||||
|
||||
// Las Secciones Electorales de la Provincia (esto podría venir de la API en el futuro)
|
||||
const secciones = [
|
||||
{ id: '1', nombre: 'Primera Sección' },
|
||||
{ id: '2', nombre: 'Segunda Sección' },
|
||||
{ id: '3', nombre: 'Tercera Sección' },
|
||||
{ id: '4', nombre: 'Cuarta Sección' },
|
||||
{ id: '5', nombre: 'Quinta Sección' },
|
||||
{ id: '6', nombre: 'Sexta Sección' },
|
||||
{ id: '7', nombre: 'Séptima Sección' },
|
||||
{ id: '8', nombre: 'Octava Sección (Capital)' },
|
||||
];
|
||||
|
||||
export const BancasWidget = () => {
|
||||
const [seccionActual, setSeccionActual] = useState('1'); // Empezamos con la Primera Sección
|
||||
const [data, setData] = useState<ProyeccionBancas | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await getBancasPorSeccion(seccionActual);
|
||||
setData(result);
|
||||
} catch (error) {
|
||||
console.error(`Error cargando datos de bancas para sección ${seccionActual}:`, error);
|
||||
setData(null); // Limpiar datos en caso de error
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [seccionActual]); // Se ejecuta cada vez que cambia la sección
|
||||
|
||||
const waffleData = data?.proyeccion.map(p => ({
|
||||
id: p.agrupacionNombre,
|
||||
label: p.agrupacionNombre,
|
||||
value: p.bancas,
|
||||
})) || [];
|
||||
|
||||
const totalBancas = waffleData.reduce((sum, current) => sum + current.value, 0);
|
||||
|
||||
return (
|
||||
<div className="bancas-widget-container">
|
||||
<div className="bancas-header">
|
||||
<h4>Distribución de Bancas</h4>
|
||||
<select value={seccionActual} onChange={e => setSeccionActual(e.target.value)}>
|
||||
{secciones.map(s => <option key={s.id} value={s.id}>{s.nombre}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="waffle-chart-container">
|
||||
{loading ? <p>Cargando...</p> : !data ? <p>No hay datos disponibles para esta sección.</p> :
|
||||
<ResponsiveWaffle
|
||||
data={waffleData}
|
||||
total={totalBancas}
|
||||
rows={8}
|
||||
columns={10}
|
||||
fillDirection="bottom"
|
||||
padding={3}
|
||||
colors={NIVO_COLORS}
|
||||
borderColor={{ from: 'color', modifiers: [['darker', 0.3]] }}
|
||||
animate={true}
|
||||
legends={[
|
||||
{
|
||||
anchor: 'bottom',
|
||||
direction: 'row',
|
||||
justify: false,
|
||||
translateX: 0,
|
||||
translateY: 40,
|
||||
itemsSpacing: 4,
|
||||
itemWidth: 100,
|
||||
itemHeight: 20,
|
||||
itemTextColor: '#999',
|
||||
itemDirection: 'left-to-right',
|
||||
symbolSize: 20,
|
||||
},
|
||||
]}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
82
Elecciones-Web/Restaurar/components/TickerWidget.css
Normal file
82
Elecciones-Web/Restaurar/components/TickerWidget.css
Normal file
@@ -0,0 +1,82 @@
|
||||
/* src/components/TickerWidget.css */
|
||||
.ticker-container {
|
||||
background-color: #2a2a2e;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.ticker-container.loading, .ticker-container.error {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.ticker-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #444;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ticker-header h3 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.ticker-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.ticker-stats strong {
|
||||
color: #a7c7e7;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.ticker-results {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.ticker-party .party-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 5px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.ticker-party .party-name {
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.ticker-party .party-percent {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.party-bar-background {
|
||||
background-color: #444;
|
||||
border-radius: 4px;
|
||||
height: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.party-bar-foreground {
|
||||
background-color: #646cff;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
67
Elecciones-Web/Restaurar/components/TickerWidget.tsx
Normal file
67
Elecciones-Web/Restaurar/components/TickerWidget.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
// src/components/TickerWidget.tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getResumenProvincial } from '../apiService';
|
||||
import type { ResumenProvincial } from '../types';
|
||||
import './TickerWidget.css';
|
||||
|
||||
const formatPercent = (num: number) => `${num.toFixed(2).replace('.', ',')}%`;
|
||||
const NIVO_COLORS = [
|
||||
"#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
|
||||
"#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
|
||||
];
|
||||
|
||||
export const TickerWidget = () => {
|
||||
const [data, setData] = useState<ResumenProvincial | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const result = await getResumenProvincial();
|
||||
setData(result);
|
||||
} catch (error) {
|
||||
console.error("Error cargando resumen provincial:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData(); // Carga inicial
|
||||
const intervalId = setInterval(fetchData, 30000); // Actualiza cada 30 segundos
|
||||
|
||||
return () => clearInterval(intervalId); // Limpia el intervalo al desmontar el componente
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <div className="ticker-container loading">Cargando resultados provinciales...</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div className="ticker-container error">No se pudieron cargar los datos.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ticker-container">
|
||||
<div className="ticker-header">
|
||||
<h3>{data.provinciaNombre}</h3>
|
||||
<div className="ticker-stats">
|
||||
<span>Mesas Escrutadas: <strong>{formatPercent(data.porcentajeEscrutado)}</strong></span>
|
||||
<span>Participación: <strong>{formatPercent(data.porcentajeParticipacion)}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ticker-results">
|
||||
{data.resultados.slice(0, 3).map((partido, index) => (
|
||||
<div key={`${partido.nombre}-${index}`} className="ticker-party"> {/* <-- CAMBIO AQUÍ */}
|
||||
<div className="party-info">
|
||||
<span className="party-name">{partido.nombre}</span>
|
||||
<span className="party-percent">{formatPercent(partido.porcentaje)}</span>
|
||||
</div>
|
||||
<div className="party-bar-background">
|
||||
<div className="party-bar-foreground" style={{ width: `${partido.porcentaje}%`, backgroundColor: NIVO_COLORS[index % NIVO_COLORS.length] }}></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user