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