From 2e67e590d60dcfbbb496cc778bc26b9dddf91e62 Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Tue, 8 Sep 2026 21:52:29 +0200 Subject: [PATCH] Aggiunge grafico andamento visite in admin Analytics. Serie giornaliera di pageview/click/movimenti nel periodo filtrato, sopra la tabella pagine. Co-authored-by: Cursor --- .../controllers/admin/analytics_controller.rb | 17 ++++ .../app/views/admin/analytics/index.html.erb | 34 ++++++++ backend/app/views/layouts/admin.html.erb | 2 +- backend/config/locales/admin.de.yml | 2 + backend/config/locales/admin.en.yml | 2 + backend/config/locales/admin.es.yml | 2 + backend/config/locales/admin.fr.yml | 2 + backend/config/locales/admin.it.yml | 2 + backend/public/admin-analytics.js | 81 +++++++++++++++++++ backend/public/admin.css | 9 +++ backend/spec/requests/admin/analytics_spec.rb | 19 +++++ 11 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 backend/public/admin-analytics.js diff --git a/backend/app/controllers/admin/analytics_controller.rb b/backend/app/controllers/admin/analytics_controller.rb index 03e0ca3..df9e22c 100644 --- a/backend/app/controllers/admin/analytics_controller.rb +++ b/backend/app/controllers/admin/analytics_controller.rb @@ -36,6 +36,23 @@ module Admin max_scroll: max_scroll_by_path[path].to_i } end.sort_by { |r| [-r[:pageviews], -r[:moves], -r[:clicks], r[:page_path]] } + + pageviews_by_day = scope.group(:day).sum(:pageview_count) + clicks_by_day = cell_scope.group(:day).sum(:click_count) + moves_by_day = cell_scope.group(:day).sum(:move_count) + @trend = (@filters[:from]..@filters[:to]).map do |day| + { + day: day.iso8601, + pageviews: pageviews_by_day[day].to_i, + clicks: clicks_by_day[day].to_i, + moves: moves_by_day[day].to_i + } + end + @trend_totals = { + pageviews: @trend.sum { |r| r[:pageviews] }, + clicks: @trend.sum { |r| r[:clicks] }, + moves: @trend.sum { |r| r[:moves] } + } end def show diff --git a/backend/app/views/admin/analytics/index.html.erb b/backend/app/views/admin/analytics/index.html.erb index 80f2226..c415afd 100644 --- a/backend/app/views/admin/analytics/index.html.erb +++ b/backend/app/views/admin/analytics/index.html.erb @@ -58,6 +58,40 @@ <% end %> +<% if @trend.any? && (@trend_totals[:pageviews].positive? || @trend_totals[:clicks].positive? || @trend_totals[:moves].positive?) %> +
+

<%= t("admin.analytics.index.trend_title") %>

+

<%= t("admin.analytics.index.trend_lead") %>

