Browse Source

Analytics, continued

imwald
Nuša Pukšič 3 months ago
parent
commit
d9c71e41b1
  1. 41
      assets/controllers/unique_visitors_per_day_chart_controller.js
  2. 41
      assets/controllers/visits_per_day_chart_controller.js
  3. 2
      composer.lock
  4. 6
      importmap.php
  5. 20
      src/Controller/Administration/VisitorAnalyticsController.php
  6. 38
      templates/admin/analytics.html.twig

41
assets/controllers/unique_visitors_per_day_chart_controller.js

@ -0,0 +1,41 @@
import { Controller } from "@hotwired/stimulus";
import Chart from "chart.js/auto";
export default class extends Controller {
static values = {
labels: Array,
counts: Array
}
connect() {
if (!this.hasLabelsValue || !this.hasCountsValue) return;
const ctx = this.element.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: this.labelsValue,
datasets: [{
label: 'Unique Visitors',
data: this.countsValue,
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
fill: true,
tension: 0.2,
pointRadius: 2
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
title: { display: false }
},
scales: {
x: { title: { display: true, text: 'Date' } },
y: { title: { display: true, text: 'Unique Visitors' }, beginAtZero: true, precision: 0 }
}
}
});
}
}

41
assets/controllers/visits_per_day_chart_controller.js

@ -0,0 +1,41 @@
import { Controller } from "@hotwired/stimulus";
import Chart from "chart.js/auto";
export default class extends Controller {
static values = {
labels: Array,
counts: Array
}
connect() {
if (!this.hasLabelsValue || !this.hasCountsValue) return;
const ctx = this.element.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: this.labelsValue,
datasets: [{
label: 'Visits',
data: this.countsValue,
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
fill: true,
tension: 0.2,
pointRadius: 2
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
title: { display: false }
},
scales: {
x: { title: { display: true, text: 'Date' } },
y: { title: { display: true, text: 'Visits' }, beginAtZero: true, precision: 0 }
}
}
});
}
}

2
composer.lock generated

@ -13062,6 +13062,6 @@
"ext-openssl": "*", "ext-openssl": "*",
"ext-redis": "*" "ext-redis": "*"
}, },
"platform-dev": {}, "platform-dev": [],
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"
} }

6
importmap.php

@ -96,4 +96,10 @@ return [
'nostr-tools/nip46' => [ 'nostr-tools/nip46' => [
'version' => '2.17.0', 'version' => '2.17.0',
], ],
'chart.js/auto' => [
'version' => '4.5.0',
],
'@kurkle/color' => [
'version' => '0.3.4',
],
]; ];

20
src/Controller/Administration/VisitorAnalyticsController.php

@ -41,6 +41,25 @@ class VisitorAnalyticsController extends AbstractController
$mostPopularRoutes = $visitRepository->getMostPopularRoutes(5); $mostPopularRoutes = $visitRepository->getMostPopularRoutes(5);
$recentVisits = $visitRepository->getRecentVisits(10); $recentVisits = $visitRepository->getRecentVisits(10);
// Calculate unique visitors per day for the last 7 days
$uniqueVisitorsPerDay = [];
for ($i = 6; $i >= 0; $i--) {
$day = (new \DateTimeImmutable("today"))->modify("-{$i} days");
$start = $day->setTime(0, 0, 0);
$end = $day->setTime(23, 59, 59);
$qb = $visitRepository->createQueryBuilder('v');
$qb->select('COUNT(DISTINCT v.sessionId)')
->where('v.visitedAt BETWEEN :start AND :end')
->andWhere('v.sessionId IS NOT NULL')
->setParameter('start', $start)
->setParameter('end', $end);
$count = (int) $qb->getQuery()->getSingleScalarResult();
$uniqueVisitorsPerDay[] = [
'day' => $day->format('Y-m-d'),
'count' => $count
];
}
return $this->render('admin/analytics.html.twig', [ return $this->render('admin/analytics.html.twig', [
'visitStats' => $visitStats, 'visitStats' => $visitStats,
'last24hCount' => $last24hCount, 'last24hCount' => $last24hCount,
@ -56,6 +75,7 @@ class VisitorAnalyticsController extends AbstractController
'visitsPerDay' => $visitsPerDay, 'visitsPerDay' => $visitsPerDay,
'mostPopularRoutes' => $mostPopularRoutes, 'mostPopularRoutes' => $mostPopularRoutes,
'recentVisits' => $recentVisits, 'recentVisits' => $recentVisits,
'uniqueVisitorsPerDay' => $uniqueVisitorsPerDay,
]); ]);
} }
} }

38
templates/admin/analytics.html.twig

@ -35,9 +35,47 @@
</div> </div>
</div> </div>
<div class="analytics-card">
<h2>Unique Visitors Per Day (Last 7 Days)</h2>
<canvas id="uniqueVisitorsPerDayChart"
data-controller="unique-visitors-per-day-chart"
data-unique-visitors-per-day-chart-labels-value='{{ uniqueVisitorsPerDay|map(stat => stat.day)|json_encode()|e('html_attr') }}'
data-unique-visitors-per-day-chart-counts-value='{{ uniqueVisitorsPerDay|map(stat => stat.count)|json_encode()|e('html_attr') }}'
height="80"></canvas>
<noscript>
<p><em>Enable JavaScript to see the unique visitors per day chart.</em></p>
</noscript>
<div style="margin-top:1em"></div>
<table class="analytics-table">
<thead>
<tr>
<th>Date</th>
<th class="text-right">Unique Visitors</th>
</tr>
</thead>
<tbody>
{% for stat in uniqueVisitorsPerDay %}
<tr>
<td>{{ stat.day }}</td>
<td class="text-right">{{ stat.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="analytics-card"> <div class="analytics-card">
<h2>Visits Per Day (Last 30 Days)</h2> <h2>Visits Per Day (Last 30 Days)</h2>
{% if visitsPerDay|length > 0 %} {% if visitsPerDay|length > 0 %}
<canvas id="visitsPerDayChart"
data-controller="visits-per-day-chart"
data-visits-per-day-chart-labels-value='{{ visitsPerDay|map(stat => stat.day|date('Y-m-d'))|json_encode()|e('html_attr') }}'
data-visits-per-day-chart-counts-value='{{ visitsPerDay|map(stat => stat.count)|json_encode()|e('html_attr') }}'
height="80"></canvas>
<noscript>
<p><em>Enable JavaScript to see the visits per day chart.</em></p>
</noscript>
<div style="margin-top:1em"></div>
<table class="analytics-table"> <table class="analytics-table">
<thead> <thead>
<tr> <tr>

Loading…
Cancel
Save