548 lines
No EOL
18 KiB
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 %} |