Feat Widgets 2030
This commit is contained in:
@@ -43,6 +43,16 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* APLICA LA TRANSICIÓN POR DEFECTO A ZOOMABLEGROUP */
|
||||
.mapa-container .rsm-zoomable-group {
|
||||
transition: transform 400ms ease-in-out;
|
||||
}
|
||||
|
||||
/* DESACTIVA LA TRANSICIÓN CUANDO SE ESTÁ ARRASTRANDO */
|
||||
.mapa-container .rsm-zoomable-group.panning {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.rsm-geography {
|
||||
transition: opacity 0.3s ease, transform 0.2s ease, filter 0.2s ease, fill 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -47,7 +47,7 @@ type PartidoGeography = Feature<Geometry, PartidoProperties> & { rsmKey: string
|
||||
const API_BASE_URL = 'http://localhost:5217/api';
|
||||
const MIN_ZOOM = 1;
|
||||
const MAX_ZOOM = 8;
|
||||
const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -600], [1100, 300]];
|
||||
const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -1000], [1100, 800]];
|
||||
const INITIAL_POSITION = { center: [-60.5, -37.2] as PointTuple, zoom: MIN_ZOOM };
|
||||
const DEFAULT_MAP_COLOR = '#E0E0E0';
|
||||
|
||||
@@ -63,6 +63,7 @@ const MapaBsAs = () => {
|
||||
const [selectedAmbitoId, setSelectedAmbitoId] = useState<number | null>(null);
|
||||
const [selectedCategoriaId, setSelectedCategoriaId] = useState<number>(6);
|
||||
const [tooltipContent, setTooltipContent] = useState('');
|
||||
const [isPanning, setIsPanning] = useState(false);
|
||||
|
||||
const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapa[]>({
|
||||
queryKey: ['mapaResultadosPorMunicipio', selectedCategoriaId],
|
||||
@@ -121,7 +122,7 @@ const MapaBsAs = () => {
|
||||
}
|
||||
}, [selectedAmbitoId, handleReset, resultadosPorDepartamento]);
|
||||
|
||||
const handleMoveEnd = (newPosition: { coordinates: PointTuple; zoom: number }) => {
|
||||
const handleMoveEnd = (newPosition: { coordinates: PointTuple; zoom: number }) => { // <--- CORRECCIÓN 1
|
||||
if (newPosition.zoom <= MIN_ZOOM) {
|
||||
if (position.zoom > MIN_ZOOM || selectedAmbitoId !== null) {
|
||||
handleReset();
|
||||
@@ -189,8 +190,12 @@ const MapaBsAs = () => {
|
||||
<ZoomableGroup
|
||||
center={position.center}
|
||||
zoom={position.zoom}
|
||||
onMoveEnd={handleMoveEnd}
|
||||
style={{ transition: "transform 400ms ease-in-out" }}
|
||||
className={isPanning ? 'panning' : ''}
|
||||
onMoveStart={() => setIsPanning(true)}
|
||||
onMoveEnd={(newPosition: { coordinates: PointTuple; zoom: number }) => {
|
||||
setIsPanning(false);
|
||||
handleMoveEnd(newPosition);
|
||||
}}
|
||||
translateExtent={TRANSLATE_EXTENT}
|
||||
minZoom={MIN_ZOOM}
|
||||
maxZoom={MAX_ZOOM}
|
||||
|
||||
@@ -30,61 +30,59 @@ const DEFAULT_MAP_COLOR = '#E0E0E0';
|
||||
const CATEGORIAS: Categoria[] = [{ id: 5, nombre: 'Senadores' }, { id: 6, nombre: 'Diputados' }];
|
||||
const SECCION_ID_TO_ROMAN: Record<string, string> = { '1': 'I', '2': 'II', '3': 'III', '4': 'IV', '5': 'V', '6': 'VI', '7': 'VII', '8': 'VIII' };
|
||||
const ROMAN_TO_SECCION_ID: Record<string, string> = { 'I': '1', 'II': '2', 'III': '3', 'IV': '4', 'V': '5', 'VI': '6', 'VII': '7', 'VIII': '8' };
|
||||
// --- CORRECCIÓN 1: Mover NOMBRES_SECCIONES aquí para que sea global al archivo ---
|
||||
const NOMBRES_SECCIONES: Record<string, string> = {
|
||||
'I': 'Sección Primera', 'II': 'Sección Segunda', 'III': 'Sección Tercera', 'IV': 'Sección Cuarta',
|
||||
'V': 'Sección Quinta', 'VI': 'Sección Sexta', 'VII': 'Sección Séptima', 'VIII': 'Sección Capital'
|
||||
};
|
||||
const MIN_ZOOM = 1;
|
||||
const MAX_ZOOM = 5;
|
||||
const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -600], [1100, 300]];
|
||||
const TRANSLATE_EXTENT: [[number, number], [number, number]] = [[-100, -1000], [1100, 800]];
|
||||
const INITIAL_POSITION = { center: [-60.5, -37.2] as PointTuple, zoom: MIN_ZOOM };
|
||||
|
||||
|
||||
// --- Componente de Detalle ---
|
||||
const DetalleSeccion = ({ seccion, categoriaId, onReset }: { seccion: SeccionGeography | null, categoriaId: number, onReset: () => void }) => {
|
||||
const seccionId = seccion ? ROMAN_TO_SECCION_ID[seccion.properties.seccion] : null;
|
||||
const seccionId = seccion ? ROMAN_TO_SECCION_ID[seccion.properties.seccion] : null;
|
||||
|
||||
const { data: resultadosDetalle, isLoading, error } = useQuery<ResultadoDetalleSeccion[]>({
|
||||
queryKey: ['detalleSeccion', seccionId, categoriaId],
|
||||
queryFn: () => getDetalleSeccion(seccionId!, categoriaId),
|
||||
enabled: !!seccionId,
|
||||
});
|
||||
const { data: resultadosDetalle, isLoading, error } = useQuery<ResultadoDetalleSeccion[]>({
|
||||
queryKey: ['detalleSeccion', seccionId, categoriaId],
|
||||
queryFn: () => getDetalleSeccion(seccionId!, categoriaId),
|
||||
enabled: !!seccionId,
|
||||
});
|
||||
|
||||
if (!seccion) {
|
||||
return (
|
||||
<div className="detalle-placeholder">
|
||||
<h3>Resultados por Sección</h3>
|
||||
<p>Haga clic en una sección del mapa para ver los resultados detallados.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) return (<div className="detalle-loading"><div className="spinner"></div><p>Cargando resultados de la sección...</p></div>);
|
||||
if (error) return <div className="detalle-error">Error al cargar los datos de la sección.</div>;
|
||||
|
||||
const nombreSeccionLegible = NOMBRES_SECCIONES[seccion.properties.seccion] || "Sección Desconocida";
|
||||
|
||||
if (!seccion) {
|
||||
return (
|
||||
<div className="detalle-placeholder">
|
||||
<h3>Resultados por Sección</h3>
|
||||
<p>Haga clic en una sección del mapa para ver los resultados detallados.</p>
|
||||
</div>
|
||||
<div className="detalle-content">
|
||||
<button className="reset-button-panel" onClick={onReset}>← VOLVER</button>
|
||||
<h3>{nombreSeccionLegible}</h3>
|
||||
<ul className="resultados-lista">
|
||||
{resultadosDetalle?.map((r) => (
|
||||
<li key={r.id}>
|
||||
<div className="resultado-info">
|
||||
<span className="partido-nombre">{r.nombre}</span>
|
||||
<span className="partido-votos">{r.votos.toLocaleString('es-AR')} ({r.porcentaje.toFixed(2)}%)</span>
|
||||
</div>
|
||||
<div className="progress-bar">
|
||||
<div className="progress-fill" style={{ width: `${r.porcentaje}%`, backgroundColor: r.color || DEFAULT_MAP_COLOR }}></div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) return (<div className="detalle-loading"><div className="spinner"></div><p>Cargando resultados de la sección...</p></div>);
|
||||
if (error) return <div className="detalle-error">Error al cargar los datos de la sección.</div>;
|
||||
|
||||
const nombreSeccionLegible = NOMBRES_SECCIONES[seccion.properties.seccion] || "Sección Desconocida";
|
||||
|
||||
return (
|
||||
<div className="detalle-content">
|
||||
<button className="reset-button-panel" onClick={onReset}>← VOLVER</button>
|
||||
<h3>{nombreSeccionLegible}</h3>
|
||||
<ul className="resultados-lista">
|
||||
{resultadosDetalle?.map((r) => (
|
||||
<li key={r.id}>
|
||||
<div className="resultado-info">
|
||||
<span className="partido-nombre">{r.nombre}</span>
|
||||
<span className="partido-votos">{r.votos.toLocaleString('es-AR')} ({r.porcentaje.toFixed(2)}%)</span>
|
||||
</div>
|
||||
<div className="progress-bar">
|
||||
{/* --- CORRECCIÓN 2: Usar el color de la API --- */}
|
||||
<div className="progress-fill" style={{ width: `${r.porcentaje}%`, backgroundColor: r.color || DEFAULT_MAP_COLOR }}></div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// --- Componente de Controles del Mapa ---
|
||||
@@ -100,86 +98,89 @@ const MapaBsAsSecciones = () => {
|
||||
const [selectedCategoriaId, setSelectedCategoriaId] = useState<number>(6);
|
||||
const [clickedSeccion, setClickedSeccion] = useState<SeccionGeography | null>(null);
|
||||
const [tooltipContent, setTooltipContent] = useState('');
|
||||
const [isPanning, setIsPanning] = useState(false);
|
||||
|
||||
const { data: geoData, isLoading: isLoadingGeo } = useQuery<any>({
|
||||
queryKey: ['mapaGeoDataSecciones'],
|
||||
queryFn: async () => (await axios.get('./secciones-electorales-pba.topojson')).data,
|
||||
});
|
||||
const { data: geoData, isLoading: isLoadingGeo } = useQuery<any>({
|
||||
queryKey: ['mapaGeoDataSecciones'],
|
||||
queryFn: async () => (await axios.get('./secciones-electorales-pba.topojson')).data,
|
||||
});
|
||||
|
||||
const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapaSeccion[]>({
|
||||
queryKey: ['mapaResultadosPorSeccion', selectedCategoriaId],
|
||||
queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-seccion?categoriaId=${selectedCategoriaId}`)).data,
|
||||
});
|
||||
const { data: resultadosData, isLoading: isLoadingResultados } = useQuery<ResultadoMapaSeccion[]>({
|
||||
queryKey: ['mapaResultadosPorSeccion', selectedCategoriaId],
|
||||
queryFn: async () => (await axios.get(`${API_BASE_URL}/Resultados/mapa-por-seccion?categoriaId=${selectedCategoriaId}`)).data,
|
||||
});
|
||||
|
||||
const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery<Agrupacion[]>({
|
||||
queryKey: ['catalogoAgrupaciones'],
|
||||
queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data,
|
||||
});
|
||||
const { data: agrupacionesData, isLoading: isLoadingAgrupaciones } = useQuery<Agrupacion[]>({
|
||||
queryKey: ['catalogoAgrupaciones'],
|
||||
queryFn: async () => (await axios.get(`${API_BASE_URL}/Catalogos/agrupaciones`)).data,
|
||||
});
|
||||
|
||||
const { nombresAgrupaciones, resultadosPorSeccionRomana } = useMemo<{
|
||||
nombresAgrupaciones: Map<string, string>;
|
||||
resultadosPorSeccionRomana: Map<string, ResultadoMapaSeccion>;
|
||||
}>(() => {
|
||||
const nombresMap = new Map<string, string>();
|
||||
const resultadosMap = new Map<string, ResultadoMapaSeccion>();
|
||||
const { nombresAgrupaciones, resultadosPorSeccionRomana } = useMemo<{
|
||||
nombresAgrupaciones: Map<string, string>;
|
||||
resultadosPorSeccionRomana: Map<string, ResultadoMapaSeccion>;
|
||||
}>((
|
||||
) => {
|
||||
const nombresMap = new Map<string, string>();
|
||||
const resultadosMap = new Map<string, ResultadoMapaSeccion>();
|
||||
|
||||
if (agrupacionesData) {
|
||||
agrupacionesData.forEach(a => nombresMap.set(a.id, a.nombre));
|
||||
}
|
||||
if (resultadosData) {
|
||||
resultadosData.forEach(r => {
|
||||
const roman = SECCION_ID_TO_ROMAN[r.seccionId];
|
||||
if (roman) resultadosMap.set(roman, r);
|
||||
});
|
||||
}
|
||||
return { nombresAgrupaciones: nombresMap, resultadosPorSeccionRomana: resultadosMap };
|
||||
}, [agrupacionesData, resultadosData]);
|
||||
if (agrupacionesData) {
|
||||
agrupacionesData.forEach(a => nombresMap.set(a.id, a.nombre));
|
||||
}
|
||||
if (resultadosData) {
|
||||
resultadosData.forEach(r => {
|
||||
const roman = SECCION_ID_TO_ROMAN[r.seccionId];
|
||||
if (roman) resultadosMap.set(roman, r);
|
||||
});
|
||||
}
|
||||
return { nombresAgrupaciones: nombresMap, resultadosPorSeccionRomana: resultadosMap };
|
||||
}, [agrupacionesData, resultadosData]);
|
||||
|
||||
const isLoading = isLoadingGeo || isLoadingResultados || isLoadingAgrupaciones;
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
setClickedSeccion(null);
|
||||
setPosition(INITIAL_POSITION);
|
||||
}, []);
|
||||
const handleReset = useCallback(() => {
|
||||
setClickedSeccion(null);
|
||||
setPosition(INITIAL_POSITION);
|
||||
}, []);
|
||||
|
||||
const handleGeographyClick = useCallback((geo: SeccionGeography) => {
|
||||
if (clickedSeccion?.rsmKey === geo.rsmKey) {
|
||||
handleReset();
|
||||
} else {
|
||||
const centroid = geoCentroid(geo as any) as PointTuple;
|
||||
setPosition({ center: centroid, zoom: 2 });
|
||||
setClickedSeccion(geo);
|
||||
}
|
||||
}, [clickedSeccion, handleReset]);
|
||||
|
||||
const handleMoveEnd = (newPosition: { coordinates: PointTuple; zoom: number }) => {
|
||||
if (newPosition.zoom <= MIN_ZOOM) {
|
||||
if (position.zoom > MIN_ZOOM || clickedSeccion !== null) {
|
||||
const handleGeographyClick = useCallback((geo: SeccionGeography) => {
|
||||
if (clickedSeccion?.rsmKey === geo.rsmKey) {
|
||||
handleReset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (newPosition.zoom < position.zoom && clickedSeccion !== null) {
|
||||
setClickedSeccion(null);
|
||||
}
|
||||
setPosition({ center: newPosition.coordinates, zoom: newPosition.zoom });
|
||||
};
|
||||
} else {
|
||||
const centroid = geoCentroid(geo as any) as PointTuple;
|
||||
setPosition({ center: centroid, zoom: 2 });
|
||||
setClickedSeccion(geo);
|
||||
}
|
||||
}, [clickedSeccion, handleReset]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => e.key === 'Escape' && handleReset();
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleReset]);
|
||||
const handleMoveEnd = (newPosition: { coordinates: PointTuple; zoom: number }) => {
|
||||
if (newPosition.zoom <= MIN_ZOOM) {
|
||||
if (position.zoom > MIN_ZOOM || clickedSeccion !== null) {
|
||||
handleReset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (newPosition.zoom < position.zoom && clickedSeccion !== null) {
|
||||
setClickedSeccion(null);
|
||||
}
|
||||
setPosition({ center: newPosition.coordinates, zoom: newPosition.zoom });
|
||||
};
|
||||
|
||||
const getSectionFillColor = (seccionRomana: string) => {
|
||||
return resultadosPorSeccionRomana.get(seccionRomana)?.colorGanador || DEFAULT_MAP_COLOR;
|
||||
};
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => e.key === 'Escape' && handleReset();
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleReset]);
|
||||
|
||||
const getSectionFillColor = (seccionRomana: string) => {
|
||||
return resultadosPorSeccionRomana.get(seccionRomana)?.colorGanador || DEFAULT_MAP_COLOR;
|
||||
};
|
||||
|
||||
const handleZoomIn = () => {
|
||||
if (position.zoom < MAX_ZOOM) {
|
||||
setPosition(p => ({ ...p, zoom: Math.min(p.zoom * 1.5, MAX_ZOOM) }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleZoomIn = () => {
|
||||
if (position.zoom < MAX_ZOOM) {
|
||||
setPosition(p => ({ ...p, zoom: Math.min(p.zoom * 1.5, MAX_ZOOM) }));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mapa-wrapper">
|
||||
@@ -195,11 +196,15 @@ const MapaBsAsSecciones = () => {
|
||||
<ZoomableGroup
|
||||
center={position.center}
|
||||
zoom={position.zoom}
|
||||
onMoveEnd={handleMoveEnd}
|
||||
onMoveEnd={(newPosition: { coordinates: PointTuple; zoom: number }) => {
|
||||
setIsPanning(false);
|
||||
handleMoveEnd(newPosition);
|
||||
}}
|
||||
minZoom={MIN_ZOOM}
|
||||
maxZoom={MAX_ZOOM}
|
||||
translateExtent={TRANSLATE_EXTENT}
|
||||
style={{ transition: "transform 400ms ease-in-out" }}
|
||||
className={isPanning ? 'panning' : ''}
|
||||
onMoveStart={() => setIsPanning(true)}
|
||||
filterZoomEvent={(e: WheelEvent) => {
|
||||
if (e.deltaY > 0) {
|
||||
handleReset();
|
||||
@@ -228,7 +233,6 @@ const MapaBsAsSecciones = () => {
|
||||
onClick={isClickable ? () => handleGeographyClick(geo) : undefined}
|
||||
onMouseEnter={() => {
|
||||
if (isClickable) {
|
||||
// --- CORRECCIÓN 3: Tooltip con nombre de sección ---
|
||||
const nombreSeccionLegible = NOMBRES_SECCIONES[geo.properties.seccion] || "Sección Desconocida";
|
||||
setTooltipContent(`${nombreSeccionLegible}: ${nombreGanador}`);
|
||||
}
|
||||
@@ -272,32 +276,32 @@ const MapaBsAsSecciones = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// --- Sub-componente para la Leyenda ---
|
||||
// --- Sub-componente para la Leyenda (sin cambios) ---
|
||||
const LegendSecciones = ({ resultados, nombresAgrupaciones }: { resultados: Map<string, ResultadoMapaSeccion>, nombresAgrupaciones: Map<string, string> }) => {
|
||||
const legendItems = useMemo(() => {
|
||||
const ganadoresUnicos = new Map<string, { nombre: string; color: string }>();
|
||||
resultados.forEach(resultado => {
|
||||
if (resultado.agrupacionGanadoraId && resultado.colorGanador && !ganadoresUnicos.has(resultado.agrupacionGanadoraId)) {
|
||||
ganadoresUnicos.set(resultado.agrupacionGanadoraId, {
|
||||
nombre: nombresAgrupaciones.get(resultado.agrupacionGanadoraId) || 'Desconocido',
|
||||
color: resultado.colorGanador
|
||||
const legendItems = useMemo(() => {
|
||||
const ganadoresUnicos = new Map<string, { nombre: string; color: string }>();
|
||||
resultados.forEach(resultado => {
|
||||
if (resultado.agrupacionGanadoraId && resultado.colorGanador && !ganadoresUnicos.has(resultado.agrupacionGanadoraId)) {
|
||||
ganadoresUnicos.set(resultado.agrupacionGanadoraId, {
|
||||
nombre: nombresAgrupaciones.get(resultado.agrupacionGanadoraId) || 'Desconocido',
|
||||
color: resultado.colorGanador
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return Array.from(ganadoresUnicos.values());
|
||||
}, [resultados, nombresAgrupaciones]);
|
||||
return Array.from(ganadoresUnicos.values());
|
||||
}, [resultados, nombresAgrupaciones]);
|
||||
|
||||
return (
|
||||
<div className="legend">
|
||||
<h4>Ganadores por Sección</h4>
|
||||
{legendItems.map(item => (
|
||||
<div key={item.nombre} className="legend-item">
|
||||
<div className="legend-color-box" style={{ backgroundColor: item.color }} />
|
||||
<span>{item.nombre}</span>
|
||||
return (
|
||||
<div className="legend">
|
||||
<h4>Ganadores por Sección</h4>
|
||||
{legendItems.map(item => (
|
||||
<div key={item.nombre} className="legend-item">
|
||||
<div className="legend-color-box" style={{ backgroundColor: item.color }} />
|
||||
<span>{item.nombre}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default MapaBsAsSecciones;
|
||||
@@ -83,7 +83,7 @@ export const TelegramaWidget = () => {
|
||||
setTelegrama(null);
|
||||
getTelegramaPorId(selectedMesa)
|
||||
.then(setTelegrama)
|
||||
.catch(() => setError(`No se encontró el telegrama para la mesa seleccionada.`))
|
||||
.catch(() => setError(`El telegrama para la mesa seleccionada, aún no se cargó en el sistema.`))
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
}, [selectedMesa]);
|
||||
|
||||
@@ -21,16 +21,17 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e0e0e0; /* Borde más claro */
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ticker-header h3 {
|
||||
margin: 0;
|
||||
color: #212529; /* Color de título oscuro */
|
||||
color: #212529;
|
||||
font-size: 1.2em;
|
||||
font-weight: 700;
|
||||
padding-right: 15px; /* Espacio para que no se pegue al dropdown en escritorio */
|
||||
}
|
||||
|
||||
.ticker-stats {
|
||||
@@ -41,14 +42,14 @@
|
||||
}
|
||||
|
||||
.ticker-stats strong {
|
||||
color: #0073e6; /* Se usa el azul primario para destacar */
|
||||
color: #0073e6;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.ticker-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px; /* Espacio entre partidos */
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ticker-party .party-info {
|
||||
@@ -71,7 +72,7 @@
|
||||
}
|
||||
|
||||
.party-bar-background {
|
||||
background-color: #e9ecef; /* Fondo de barra claro */
|
||||
background-color: #e9ecef;
|
||||
border-radius: 4px;
|
||||
height: 10px;
|
||||
overflow: hidden;
|
||||
@@ -81,18 +82,17 @@
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.5s ease-in-out;
|
||||
/* El color de fondo se sigue aplicando desde el componente, esto es correcto */
|
||||
}
|
||||
|
||||
.ticker-results {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); /* Aumentamos el tamaño mínimo */
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.ticker-party {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px; /* Espacio entre logo y detalles */
|
||||
gap: 10px;
|
||||
}
|
||||
.party-logo {
|
||||
flex-shrink: 0;
|
||||
@@ -108,5 +108,29 @@
|
||||
}
|
||||
.party-details {
|
||||
flex-grow: 1;
|
||||
min-width: 0; /* Previene que el flex item se desborde */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* --- ESTILOS PARA RESPONSIVIDAD MÓVIL --- */
|
||||
@media (max-width: 600px) {
|
||||
.ticker-header {
|
||||
flex-direction: column;
|
||||
/* Centra los elementos hijos horizontalmente */
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ticker-header h3 {
|
||||
font-size: 1.1em;
|
||||
padding-right: 0; /* Quitamos el padding en móvil */
|
||||
}
|
||||
|
||||
/* Esta regla asegura que el dropdown siga ocupando todo el ancho */
|
||||
.ticker-header > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ticker-party .party-name {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,6 @@ public class ResultadosController : ControllerBase
|
||||
|
||||
var resultadosVotos = await _dbContext.ResultadosVotos.AsNoTracking()
|
||||
.Include(rv => rv.AgrupacionPolitica)
|
||||
// --- CORRECCIÓN: Usamos la 'categoriaId' que viene como parámetro ---
|
||||
.Where(rv => rv.AmbitoGeograficoId == ambito.Id && rv.CategoriaId == categoriaId)
|
||||
.ToListAsync();
|
||||
|
||||
@@ -270,7 +269,7 @@ public class ResultadosController : ControllerBase
|
||||
// Mapeamos los resultados de los partidos
|
||||
var resultadosPartidosDto = resultadosVotos
|
||||
.OrderByDescending(r => r.CantidadVotos)
|
||||
.Select(rv => new AgrupacionResultadoDto // Assuming AgrupacionResultadoDto is the correct DTO for individual party results
|
||||
.Select(rv => new AgrupacionResultadoDto
|
||||
{
|
||||
Id = rv.AgrupacionPolitica.Id,
|
||||
Nombre = rv.AgrupacionPolitica.NombreCorto ?? rv.AgrupacionPolitica.Nombre,
|
||||
@@ -360,8 +359,6 @@ public class ResultadosController : ControllerBase
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// --- FIN DE LA CORRECCIÓN CLAVE ---
|
||||
|
||||
var presidenteDiputados = bancasPorAgrupacion
|
||||
.Where(b => b.Camara == Core.Enums.TipoCamara.Diputados)
|
||||
.OrderByDescending(b => b.BancasTotales)
|
||||
@@ -579,7 +576,6 @@ public class ResultadosController : ControllerBase
|
||||
r.Agrupacion.Color,
|
||||
LogoUrl = logosConcejales.GetValueOrDefault(r.Agrupacion.Id)?.LogoUrl,
|
||||
Votos = r.Votos,
|
||||
// --- CORRECCIÓN CLAVE ---
|
||||
// 3. Usamos el nombre de propiedad correcto que el frontend espera: 'votosPorcentaje'
|
||||
VotosPorcentaje = totalVotosSeccion > 0 ? ((decimal)r.Votos * 100 / totalVotosSeccion) : 0
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6732a0e826a402495269212729673ebf1ff01916")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","KaNgYB2ifCIE3p/Tay5fVAWfGAbZ/FRwD44afnqRoKI=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","iL0B6pwfSYZpOYzq7AuHcEbBAAVseMon4HovdUC\u002BTcU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","FktDKOD55tGexuQTZYqJXFJKcfFUYha2UUveJ7i4d\u002B0=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","yfarYtFn36AY3Hcf8kh\u002BwBS7wI/HSaScE\u002BTOvW5GoAM="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","KaNgYB2ifCIE3p/Tay5fVAWfGAbZ/FRwD44afnqRoKI=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","iL0B6pwfSYZpOYzq7AuHcEbBAAVseMon4HovdUC\u002BTcU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["h1yBBcAgq4jIQ1vINVvluRQMeuJlGA3/Zciq/j5c0AM=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","6WTvWQ72AaZBYOVSmaxaci9tc1dW5p7IK9Kscjj2cb0=","vAy46VJ9Gp8QqG/Px4J1mj8jL6ws4/A01UKRmMYfYek=","cdgbHR/E4DJsddPc\u002BTpzoUMOVNaFJZm33Pw7AxU9Ees=","4r4JGR4hS5m4rsLfuCSZxzrknTBxKFkLQDXc\u002B2KbqTU=","yVoZ4UnBcSOapsJIi046hnn7ylD3jAcEBUxQ\u002Brkvj/4=","/GfbpJthEWmsuz0uFx1QLHM7gyM1wLLeQgAIl4SzUD4=","i5\u002B5LcfxQD8meRAkQbVf4wMvjxSE4\u002BjCd2/FdPtMpms=","AvSkxVPIg0GjnB1RJ4hDNyo9p9GONrzDs8uVuixH\u002BOE=","IgT9pOgRnK37qfILj2QcjFoBZ180HMt\u002BScgje2iYOo4=","ezUlOMzNZmyKDIe1wwXqvX\u002BvhwfB992xNVts7r2zcTc=","y2BV4WpkQuLfqQhfOQBtmuzh940c3s4LAopGKfztfTE=","lHTUEsMkDu8nqXtfTwl7FRfgocyyc7RI5O/edTHN1\u002B0=","A7nz7qgOtQ1CwZZLvNnr0b5QZB3fTi3y4i6y7rBIcxQ=","znnuRi2tsk7AACuYo4WSgj7NcLriG4PKVaF4L35SvDk=","GelE32odx/vTului267wqi6zL3abBnF9yvwC2Q66LoM=","FktDKOD55tGexuQTZYqJXFJKcfFUYha2UUveJ7i4d\u002B0=","6CAjHexjcmVc1caYyfNvMfhJRU6qtmi57Siv1ysirg0=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","yfarYtFn36AY3Hcf8kh\u002BwBS7wI/HSaScE\u002BTOvW5GoAM="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6732a0e826a402495269212729673ebf1ff01916")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6732a0e826a402495269212729673ebf1ff01916")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+9393d2bc05f1fda0ad9e78d988aa3fc088cfc2d7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+6732a0e826a402495269212729673ebf1ff01916")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
Reference in New Issue
Block a user