Public
Edited
Nov 26, 2024
Insert cell
Insert cell
viewof tarjetas = {
const initCards = async () => {
const container = document.getElementById('cardContainer');
const input = document.getElementById('searchInput');
const sortAscButton = document.getElementById('sortAsc');
const sortDescButton = document.getElementById('sortDesc');
const modal = document.createElement('div');
modal.id = "infoModal";
modal.style.display = "none";
modal.style.position = "absolute";
modal.style.backgroundColor = "white";
modal.style.border = "1px solid #ccc";
modal.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)";
modal.style.padding = "10px";
modal.style.zIndex = "1000";

document.body.appendChild(modal);

if (!container || !input || !sortAscButton || !sortDescButton) {
console.warn("Esperando al DOM...");
await new Promise(resolve => setTimeout(resolve, 100));
return initCards();
}

input.addEventListener('keyup', filterCards);
sortAscButton.addEventListener('click', () => sortCards('asc'));
sortDescButton.addEventListener('click', () => sortCards('desc'));

container.addEventListener('click', (event) => {
if (event.target.classList.contains('info-button')) {
const personaId = event.target.dataset.id;
const persona = DATOS.find(p => p.id === personaId);
showModal(event, persona);
}
});

document.addEventListener('click', (event) => {
if (!modal.contains(event.target) && !event.target.classList.contains('info-button')) {
modal.style.display = "none";
}
});
};

initCards();

return html`
<div>
<div class="search-bar">
<input type="text" id="searchInput" placeholder="Buscar por nombre o federación..." />
<button id="sortDesc" class="sort-button">Ordenar ↓</button>
<button id="sortAsc" class="sort-button">Ordenar ↑</button>
</div>
<div class="card-container" id="cardContainer">
${DATOS.map(persona => html`
<div class="card">
<!-- MODAL -->
${persona.extra1 || persona.extra2 || persona.extra3 || persona.extra4 ? html`
<button class="info-button" data-id="${persona.id}">+</button>
` : ''}
<!-- LOGO -->
<div class="logo-section">
<img src="${persona.logo}" alt="Logo" class="logo-img">
</div>
<!-- NOMBRE Y FEDERACIÓN -->
<div class="info-section">
<div class="nombre">${persona.nombre}</div>
<div class="federacion">${persona.genero} ${persona.federacion}</div>
</div>
<!-- SALARIO -->
<div class="highlight-section">
<div class="salario">${persona.salario} €</div>
</div>
<!-- AÑO -->
<div class="info-extra">
<div class="año"><a href="${persona.enlace}" target="_blank">Salario bruto relativo a ${persona.año}</a></div>
</div>
<!-- LÍNEA SEPARADORA -->
<div class="divider"></div>
<!-- FINANCIACIÓN -->
<div class="final-info">
<div class="financiacion">Financiación pública: ${persona.financiacion}</div>
</div>
</div>
`)}
</div>
</div>
`;
};
Insert cell
viewof buscador = {
const input = document.getElementById('searchInput');
const container = document.getElementById('cardContainer');
if (input && container) {
input.addEventListener('keyup', filterCards);
} else {
console.error("El buscador o contenedor de tarjetas no están listos.");
}
return null;
};
Insert cell
d3 = require("d3@7")
Insert cell
url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQ1uWFBV-RLrQX1vRDWAO5sZ23EYon_qTrLy-s5Jwxo8YJBRXC42Plg-Jk6_Q14u9JHT9jQt96S8jL9/pub?output=csv"
Insert cell
DATOS = d3.csv(url).then(data =>
data.map((d, index) => ({
id: `persona-${index}`,
federacion: d.FEDERACION,
nombre: d.NOMBRE,
genero: d.GENERO,
salario: d.SALARIO,
año: d.AÑO,
financiacion: d.FINANCIACION,
logo: d.LOGO,
extra1: d.EXTRA1,
extra2: d.EXTRA2,
extra3: d.EXTRA3,
extra4: d.EXTRA4,
enlace: d.ENLACE,
}))
)
Insert cell
function normalizeText(text) {
return text.normalize('NFD').replace(/[\u0300-\u036f]/g, "").toLowerCase();
}
Insert cell
function filterCards() {
const input = document.getElementById('searchInput');
const filter = normalizeText(input.value);
const container = document.getElementById("cardContainer");
const cards = container.getElementsByClassName('card');
for (let i = 0; i < cards.length; i++) {
const nombre = cards[i].querySelector(".nombre");
const federacion = cards[i].querySelector(".federacion");

if (nombre && federacion) {
const nombreText = normalizeText(nombre.textContent || nombre.innerText);
const federacionText = normalizeText(federacion.textContent || federacion.innerText);
if (nombreText.includes(filter) || federacionText.includes(filter)) {
cards[i].style.display = "";
} else {
cards[i].style.display = "none";
}
}
}
}
Insert cell
evento = {
const input = document.getElementById('searchInput');
if (input) {
input.addEventListener('keyup', filterCards);
}
return null;
}
Insert cell
function sortCards(order) {
const container = document.getElementById('cardContainer');
const cards = Array.from(container.getElementsByClassName('card'));

cards.sort((a, b) => {
const salarioA = parseFloat(a.querySelector('.salario').textContent.replace('€', '').trim());
const salarioB = parseFloat(b.querySelector('.salario').textContent.replace('€', '').trim());

return order === 'asc' ? salarioA - salarioB : salarioB - salarioA;
});

cards.forEach(card => container.appendChild(card));
}
Insert cell
function showModal(event, persona) {
if (!persona) return;
const modal = document.getElementById('infoModal');
const modalWidth = 300;
const modalHeight = 200;

modal.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center;">
<h3>Otras retribuciones</h3>
<button id="closeModal" style="background: none; border: none; font-size: 18px; cursor: pointer;">✖</button>
</div>
<p><b>Gastos de viaje y estancias:</b> ${persona.extra1 || '-'}</p>
<p><b>En especie:</b> ${persona.extra2 || '-'}</p>
<p><b>Dietas:</b> ${persona.extra3 || '-'}</p>
<p><b>Gastos de representación:</b> ${persona.extra4 || '-'}</p>
`;

const { clientX, clientY } = event;
const { innerWidth, innerHeight } = window;
const xOffset = 10;
const yOffset = 10;

let top = clientY + yOffset;
let left = clientX + xOffset;

if (clientX + modalWidth + xOffset > innerWidth) {
left = clientX - modalWidth - xOffset;
}

if (clientY + modalHeight + yOffset > innerHeight) {
top = clientY - modalHeight - yOffset;
}

modal.style.top = `${top}px`;
modal.style.left = `${left}px`;
modal.style.display = "block";

const closeModal = document.getElementById('closeModal');
closeModal.addEventListener('click', () => {
modal.style.display = "none";
});
}
Insert cell
estilos = html`
<style>
/* Estilos generales */
.sort-button {
background-color: #01f3b3;
color: white;
border: none;
padding: 10px 15px;
margin: 5px;
font-size: 14px;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s;
}

/* Botón del modal */
.info-button {
position: absolute;
top: 10px;
right: 10px;
background-color: #01f3b3;
border: none;
color: white;
font-size: 14px;
font-weight: bold;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
}

.info-button:hover {
background-color: #00c494;
}

/* Modal */
#infoModal {
position: absolute;
background-color: white;
border: 3px solid black;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 20px;
z-index: 1000;
width: 300px;
animation: fadeIn 0.3s ease-out;
font-family: Arial, sans-serif;
}

#infoModal h3 {
color: #01f3b3;
margin: 0 0 10px;
font-size: 18px;
border-bottom: 1px solid #ddd;
padding-bottom: 5px;
}

#infoModal p {
color: #444;
margin: 5px 0;
font-size: 14px;
}

#closeModal {
background: none;
border: none;
font-size: 18px;
color: #666;
cursor: pointer;
transition: color 0.3s ease;
}

#closeModal:hover {
color: #ff5e5e;
}

/* Animaciones */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.card-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
justify-content: center;
}

.card {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border: 2px solid #01f3b3;
border-radius: 12px;
max-width: 300px;
min-height: 500px;
box-sizing: border-box;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
text-align: center;
transition: transform 0.3s;
display: flex;
justify-content: space-between;
position: relative;
}

.card:hover {
transform: scale(1.05);
}

.logo-section {
margin-bottom: 10px;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
}

.logo-img {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
}

.info-section {
margin-bottom: 10px;
min-height: 80px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.nombre {
font-size: 18px;
font-weight: bold;
color: #333;
line-height: 1.2;
text-align: center;
min-height: 50px;
}

.federacion {
font-size: 14px;
color: #666;
margin-top: 5px;
min-height: 20px;
}

.highlight-section {
background-color: #01f3b3;
color: black;
padding: 8px 12px;
border-radius: 8px;
font-size: 20px;
font-weight: bold;
margin: 12px 0;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
}

.info-extra {
font-size: 12px;
color: #888;
margin-bottom: 12px;
min-height: 18px;
display: flex;
align-items: center;
justify-content: center;
}

.divider {
width: 80%;
height: 1px;
background-color: #ddd;
margin: 12px 0;
}

.final-info {
font-size: 12px;
color: #444;
min-height: 30px;
display: flex;
align-items: center;
justify-content: center;
}

.search-bar {
margin-bottom: 20px;
text-align: center;
}

.search-bar input[type="text"] {
width: 80%;
max-width: 400px;
padding: 10px;
font-size: 16px;
border-radius: 8px;
border: 2px solid #ccc
}

.search-bar input[type="text"]:hover,
.search-bar input[type="text"]:focus {
border-color: #01f3b3;
}

/* Estilos para el botón "+" siempre visible en la tarjeta */
.extra-button {
background-color: #01f3b3;
color: white;
border: none;
padding: 5px 10px;
border-radius: 8px;
cursor: pointer;
position: absolute;
top: 10px;
right: 10px;
font-size: 14px;
z-index: 1000;
}

/* Estilos para el modal */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}

.modal-content {
background-color: white;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
text-align: left;
border-radius: 10px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
}

/* Estilos para el botón de cierre del modal */
.close-button {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}

.close-button:hover,
.close-button:focus {
color: black;
text-decoration: none;
}
</style>
<img src=${await FileAttachment("newtral_logo.png").url()} width="30" height="30" style="vertical-align: middle;">
<span style="font-family: Helvetica; border-bottom: 3px solid #01f3b3;">NewtralData</span>
`;
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more