| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  | // src/features/legislativas/nacionales/HomeCarouselNacionalWidget.tsx
 | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  | import { useQuery } from '@tanstack/react-query'; | 
					
						
							|  |  |  | import { getHomeResumenNacional } from '../../../apiService'; | 
					
						
							|  |  |  | import { ImageWithFallback } from '../../../components/common/ImageWithFallback'; | 
					
						
							|  |  |  | import { assetBaseUrl } from '../../../apiService'; | 
					
						
							|  |  |  | import { Swiper, SwiperSlide } from 'swiper/react'; | 
					
						
							|  |  |  | import { Navigation, A11y } from 'swiper/modules'; | 
					
						
							| 
									
										
										
										
											2025-10-23 18:19:13 -03:00
										 |  |  | import { TfiMapAlt } from "react-icons/tfi"; | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // @ts-ignore
 | 
					
						
							|  |  |  | import 'swiper/css'; | 
					
						
							|  |  |  | // @ts-ignore
 | 
					
						
							|  |  |  | import 'swiper/css/navigation'; | 
					
						
							|  |  |  | import styles from './HomeCarouselWidget.module.css'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface Props { | 
					
						
							|  |  |  |   eleccionId: number; | 
					
						
							|  |  |  |   categoriaId: number; | 
					
						
							|  |  |  |   titulo: string; | 
					
						
							| 
									
										
										
										
											2025-10-23 15:34:09 -03:00
										 |  |  |   mapLinkUrl: string; | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  | const formatPercent = (num: number | null | undefined) => `${(num || 0).toFixed(2).replace('.', ',')}%`; | 
					
						
							|  |  |  | const formatNumber = (num: number) => num.toLocaleString('es-AR'); | 
					
						
							|  |  |  | const formatDateTime = (dateString: string | undefined | null) => { | 
					
						
							|  |  |  |   if (!dateString) return '...'; | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     const date = new Date(dateString); | 
					
						
							|  |  |  |     if (isNaN(date.getTime())) { | 
					
						
							|  |  |  |       return dateString; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const day = String(date.getDate()).padStart(2, '0'); | 
					
						
							|  |  |  |     const month = String(date.getMonth() + 1).padStart(2, '0'); | 
					
						
							|  |  |  |     const year = date.getFullYear(); | 
					
						
							|  |  |  |     const hours = String(date.getHours()).padStart(2, '0'); | 
					
						
							|  |  |  |     const minutes = String(date.getMinutes()).padStart(2, '0'); | 
					
						
							|  |  |  |     return `${day}/${month}/${year}, ${hours}:${minutes} hs.`; | 
					
						
							|  |  |  |   } catch (e) { | 
					
						
							|  |  |  |     return dateString; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-23 15:34:09 -03:00
										 |  |  | export const HomeCarouselNacionalWidget = ({ eleccionId, categoriaId, titulo, mapLinkUrl }: Props) => { | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  |   const uniqueId = `swiper-${Math.random().toString(36).substring(2, 9)}`; | 
					
						
							|  |  |  |   const prevButtonClass = `prev-${uniqueId}`; | 
					
						
							|  |  |  |   const nextButtonClass = `next-${uniqueId}`; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  |   const { data, isLoading, error } = useQuery({ | 
					
						
							|  |  |  |     queryKey: ['homeResumenNacional', eleccionId, categoriaId], | 
					
						
							|  |  |  |     queryFn: () => getHomeResumenNacional(eleccionId, categoriaId), | 
					
						
							| 
									
										
										
										
											2025-10-26 21:51:04 -03:00
										 |  |  |     refetchInterval: 180000, | 
					
						
							| 
									
										
										
										
											2025-10-20 12:45:49 -03:00
										 |  |  |   }) | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  |   if (isLoading) return <div>Cargando widget...</div>; | 
					
						
							|  |  |  |   if (error || !data) return <div>No se pudieron cargar los datos.</div>; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <div className={styles.homeCarouselWidget}> | 
					
						
							| 
									
										
										
										
											2025-10-24 11:46:37 -03:00
										 |  |  |       <div className={`${styles.widgetHeader} ${styles.headerSingleLine}`}> | 
					
						
							| 
									
										
										
										
											2025-10-23 15:34:09 -03:00
										 |  |  |         <h2 className={styles.widgetTitle}>{titulo}</h2> | 
					
						
							|  |  |  |         <a href={mapLinkUrl} className={styles.mapLinkButton}> | 
					
						
							| 
									
										
										
										
											2025-10-23 18:19:13 -03:00
										 |  |  |           <TfiMapAlt /> | 
					
						
							| 
									
										
										
										
											2025-10-23 15:34:09 -03:00
										 |  |  |           <span className={styles.buttonText}>Ver Mapa</span> | 
					
						
							|  |  |  |         </a> | 
					
						
							|  |  |  |       </div> | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |       <div className={styles.carouselContainer}> | 
					
						
							|  |  |  |         <Swiper | 
					
						
							|  |  |  |           modules={[Navigation, A11y]} | 
					
						
							|  |  |  |           spaceBetween={16} | 
					
						
							|  |  |  |           slidesPerView={1.3} | 
					
						
							|  |  |  |           navigation={{ | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  |             prevEl: `.${prevButtonClass}`, | 
					
						
							|  |  |  |             nextEl: `.${nextButtonClass}`, | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  |           }} | 
					
						
							|  |  |  |           breakpoints={{ | 
					
						
							|  |  |  |             320: { slidesPerView: 1.25, spaceBetween: 10 }, | 
					
						
							|  |  |  |             430: { slidesPerView: 1.4, spaceBetween: 12 }, | 
					
						
							|  |  |  |             640: { slidesPerView: 2.5 }, | 
					
						
							|  |  |  |             1024: { slidesPerView: 3 }, | 
					
						
							|  |  |  |             1200: { slidesPerView: 3.5 } | 
					
						
							|  |  |  |           }} | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           {data.resultados.map(candidato => ( | 
					
						
							|  |  |  |             <SwiperSlide key={candidato.agrupacionId}> | 
					
						
							|  |  |  |               <div className={styles.candidateCard} style={{ '--candidate-color': candidato.color || '#ccc' } as React.CSSProperties}> | 
					
						
							|  |  |  |                 <div className={styles.candidatePhotoWrapper}> | 
					
						
							|  |  |  |                   <ImageWithFallback | 
					
						
							|  |  |  |                     src={candidato.fotoUrl ?? undefined} | 
					
						
							|  |  |  |                     fallbackSrc={`${assetBaseUrl}/default-avatar.png`} | 
					
						
							|  |  |  |                     alt={candidato.nombreCandidato ?? ''} | 
					
						
							|  |  |  |                     className={styles.candidatePhoto} | 
					
						
							|  |  |  |                   /> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |                 <div className={styles.candidateDetails}> | 
					
						
							|  |  |  |                   <div className={styles.candidateInfo}> | 
					
						
							|  |  |  |                     {candidato.nombreCandidato ? ( | 
					
						
							|  |  |  |                       <> | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  |                         <span className={styles.candidateName}>{candidato.nombreCandidato}</span> | 
					
						
							|  |  |  |                         <span className={styles.partyName}>{candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion}</span> | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  |                       </> | 
					
						
							|  |  |  |                     ) : ( | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  |                       <span className={styles.candidateName}>{candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion}</span> | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  |                     )} | 
					
						
							|  |  |  |                   </div> | 
					
						
							|  |  |  |                   <div className={styles.candidateResults}> | 
					
						
							|  |  |  |                     <span className={styles.percentage}>{formatPercent(candidato.porcentaje)}</span> | 
					
						
							|  |  |  |                     <span className={styles.votes}>{formatNumber(candidato.votos)} votos</span> | 
					
						
							|  |  |  |                   </div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |               </div> | 
					
						
							|  |  |  |             </SwiperSlide> | 
					
						
							|  |  |  |           ))} | 
					
						
							|  |  |  |         </Swiper> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 10:51:51 -03:00
										 |  |  |         <div className={`${styles.navButton} ${styles.navButtonPrev} ${prevButtonClass}`}></div> | 
					
						
							|  |  |  |         <div className={`${styles.navButton} ${styles.navButtonNext} ${nextButtonClass}`}></div> | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  |       </div> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-23 10:51:50 -03:00
										 |  |  |       <div className={styles.topStatsBar}> | 
					
						
							|  |  |  |         <div> | 
					
						
							|  |  |  |           <span>Participación</span> | 
					
						
							|  |  |  |           <strong>{formatPercent(data.estadoRecuento?.participacionPorcentaje)}</strong> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         <div> | 
					
						
							|  |  |  |           <span className={styles.longText}>Mesas escrutadas</span> | 
					
						
							|  |  |  |           <span className={styles.shortText}>Escrutado</span> | 
					
						
							|  |  |  |           <strong>{formatPercent(data.estadoRecuento?.mesasTotalizadasPorcentaje)}</strong> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         <div> | 
					
						
							|  |  |  |           <span className={styles.longText}>Votos en blanco</span> | 
					
						
							|  |  |  |           <span className={styles.shortText}>En blanco</span> | 
					
						
							|  |  |  |           <strong>{formatPercent(data.votosEnBlancoPorcentaje)}</strong> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         <div> | 
					
						
							|  |  |  |           <span className={styles.longText}>Votos totales</span> | 
					
						
							|  |  |  |           <span className={styles.shortText}>Votos</span> | 
					
						
							|  |  |  |           <strong>{formatNumber(data.votosTotales)}</strong> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 15:49:15 -03:00
										 |  |  |       <div className={styles.widgetFooter}> | 
					
						
							|  |  |  |         Última actualización: {formatDateTime(data.ultimaActualizacion)} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; |