Feat CarouselNacional y Fix Workers
This commit is contained in:
		| @@ -5,6 +5,7 @@ import { CongresoNacionalWidget } from './nacionales/CongresoNacionalWidget'; | ||||
| import { PanelNacionalWidget } from './nacionales/PanelNacionalWidget'; | ||||
| import { HomeCarouselWidget } from './nacionales/HomeCarouselWidget'; | ||||
| import './DevAppStyle.css' | ||||
| import { HomeCarouselNacionalWidget } from './nacionales/HomeCarouselNacionalWidget'; | ||||
|  | ||||
| // --- NUEVO COMPONENTE REUTILIZABLE PARA CONTENIDO COLAPSABLE --- | ||||
| const CollapsibleWidgetWrapper = ({ children }: { children: React.ReactNode }) => { | ||||
| @@ -52,22 +53,34 @@ export const DevAppLegislativas = () => { | ||||
|             <PanelNacionalWidget eleccionId={2} /> | ||||
|  | ||||
|             <div style={sectionStyle}> | ||||
|                 <h2>Widget: Carrusel de Resultados (Home)</h2> | ||||
|                 <h2>Widget: Carrusel de Resultados Provincias (Home)</h2> | ||||
|                 <p style={descriptionStyle}> | ||||
|                     Uso: <code style={codeStyle}><HomeCarouselWidget eleccionId={2} distritoId="02" categoriaId={2} titulo="Diputados - Provincia de Buenos Aires" /></code> | ||||
|                     Uso: <code style={codeStyle}><HomeCarouselWidget eleccionId={2} distritoId="02" categoriaId={3} titulo="Diputados - Provincia de Buenos Aires" /></code> | ||||
|                 </p> | ||||
|                 <HomeCarouselWidget | ||||
|                     eleccionId={2} // Nacional | ||||
|                     distritoId="02" // Buenos Aires | ||||
|                     categoriaId={2} // Diputados Nacionales | ||||
|                     categoriaId={3} // Diputados Nacionales | ||||
|                     titulo="Diputados - Provincia de Buenos Aires" | ||||
|                 /> | ||||
|             </div> | ||||
|  | ||||
|             <div style={sectionStyle}> | ||||
|                 <h2>Widget: Carrusel de Resultados Nación (Home)</h2> | ||||
|                 <p style={descriptionStyle}> | ||||
|                     Uso: <code style={codeStyle}><HomeCarouselNacionalWidget eleccionId={2} categoriaId={3} titulo="Diputados - Argentina" /></code> | ||||
|                 </p> | ||||
|                 <HomeCarouselNacionalWidget | ||||
|                     eleccionId={2} | ||||
|                     categoriaId={3} // 3 para Diputados, 2 para Senadores | ||||
|                     titulo="Diputados - Total País" | ||||
|                 /> | ||||
|             </div> | ||||
|  | ||||
|             {/* --- SECCIÓN PARA EL WIDGET DE TARJETAS CON EJEMPLOS --- */} | ||||
|             <div style={sectionStyle}> | ||||
|                 <h2>Widget: Resultados por Provincia (Tarjetas)</h2> | ||||
|                  | ||||
|  | ||||
|                 <hr /> | ||||
|  | ||||
|                 <h3 style={{ marginTop: '2rem' }}>1. Vista por Defecto</h3> | ||||
| @@ -89,7 +102,7 @@ export const DevAppLegislativas = () => { | ||||
|                     Ejemplo Buenos Aires: <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="02" /></code> | ||||
|                 </p> | ||||
|                 <ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="02" /> | ||||
|                  | ||||
|  | ||||
|                 <p style={{ ...descriptionStyle, marginTop: '2rem' }}> | ||||
|                     Ejemplo Chaco (que también renueva Senadores): <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="06" /></code> | ||||
|                 </p> | ||||
| @@ -101,10 +114,10 @@ export const DevAppLegislativas = () => { | ||||
|                 <p style={descriptionStyle}> | ||||
|                     Muestra todas las provincias que votan para una categoría específica. | ||||
|                     <br /> | ||||
|                     Ejemplo Senadores (ID 1): <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={1} /></code> | ||||
|                     Ejemplo Senadores (ID 2): <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={2} /></code> | ||||
|                 </p> | ||||
|                 <ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={1} /> | ||||
|                  | ||||
|                 <ResultadosNacionalesCardsWidget eleccionId={2} focoCategoriaId={2} /> | ||||
|  | ||||
|                 <hr style={{ marginTop: '2rem' }} /> | ||||
|  | ||||
|                 <h3 style={{ marginTop: '2rem' }}>4. Indicando Cantidad de Resultados (cantidadResultados)</h3> | ||||
| @@ -135,9 +148,9 @@ export const DevAppLegislativas = () => { | ||||
|                     <br /> | ||||
|                     Ejemplo: Mostrar el TOP 1 (el ganador) para la categoría de SENADORES en la provincia de RÍO NEGRO (Distrito ID "16"). | ||||
|                     <br /> | ||||
|                     Uso: <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={1} cantidadResultados={1} /></code> | ||||
|                     Uso: <code style={codeStyle}><ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={2} cantidadResultados={1} /></code> | ||||
|                 </p> | ||||
|                 <ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={1} cantidadResultados={1} /> | ||||
|                 <ResultadosNacionalesCardsWidget eleccionId={2} focoDistritoId="16" focoCategoriaId={2} cantidadResultados={1} /> | ||||
|  | ||||
|             </div> | ||||
|         </div> | ||||
|   | ||||
| @@ -0,0 +1,146 @@ | ||||
| // src/features/legislativas/nacionales/HomeCarouselNacionalWidget.tsx | ||||
| 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'; | ||||
|  | ||||
| // @ts-ignore | ||||
| import 'swiper/css'; | ||||
| // @ts-ignore | ||||
| import 'swiper/css/navigation'; | ||||
| import styles from './HomeCarouselWidget.module.css'; | ||||
|  | ||||
| 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; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| // Las props ya no incluyen distritoId | ||||
| interface Props { | ||||
|   eleccionId: number; | ||||
|   categoriaId: number; | ||||
|   titulo: string; | ||||
| } | ||||
|  | ||||
| export const HomeCarouselNacionalWidget = ({ eleccionId, categoriaId, titulo }: Props) => { | ||||
|   const { data, isLoading, error } = useQuery({ | ||||
|     // La queryKey ahora no necesita distritoId | ||||
|     queryKey: ['homeResumenNacional', eleccionId, categoriaId], | ||||
|     // Llama a la nueva función de la API | ||||
|     queryFn: () => getHomeResumenNacional(eleccionId, categoriaId), | ||||
|   }); | ||||
|   if (isLoading) return <div>Cargando widget...</div>; | ||||
|   if (error || !data) return <div>No se pudieron cargar los datos.</div>; | ||||
|  | ||||
|   return ( | ||||
|     <div className={styles.homeCarouselWidget}> | ||||
|       <h2 className={styles.widgetTitle}>{titulo}</h2> | ||||
|  | ||||
|       <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> | ||||
|  | ||||
|       <div className={styles.carouselContainer}> | ||||
|         <Swiper | ||||
|           modules={[Navigation, A11y]} | ||||
|           spaceBetween={16} | ||||
|           slidesPerView={1.3} | ||||
|           navigation={{ | ||||
|             prevEl: `.${styles.navButtonPrev}`, | ||||
|             nextEl: `.${styles.navButtonNext}`, | ||||
|           }} | ||||
|           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 ? ( | ||||
|                       <> | ||||
|                         <span className={styles.candidateName}> | ||||
|                           {candidato.nombreCandidato} | ||||
|                         </span> | ||||
|                         <span className={styles.partyName}> | ||||
|                           {candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion} | ||||
|                         </span> | ||||
|                       </> | ||||
|                     ) : ( | ||||
|                       <span className={styles.candidateName}> | ||||
|                         {candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion} | ||||
|                       </span> | ||||
|                     )} | ||||
|                   </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> | ||||
|  | ||||
|         <div className={`${styles.navButton} ${styles.navButtonPrev}`}></div> | ||||
|         <div className={`${styles.navButton} ${styles.navButtonNext}`}></div> | ||||
|       </div> | ||||
|  | ||||
|       <div className={styles.widgetFooter}> | ||||
|         Última actualización: {formatDateTime(data.ultimaActualizacion)} | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user