- Añadir AJAX handlers para paginación en las 3 tablas (búsquedas, clicks, sin resultados) - Implementar controles de paginación estilo sitio principal (Inicio, números, Ver más, Fin) - Añadir overlay de carga que mantiene el tamaño del card (sin saltos visuales) - Estilos de paginación: botones con padding 8px 16px, border-radius 6px, activo naranja #FF8600 - Spinner CSS puro centrado durante la carga - Deshabilitar pointer-events mientras carga para evitar doble clic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
315 lines
12 KiB
JavaScript
315 lines
12 KiB
JavaScript
/**
|
|
* ROI APU Search - Analytics Dashboard Scripts
|
|
*
|
|
* @package ROI_APU_Search
|
|
* @since 1.2.0
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// State
|
|
var currentPages = {
|
|
'top-searches': 1,
|
|
'top-clicks': 1,
|
|
'zero-results': 1
|
|
};
|
|
|
|
/**
|
|
* Initialize Bootstrap tooltips
|
|
*/
|
|
function initTooltips() {
|
|
var tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
|
if (tooltipTriggerList.length > 0 && typeof bootstrap !== 'undefined') {
|
|
Array.prototype.slice.call(tooltipTriggerList).forEach(function(el) {
|
|
new bootstrap.Tooltip(el);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show success message
|
|
*/
|
|
function showSuccessMessage(message) {
|
|
var container = document.getElementById('alertContainer');
|
|
if (!container) return;
|
|
container.innerHTML = '<div class="admin-notice notice-success mb-3"><i class="bi bi-check-circle me-2"></i><strong>' + message + '</strong></div>';
|
|
setTimeout(function() { container.innerHTML = ''; }, 3000);
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
*/
|
|
function showErrorMessage(message) {
|
|
var container = document.getElementById('alertContainer');
|
|
if (!container) return;
|
|
container.innerHTML = '<div class="admin-notice notice-error mb-3"><i class="bi bi-exclamation-circle me-2"></i><strong>' + message + '</strong></div>';
|
|
}
|
|
|
|
/**
|
|
* Escape HTML
|
|
*/
|
|
function escapeHtml(text) {
|
|
if (!text) return '';
|
|
var div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* Format number
|
|
*/
|
|
function formatNumber(num) {
|
|
return parseInt(num, 10).toLocaleString('es-ES');
|
|
}
|
|
|
|
/**
|
|
* Load page via AJAX
|
|
*/
|
|
function loadPage(tableId, page) {
|
|
var config = {
|
|
'top-searches': {
|
|
action: 'roi_apu_paginate_searches',
|
|
bodyId: 'top-searches-body',
|
|
paginationId: 'pagination-searches'
|
|
},
|
|
'top-clicks': {
|
|
action: 'roi_apu_paginate_clicks',
|
|
bodyId: 'top-clicks-body',
|
|
paginationId: 'pagination-clicks'
|
|
},
|
|
'zero-results': {
|
|
action: 'roi_apu_paginate_zero_results',
|
|
bodyId: 'zero-results-body',
|
|
paginationId: 'pagination-zero-results'
|
|
}
|
|
};
|
|
|
|
var cfg = config[tableId];
|
|
if (!cfg) {
|
|
console.error('Invalid tableId:', tableId);
|
|
return;
|
|
}
|
|
|
|
var tbody = document.getElementById(cfg.bodyId);
|
|
var paginationDiv = document.getElementById(cfg.paginationId);
|
|
|
|
if (!tbody) {
|
|
console.error('tbody not found:', cfg.bodyId);
|
|
return;
|
|
}
|
|
|
|
// Find the card container and add loading state (keeps size, shows overlay)
|
|
var card = tbody.closest('.card');
|
|
if (card) {
|
|
card.classList.add('is-loading-table');
|
|
}
|
|
|
|
// Build form data
|
|
var formData = new FormData();
|
|
formData.append('action', cfg.action);
|
|
formData.append('nonce', window.roiApuDashboardAjax.nonce);
|
|
formData.append('page', page);
|
|
formData.append('days', window.roiApuDashboardData ? window.roiApuDashboardData.period : 30);
|
|
|
|
// AJAX request
|
|
fetch(window.roiApuDashboardAjax.ajaxUrl, {
|
|
method: 'POST',
|
|
body: formData,
|
|
credentials: 'same-origin'
|
|
})
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(result) {
|
|
if (result.success && result.data && result.data.data) {
|
|
currentPages[tableId] = page;
|
|
|
|
// Render rows based on table type
|
|
var html = '';
|
|
var siteUrl = window.roiApuDashboardData ? window.roiApuDashboardData.siteUrl : '';
|
|
|
|
result.data.data.forEach(function(row) {
|
|
if (tableId === 'top-searches') {
|
|
var ctrClass = parseFloat(row.ctr) > 0 ? 'bg-success' : 'bg-secondary';
|
|
html += '<tr><td><strong>' + escapeHtml(row.q_term) + '</strong></td>';
|
|
html += '<td class="text-center">' + formatNumber(row.busquedas) + '</td>';
|
|
html += '<td class="text-center">' + formatNumber(row.clicks) + '</td>';
|
|
html += '<td class="text-center"><span class="badge ' + ctrClass + '">' + escapeHtml(row.ctr) + '%</span></td>';
|
|
html += '<td class="text-center">' + formatNumber(row.resultados) + '</td></tr>';
|
|
} else if (tableId === 'top-clicks') {
|
|
var posClass = parseFloat(row.pos_prom) <= 3 ? 'bg-success' : (parseFloat(row.pos_prom) <= 5 ? 'bg-warning text-dark' : 'bg-secondary');
|
|
var title = row.post_title.length > 80 ? row.post_title.substring(0, 77) + '...' : row.post_title;
|
|
html += '<tr><td><strong>' + escapeHtml(title) + '</strong></td>';
|
|
html += '<td class="text-center">' + formatNumber(row.clicks) + '</td>';
|
|
html += '<td class="text-center"><span class="badge ' + posClass + '">' + escapeHtml(row.pos_prom) + '</span></td>';
|
|
html += '<td class="text-center"><div class="btn-group btn-group-sm">';
|
|
html += '<a href="/wp-admin/post.php?post=' + row.post_id + '&action=edit" target="_blank" class="btn btn-outline-primary"><i class="bi bi-pencil"></i></a>';
|
|
html += '<a href="' + siteUrl + '/' + row.post_name + '/" target="_blank" class="btn btn-outline-secondary"><i class="bi bi-box-arrow-up-right"></i></a>';
|
|
html += '</div></td></tr>';
|
|
} else if (tableId === 'zero-results') {
|
|
var date = new Date(row.ultima_busqueda);
|
|
var formattedDate = date.toLocaleDateString('es-ES');
|
|
html += '<tr><td><strong>' + escapeHtml(row.q_term) + '</strong></td>';
|
|
html += '<td class="text-center"><span class="badge bg-danger">' + formatNumber(row.frecuencia) + '</span></td>';
|
|
html += '<td class="text-center"><small class="text-muted">' + formattedDate + '</small></td></tr>';
|
|
}
|
|
});
|
|
|
|
tbody.innerHTML = html;
|
|
|
|
// Remove loading state
|
|
if (card) {
|
|
card.classList.remove('is-loading-table');
|
|
}
|
|
|
|
// Update pagination
|
|
if (paginationDiv) {
|
|
paginationDiv.innerHTML = renderPagination(page, result.data.pages, tableId);
|
|
bindPaginationEvents(paginationDiv, tableId);
|
|
}
|
|
} else {
|
|
if (card) {
|
|
card.classList.remove('is-loading-table');
|
|
}
|
|
showErrorMessage('Error al cargar datos');
|
|
console.error('AJAX error:', result);
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
if (card) {
|
|
card.classList.remove('is-loading-table');
|
|
}
|
|
console.error('Fetch error:', error);
|
|
showErrorMessage('Error de conexión');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Render pagination HTML
|
|
*/
|
|
function renderPagination(currentPage, totalPages, tableId) {
|
|
if (totalPages <= 1) return '';
|
|
|
|
var html = '<nav class="mt-3"><ul class="pagination justify-content-center mb-0 flex-wrap" data-table="' + tableId + '">';
|
|
|
|
// Inicio
|
|
var firstDisabled = currentPage <= 1 ? ' disabled' : '';
|
|
html += '<li class="page-item' + firstDisabled + '"><a class="page-link" href="javascript:void(0)" data-page="1">Inicio</a></li>';
|
|
|
|
// Page numbers
|
|
var startPage = Math.max(1, currentPage - 2);
|
|
var endPage = Math.min(totalPages, currentPage + 2);
|
|
if (currentPage <= 2) endPage = Math.min(totalPages, 5);
|
|
if (currentPage >= totalPages - 1) startPage = Math.max(1, totalPages - 4);
|
|
|
|
for (var i = startPage; i <= endPage; i++) {
|
|
var active = i === currentPage ? ' active' : '';
|
|
html += '<li class="page-item' + active + '"><a class="page-link" href="javascript:void(0)" data-page="' + i + '">' + i + '</a></li>';
|
|
}
|
|
|
|
// Ver más
|
|
if (endPage < totalPages) {
|
|
var nextPage = Math.min(currentPage + 5, totalPages);
|
|
html += '<li class="page-item"><a class="page-link" href="javascript:void(0)" data-page="' + nextPage + '">Ver más</a></li>';
|
|
}
|
|
|
|
// Fin
|
|
var lastDisabled = currentPage >= totalPages ? ' disabled' : '';
|
|
html += '<li class="page-item' + lastDisabled + '"><a class="page-link" href="javascript:void(0)" data-page="' + totalPages + '">Fin</a></li>';
|
|
|
|
html += '</ul></nav>';
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Bind click events to pagination links
|
|
*/
|
|
function bindPaginationEvents(container, tableId) {
|
|
var links = container.querySelectorAll('a[data-page]');
|
|
links.forEach(function(link) {
|
|
link.onclick = function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var page = parseInt(this.getAttribute('data-page'), 10);
|
|
if (page && page !== currentPages[tableId]) {
|
|
loadPage(tableId, page);
|
|
}
|
|
return false;
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize all pagination
|
|
*/
|
|
function initPagination() {
|
|
// Bind events to existing pagination containers
|
|
var containers = [
|
|
{ id: 'pagination-searches', table: 'top-searches' },
|
|
{ id: 'pagination-clicks', table: 'top-clicks' },
|
|
{ id: 'pagination-zero-results', table: 'zero-results' }
|
|
];
|
|
|
|
containers.forEach(function(cfg) {
|
|
var container = document.getElementById(cfg.id);
|
|
if (container) {
|
|
bindPaginationEvents(container, cfg.table);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Copy markdown to clipboard
|
|
*/
|
|
function copyMarkdown() {
|
|
var data = window.roiApuDashboardData;
|
|
if (!data) {
|
|
showErrorMessage('No hay datos disponibles');
|
|
return;
|
|
}
|
|
|
|
var md = '# Reporte Analytics - Buscador APUs\n\n';
|
|
md += '**Período**: Últimos ' + data.period + ' días\n';
|
|
md += '**Generado**: ' + data.generated + '\n\n';
|
|
md += '## Métricas Clave\n\n';
|
|
md += '| Métrica | Valor |\n|---------|-------|\n';
|
|
md += '| Búsquedas | ' + data.kpis.totalBusquedas + ' |\n';
|
|
md += '| CTR | ' + data.kpis.ctr + ' |\n';
|
|
md += '| Sin Resultados | ' + data.kpis.sinResultados + ' |\n';
|
|
md += '| Pos. Promedio | ' + data.kpis.posProm + ' |\n\n';
|
|
md += '*Generado por ROI APU Search Dashboard*\n';
|
|
|
|
navigator.clipboard.writeText(md).then(function() {
|
|
showSuccessMessage('Copiado al portapapeles');
|
|
}).catch(function() {
|
|
showErrorMessage('Error al copiar');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize
|
|
*/
|
|
function init() {
|
|
initTooltips();
|
|
initPagination();
|
|
|
|
var exportBtn = document.getElementById('btn-export-md');
|
|
if (exportBtn) {
|
|
exportBtn.onclick = copyMarkdown;
|
|
}
|
|
}
|
|
|
|
// Run when ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
// Global access for debugging
|
|
window.roiApuDashboard = {
|
|
loadPage: loadPage,
|
|
currentPages: currentPages
|
|
};
|
|
|
|
})();
|