Files
MatchLiveTv/backend/app/views/admin/analytics/show.html.erb
T
eminuxandCursor 8a07c5d569 Aggiunge heatmaps first-party (click/scroll) con pagina admin Analytics.
Raccoglie eventi aggregati dietro consenso cookie, senza PII né tracking su admin/regia/live/replay; GA4 resta invariato.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 22:50:15 +02:00

116 lines
4.1 KiB
ERB

<% content_for :body_class, "admin-body" %>
<div class="admin-page-head admin-session-head">
<div>
<p class="muted admin-page-sub">
<%= link_to t("admin.analytics.show.back"), admin_analytics_path(from: @filters[:from], to: @filters[:to], device: @filters[:device]) %>
</p>
<h2 class="admin-page-title"><%= t("admin.analytics.show.title") %></h2>
<p><code class="admin-mono"><%= @page_path %></code></p>
</div>
</div>
<div class="panel admin-sessions-filters">
<%= form_with url: admin_analytics_page_path, method: :get, local: true, class: "admin-filter-form" do %>
<%= hidden_field_tag :page_path, @page_path %>
<div class="admin-filter-grid">
<label class="admin-filter-field">
<span><%= t("admin.analytics.filters.from") %></span>
<%= date_field_tag :from, @filters[:from] %>
</label>
<label class="admin-filter-field">
<span><%= t("admin.analytics.filters.to") %></span>
<%= date_field_tag :to, @filters[:to] %>
</label>
<label class="admin-filter-field">
<span><%= t("admin.analytics.filters.device") %></span>
<%= select_tag :device,
options_for_select(
[[t("admin.analytics.filters.any"), ""]] + AnalyticsEvent::DEVICES.map { |d| [d, d] },
@filters[:device]
) %>
</label>
</div>
<div class="admin-filter-actions">
<%= submit_tag t("admin.analytics.filters.apply"), class: "admin-btn admin-btn--primary admin-btn--sm" %>
</div>
<% end %>
</div>
<div class="admin-session-grid">
<section class="panel">
<h3><%= t("admin.analytics.show.summary") %></h3>
<dl class="admin-kv">
<div>
<dt><%= t("admin.analytics.index.table.pageviews") %></dt>
<dd><%= @pageviews %></dd>
</div>
<div>
<dt><%= t("admin.analytics.index.table.clicks") %></dt>
<dd><%= @cells.values.sum %></dd>
</div>
<div>
<dt><%= t("admin.analytics.index.table.avg_scroll") %></dt>
<dd><%= @avg_scroll %>%</dd>
</div>
<div>
<dt><%= t("admin.analytics.index.table.max_scroll") %></dt>
<dd><%= @max_scroll %>%</dd>
</div>
</dl>
</section>
<section class="panel" style="grid-column: span 2;">
<h3><%= t("admin.analytics.show.scroll_depth") %></h3>
<div class="admin-scroll-bar" aria-hidden="true">
<div class="admin-scroll-bar__avg" style="width: <%= @avg_scroll %>%"></div>
<div class="admin-scroll-bar__max" style="left: <%= @max_scroll %>%"></div>
</div>
<p class="muted admin-table-sub">
<%= t("admin.analytics.show.scroll_hint", avg: @avg_scroll, max: @max_scroll) %>
</p>
</section>
</div>
<section class="panel">
<h3><%= t("admin.analytics.show.heatmap_title") %></h3>
<% if @cells.any? %>
<div class="admin-heatmap-wrap">
<canvas
id="admin-heatmap"
class="admin-heatmap"
width="800"
height="1200"
data-grid="<%= @grid %>"
data-max="<%= @max_clicks %>"
data-cells="<%= @cells.map { |(x, y), c| { x: x, y: y, c: c } }.to_json %>"
></canvas>
</div>
<script>
(function () {
var canvas = document.getElementById("admin-heatmap");
if (!canvas) return;
var grid = parseInt(canvas.getAttribute("data-grid"), 10) || 40;
var max = parseInt(canvas.getAttribute("data-max"), 10) || 1;
var cells = [];
try { cells = JSON.parse(canvas.getAttribute("data-cells") || "[]"); } catch (e) { cells = []; }
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
ctx.fillStyle = "#0d0d12";
ctx.fillRect(0, 0, cw, ch);
var cellW = cw / grid;
var cellH = ch / grid;
cells.forEach(function (cell) {
var intensity = Math.min(1, cell.c / max);
var alpha = 0.15 + intensity * 0.75;
ctx.fillStyle = "rgba(229, 57, 53, " + alpha + ")";
ctx.fillRect(cell.x * cellW, cell.y * cellH, cellW + 0.5, cellH + 0.5);
});
})();
</script>
<% else %>
<p class="empty"><%= t("admin.analytics.show.no_clicks") %></p>
<% end %>
</section>