diff --git a/admin/css/admin-style.css b/admin/css/admin-style.css new file mode 100644 index 0000000..b39bcaa --- /dev/null +++ b/admin/css/admin-style.css @@ -0,0 +1,139 @@ +/** + * Peptide News — Admin Styles + * + * @since 1.0.0 + */ + +/* Dashboard layout */ +.peptide-news-dashboard { + max-width: 1400px; +} + +/* Date filter bar */ +.pn-date-filter { + display: flex; + align-items: center; + gap: 10px; + margin: 20px 0; + padding: 15px 20px; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 8px; +} + +.pn-date-filter input[type="date"] { + padding: 6px 10px; + border: 1px solid #cbd5e1; + border-radius: 6px; +} + +/* Summary cards */ +.pn-summary-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin: 20px 0; +} + +.pn-card { + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 24px; + text-align: center; + transition: box-shadow 0.2s ease; +} + +.pn-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.pn-card h3 { + font-size: 32px; + font-weight: 700; + color: #1e293b; + margin: 0 0 4px; +} + +.pn-card p { + font-size: 13px; + color: #64748b; + margin: 0; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Charts */ +.pn-charts-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin: 20px 0; +} + +.pn-chart-wide { + grid-column: 1 / -1; +} + +.pn-chart-box { + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 20px; + position: relative; +} + +.pn-chart-box canvas { + max-height: 300px; + width: 100% !important; +} + +.pn-chart-box h2 { + font-size: 16px; + font-weight: 600; + color: #334155; + margin: 0 0 16px; + padding: 0; +} + +/* Tables */ +.pn-table-section { + margin: 30px 0; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 10px; + padding: 20px; +} + +.pn-table-section h2 { + font-size: 16px; + font-weight: 600; + color: #334155; + margin: 0 0 16px; +} + +.pn-table-section .wp-list-table { + border: none; +} + +/* Articles page title count */ +.title-count { + font-weight: 400; + color: #64748b; + font-size: 16px; +} + +/* Responsive */ +@media (max-width: 782px) { + .pn-charts-row { + grid-template-columns: 1fr; + } + + .pn-summary-cards { + grid-template-columns: repeat(2, 1fr); + } + + .pn-date-filter { + flex-wrap: wrap; + } +} diff --git a/admin/js/admin-script.js b/admin/js/admin-script.js new file mode 100644 index 0000000..594f2e4 --- /dev/null +++ b/admin/js/admin-script.js @@ -0,0 +1,168 @@ +/** + * Admin JS for Peptide News Analytics Dashboard + * + * Initialises Chart.js charts using the data exported by dashboard.php + * via the global `peptideNewsDashboardData` object. + * + * @since 1.1.0 + */ + +(function () { + 'use strict'; + + /* ── Guard: only run when dashboard data is available ── */ + if (typeof peptideNewsDashboardData === 'undefined') { + return; + } + + var data = peptideNewsDashboardData; + + /* ── Shared colour palette ── */ + var colours = { + blue: 'rgba(59, 130, 246, 1)', + blueFill: 'rgba(59, 130, 246, 0.08)', + green: 'rgba(16, 185, 129, 1)', + greenFill: 'rgba(16, 185, 129, 0.08)', + palette: [ + 'rgba(59, 130, 246, 0.85)', + 'rgba(16, 185, 129, 0.85)', + 'rgba(245, 158, 11, 0.85)', + 'rgba(239, 68, 68, 0.85)', + 'rgba(139, 92, 246, 0.85)', + 'rgba(236, 72, 153, 0.85)', + 'rgba(20, 184, 166, 0.85)', + 'rgba(249, 115, 22, 0.85)' + ] + }; + + /* ── 1. Click Trends — Line Chart ── */ + var trendsCanvas = document.getElementById('pn-trends-chart'); + if (trendsCanvas && data.trends) { + var trendsLabels = data.trends.map(function (row) { return row.click_date; }); + var trendsClicks = data.trends.map(function (row) { return parseInt(row.total_clicks, 10) || 0; }); + var trendsUnique = data.trends.map(function (row) { return parseInt(row.total_unique, 10) || 0; }); + + new Chart(trendsCanvas, { + type: 'line', + data: { + labels: trendsLabels, + datasets: [ + { + label: 'Total Clicks', + data: trendsClicks, + borderColor: colours.blue, + backgroundColor: colours.blueFill, + borderWidth: 2, + pointRadius: 3, + pointHoverRadius: 5, + fill: true, + tension: 0.3 + }, + { + label: 'Unique Visitors', + data: trendsUnique, + borderColor: colours.green, + backgroundColor: colours.greenFill, + borderWidth: 2, + pointRadius: 3, + pointHoverRadius: 5, + fill: true, + tension: 0.3 + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + mode: 'index', + intersect: false + }, + plugins: { + legend: { + position: 'top', + labels: { usePointStyle: true, padding: 16 } + } + }, + scales: { + x: { + grid: { display: false }, + ticks: { maxTicksLimit: 12 } + }, + y: { + beginAtZero: true, + ticks: { precision: 0 } + } + } + } + }); + } + + /* ── 2. Device Breakdown — Doughnut Chart ── */ + var devicesCanvas = document.getElementById('pn-devices-chart'); + if (devicesCanvas && data.devices) { + var deviceLabels = data.devices.map(function (row) { return row.device_type || 'Unknown'; }); + var deviceCounts = data.devices.map(function (row) { return parseInt(row.click_count, 10) || 0; }); + + new Chart(devicesCanvas, { + type: 'doughnut', + data: { + labels: deviceLabels, + datasets: [{ + data: deviceCounts, + backgroundColor: colours.palette.slice(0, deviceLabels.length), + borderWidth: 2, + borderColor: '#fff' + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom', + labels: { usePointStyle: true, padding: 12 } + } + }, + cutout: '55%' + } + }); + } + + /* ── 3. Source Performance — Bar Chart ── */ + var sourcesCanvas = document.getElementById('pn-sources-chart'); + if (sourcesCanvas && data.sources) { + var sourceLabels = data.sources.map(function (row) { return row.source || 'Unknown'; }); + var sourceCounts = data.sources.map(function (row) { return parseInt(row.click_count, 10) || 0; }); + + new Chart(sourcesCanvas, { + type: 'bar', + data: { + labels: sourceLabels, + datasets: [{ + label: 'Clicks', + data: sourceCounts, + backgroundColor: colours.palette.slice(0, sourceLabels.length), + borderRadius: 4, + maxBarThickness: 48 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { display: false } + }, + scales: { + x: { + grid: { display: false } + }, + y: { + beginAtZero: true, + ticks: { precision: 0 } + } + } + } + }); + } +})(); diff --git a/peptide-news-plugin/admin/css/admin-style.css b/peptide-news-plugin/admin/css/admin-style.css index 52bca06..b39bcaa 100644 --- a/peptide-news-plugin/admin/css/admin-style.css +++ b/peptide-news-plugin/admin/css/admin-style.css @@ -80,6 +80,12 @@ border: 1px solid #e2e8f0; border-radius: 10px; padding: 20px; + position: relative; +} + +.pn-chart-box canvas { + max-height: 300px; + width: 100% !important; } .pn-chart-box h2 { diff --git a/peptide-news-plugin/admin/js/admin-script.js b/peptide-news-plugin/admin/js/admin-script.js index 5968919..594f2e4 100644 --- a/peptide-news-plugin/admin/js/admin-script.js +++ b/peptide-news-plugin/admin/js/admin-script.js @@ -1,23 +1,168 @@ /** * Admin JS for Peptide News Analytics Dashboard + * + * Initialises Chart.js charts using the data exported by dashboard.php + * via the global `peptideNewsDashboardData` object. + * + * @since 1.1.0 */ -(function() { - const ctx = document.getElementById('peptide-analytics-chart')?.getContext('2d'); - if (!ctx) return; +(function () { + 'use strict'; - new Chart(ctx, { - type: 'line', - data: { - labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], - datasets: [ - { - label: 'Clicks', - data: [12, 19, 3, 5, 2, 3], - borderColor: 'rgb(75, 192, 192)', - tension: 0.1 + /* ── Guard: only run when dashboard data is available ── */ + if (typeof peptideNewsDashboardData === 'undefined') { + return; + } + + var data = peptideNewsDashboardData; + + /* ── Shared colour palette ── */ + var colours = { + blue: 'rgba(59, 130, 246, 1)', + blueFill: 'rgba(59, 130, 246, 0.08)', + green: 'rgba(16, 185, 129, 1)', + greenFill: 'rgba(16, 185, 129, 0.08)', + palette: [ + 'rgba(59, 130, 246, 0.85)', + 'rgba(16, 185, 129, 0.85)', + 'rgba(245, 158, 11, 0.85)', + 'rgba(239, 68, 68, 0.85)', + 'rgba(139, 92, 246, 0.85)', + 'rgba(236, 72, 153, 0.85)', + 'rgba(20, 184, 166, 0.85)', + 'rgba(249, 115, 22, 0.85)' + ] + }; + + /* ── 1. Click Trends — Line Chart ── */ + var trendsCanvas = document.getElementById('pn-trends-chart'); + if (trendsCanvas && data.trends) { + var trendsLabels = data.trends.map(function (row) { return row.click_date; }); + var trendsClicks = data.trends.map(function (row) { return parseInt(row.total_clicks, 10) || 0; }); + var trendsUnique = data.trends.map(function (row) { return parseInt(row.total_unique, 10) || 0; }); + + new Chart(trendsCanvas, { + type: 'line', + data: { + labels: trendsLabels, + datasets: [ + { + label: 'Total Clicks', + data: trendsClicks, + borderColor: colours.blue, + backgroundColor: colours.blueFill, + borderWidth: 2, + pointRadius: 3, + pointHoverRadius: 5, + fill: true, + tension: 0.3 + }, + { + label: 'Unique Visitors', + data: trendsUnique, + borderColor: colours.green, + backgroundColor: colours.greenFill, + borderWidth: 2, + pointRadius: 3, + pointHoverRadius: 5, + fill: true, + tension: 0.3 + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + mode: 'index', + intersect: false + }, + plugins: { + legend: { + position: 'top', + labels: { usePointStyle: true, padding: 16 } + } + }, + scales: { + x: { + grid: { display: false }, + ticks: { maxTicksLimit: 12 } + }, + y: { + beginAtZero: true, + ticks: { precision: 0 } + } } - ] } }); + } + + /* ── 2. Device Breakdown — Doughnut Chart ── */ + var devicesCanvas = document.getElementById('pn-devices-chart'); + if (devicesCanvas && data.devices) { + var deviceLabels = data.devices.map(function (row) { return row.device_type || 'Unknown'; }); + var deviceCounts = data.devices.map(function (row) { return parseInt(row.click_count, 10) || 0; }); + + new Chart(devicesCanvas, { + type: 'doughnut', + data: { + labels: deviceLabels, + datasets: [{ + data: deviceCounts, + backgroundColor: colours.palette.slice(0, deviceLabels.length), + borderWidth: 2, + borderColor: '#fff' + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom', + labels: { usePointStyle: true, padding: 12 } + } + }, + cutout: '55%' + } + }); + } + + /* ── 3. Source Performance — Bar Chart ── */ + var sourcesCanvas = document.getElementById('pn-sources-chart'); + if (sourcesCanvas && data.sources) { + var sourceLabels = data.sources.map(function (row) { return row.source || 'Unknown'; }); + var sourceCounts = data.sources.map(function (row) { return parseInt(row.click_count, 10) || 0; }); + + new Chart(sourcesCanvas, { + type: 'bar', + data: { + labels: sourceLabels, + datasets: [{ + label: 'Clicks', + data: sourceCounts, + backgroundColor: colours.palette.slice(0, sourceLabels.length), + borderRadius: 4, + maxBarThickness: 48 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { display: false } + }, + scales: { + x: { + grid: { display: false } + }, + y: { + beginAtZero: true, + ticks: { precision: 0 } + } + } + } + }); + } })();