Files
MatchLiveTv/backend/app/views/admin/costs/index.html.erb
T
eminuxandCursor 29c8afb94d Admin analytics: heatmap per device, anteprima staff e analisi costi.
Separa heatmap mobile/desktop, opt-out analytics per operatori, dashboard costi con KPI e trend mensili.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 19:25:38 +02:00

198 lines
8.1 KiB
ERB

<% content_for :body_class, "admin-body" %>
<% content_for :head do %>
<script>
window.adminCostTrend = <%= raw @trend.to_json %>;
window.adminCostI18n = {
cost: <%= raw t("admin.costs.charts.cost_revenue").to_json %>,
revenue: <%= raw t("admin.costs.kpi.revenue").to_json %>,
margin: <%= raw t("admin.costs.kpi.margin").to_json %>,
costPerHour: <%= raw t("admin.costs.charts.cost_per_hour").to_json %>,
hours: <%= raw t("admin.costs.charts.hours").to_json %>
};
</script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" crossorigin="anonymous"></script>
<script src="/admin-costs.js?v=1" defer></script>
<% end %>
<div class="admin-page-head">
<h2 class="admin-page-title"><%= t("admin.costs.index.title") %></h2>
<p class="muted admin-page-sub"><%= t("admin.costs.index.lead") %></p>
</div>
<div class="panel admin-sessions-filters">
<%= form_with url: admin_costs_path, method: :get, local: true, class: "admin-filter-form" do %>
<div class="admin-filter-grid">
<label class="admin-filter-field">
<span><%= t("admin.costs.index.month") %></span>
<%= select_tag :month,
options_for_select(
@month_options.map { |m| [admin_month_label(m), m.strftime("%Y-%m")] },
@month.strftime("%Y-%m")
) %>
</label>
</div>
<div class="admin-filter-actions">
<%= submit_tag t("admin.costs.index.apply"), class: "admin-btn admin-btn--primary admin-btn--sm" %>
<%= link_to t("admin.costs.index.add_entry"),
new_admin_cost_entry_path(month: @month.strftime("%Y-%m")),
class: "admin-btn admin-btn--outline admin-btn--sm" %>
</div>
<% end %>
</div>
<section class="kpi-grid">
<div class="kpi-card kpi-card--accent">
<div class="kpi-label"><%= t("admin.costs.kpi.platform_cost") %></div>
<div class="kpi-value"><%= format_euros(@summary[:cost_cents]) %></div>
<div class="kpi-sub"><%= admin_month_label(@month) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label"><%= t("admin.costs.kpi.revenue") %></div>
<div class="kpi-value"><%= format_euros(@summary[:revenue_cents]) %></div>
<div class="kpi-sub">
<%= t("admin.costs.kpi.margin") %>: <%= format_euros(@summary[:margin_cents]) %>
<% if @summary[:margin_pct] %>
· <%= @summary[:margin_pct] %>%
<% end %>
</div>
</div>
<div class="kpi-card">
<div class="kpi-label"><%= t("admin.costs.kpi.cost_per_hour") %></div>
<div class="kpi-value"><%= format_euros(@summary[:cost_per_hour_cents]) %></div>
<div class="kpi-sub">
<%= t("admin.costs.kpi.revenue_per_hour") %>: <%= format_euros(@summary[:revenue_per_hour_cents]) %>
</div>
</div>
<div class="kpi-card">
<div class="kpi-label"><%= t("admin.costs.kpi.hours") %></div>
<div class="kpi-value"><%= format_hours(@summary[:hours]) %></div>
<div class="kpi-sub">
<%= t("admin.costs.kpi.sessions") %>: <%= @summary[:sessions] %>
· <%= t("admin.costs.kpi.clubs_active") %>: <%= @summary[:clubs_active] %>
</div>
</div>
<div class="kpi-card">
<div class="kpi-label"><%= t("admin.costs.kpi.cost_per_session") %></div>
<div class="kpi-value"><%= format_euros(@summary[:cost_per_session_cents]) %></div>
<div class="kpi-sub"><%= t("admin.costs.kpi.cost_per_club") %>: <%= format_euros(@summary[:cost_per_club_cents]) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label"><%= t("admin.costs.kpi.storage") %></div>
<div class="kpi-value" style="font-size:1.2rem"><%= format_bytes(@summary[:storage_bytes]) %></div>
<div class="kpi-sub">
<%= t("admin.costs.kpi.cost_per_gb") %>: <%= format_euros(@summary[:cost_per_gb_cents]) %>
· <%= t("admin.costs.kpi.storage_note") %>
</div>
</div>
</section>
<section class="panel" style="margin-bottom:1.25rem">
<h3><%= t("admin.costs.index.trends_title") %></h3>
<div class="charts-grid">
<div class="chart-card">
<h3><%= t("admin.costs.charts.cost_revenue") %></h3>
<div class="chart-wrap"><canvas id="chart-cost-revenue"></canvas></div>
</div>
<div class="chart-card">
<h3><%= t("admin.costs.charts.cost_per_hour") %></h3>
<div class="chart-wrap"><canvas id="chart-cost-hour"></canvas></div>
</div>
<div class="chart-card">
<h3><%= t("admin.costs.charts.hours") %></h3>
<div class="chart-wrap"><canvas id="chart-hours"></canvas></div>
</div>
</div>
</section>
<section class="panel" style="margin-bottom:1.25rem">
<h3><%= t("admin.costs.index.entries_title") %></h3>
<p class="muted admin-table-sub"><%= t("admin.costs.index.entries_lead") %></p>
<% if @entries.any? %>
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th><%= t("admin.costs.table.label") %></th>
<th><%= t("admin.costs.table.amount") %></th>
<th><%= t("admin.costs.table.notes") %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<tr>
<td><strong><%= entry.label %></strong></td>
<td><%= format_euros(entry.amount_cents) %></td>
<td class="muted"><%= entry.notes.presence || t("admin.common.dash") %></td>
<td class="admin-actions">
<%= link_to t("admin.announcements.actions.edit"),
edit_admin_cost_entry_path(entry),
class: "admin-btn admin-btn--sm" %>
<%= button_to t("admin.costs.entries.delete"),
admin_cost_entry_path(entry),
method: :delete,
class: "admin-btn admin-btn--sm admin-btn--danger",
form: { data: { confirm: t("admin.costs.entries.delete_confirm") } } %>
</td>
</tr>
<% end %>
</tbody>
<tfoot>
<tr>
<th><%= t("admin.costs.kpi.platform_cost") %></th>
<th><%= format_euros(@summary[:cost_cents]) %></th>
<th colspan="2"></th>
</tr>
</tfoot>
</table>
</div>
<% else %>
<p class="empty"><%= t("admin.costs.index.no_entries") %></p>
<% end %>
</section>
<section class="panel">
<h3><%= t("admin.costs.index.clubs_title") %></h3>
<p class="muted admin-table-sub"><%= t("admin.costs.index.clubs_lead") %></p>
<% if @clubs.any? %>
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th><%= t("admin.costs.table.club") %></th>
<th><%= t("admin.costs.table.plan") %></th>
<th><%= t("admin.costs.table.hours") %></th>
<th><%= t("admin.costs.table.hours_share") %></th>
<th><%= t("admin.costs.table.allocated_cost") %></th>
<th><%= t("admin.costs.table.revenue") %></th>
<th><%= t("admin.costs.table.margin") %></th>
<th><%= t("admin.costs.table.cost_per_hour") %></th>
<th><%= t("admin.costs.table.cost_per_session") %></th>
<th><%= t("admin.costs.table.storage") %></th>
</tr>
</thead>
<tbody>
<% @clubs.each do |row| %>
<tr>
<td>
<%= link_to row[:club_name], admin_club_path(row[:club_id]), class: "admin-table-strong" %>
</td>
<td class="muted"><%= row[:plan_slug] || t("admin.common.dash") %></td>
<td><%= format_hours(row[:hours]) %></td>
<td class="muted"><%= row[:hours_share_pct] %>%</td>
<td><%= format_euros(row[:allocated_cost_cents]) %></td>
<td><%= format_euros(row[:revenue_cents]) %></td>
<td><%= format_euros(row[:margin_cents]) %></td>
<td><%= format_euros(row[:cost_per_hour_cents]) %></td>
<td><%= format_euros(row[:cost_per_session_cents]) %></td>
<td class="muted"><%= format_bytes(row[:storage_bytes]) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<p class="empty"><%= t("admin.costs.index.no_clubs") %></p>
<% end %>
</section>