198 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // 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 { Tooltip } from 'react-tooltip';
 | |
| import './BancasWidget.css';
 | |
| import type { Property } from 'csstype';
 | |
| 
 | |
| type CamaraType = 'diputados' | 'senadores';
 | |
| 
 | |
| // --- CAMBIO: Estilos para el nuevo selector ---
 | |
| const customSelectStyles = {
 | |
|     control: (base: any) => ({ ...base, minWidth: '200px', border: '1px solid #ced4da', boxShadow: 'none', '&:hover': { borderColor: '#86b7fe' } }),
 | |
|     menu: (base: any) => ({ ...base, zIndex: 10 }),
 | |
| };
 | |
| 
 | |
| const WaffleDisplay = ({ data }: { data: ProyeccionBancas['proyeccion'] }) => {
 | |
|     // El componente WaffleDisplay no necesita cambios en su lógica
 | |
|     return (
 | |
|         <div className="waffle-grid-container">
 | |
|             {data.map(partido => (
 | |
|                 partido.bancas > 0 && (
 | |
|                     <div
 | |
|                         key={partido.agrupacionId}
 | |
|                         className="partido-bloque"
 | |
|                         data-tooltip-id="banca-tooltip"
 | |
|                         data-tooltip-content={`${partido.nombreCorto || partido.agrupacionNombre}: ${partido.bancas} bancas`}
 | |
|                     >
 | |
|                         {Array.from({ length: partido.bancas }).map((_, index) => (
 | |
|                             <div
 | |
|                                 key={index}
 | |
|                                 className="waffle-cell"
 | |
|                                 style={{ backgroundColor: partido.color as Property.BackgroundColor || '#cccccc' }}
 | |
|                             />
 | |
|                         ))}
 | |
|                     </div>
 | |
|                 )
 | |
|             ))}
 | |
|         </div>
 | |
|     );
 | |
| };
 | |
| 
 | |
