Feat Widgets
- Widget de Home - Widget Cards por Provincias - Widget Mapa por Categorias
This commit is contained in:
		| @@ -11,6 +11,7 @@ import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types' | ||||
| import { MapaProvincial } from './MapaProvincial'; | ||||
| import { CabaLupa } from './CabaLupa'; | ||||
| import { BiZoomIn, BiZoomOut } from "react-icons/bi"; | ||||
| import toast from 'react-hot-toast'; | ||||
|  | ||||
| const DEFAULT_MAP_COLOR = '#E0E0E0'; | ||||
| const FADED_BACKGROUND_COLOR = '#F0F0F0'; | ||||
| @@ -166,9 +167,45 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|     }; | ||||
|   }, [position, nivel]); | ||||
|  | ||||
|   const handleZoomIn = () => setPosition(prev => ({ ...prev, zoom: Math.min(prev.zoom * 1.8, 100) })); | ||||
|   const panEnabled = | ||||
|     nivel === 'provincia' && | ||||
|     initialProvincePositionRef.current !== null && | ||||
|     position.zoom > initialProvincePositionRef.current.zoom && | ||||
|     !nombreMunicipioSeleccionado; | ||||
|  | ||||
|   // --- INICIO DE LA CORRECCIÓN --- | ||||
|  | ||||
|   const handleZoomIn = () => { | ||||
|     // Solo mostramos la notificación si el paneo NO está ya habilitado | ||||
|     if (!panEnabled && initialProvincePositionRef.current) { | ||||
|       // Calculamos cuál será el nuevo nivel de zoom | ||||
|       const newZoom = position.zoom * 1.8; | ||||
|       // Si el nuevo zoom supera el umbral inicial, activamos la notificación | ||||
|       if (newZoom > initialProvincePositionRef.current.zoom) { | ||||
|         toast.success('Desplazamiento Habilitado', { | ||||
|           icon: '🖐️', | ||||
|           style: { background: '#32e5f1ff', color: 'white' }, | ||||
|           duration: 1000, | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|     setPosition(prev => ({ ...prev, zoom: Math.min(prev.zoom * 1.8, 100) })); | ||||
|   }; | ||||
|  | ||||
|   const handleZoomOut = () => { | ||||
|     // Solo mostramos la notificación si el paneo SÍ está habilitado actualmente | ||||
|     if (panEnabled && initialProvincePositionRef.current) { | ||||
|       const newZoom = position.zoom / 1.8; | ||||
|       // Si el nuevo zoom es igual o menor al umbral, desactivamos | ||||
|       if (newZoom <= initialProvincePositionRef.current.zoom) { | ||||
|         toast.error('Desplazamiento Deshabilitado', { | ||||
|           icon: '🔒', | ||||
|           style: { background: '#32e5f1ff', color: 'white' }, | ||||
|           duration: 1000, | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|     // La lógica para actualizar la posición no cambia | ||||
|     setPosition(prev => { | ||||
|       const newZoom = Math.max(prev.zoom / 1.8, 1); | ||||
|       const initialPos = initialProvincePositionRef.current; | ||||
| @@ -182,12 +219,6 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom | ||||
|     setIsPanning(false); | ||||
|   }; | ||||
|  | ||||
|   const panEnabled = | ||||
|     nivel === 'provincia' && | ||||
|     initialProvincePositionRef.current !== null && | ||||
|     position.zoom > initialProvincePositionRef.current.zoom && | ||||
|     !nombreMunicipioSeleccionado; | ||||
|  | ||||
|   const filterInteractionEvents = (event: any) => { | ||||
|     if (event.sourceEvent && event.sourceEvent.type === 'wheel') return false; | ||||
|     return panEnabled; | ||||
|   | ||||
| @@ -71,15 +71,25 @@ export const PanelResultados = ({ resultados, estadoRecuento }: PanelResultadosP | ||||
|  | ||||
|       <div className="panel-partidos-container"> | ||||
|         {resultados.map(partido => ( | ||||
|           <div key={partido.id} className="partido-fila" style={{ borderLeftColor: partido.color || '#888' }}> | ||||
|           <div | ||||
|             key={partido.id} | ||||
|             className="partido-fila" | ||||
|             style={{ borderLeftColor: partido.color || '#ccc' }} | ||||
|           > | ||||
|             <div className="partido-logo"> | ||||
|               <ImageWithFallback src={partido.logoUrl || undefined} fallbackSrc={`${assetBaseUrl}/default-avatar.png`} alt={partido.nombre} /> | ||||
|             </div> | ||||
|             <div className="partido-main-content"> | ||||
|               <div className="partido-top-row"> | ||||
|                 <div className="partido-info-wrapper"> | ||||
|                   <span className="partido-nombre">{partido.nombreCorto || partido.nombre}</span> | ||||
|                   {partido.nombreCandidato && <span className="candidato-nombre">{partido.nombreCandidato}</span>} | ||||
|                   {partido.nombreCandidato ? ( | ||||
|                     <> | ||||
|                       <span className="candidato-nombre">{partido.nombreCandidato}</span> | ||||
|                       <span className="partido-nombre">{partido.nombreCorto || partido.nombre}</span> | ||||
|                     </> | ||||
|                   ) : ( | ||||
|                     <span className="partido-nombre">{partido.nombreCorto || partido.nombre}</span> | ||||
|                   )} | ||||
|                 </div> | ||||
|                 <div className="partido-stats"> | ||||
|                   <span className="partido-porcentaje"> | ||||
|   | ||||
| @@ -1,78 +1,110 @@ | ||||
| // src/features/legislativas/nacionales/components/ProvinciaCard.tsx | ||||
| import type { ResumenProvincia } from '../../../../types/types'; | ||||
| import type { ResumenProvincia, CategoriaResumen } from '../../../../types/types'; | ||||
| import { MiniMapaSvg } from './MiniMapaSvg'; | ||||
| import { ImageWithFallback } from '../../../../components/common/ImageWithFallback'; | ||||
| import { assetBaseUrl } from '../../../../apiService'; | ||||
|  | ||||
| // --- 1. AÑADIR LA PROP A AMBAS INTERFACES --- | ||||
| interface CategoriaDisplayProps { | ||||
|     categoria: CategoriaResumen; | ||||
|     mostrarBancas?: boolean; | ||||
| } | ||||
|  | ||||
| interface ProvinciaCardProps { | ||||
|     data: ResumenProvincia; | ||||
|     mostrarBancas?: boolean; | ||||
| } | ||||
|  | ||||
| const formatNumber = (num: number) => num.toLocaleString('es-AR'); | ||||
| const formatPercent = (num: number) => `${num.toFixed(2).replace('.', ',')}%`; | ||||
|  | ||||
| export const ProvinciaCard = ({ data }: ProvinciaCardProps) => { | ||||
|     // Determinamos el color del ganador para pasárselo al mapa. | ||||
|     // Si no hay ganador, usamos un color gris por defecto. | ||||
|     const colorGanador = data.resultados[0]?.color || '#d1d1d1'; | ||||
| // --- 2. RECIBIR Y USAR LA PROP EN EL SUB-COMPONENTE --- | ||||
| const CategoriaDisplay = ({ categoria, mostrarBancas }: CategoriaDisplayProps) => { | ||||
|     return ( | ||||
|         <div className="categoria-bloque"> | ||||
|             <h4 className="categoria-titulo">{categoria.categoriaNombre}</h4> | ||||
|  | ||||
|             {categoria.resultados.map(res => ( | ||||
|                 <div  | ||||
|                     key={res.agrupacionId}  | ||||
|                     className="candidato-row" | ||||
|                     style={{ borderLeftColor: res.color || '#ccc' }} | ||||
|                 > | ||||
|                     <ImageWithFallback | ||||
|                         src={res.fotoUrl ?? undefined} | ||||
|                         fallbackSrc={`${assetBaseUrl}/default-avatar.png`} | ||||
|                         alt={res.nombreCandidato ?? res.nombreAgrupacion} | ||||
|                         className="candidato-foto" | ||||
|                     /> | ||||
|  | ||||
|                     <div className="candidato-data"> | ||||
|                         {res.nombreCandidato && ( | ||||
|                             <span className="candidato-nombre">{res.nombreCandidato}</span> | ||||
|                         )} | ||||
|                         <span className={`candidato-partido ${!res.nombreCandidato ? 'main-title' : ''}`}> | ||||
|                             {res.nombreAgrupacion} | ||||
|                         </span> | ||||
|                         <div className="progress-bar-container"> | ||||
|                             <div className="progress-bar" style={{ width: `${res.porcentaje}%`, backgroundColor: res.color || '#ccc' }} /> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div className="candidato-stats"> | ||||
|                         <span className="stats-percent">{formatPercent(res.porcentaje)}</span> | ||||
|                         <span className="stats-votos">{formatNumber(res.votos)} votos</span> | ||||
|                     </div> | ||||
|  | ||||
|                     {/* --- 3. RENDERIZADO CONDICIONAL DEL CUADRO DE BANCAS --- */} | ||||
|                     {/* Este div solo se renderizará si mostrarBancas es true */} | ||||
|                     {mostrarBancas && ( | ||||
|                         <div className="stats-bancas"> | ||||
|                             +{res.bancasObtenidas} | ||||
|                             <span>Bancas</span> | ||||
|                         </div> | ||||
|                     )} | ||||
|                 </div> | ||||
|             ))} | ||||
|  | ||||
|             <footer className="card-footer"> | ||||
|                 <div> | ||||
|                     <span>Participación</span> | ||||
|                     <strong>{formatPercent(categoria.estadoRecuento?.participacionPorcentaje ?? 0)}</strong> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                     <span>Mesas escrutadas</span> | ||||
|                     <strong>{formatPercent(categoria.estadoRecuento?.mesasTotalizadasPorcentaje ?? 0)}</strong> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                     <span>Votos totales</span> | ||||
|                     <strong>{formatNumber(categoria.estadoRecuento?.cantidadVotantes ?? 0)}</strong> | ||||
|                 </div> | ||||
|             </footer> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| // --- 4. RECIBIR Y PASAR LA PROP EN EL COMPONENTE PRINCIPAL --- | ||||
| export const ProvinciaCard = ({ data, mostrarBancas }: ProvinciaCardProps) => { | ||||
|     const colorGanador = data.categorias[0]?.resultados[0]?.color || '#d1d1d1'; | ||||
|  | ||||
|     return ( | ||||
|         <div className="provincia-card"> | ||||
|             <header className="card-header"> | ||||
|                 <div className="header-info"> | ||||
|                     <h3 style={{ whiteSpace: 'normal' }}>{data.provinciaNombre}</h3> | ||||
|                     <span>DIPUTADOS NACIONALES</span> | ||||
|                 </div> | ||||
|                 <div className="header-map"> | ||||
|                     <MiniMapaSvg provinciaNombre={data.provinciaNombre} fillColor={colorGanador} /> | ||||
|                 </div> | ||||
|             </header> | ||||
|             <div className="card-body"> | ||||
|                 {data.resultados.map(res => ( | ||||
|                     <div key={res.agrupacionId} className="candidato-row"> | ||||
|                         <ImageWithFallback src={res.fotoUrl ?? undefined} fallbackSrc={`${assetBaseUrl}/default-avatar.png`} alt={res.nombreCandidato ?? res.nombreAgrupacion} className="candidato-foto" /> | ||||
|  | ||||
|                         <div className="candidato-data"> | ||||
|                             {res.nombreCandidato && ( | ||||
|                                 <span className="candidato-nombre">{res.nombreCandidato}</span> | ||||
|                             )} | ||||
|  | ||||
|                             <span className={`candidato-partido ${!res.nombreCandidato ? 'main-title' : ''}`}> | ||||
|                                 {res.nombreAgrupacion} | ||||
|                             </span> | ||||
|  | ||||
|                             <div className="progress-bar-container"> | ||||
|                                 <div className="progress-bar" style={{ width: `${res.porcentaje}%`, backgroundColor: res.color || '#ccc' }} /> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <div className="candidato-stats"> | ||||
|                             <span className="stats-percent">{formatPercent(res.porcentaje)}</span> | ||||
|                             <span className="stats-votos">{formatNumber(res.votos)} votos</span> | ||||
|                         </div> | ||||
|                         <div className="stats-bancas"> | ||||
|                             +{res.bancasObtenidas} | ||||
|                             <span>Bancas</span> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 {data.categorias.map(categoria => ( | ||||
|                     <CategoriaDisplay | ||||
|                         key={categoria.categoriaId} | ||||
|                         categoria={categoria} | ||||
|                         mostrarBancas={mostrarBancas} // Pasar la prop hacia abajo | ||||
|                     /> | ||||
|                 ))} | ||||
|             </div> | ||||
|             <footer className="card-footer"> | ||||
|                 <div> | ||||
|                     <span>Participación</span> | ||||
|                     {/* Usamos los datos reales del estado de recuento */} | ||||
|                     <strong>{formatPercent(data.estadoRecuento?.participacionPorcentaje ?? 0)}</strong> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                     <span>Mesas escrutadas</span> | ||||
|                     <strong>{formatPercent(data.estadoRecuento?.mesasTotalizadasPorcentaje ?? 0)}</strong> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                     <span>Votos totales</span> | ||||
|                     {/* Usamos el nuevo campo cantidadVotantes */} | ||||
|                     <strong>{formatNumber(data.estadoRecuento?.cantidadVotantes ?? 0)}</strong> | ||||
|                 </div> | ||||
|             </footer> | ||||
|         </div> | ||||
|     ); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user