Feats y Fixs Varios

This commit is contained in:
2025-08-30 11:31:45 -03:00
parent 3b8c6bf754
commit 608ae655be
22 changed files with 732 additions and 100 deletions

View File

@@ -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;
}

View File

@@ -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 */
}

View File

@@ -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

View File

@@ -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'
});
}

View File

@@ -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'
});
}

View 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;
};
});
}