Fix chart sizing issue on analytics dashboard

Charts were not rendering properly because admin-script.js targeted a
non-existent canvas ID (peptide-analytics-chart) instead of the actual
IDs (pn-trends-chart, pn-devices-chart, pn-sources-chart). The script
also used hardcoded demo data instead of the peptideNewsDashboardData
object exported from PHP.

Changes:
- Rewrite admin-script.js to initialise all three dashboard charts
  (line, doughnut, bar) using the correct canvas IDs and PHP data
- Set maintainAspectRatio: false so explicit canvas heights are respected
- Add max-height CSS constraint on chart canvases to prevent unbounded
  vertical growth
- Sync JS/CSS to admin/ root directory to match PEPTIDE_NEWS_PLUGIN_URL
  asset path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Terence Ong
2026-04-06 10:45:42 +07:00
parent 4367d68202
commit 973fb1117a
4 changed files with 472 additions and 14 deletions

139
admin/css/admin-style.css Normal file
View File

@@ -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;
}
}

168
admin/js/admin-script.js Normal file
View File

@@ -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 }
}
}
}
});
}
})();

View File

@@ -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 {

View File

@@ -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 }
}
}
}
});
}
})();