Feats y Fixs Varios
This commit is contained in:
BIN
Elecciones-Web/frontend/public/default-avatar.png
Normal file
BIN
Elecciones-Web/frontend/public/default-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -42,6 +42,7 @@ interface PartidoData {
|
||||
export interface BancadaDetalle {
|
||||
id: number; // Este es el ID de la Bancada
|
||||
camara: number; // 0 o 1
|
||||
numeroBanca: number;
|
||||
agrupacionPoliticaId: string | null;
|
||||
ocupante: OcupanteBanca | null;
|
||||
}
|
||||
|
||||
@@ -201,4 +201,9 @@
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#seat-tooltip.react-tooltip {
|
||||
opacity: 1 !important;
|
||||
background-color: white; /* Opcional: asegura un fondo sólido */
|
||||
}
|
||||
@@ -29,44 +29,44 @@ export const CongresoWidget = () => {
|
||||
|
||||
const datosCamaraActual = composicionData ? composicionData[camaraActiva] : null;
|
||||
|
||||
const esModoOficial = bancadasDetalle.length > 0;
|
||||
|
||||
// --- LÓGICA DE SEATFILLDATA ---
|
||||
const seatFillData = useMemo(() => {
|
||||
if (!datosCamaraActual) return [];
|
||||
|
||||
// --- LÓGICA DEL INTERRUPTOR ---
|
||||
// Verificamos si la respuesta de la API contiene datos de ocupantes.
|
||||
// Si bancadasDetalle tiene elementos, significa que el modo "Oficial" está activo en el backend.
|
||||
const modoOficialActivo = bancadasDetalle.length > 0;
|
||||
|
||||
if (modoOficialActivo) {
|
||||
// --- MODO OFICIAL: Construir desde las bancas físicas ---
|
||||
if (esModoOficial) {
|
||||
// --- MODO OFICIAL ---
|
||||
const camaraId = camaraActiva === 'diputados' ? 0 : 1;
|
||||
const bancadasDeCamara = bancadasDetalle.filter(b => b.camara === camaraId);
|
||||
|
||||
const colorMap = new Map<string, string>();
|
||||
datosCamaraActual.partidos.forEach(p => {
|
||||
if (p.id && p.color) {
|
||||
colorMap.set(p.id, p.color);
|
||||
datosCamaraActual.partidos.forEach(p => { if (p.id && p.color) colorMap.set(p.id, p.color); });
|
||||
|
||||
// 1. Creamos un array del tamaño correcto, lleno de 'null's
|
||||
const size = camaraActiva === 'diputados' ? 92 : 46;
|
||||
const finalSeatData = new Array(size).fill(null);
|
||||
|
||||
// 2. Poblamos el array usando NumeroBanca como índice
|
||||
bancadasDeCamara.forEach(bancada => {
|
||||
// El índice del SVG es NumeroBanca - 1
|
||||
const index = bancada.numeroBanca - 1;
|
||||
if (index >= 0 && index < size) {
|
||||
finalSeatData[index] = {
|
||||
color: bancada.agrupacionPoliticaId ? colorMap.get(bancada.agrupacionPoliticaId) || DEFAULT_COLOR : DEFAULT_COLOR,
|
||||
ocupante: bancada.ocupante
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const bancadasOrdenadas = bancadasDeCamara.sort((a, b) => a.id - b.id);
|
||||
|
||||
return bancadasOrdenadas.map(bancada => ({
|
||||
color: bancada.agrupacionPoliticaId ? colorMap.get(bancada.agrupacionPoliticaId) || DEFAULT_COLOR : DEFAULT_COLOR,
|
||||
ocupante: bancada.ocupante
|
||||
}));
|
||||
return finalSeatData;
|
||||
|
||||
} else {
|
||||
// --- MODO PROYECCIÓN: Construir desde los totales de los partidos ---
|
||||
// Esta es la lógica original que teníamos para el modo proyección.
|
||||
// --- MODO PROYECCIÓN ---
|
||||
return datosCamaraActual.partidos.flatMap(party => {
|
||||
const seatColor = party.color || DEFAULT_COLOR;
|
||||
// En modo proyección, no hay ocupantes individuales.
|
||||
return Array(party.bancasTotales).fill({ color: seatColor, ocupante: null });
|
||||
});
|
||||
}
|
||||
|
||||
}, [datosCamaraActual, bancadasDetalle, camaraActiva]);
|
||||
|
||||
if (isLoadingComposicion) return <div className="congreso-container loading">Cargando...</div>;
|
||||
@@ -78,11 +78,23 @@ export const CongresoWidget = () => {
|
||||
<div className="congreso-container">
|
||||
<div className="congreso-grafico">
|
||||
{camaraActiva === 'diputados' ? (
|
||||
<ParliamentLayout seatData={seatFillData} presidenteBancada={datosCamaraActual.presidenteBancada} />
|
||||
<ParliamentLayout
|
||||
seatData={seatFillData}
|
||||
// --- INICIO DE LA CORRECCIÓN ---
|
||||
// Solo pasamos la prop 'presidenteBancada' si NO estamos en modo oficial
|
||||
presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined}
|
||||
// --- FIN DE LA CORRECCIÓN ---
|
||||
/>
|
||||
) : (
|
||||
<SenateLayout seatData={seatFillData} presidenteBancada={datosCamaraActual.presidenteBancada} />
|
||||
<SenateLayout
|
||||
seatData={seatFillData}
|
||||
// --- INICIO DE LA CORRECCIÓN ---
|
||||
presidenteBancada={!esModoOficial ? datosCamaraActual.presidenteBancada : undefined}
|
||||
// --- FIN DE LA CORRECCIÓN ---
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="congreso-summary">
|
||||
<div className="chamber-tabs">
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// src/components/ParliamentLayout.tsx
|
||||
import React from 'react';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { handleImageFallback } from './imageFallback';
|
||||
|
||||
// Interfaces (no cambian)
|
||||
interface SeatFillData {
|
||||
@@ -14,7 +15,7 @@ interface SeatFillData {
|
||||
interface ParliamentLayoutProps {
|
||||
seatData: SeatFillData[];
|
||||
size?: number;
|
||||
presidenteBancada: { color: string | null } | null;
|
||||
presidenteBancada?: { color: string | null } | null;
|
||||
}
|
||||
|
||||
const PRESIDENTE_SEAT_INDEX = 91;
|
||||
@@ -24,9 +25,14 @@ export const ParliamentLayout: React.FC<ParliamentLayoutProps> = ({
|
||||
size = 400,
|
||||
presidenteBancada,
|
||||
}) => {
|
||||
// HOOK DE IMAGENES POR DEFECTO
|
||||
useLayoutEffect(() => {
|
||||
// Se ejecuta después de que el componente y el tooltip se hayan renderizado
|
||||
handleImageFallback('.seat-tooltip img', '/default-avatar.png');
|
||||
}, [seatData, presidenteBancada]); // Dependencias: se vuelve a ejecutar si estos datos cambian
|
||||
const uniqueColors = [...new Set(seatData.map(d => d.color))];
|
||||
|
||||
// --- NUEVO ARRAY DE ELEMENTOS ORDENADO ---
|
||||
// --- ARRAY DE ELEMENTOS ORDENADO ---
|
||||
const seatElements = [
|
||||
<circle key="seat-0" id="seat-0" r="12" cy="268.306" cx="202.26" transform="matrix(-0.632908, 0.774227, 0.774227, 0.632908, 0, 0)" />,
|
||||
<circle key="seat-1" id="seat-1" r="12" cy="214.247" cx="223.62" transform="matrix(-0.632908, 0.774227, 0.774227, 0.632908, 0, 0)" />,
|
||||
@@ -135,14 +141,12 @@ export const ParliamentLayout: React.FC<ParliamentLayoutProps> = ({
|
||||
|
||||
const renderedElements = seatElements.map((child, index) => {
|
||||
// --- CASO ESPECIAL: ASIENTO PRESIDENCIAL ---
|
||||
if (index === PRESIDENTE_SEAT_INDEX) {
|
||||
if (presidenteBancada && index === PRESIDENTE_SEAT_INDEX) {
|
||||
return React.cloneElement(child, {
|
||||
fill: presidenteBancada?.color || '#A9A9A9',
|
||||
fill: presidenteBancada.color || '#A9A9A9',
|
||||
stroke: '#000000',
|
||||
strokeWidth: 2,
|
||||
// Le damos un tooltip genérico al presidente
|
||||
'data-tooltip-id': 'seat-tooltip',
|
||||
'data-tooltip-html': `<div class="seat-tooltip"><p>Presidencia de la Cámara</p></div>`
|
||||
'data-tooltip-id': 'seat-tooltip'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// src/components/SenateLayout.tsx
|
||||
import React from 'react';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { handleImageFallback } from './imageFallback';
|
||||
|
||||
// Interfaces
|
||||
interface SeatFillData {
|
||||
@@ -14,7 +15,7 @@ interface SeatFillData {
|
||||
interface SenateLayoutProps {
|
||||
seatData: SeatFillData[];
|
||||
size?: number;
|
||||
presidenteBancada: { color: string | null } | null;
|
||||
presidenteBancada?: { color: string | null } | null;
|
||||
}
|
||||
|
||||
const PRESIDENTE_SEAT_INDEX = 45; // El último asiento (índice 45 de 46)
|
||||
@@ -24,6 +25,12 @@ export const SenateLayout: React.FC<SenateLayoutProps> = ({
|
||||
size = 400,
|
||||
presidenteBancada,
|
||||
}) => {
|
||||
// HOOK DE IMAGENES POR DEFECTO
|
||||
useLayoutEffect(() => {
|
||||
// Se ejecuta después de que el componente y el tooltip se hayan renderizado
|
||||
handleImageFallback('.seat-tooltip img', '/default-avatar.png');
|
||||
}, [seatData, presidenteBancada]); // Dependencias: se vuelve a ejecutar si estos datos cambian
|
||||
|
||||
const uniqueColors = [...new Set(seatData.map(d => d.color).filter(Boolean))];
|
||||
|
||||
// --- NUEVO ARRAY DE ELEMENTOS ORDENADO PARA EL SENADO ---
|
||||
@@ -89,14 +96,12 @@ export const SenateLayout: React.FC<SenateLayoutProps> = ({
|
||||
|
||||
const renderedElements = seatElements.map((child, index) => {
|
||||
// --- CASO ESPECIAL: ASIENTO PRESIDENCIAL ---
|
||||
if (index === PRESIDENTE_SEAT_INDEX) {
|
||||
if (presidenteBancada && index === PRESIDENTE_SEAT_INDEX) {
|
||||
return React.cloneElement(child, {
|
||||
fill: presidenteBancada?.color || '#A9A9A9',
|
||||
fill: presidenteBancada.color || '#A9A9A9',
|
||||
stroke: '#000000',
|
||||
strokeWidth: 2,
|
||||
// Le damos un tooltip genérico al presidente
|
||||
'data-tooltip-id': 'seat-tooltip',
|
||||
'data-tooltip-html': `<div class="seat-tooltip"><p>Presidencia de la Cámara</p></div>`
|
||||
'data-tooltip-id': 'seat-tooltip'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
13
Elecciones-Web/frontend/src/components/imageFallback.ts
Normal file
13
Elecciones-Web/frontend/src/components/imageFallback.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// src/components/imageFallback.ts
|
||||
|
||||
export function handleImageFallback(selector: string, fallbackImageUrl: string) {
|
||||
// Le decimos a TypeScript que el resultado será una lista de elementos de imagen HTML
|
||||
const images: NodeListOf<HTMLImageElement> = document.querySelectorAll(selector);
|
||||
|
||||
images.forEach(img => {
|
||||
// Ahora que 'img' es de tipo HTMLImageElement, TypeScript sabe que 'onerror' y 'src' son válidos.
|
||||
img.onerror = () => {
|
||||
img.src = fallbackImageUrl;
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user