| export const BancasWidget = () => {
 | |
|     const [secciones, setSecciones] = useState<MunicipioSimple[]>([]);
 | |
|     // --- CAMBIO: Adaptar el estado para react-select ---
 | |
|     const [selectedSeccion, setSelectedSeccion] = useState<{ value: string; label: string } | null>(null);
 | |
|     const [camaraActiva, setCamaraActiva] = useState<CamaraType>('diputados');
 | |
| 
 | |
|     useEffect(() => {
 | |
|         const fetchSecciones = async () => {
 | |
|             try {
 | |
|                 const seccionesData = await getSeccionesElectoralesConCargos();
 | |
|                 if (seccionesData && seccionesData.length > 0) {
 | |
|                     // --- LÓGICA DE ORDENAMIENTO ---
 | |
|                     const orden = new Map([
 | |
|                         ['Capital', 0], ['Primera', 1], ['Segunda', 2], ['Tercera', 3],
 | |
|                         ['Cuarta', 4], ['Quinta', 5], ['Sexta', 6], ['Séptima', 7]
 | |
|                     ]);
 | |
| 
 | |
|                     const getOrden = (nombre: string) => {
 | |
|                         const match = nombre.match(/Capital|Primera|Segunda|Tercera|Cuarta|Quinta|Sexta|Séptima/);
 | |
|                         return match ? orden.get(match[0]) ?? 99 : 99;
 | |
|                     };
 | |
|                     
 | |
|                     // Ordenamos el array de datos ANTES de guardarlo en el estado
 | |
|                     seccionesData.sort((a, b) => getOrden(a.nombre) - getOrden(b.nombre));
 | |
| 
 | |
|                     setSecciones(seccionesData);
 | |
| 
 | |
|                     if (!selectedSeccion) {
 | |
|                         setSelectedSeccion({ value: seccionesData[0].id, label: seccionesData[0].nombre });
 | |
|                     }
 | |
|                 }
 | |
|             } catch (err) {
 | |
|                 console.error("Error cargando secciones electorales:", err);
 | |
|             }
 | |
|         };
 | |
|         fetchSecciones();
 | |
|     }, [selectedSeccion]);
 | |
| 
 | |
|     // --- CAMBIO: Formatear opciones para react-select ---
 | |
|     const seccionOptions = useMemo(() =>
 | |
|         secciones.map(s => ({ value: s.id, label: s.nombre })),
 | |
|         [secciones]);
 | |
| 
 | |
|     const seccionSeleccionada = useMemo(() =>
 | |
|         secciones.find(s => s.id === selectedSeccion?.value),
 | |
|         [secciones, selectedSeccion]);
 | |
| 
 | |
|     const camarasDisponibles = useMemo(() =>
 | |
|         seccionSeleccionada?.camarasDisponibles || [],
 | |
|         [seccionSeleccionada]);
 | |
| 
 | |
|     useEffect(() => {
 | |
|         if (seccionSeleccionada && camarasDisponibles.length > 0) {
 | |
|             if (!camarasDisponibles.includes(camaraActiva)) {
 | |
|                 setCamaraActiva(camarasDisponibles[0]);
 | |
|             }
 | |
|         }
 | |
|     }, [seccionSeleccionada, camarasDisponibles, camaraActiva]);
 | |
| 
 | |
|     const {
 | |
|         data,
 | |
|         isLoading,
 | |
|         error
 | |
|     } = useQuery<ProyeccionBancas, Error>({
 | |
|         queryKey: ['bancasPorSeccion', selectedSeccion?.value, camaraActiva],
 | |
|         queryFn: () => getBancasPorSeccion(1,selectedSeccion!.value, camaraActiva),
 | |
|         enabled: !!selectedSeccion && camarasDisponibles.includes(camaraActiva),
 | |
|         retry: (failureCount, error: any) => {
 | |
|             if (error.response?.status === 404) return false;
 | |
|             return failureCount < 3;
 | |
|         },
 | |
|     });
 | |
| 
 | |
|     const getErrorMessage = () => {
 | |
|         if (error) {
 | |
|             if ((error as any).response?.status === 404) {
 | |
|                 return `La proyección para ${camaraActiva} en esta sección aún no está disponible.`;
 | |
|             }
 | |
|             return "No se pudo conectar para obtener los datos.";
 | |
|         }
 | |
|         return null;
 | |
|     };
 | |
|     const errorMessage = getErrorMessage();
 | |
| 
 | |
|     // --- CAMBIO: Ordenar la leyenda (y por lo tanto el gráfico) de más a menos bancas ---
 | |
|     const leyendaData = useMemo(() =>
 | |
|         data?.proyeccion
 | |
|             .filter(p => p.bancas > 0)
 | |
|             .sort((a, b) => b.bancas - a.bancas) // Ordena de mayor a menor
 | |
|         || [],
 | |
|         [data]);
 | |
| 
 | |
|     const totalBancasEnJuego = useMemo(() =>
 | |
|         data?.proyeccion.reduce((sum, p) => sum + p.bancas, 0) || 0,
 | |
|         [data]);
 | |
| 
 | |
|     return (
 | |
|         <div className="bancas-widget-container">
 | |
|             <div className="bancas-header">
 | |
|                 <h4>Bancas Proyectadas: {totalBancasEnJuego}</h4>
 | |
|                 <Select
 | |
|                     options={seccionOptions}
 | |
|                     value={selectedSeccion}
 | |
|                     onChange={(option) => setSelectedSeccion(option)}
 | |
|                     isLoading={secciones.length === 0}
 | |
|                     placeholder="Seleccionar..."
 | |
|                     styles={customSelectStyles}
 | |
|                     isSearchable={false}
 | |
|                 />
 | |
|             </div>
 | |
| 
 | |
|             <div className="chamber-tabs-bancas">
 | |
|                 <button
 | |
|                     className={camaraActiva === 'diputados' ? 'active' : ''}
 | |
|                     onClick={() => setCamaraActiva('diputados')}
 | |
|                     disabled={seccionSeleccionada && !camarasDisponibles.includes('diputados')}
 | |
|                 >
 | |
|                     Diputados
 | |
|                 </button>
 | |
|                 <button
 | |
|                     className={camaraActiva === 'senadores' ? 'active' : ''}
 | |
|                     onClick={() => setCamaraActiva('senadores')}
 | |
|                     disabled={seccionSeleccionada && !camarasDisponibles.includes('senadores')}
 | |
|                 >
 | |
|                     Senadores
 | |
|                 </button>
 | |
|             </div>
 | |
| 
 | |
|             <div className="bancas-content-container">
 | |
|                 <div className="waffle-chart-container">
 | |
|                     {isLoading ? <p className="loading-text">Cargando...</p> :
 | |
|                         errorMessage ? <p className="error-text">{errorMessage}</p> :
 | |
|                             totalBancasEnJuego > 0 ? <WaffleDisplay data={leyendaData} /> :
 | |
|                                 <p>No hay bancas proyectadas para mostrar.</p>
 | |
|                     }
 | |
|                 </div>
 | |
|                 <div className="leyenda-container">
 | |
|                     <ul className="partido-lista-bancas">
 | |
|                         {leyendaData.map(partido => (
 | |
|                             <li key={partido.agrupacionId}>
 | |
|                                 <span className="partido-color-box" style={{ backgroundColor: partido.color as Property.BackgroundColor || '#cccccc' }}></span>
 | |
|                                 <span className="partido-nombre">
 | |
|                                     {partido.nombreCorto || partido.agrupacionNombre}
 | |
|                                 </span>
 | |
|                                 <strong className="partido-bancas">{partido.bancas}</strong>
 | |
|                             </li>
 | |
|                         ))}
 | |
|                     </ul>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <Tooltip id="banca-tooltip" />
 | |
|         </div>
 | |
|     );
 | |
| }; |