Feat Widgets
- Widget de Home - Widget Cards por Provincias - Widget Mapa por Categorias
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
// src/features/legislativas/nacionales/HomeCarouselWidget.tsx
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getHomeResumen } 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 './HomeCarouselWidget.css';
|
||||
|
||||
interface Props {
|
||||
eleccionId: number;
|
||||
distritoId: string;
|
||||
categoriaId: number;
|
||||
titulo: string;
|
||||
}
|
||||
|
||||
const formatPercent = (num: number | null | undefined) => `${(num || 0).toFixed(2).replace('.', ',')}%`;
|
||||
const formatNumber = (num: number) => num.toLocaleString('es-AR');
|
||||
|
||||
// --- Lógica de formateo de fecha ---
|
||||
const formatDateTime = (dateString: string | undefined | null) => {
|
||||
if (!dateString) return '...';
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
// Verificar si la fecha es válida
|
||||
if (isNaN(date.getTime())) {
|
||||
return dateString; // Si no se puede parsear, devolver el string original
|
||||
}
|
||||
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; // En caso de cualquier error, devolver el string original
|
||||
}
|
||||
};
|
||||
|
||||
export const HomeCarouselWidget = ({ eleccionId, distritoId, categoriaId, titulo }: Props) => {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['homeResumen', eleccionId, distritoId, categoriaId],
|
||||
queryFn: () => getHomeResumen(eleccionId, distritoId, categoriaId),
|
||||
});
|
||||
|
||||
if (isLoading) return <div>Cargando widget...</div>;
|
||||
if (error || !data) return <div>No se pudieron cargar los datos.</div>;
|
||||
|
||||
return (
|
||||
<div className="home-carousel-widget">
|
||||
<h2 className="widget-title">{titulo}</h2>
|
||||
|
||||
<div className="top-stats-bar">
|
||||
<div>
|
||||
<span>Participación</span>
|
||||
<strong>{formatPercent(data.estadoRecuento?.participacionPorcentaje)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span className="long-text">Mesas escrutadas</span>
|
||||
<span className="short-text">Escrutado</span>
|
||||
<strong>{formatPercent(data.estadoRecuento?.mesasTotalizadasPorcentaje)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span className="long-text">Votos en blanco</span>
|
||||
<span className="short-text">En blanco</span>
|
||||
<strong>{formatPercent(data.votosEnBlancoPorcentaje)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span className="long-text">Votos totales</span>
|
||||
<span className="short-text">Votos</span>
|
||||
<strong>{formatNumber(data.votosTotales)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Swiper
|
||||
modules={[Navigation, A11y]}
|
||||
spaceBetween={16}
|
||||
slidesPerView={1.15}
|
||||
navigation
|
||||
breakpoints={{ 640: { slidesPerView: 2 }, 1024: { slidesPerView: 3 }, 1200: { slidesPerView: 3.5 } }} // Añadir breakpoint
|
||||
>
|
||||
{data.resultados.map(candidato => (
|
||||
<SwiperSlide key={candidato.agrupacionId}>
|
||||
<div className="candidate-card" style={{ '--candidate-color': candidato.color || '#ccc' } as React.CSSProperties}>
|
||||
|
||||
<div className="candidate-photo-wrapper">
|
||||
<ImageWithFallback
|
||||
src={candidato.fotoUrl ?? undefined}
|
||||
fallbackSrc={`${assetBaseUrl}/default-avatar.png`}
|
||||
alt={candidato.nombreCandidato ?? ''}
|
||||
className="candidate-photo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="candidate-details">
|
||||
<div className="candidate-info">
|
||||
{candidato.nombreCandidato ? (
|
||||
// CASO 1: Hay un candidato (se muestran dos líneas)
|
||||
<>
|
||||
<span className="candidate-name">
|
||||
{candidato.nombreCandidato}
|
||||
</span>
|
||||
<span className="party-name">
|
||||
{candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
// CASO 2: No hay candidato (se muestra solo una línea)
|
||||
<span className="candidate-name">
|
||||
{candidato.nombreCortoAgrupacion || candidato.nombreAgrupacion}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="candidate-results">
|
||||
<span className="percentage">{formatPercent(candidato.porcentaje)}</span>
|
||||
<span className="votes">{formatNumber(candidato.votos)} votos</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
<div className="widget-footer">
|
||||
Última actualización: {formatDateTime(data.ultimaActualizacion)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user