diff --git a/admin/assets/dashboard.css b/admin/assets/dashboard.css
index bdd2aeb..ed0b573 100644
--- a/admin/assets/dashboard.css
+++ b/admin/assets/dashboard.css
@@ -89,6 +89,46 @@ body .card {
display: none;
}
+/* =================================================================
+ Table Loading State (overlay sin cambiar tamaño)
+ ================================================================= */
+
+.dashboard-analytics-wrap .card.is-loading-table {
+ position: relative;
+ pointer-events: none;
+}
+
+.dashboard-analytics-wrap .card.is-loading-table::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(255, 255, 255, 0.7);
+ z-index: 10;
+}
+
+.dashboard-analytics-wrap .card.is-loading-table::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 24px;
+ height: 24px;
+ margin: -12px 0 0 -12px;
+ border: 3px solid #f3f3f3;
+ border-top: 3px solid #FF8600;
+ border-radius: 50%;
+ z-index: 11;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
/* =================================================================
KPI Cards - Sin efecto hover
================================================================= */
@@ -123,6 +163,55 @@ body .card {
border-color: #FF8600;
}
+/* =================================================================
+ Pagination - Estilo igual al sitio principal
+ ================================================================= */
+
+.dashboard-analytics-wrap .pagination {
+ gap: 0.25rem;
+ flex-wrap: wrap;
+}
+
+.dashboard-analytics-wrap .pagination .page-item .page-link {
+ padding: 8px 16px;
+ margin: 0 4px;
+ border-radius: 6px;
+ border: 1px solid #e5e7eb;
+ font-size: 1rem;
+ min-height: 42px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #374151;
+ background-color: #fff;
+ text-decoration: none;
+ transition: all 0.2s ease;
+}
+
+.dashboard-analytics-wrap .pagination .page-item .page-link:hover {
+ background-color: #f3f4f6;
+ border-color: #d1d5db;
+ color: #111827;
+}
+
+.dashboard-analytics-wrap .pagination .page-item.active .page-link {
+ background-color: #FF8600;
+ border-color: #FF8600;
+ color: #fff;
+}
+
+.dashboard-analytics-wrap .pagination .page-item.disabled .page-link {
+ background-color: #f9fafb;
+ border-color: #e5e7eb;
+ color: #9ca3af;
+ cursor: not-allowed;
+}
+
+/* Botones de navegación (flechas) */
+.dashboard-analytics-wrap .pagination .page-item .page-link i {
+ font-size: 0.875rem;
+}
+
/* =================================================================
Responsive
================================================================= */
@@ -135,4 +224,11 @@ body .card {
.dashboard-analytics-wrap .card-body {
padding: 0.75rem;
}
+
+ .dashboard-analytics-wrap .pagination .page-item .page-link {
+ padding: 6px 12px;
+ font-size: 0.875rem;
+ min-height: 36px;
+ margin: 0 2px;
+ }
}
diff --git a/admin/assets/dashboard.js b/admin/assets/dashboard.js
index 11efe0f..e666d44 100644
--- a/admin/assets/dashboard.js
+++ b/admin/assets/dashboard.js
@@ -8,226 +8,307 @@
(function() {
'use strict';
+ // State
+ var currentPages = {
+ 'top-searches': 1,
+ 'top-clicks': 1,
+ 'zero-results': 1
+ };
+
/**
* Initialize Bootstrap tooltips
*/
function initTooltips() {
- const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
+ var tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
if (tooltipTriggerList.length > 0 && typeof bootstrap !== 'undefined') {
- [...tooltipTriggerList].map(el => new bootstrap.Tooltip(el));
- }
- }
-
- /**
- * Copy markdown to clipboard
- * Implementation in FASE 9
- */
- function copyMarkdown() {
- const markdown = generateMarkdown();
-
- navigator.clipboard.writeText(markdown).then(function() {
- showSuccessMessage('Copiado: El resumen está en tu portapapeles.');
- }).catch(function(err) {
- console.error('Error al copiar:', err);
- showErrorMessage('Error al copiar al portapapeles.');
- });
- }
-
- /**
- * Generate comprehensive markdown report from dashboard data (v2)
- */
- function generateMarkdown() {
- const data = window.roiApuDashboardData;
- if (!data) {
- return '# Error: No hay datos disponibles';
- }
-
- let md = '';
-
- // Header
- md += '# Reporte Analytics - Buscador APUs\n\n';
- md += `**Período**: Últimos ${data.period} días\n`;
- md += `**Generado**: ${data.generated}\n`;
- md += `**Sitio**: ${data.siteUrl}\n\n`;
-
- // KPIs Table
- md += '## Métricas Clave\n\n';
- md += '| Métrica | Valor |\n';
- md += '|---------|-------|\n';
- md += `| Búsquedas totales | ${data.kpis.totalBusquedas} |\n`;
- md += `| CTR (Click-Through Rate) | ${data.kpis.ctr} |\n`;
- md += `| Búsquedas sin resultados | ${data.kpis.sinResultados} |\n`;
- md += `| Posición promedio clicks | ${data.kpis.posProm} |\n\n`;
-
- // Click Distribution
- if (data.clickDistribution && data.clickDistribution.length > 0) {
- md += '## Distribución de Clicks por Posición\n\n';
- md += '| Posición | Clicks | Porcentaje |\n';
- md += '|----------|--------|------------|\n';
- data.clickDistribution.forEach(function(dist) {
- md += `| ${dist.posicion} | ${dist.clicks} | ${dist.porcentaje}% |\n`;
+ Array.prototype.slice.call(tooltipTriggerList).forEach(function(el) {
+ new bootstrap.Tooltip(el);
});
- md += '\n';
}
-
- md += '---\n\n';
- md += '## RECOMENDACIONES ACCIONABLES\n\n';
-
- // 🔴 Urgent: Zero Results
- if (data.zeroResults && data.zeroResults.length > 0) {
- md += '### 🔴 ACCIÓN URGENTE: Contenido a Crear\n\n';
- md += 'Los usuarios buscan esto pero NO encuentran resultados:\n\n';
- md += '| Término | Frecuencia |\n';
- md += '|---------|------------|\n';
- data.zeroResults.forEach(function(term) {
- md += `| ${term.term} | ${term.frecuencia} veces |\n`;
- });
- md += '\n**Acción**: Crear APUs o contenido para estos términos.\n\n';
- }
-
- // 🟡 CTR 0% Section
- if (data.ctrZero && data.ctrZero.length > 0) {
- md += '### 🟡 REVISAR: Títulos con CTR 0%\n\n';
- md += 'Estos términos tienen resultados pero nadie hace click:\n\n';
- md += '| Término | Búsquedas | Resultados |\n';
- md += '|---------|-----------|------------|\n';
- data.ctrZero.forEach(function(term) {
- md += `| ${term.term} | ${term.busquedas} | ${term.resultados} |\n`;
- });
- md += '\n**Acción**: Mejorar títulos y descripciones de los APUs mostrados.\n\n';
- }
-
- // 🎯 Quick Wins
- if (data.quickWins && data.quickWins.length > 0) {
- md += '### 🎯 QUICK WINS: Oportunidades Fáciles\n\n';
- md += 'Términos en posición 4-10 con buen CTR (una pequeña mejora = top 3):\n\n';
- md += '| Término | Búsquedas | CTR | Pos. Actual |\n';
- md += '|---------|-----------|-----|-------------|\n';
- data.quickWins.forEach(function(term) {
- md += `| ${term.term} | ${term.busquedas} | ${term.ctr}% | ${term.posProm} |\n`;
- });
- md += '\n**Acción**: Optimizar estos APUs para subir al top 3.\n\n';
- }
-
- // 📉 Decay Content
- if (data.decayContent && data.decayContent.length > 0) {
- md += '### 📉 ATENCIÓN: Contenido en Decadencia\n\n';
- md += 'Posts que perdieron >20% clicks vs período anterior:\n\n';
- md += '| Título | Cambio | Clicks (antes → ahora) |\n';
- md += '|--------|--------|------------------------|\n';
- data.decayContent.forEach(function(post) {
- md += `| [${post.title}](${post.url}) | ${post.cambioPct}% | ${post.clicksAnterior} → ${post.clicksActual} |\n`;
- });
- md += '\n**Acción**: Revisar si el contenido está desactualizado.\n\n';
- }
-
- // 🟢 Star Content
- if (data.contenidoEstrella && data.contenidoEstrella.length > 0) {
- md += '### 🟢 MANTENER: Tu Contenido Estrella\n\n';
- md += 'Posts con más clicks - mantén este contenido actualizado:\n\n';
- md += '| Título | Clicks | Pos. Prom. |\n';
- md += '|--------|--------|------------|\n';
- data.contenidoEstrella.forEach(function(post) {
- md += `| [${post.title}](${post.url}) | ${post.clicks} | ${post.posProm} |\n`;
- });
- md += '\n**Acción**: Mantener actualizado y considerar contenido relacionado.\n\n';
- }
-
- // Infraposicionados Section
- if (data.infraposicionados && data.infraposicionados.length > 0) {
- md += '### 🔼 OPORTUNIDAD: Posts Infraposicionados\n\n';
- md += 'Estos posts reciben clicks pero aparecen muy abajo:\n\n';
- md += '| Título | Clicks | Pos. Prom. |\n';
- md += '|--------|--------|------------|\n';
- data.infraposicionados.forEach(function(post) {
- md += `| ${post.title} | ${post.clicks} | ${post.posProm} |\n`;
- });
- md += '\n**Acción**: Mejorar scoring o relevancia de estos APUs.\n\n';
- }
-
- md += '---\n\n';
-
- // Summary stats
- md += '## Resumen de Datos\n\n';
- md += `- **Términos únicos buscados**: ${data.totals.searches}\n`;
- md += `- **Posts con clicks**: ${data.totals.clicks}\n`;
- md += `- **Términos sin resultados**: ${data.totals.zeroResults}\n\n`;
-
- // Questions for analysis
- md += '## Preguntas para Análisis con IA\n\n';
- md += '1. ¿Qué contenido debería crear primero basándome en las búsquedas sin resultados?\n';
- md += '2. ¿Por qué algunos términos tienen resultados pero CTR 0%? ¿Qué puedo mejorar?\n';
- md += '3. ¿Cómo optimizo los Quick Wins para llegar al top 3?\n';
- md += '4. ¿Qué patrones veo en mi contenido estrella que debería replicar?\n';
- md += '5. ¿Hay contenido en decadencia que debería actualizar urgentemente?\n\n';
-
- // Footer
- md += '---\n';
- md += '*Generado por ROI APU Search Dashboard v2*\n';
- md += '*Comparte este reporte con Claude para obtener recomendaciones detalladas.*\n';
-
- return md;
}
/**
* Show success message
*/
function showSuccessMessage(message) {
- const container = document.getElementById('alertContainer');
+ var container = document.getElementById('alertContainer');
if (!container) return;
-
- container.innerHTML = `
-
-
- ${message}
-
- `;
-
- // Auto-hide after 3 seconds
- setTimeout(function() {
- container.innerHTML = '';
- }, 3000);
+ container.innerHTML = '' + message + '
';
+ setTimeout(function() { container.innerHTML = ''; }, 3000);
}
/**
* Show error message
*/
function showErrorMessage(message) {
- const container = document.getElementById('alertContainer');
+ var container = document.getElementById('alertContainer');
if (!container) return;
-
- container.innerHTML = `
-
-
- ${message}
-
- `;
+ container.innerHTML = '' + message + '
';
}
/**
- * Initialize dashboard
+ * 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 += '' + escapeHtml(row.q_term) + ' ';
+ html += '' + formatNumber(row.busquedas) + ' ';
+ html += '' + formatNumber(row.clicks) + ' ';
+ html += '' + escapeHtml(row.ctr) + '% ';
+ html += '' + formatNumber(row.resultados) + ' ';
+ } 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 += '' + escapeHtml(title) + ' ';
+ html += '' + formatNumber(row.clicks) + ' ';
+ html += '' + escapeHtml(row.pos_prom) + ' ';
+ html += '';
+ html += '
';
+ html += '
';
+ html += '
';
+ } else if (tableId === 'zero-results') {
+ var date = new Date(row.ultima_busqueda);
+ var formattedDate = date.toLocaleDateString('es-ES');
+ html += '' + escapeHtml(row.q_term) + ' ';
+ html += '' + formatNumber(row.frecuencia) + ' ';
+ html += '' + formattedDate + ' ';
+ }
+ });
+
+ 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 = ' ';
+ 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();
- // Bind export button
- const exportBtn = document.getElementById('btn-export-md');
+ var exportBtn = document.getElementById('btn-export-md');
if (exportBtn) {
- exportBtn.addEventListener('click', copyMarkdown);
+ exportBtn.onclick = copyMarkdown;
}
}
- // Initialize when DOM is ready
+ // Run when ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
- // Expose functions globally for debugging
+ // Global access for debugging
window.roiApuDashboard = {
- copyMarkdown: copyMarkdown,
- initTooltips: initTooltips
+ loadPage: loadPage,
+ currentPages: currentPages
};
})();
diff --git a/admin/class-analytics-dashboard.php b/admin/class-analytics-dashboard.php
index 4ca19a2..f741ea0 100644
--- a/admin/class-analytics-dashboard.php
+++ b/admin/class-analytics-dashboard.php
@@ -47,6 +47,11 @@ final class ROI_APU_Analytics_Dashboard
// Private constructor for singleton
}
+ /**
+ * Items per page for pagination
+ */
+ private const ITEMS_PER_PAGE = 20;
+
/**
* Initialize the dashboard
*/
@@ -54,6 +59,167 @@ final class ROI_APU_Analytics_Dashboard
{
add_action('admin_menu', [$this, 'register_menu']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
+
+ // AJAX handlers for pagination
+ add_action('wp_ajax_roi_apu_paginate_searches', [$this, 'ajax_paginate_searches']);
+ add_action('wp_ajax_roi_apu_paginate_clicks', [$this, 'ajax_paginate_clicks']);
+ add_action('wp_ajax_roi_apu_paginate_zero_results', [$this, 'ajax_paginate_zero_results']);
+ }
+
+ /**
+ * AJAX handler for Top Searches pagination
+ */
+ public function ajax_paginate_searches(): void
+ {
+ check_ajax_referer('roi_apu_dashboard', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(['message' => 'Unauthorized'], 403);
+ }
+
+ $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
+ $days = isset($_POST['days']) ? absint($_POST['days']) : 30;
+ $offset = ($page - 1) * self::ITEMS_PER_PAGE;
+
+ $db = ROI_APU_Search_DB::get_instance();
+ $repository = new ROI_APU_Metrics_Repository($db->get_pdo(), $db->get_prefix());
+
+ $data = $repository->getTopSearches($days, self::ITEMS_PER_PAGE, $offset);
+ $total = $repository->getTotalCounts($days)['total_searches'];
+
+ wp_send_json_success([
+ 'data' => $data,
+ 'total' => $total,
+ 'page' => $page,
+ 'pages' => ceil($total / self::ITEMS_PER_PAGE),
+ ]);
+ }
+
+ /**
+ * AJAX handler for Top Clicks pagination
+ */
+ public function ajax_paginate_clicks(): void
+ {
+ check_ajax_referer('roi_apu_dashboard', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(['message' => 'Unauthorized'], 403);
+ }
+
+ $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
+ $days = isset($_POST['days']) ? absint($_POST['days']) : 30;
+ $offset = ($page - 1) * self::ITEMS_PER_PAGE;
+
+ $db = ROI_APU_Search_DB::get_instance();
+ $repository = new ROI_APU_Metrics_Repository($db->get_pdo(), $db->get_prefix());
+
+ $data = $repository->getTopClicks($days, self::ITEMS_PER_PAGE, $offset);
+ $total = $repository->getTotalCounts($days)['total_clicks'];
+
+ wp_send_json_success([
+ 'data' => $data,
+ 'total' => $total,
+ 'page' => $page,
+ 'pages' => ceil($total / self::ITEMS_PER_PAGE),
+ ]);
+ }
+
+ /**
+ * AJAX handler for Zero Results pagination
+ */
+ public function ajax_paginate_zero_results(): void
+ {
+ check_ajax_referer('roi_apu_dashboard', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(['message' => 'Unauthorized'], 403);
+ }
+
+ $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
+ $days = isset($_POST['days']) ? absint($_POST['days']) : 30;
+ $offset = ($page - 1) * self::ITEMS_PER_PAGE;
+
+ $db = ROI_APU_Search_DB::get_instance();
+ $repository = new ROI_APU_Metrics_Repository($db->get_pdo(), $db->get_prefix());
+
+ $data = $repository->getZeroResults($days, self::ITEMS_PER_PAGE, $offset);
+ $total = $repository->getTotalCounts($days)['total_zero_results'];
+
+ wp_send_json_success([
+ 'data' => $data,
+ 'total' => $total,
+ 'page' => $page,
+ 'pages' => ceil($total / self::ITEMS_PER_PAGE),
+ ]);
+ }
+
+ /**
+ * Render pagination controls
+ *
+ * @param int $currentPage Current page number
+ * @param int $totalPages Total number of pages
+ * @param string $tableId ID of the table for JS targeting
+ * @return string HTML for pagination controls
+ */
+ private function render_pagination(int $currentPage, int $totalPages, string $tableId): string
+ {
+ if ($totalPages <= 1) {
+ return '';
+ }
+
+ $html = '';
+ $html .= ' ';
+
+ return $html;
}
/**
@@ -117,6 +283,12 @@ final class ROI_APU_Analytics_Dashboard
ROI_APU_SEARCH_VERSION,
true
);
+
+ // Localize script for AJAX pagination
+ wp_localize_script('roi-apu-dashboard', 'roiApuDashboardAjax', [
+ 'ajaxUrl' => admin_url('admin-ajax.php'),
+ 'nonce' => wp_create_nonce('roi_apu_dashboard'),
+ ]);
}
/**
@@ -708,12 +880,12 @@ final class ROI_APU_Analytics_Dashboard
- 20) : ?>
-
@@ -766,12 +938,12 @@ final class ROI_APU_Analytics_Dashboard
- 20) : ?>
-
@@ -812,12 +984,12 @@ final class ROI_APU_Analytics_Dashboard
- 20) : ?>
-