+
+
+
<%= t("admin.analytics.index.table.pageviews") %>
+
<%= @trend_totals[:pageviews] %>
+
+
+
<%= t("admin.analytics.index.table.clicks") %>
+
<%= @trend_totals[:clicks] %>
+
+
+
<%= t("admin.analytics.index.table.moves") %>
+
<%= @trend_totals[:moves] %>
+
+
+
+ "> +
+
+ + + +<% end %> +
<% if @pages.any? %>
diff --git a/backend/app/views/layouts/admin.html.erb b/backend/app/views/layouts/admin.html.erb index 6b1b729..9b34054 100644 --- a/backend/app/views/layouts/admin.html.erb +++ b/backend/app/views/layouts/admin.html.erb @@ -5,7 +5,7 @@ <%= csrf_meta_tags %> - + <%= yield :head %> <% if content_for?(:replay_archive_styles) %> diff --git a/backend/config/locales/admin.de.yml b/backend/config/locales/admin.de.yml index b470b88..eae76cd 100644 --- a/backend/config/locales/admin.de.yml +++ b/backend/config/locales/admin.de.yml @@ -427,6 +427,8 @@ de: lead: Aggregierte First-Party-Heatmaps (Bewegung/Klick) und Scrolltiefe, nur mit Statistik-Einwilligung. Keine personenbezogenen Daten. none: Keine Daten im gewählten Zeitraum. heatmap: Heatmap + trend_title: Verlauf über die Zeit + trend_lead: Tägliche Pageviews, Klicks und Bewegungen im gefilterten Zeitraum. table: path: Seite pageviews: Pageviews diff --git a/backend/config/locales/admin.en.yml b/backend/config/locales/admin.en.yml index f29f9ef..7fc5f49 100644 --- a/backend/config/locales/admin.en.yml +++ b/backend/config/locales/admin.en.yml @@ -427,6 +427,8 @@ en: lead: Aggregated first-party move/click heatmaps and scroll depth, only with analytics consent. No personal data. none: No data in the selected period. heatmap: Heatmap + trend_title: Trend over time + trend_lead: Daily pageviews, clicks and moves for the filtered period. table: path: Page pageviews: Pageviews diff --git a/backend/config/locales/admin.es.yml b/backend/config/locales/admin.es.yml index 49c3ad7..4870269 100644 --- a/backend/config/locales/admin.es.yml +++ b/backend/config/locales/admin.es.yml @@ -427,6 +427,8 @@ es: lead: Heatmaps de movimientos/clics y scroll agregados (first-party), solo con consentimiento estadístico. Sin datos personales. none: No hay datos en el periodo seleccionado. heatmap: Heatmap + trend_title: Evolución en el tiempo + trend_lead: Pageviews, clics y movimientos día a día en el periodo filtrado. table: path: Página pageviews: Pageviews diff --git a/backend/config/locales/admin.fr.yml b/backend/config/locales/admin.fr.yml index 3ea30e8..5e844c5 100644 --- a/backend/config/locales/admin.fr.yml +++ b/backend/config/locales/admin.fr.yml @@ -427,6 +427,8 @@ fr: lead: Heatmaps mouvements/clics et scroll agrégés (first-party), uniquement avec consentement statistiques. Aucune donnée personnelle. none: Aucune donnée sur la période sélectionnée. heatmap: Heatmap + trend_title: Évolution dans le temps + trend_lead: Pages vues, clics et mouvements par jour sur la période filtrée. table: path: Page pageviews: Pages vues diff --git a/backend/config/locales/admin.it.yml b/backend/config/locales/admin.it.yml index 3a37e52..17f84c5 100644 --- a/backend/config/locales/admin.it.yml +++ b/backend/config/locales/admin.it.yml @@ -448,6 +448,8 @@ it: lead: Heatmap movimenti/click e scroll aggregati (first-party), solo con consenso statistico. Nessun dato personale. none: Nessun dato nel periodo selezionato. heatmap: Heatmap + trend_title: Andamento nel tempo + trend_lead: Pageview, click e movimenti giorno per giorno nel periodo filtrato. table: path: Pagina pageviews: Pageview diff --git a/backend/public/admin-analytics.js b/backend/public/admin-analytics.js new file mode 100644 index 0000000..299d139 --- /dev/null +++ b/backend/public/admin-analytics.js @@ -0,0 +1,81 @@ +(function () { + var trend = window.adminAnalyticsTrend || []; + var i18n = window.adminAnalyticsI18n || {}; + var canvas = document.getElementById("chart-analytics-trend"); + if (!canvas || !trend.length || typeof Chart === "undefined") return; + + function dayLabel(dayStr) { + var parts = dayStr.split("-"); + if (parts.length < 3) return dayStr; + var d = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10)); + return d.toLocaleDateString([], { day: "2-digit", month: "short" }); + } + + var labels = trend.map(function (row) { + return dayLabel(row.day); + }); + + new Chart(canvas, { + type: "line", + data: { + labels: labels, + datasets: [ + { + label: i18n.pageviews || "Pageviews", + data: trend.map(function (r) { return r.pageviews || 0; }), + borderColor: "rgba(229, 57, 53, 0.95)", + backgroundColor: "rgba(229, 57, 53, 0.18)", + fill: true, + tension: 0.25, + pointRadius: 3, + pointHoverRadius: 5 + }, + { + label: i18n.clicks || "Clicks", + data: trend.map(function (r) { return r.clicks || 0; }), + borderColor: "rgba(66, 165, 245, 0.95)", + backgroundColor: "transparent", + fill: false, + tension: 0.25, + pointRadius: 2, + pointHoverRadius: 4 + }, + { + label: i18n.moves || "Moves", + data: trend.map(function (r) { return r.moves || 0; }), + borderColor: "rgba(255, 193, 7, 0.85)", + backgroundColor: "transparent", + fill: false, + tension: 0.25, + pointRadius: 2, + pointHoverRadius: 4, + borderDash: [4, 3] + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { mode: "index", intersect: false }, + animation: { duration: 280 }, + scales: { + x: { + ticks: { maxTicksLimit: 14, color: "#9a9aad", font: { size: 10 } }, + grid: { color: "rgba(255,255,255,0.06)" } + }, + y: { + beginAtZero: true, + ticks: { + color: "#9a9aad", + font: { size: 10 }, + precision: 0 + }, + grid: { color: "rgba(255,255,255,0.06)" } + } + }, + plugins: { + legend: { labels: { color: "#ccc", boxWidth: 12 } } + } + } + }); +})(); diff --git a/backend/public/admin.css b/backend/public/admin.css index f214752..8ddf83e 100644 --- a/backend/public/admin.css +++ b/backend/public/admin.css @@ -173,6 +173,15 @@ body.admin-body { height: 200px; } +.chart-wrap--analytics { + height: 260px; + margin-top: 0.75rem; +} + +.admin-analytics-trend__kpi { + margin: 0.75rem 0 0; +} + .admin-panels { display: grid; grid-template-columns: 1.4fr 1fr; diff --git a/backend/spec/requests/admin/analytics_spec.rb b/backend/spec/requests/admin/analytics_spec.rb index 30aeb13..218614a 100644 --- a/backend/spec/requests/admin/analytics_spec.rb +++ b/backend/spec/requests/admin/analytics_spec.rb @@ -33,6 +33,25 @@ RSpec.describe "Admin analytics", type: :request do expect(response.body).to include("/prezzi") expect(response.body).to include("12") expect(response.body).to include("40") + expect(response.body).to include("chart-analytics-trend") + expect(response.body).to include("adminAnalyticsTrend") + end + + it "mostra il trend anche su più giorni" do + AnalyticsPageStat.create!( + day: 1.day.ago.to_date, + page_path: "/prezzi", + device: "desktop", + pageview_count: 5, + scroll_samples: 0, + scroll_sum_pct: 0, + max_scroll_pct: 0 + ) + + get admin_analytics_path, params: { from: 2.days.ago.to_date, to: Time.zone.today } + expect(response).to have_http_status(:ok) + expect(response.body).to include("\"pageviews\":5") + expect(response.body).to include("\"pageviews\":12") end it "mostra la heatmap di una pagina con tab dispositivo" do