140 lines
5.5 KiB
TypeScript
140 lines
5.5 KiB
TypeScript
// 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';
|
|
|
|
interface Props {
|
|
eleccionId: number;
|
|
categoriaId: number;
|
|
titulo: string;
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
export const HomeCarouselNacionalWidget = ({ eleccionId, categoriaId, titulo }: Props) => {
|
|
const uniqueId = `swiper-${Math.random().toString(36).substring(2, 9)}`;
|
|
const prevButtonClass = `prev-${uniqueId}`;
|
|
const nextButtonClass = `next-${uniqueId}`;
|
|
|
|
const { data, isLoading, error } = useQuery({
|
|
queryKey: ['homeResumenNacional', eleccionId, categoriaId],
|
|
queryFn: () => getHomeResumenNacional(eleccionId, categoriaId),
|
|
refetchInterval: 30000,
|
|
})
|
|
|
|
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.carouselContainer}>
|
|
<Swiper
|
|
modules={[Navigation, A11y]}
|
|
spaceBetween={16}
|
|
slidesPerView={1.3}
|
|
navigation={{
|
|
prevEl: `.${prevButtonClass}`,
|
|
nextEl: `.${nextButtonClass}`,
|
|
}}
|
|
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} ${prevButtonClass}`}></div>
|
|
<div className={`${styles.navButton} ${styles.navButtonNext} ${nextButtonClass}`}></div>
|
|
</div>
|
|
|
|
<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.widgetFooter}>
|
|
Última actualización: {formatDateTime(data.ultimaActualizacion)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |