rss2/templates/stats_entities.html

548 lines
No EOL
18 KiB
HTML

{% extends "base.html" %}
{% block title %}Monitor de Entidades{% endblock %}
{% block content %}
<div class="monitor-layout">
<!-- PANEL IZQUIERDO: FILTROS -->
<aside class="monitor-sidebar newspaper-sidebar card">
<div class="sidebar-header">
<h3><i class="fas fa-filter"></i> FILTROS</h3>
</div>
<div class="filter-section">
<label class="filter-label"><i class="fas fa-globe"></i> PAÍS</label>
<div class="custom-dropdown big-dropdown" id="countryDropdown">
<input type="text" id="countrySearch" class="form-control big-search" placeholder="Buscar país..."
value="Global" readonly>
<div class="dropdown-results static-results" id="dropdownResults"></div>
</div>
</div>
<div class="filter-section">
<label class="filter-label"><i class="far fa-calendar-alt"></i> FECHA</label>
<input type="date" id="datePicker" class="form-control big-date" placeholder="Seleccionar fecha">
</div>
<div class="filter-actions content-center">
<button id="clearFilters" class="btn btn-newspaper full-btn">
<i class="fas fa-times-circle"></i> LIMPIAR FILTROS
</button>
</div>
<div class="active-filters-summary">
<span class="badge badge-newspaper" id="timeRangeBadge">Global | Últimos 30 días</span>
</div>
</aside>
<!-- PANEL DERECHO: CONTENIDO Y TABS -->
<main class="monitor-main">
<!-- TABS DE NAVEGACIÓN -->
<div class="monitor-tabs newspaper-tabs">
<button class="monitor-tab active" onclick="switchTab('personas')">
<i class="fas fa-user-tie"></i> PERSONAS
</button>
<button class="monitor-tab" onclick="switchTab('organizaciones')">
<i class="fas fa-building"></i> ORGANIZACIONES
</button>
<button class="monitor-tab" onclick="switchTab('lugares')">
<i class="fas fa-globe-americas"></i> LUGARES
</button>
</div>
<!-- CONTENIDO TABS -->
<div class="tab-content-container">
<!-- TAB: PERSONAS -->
<div id="tab-personas" class="tab-pane active fade-in">
<div class="card entities-card full-height">
<div class="entities-container">
<div class="list-box scroll-y">
<div class="list-header-row">
<span>ENTIDAD</span>
<span>MENCIONES</span>
</div>
<ol class="entity-list" id="peopleList"></ol>
</div>
</div>
</div>
</div>
<!-- TAB: ORGANIZACIONES -->
<div id="tab-organizaciones" class="tab-pane fade-in">
<div class="card entities-card full-height">
<div class="entities-container">
<div class="list-box scroll-y">
<div class="list-header-row">
<span>ORGANIZACIÓN</span>
<span>MENCIONES</span>
</div>
<ol class="entity-list" id="orgsList"></ol>
</div>
</div>
</div>
</div>
<!-- TAB: LUGARES -->
<div id="tab-lugares" class="tab-pane fade-in">
<div class="card entities-card full-height">
<div class="entities-container">
<div class="list-box scroll-y">
<div class="list-header-row">
<span>LUGAR</span>
<span>MENCIONES</span>
</div>
<ol class="entity-list" id="placesList"></ol>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Div para tooltips globales -->
<div id="fixed-tooltip" class="entity-tooltip"></div>
<script>
// --- TABS LOGIC ---
function switchTab(tabName) {
document.querySelectorAll('.tab-pane').forEach(el => {
el.classList.remove('active');
el.style.display = 'none';
});
document.querySelectorAll('.monitor-tab').forEach(el => el.classList.remove('active'));
const selected = document.getElementById('tab-' + tabName);
if (selected) {
selected.classList.add('active');
selected.style.display = 'block';
}
const btns = document.querySelectorAll('.monitor-tab');
btns.forEach(btn => {
if (btn.textContent.toLowerCase().includes(tabName)) {
btn.classList.add('active');
}
});
}
function updateAll(country, date) {
loadPeople(country, date);
loadOrgs(country);
loadPlaces(country);
}
function createEntityLi(name, count, imgUrl, summary) {
const li = document.createElement('li');
const wikiUrl = `https://es.wikipedia.org/wiki/${encodeURIComponent(name.replace(/ /g, '_'))}`;
let imgHtml = (imgUrl && imgUrl !== "NO_IMAGE")
? `<img src="${imgUrl}" class="entity-avatar" alt="${name}">`
: `<div class="entity-avatar-placeholder">${name.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase()}</div>`;
li.innerHTML = `
<div class="entity-item-wrapper">
${imgHtml}
<div class="entity-info">
<a href="${wikiUrl}" target="_blank" class="entity-name-link"
onmouseover="showTooltip(event, '${name}', '${summary ? summary.replace(/'/g, "\\'") : ''}', '${imgUrl || ''}')"
onmouseout="hideTooltip()">
${name}
</a>
</div>
</div>
<span class="entity-count-badge">${count}</span>
`;
return li;
}
function showTooltip(event, name, summary, imgUrl) {
const tooltip = document.getElementById('fixed-tooltip');
if (!summary) return;
let imgTag = (imgUrl && imgUrl !== "NO_IMAGE")
? `<img src="${imgUrl}" style="width:100%; height:150px; object-fit:cover; border-radius:4px; margin-bottom:10px; border: 1px solid var(--border-gray);">`
: '';
tooltip.innerHTML = `
<div class="tooltip-content">
${imgTag}
<h4 style="font-family: var(--headline-font); margin: 0 0 10px 0; color: var(--ink-black); border-bottom: 2px solid var(--accent-red); padding-bottom: 5px;">${name}</h4>
<p style="font-family: var(--body-font); font-size: 0.9rem; line-height: 1.4; color: var(--newspaper-gray); margin: 0;">${summary}</p>
<div style="margin-top: 10px; font-size: 0.75rem; text-align: right; color: var(--accent-red); font-weight: bold; text-transform: uppercase;">
<i class="fab fa-wikipedia-w"></i> Wikpedia
</div>
</div>
`;
tooltip.style.visibility = 'visible';
tooltip.style.opacity = '1';
updateTooltipPos(event);
}
function hideTooltip() {
const tooltip = document.getElementById('fixed-tooltip');
tooltip.style.visibility = 'hidden';
tooltip.style.opacity = '0';
}
function updateTooltipPos(e) {
const tooltip = document.getElementById('fixed-tooltip');
let x = e.clientX + 20;
let y = e.clientY - 40;
// Prevent overflow
if (x + 320 > window.innerWidth) x = e.clientX - 340;
if (y + 400 > window.innerHeight) y = window.innerHeight - 410;
if (y < 10) y = 10;
tooltip.style.left = x + 'px';
tooltip.style.top = y + 'px';
}
document.addEventListener('mousemove', (e) => {
const tooltip = document.getElementById('fixed-tooltip');
if (tooltip.style.visibility === 'visible') {
updateTooltipPos(e);
}
});
function loadPeople(country, date) {
let url = '/stats/api/entities/people';
const params = [];
if (country && country !== 'global') params.push(`country=${encodeURIComponent(country)}`);
if (date) params.push(`date=${encodeURIComponent(date)}`);
if (params.length > 0) url += '?' + params.join('&');
const list = document.getElementById('peopleList');
if (list) list.innerHTML = '<div class="loading-newspaper">CARGANDO ARCHIVOS...</div>';
fetch(url)
.then(res => res.json())
.then(data => {
if (list) list.innerHTML = '';
if (!data.labels || data.labels.length === 0) {
list.innerHTML = '<div class="no-data">SIN REGISTROS</div>';
return;
}
data.labels.forEach((name, i) => {
list.appendChild(createEntityLi(name, data.data[i], data.images[i], data.summaries[i]));
});
})
.catch(err => {
if (list) list.innerHTML = '<div class="error-msg">ERROR DE CONEXIÓN</div>';
});
}
function loadOrgs(country) {
let url = '/stats/api/entities/orgs';
if (country && country !== 'global') url += `?country=${encodeURIComponent(country)}`;
const list = document.getElementById('orgsList');
if (list) list.innerHTML = '<div class="loading-newspaper">CARGANDO...</div>';
fetch(url)
.then(res => res.json())
.then(data => {
if (list) {
list.innerHTML = '';
if (!data.labels || data.labels.length === 0) return;
data.labels.forEach((name, i) => {
list.appendChild(createEntityLi(name, data.data[i], data.images[i], data.summaries[i]));
});
}
});
}
function loadPlaces(country) {
let url = '/stats/api/entities/places';
if (country && country !== 'global') url += `?country=${encodeURIComponent(country)}`;
const list = document.getElementById('placesList');
if (list) list.innerHTML = '<div class="loading-newspaper">CARGANDO...</div>';
fetch(url).then(res => res.json()).then(data => {
if (list) {
list.innerHTML = '';
if (!data.labels || data.labels.length === 0) return;
data.labels.forEach((name, i) => {
list.appendChild(createEntityLi(name, data.data[i], data.images[i], data.summaries[i]));
});
}
});
}
// --- FILTERS LOGIC ---
document.getElementById('clearFilters').addEventListener('click', function () {
document.getElementById('countrySearch').value = 'Global';
document.getElementById('datePicker').value = '';
renderResults('');
updateAll('global', null);
});
document.getElementById('datePicker').addEventListener('change', function (e) {
const c = document.getElementById('countrySearch').value;
updateAll(c === 'Global' ? 'global' : c, e.target.value);
});
function selectCountry(disp, val) {
document.getElementById('countrySearch').value = disp;
updateAll(val, document.getElementById('datePicker').value || null);
}
let allCountries = [];
fetch('/stats/api/countries/list').then(res => res.json()).then(data => {
allCountries = data;
renderResults('');
});
function renderResults(filter) {
const dr = document.getElementById('dropdownResults');
dr.innerHTML = '';
if (!filter || 'global'.includes(filter.toLowerCase())) {
const g = document.createElement('div');
g.className = 'dropdown-item';
g.innerHTML = '🌍 <strong>GLOBAL</strong>';
g.onclick = () => selectCountry('Global', 'global');
dr.appendChild(g);
}
allCountries.filter(c => !filter || c.name.toLowerCase().includes(filter.toLowerCase())).forEach(c => {
const i = document.createElement('div');
i.className = 'dropdown-item';
i.innerHTML = `<span class="flag">${c.flag}</span> <span class="name">${c.name.toUpperCase()}</span>`;
i.onclick = () => selectCountry(c.name, c.name);
dr.appendChild(i);
});
}
document.getElementById('countrySearch').addEventListener('input', e => renderResults(e.target.value));
document.getElementById('countrySearch').addEventListener('click', e => { e.target.value = ''; renderResults(''); });
// Initial
switchTab('personas');
updateAll('global', null);
</script>
<style>
.monitor-layout {
display: flex;
gap: 30px;
padding-top: 20px;
background: var(--paper-cream);
font-family: var(--sans-font);
}
.monitor-sidebar {
width: 320px;
flex-shrink: 0;
background: var(--paper-white);
border: 1px solid var(--ink-black);
border-radius: 0;
/* Square edges for newspaper */
padding: 20px;
box-shadow: 5px 5px 0px var(--border-gray);
position: sticky;
top: 20px;
}
.sidebar-header h3 {
font-family: var(--headline-font);
border-bottom: 3px double var(--ink-black);
padding-bottom: 10px;
margin-bottom: 20px;
text-align: center;
letter-spacing: 1px;
}
.filter-label {
font-family: var(--headline-font);
font-weight: 900;
font-size: 0.8rem;
color: var(--ink-black);
margin-bottom: 8px;
display: block;
}
.big-search,
.big-date {
border-radius: 0;
border: 1px solid var(--ink-black);
font-family: var(--sans-font);
padding: 10px;
}
.btn-newspaper {
background: var(--ink-black);
color: var(--paper-white);
border: none;
border-radius: 0;
font-weight: bold;
padding: 12px;
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 1px;
transition: all 0.2s;
}
.btn-newspaper:hover {
background: var(--accent-red);
color: white;
}
.badge-newspaper {
background: var(--paper-cream);
color: var(--ink-black);
border: 1px solid var(--ink-black);
border-radius: 0;
padding: 5px 10px;
font-family: var(--headline-font);
font-weight: bold;
}
/* TABS */
.newspaper-tabs {
border-bottom: 4px double var(--ink-black);
margin-bottom: 20px;
}
.monitor-tab {
font-family: var(--headline-font);
font-weight: 900;
text-transform: uppercase;
letter-spacing: 1px;
border: none;
background: transparent;
padding: 15px 25px;
border-radius: 0;
transition: all 0.2s;
}
.monitor-tab.active {
background: var(--ink-black);
color: var(--paper-white) !important;
}
.entities-card {
background: var(--paper-white);
border: 1px solid var(--border-gray);
border-radius: 0;
box-shadow: none;
}
.list-header-row {
background: var(--light-gray);
font-family: var(--headline-font);
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 1px;
padding: 12px 20px;
}
.entity-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid var(--light-gray);
transition: background 0.2s;
}
.entity-list li:hover {
background: var(--paper-cream);
}
.entity-item-wrapper {
display: flex;
align-items: center;
gap: 15px;
}
.entity-avatar {
width: 45px;
height: 45px;
border-radius: 50%;
object-fit: cover;
border: 1px solid var(--ink-black);
}
.entity-avatar-placeholder {
width: 45px;
height: 45px;
background: var(--light-gray);
border: 1px solid var(--border-gray);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: var(--text-gray);
font-size: 0.8rem;
}
.entity-name-link {
font-family: var(--headline-font);
font-weight: 700;
font-size: 1.1rem;
color: var(--ink-black);
text-decoration: none;
}
.entity-name-link:hover {
color: var(--accent-red);
text-decoration: underline;
}
.entity-count-badge {
font-family: var(--sans-font);
background: var(--paper-cream);
color: var(--accent-red);
border: 1px solid var(--accent-red);
padding: 4px 10px;
font-weight: 900;
font-size: 0.9rem;
}
/* TOOLTIP */
.entity-tooltip {
position: fixed;
visibility: hidden;
opacity: 0;
z-index: 10000;
background: var(--paper-white);
border: 1px solid var(--ink-black);
padding: 20px;
width: 320px;
box-shadow: 10px 10px 0px rgba(0, 0, 0, 0.2);
transition: opacity 0.3s;
pointer-events: none;
}
.loading-newspaper {
padding: 40px;
text-align: center;
font-family: var(--headline-font);
font-weight: 900;
color: var(--border-gray);
letter-spacing: 3px;
}
/* DROPDOWN CUSTOM */
.dropdown-results.static-results {
background: var(--paper-white);
border: 1px solid var(--ink-black);
max-height: 300px;
overflow-y: auto;
}
.dropdown-item {
padding: 8px 15px;
font-size: 0.85rem;
font-weight: bold;
cursor: pointer;
border-bottom: 1px solid var(--light-gray);
}
.dropdown-item:hover {
background: var(--paper-cream);
color: var(--accent-red);
}
</style>
{% endblock %}