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>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
(function () {
|
||||
var trend = window.adminCostTrend || [];
|
||||
var i18n = window.adminCostI18n || {};
|
||||
if (!trend.length || typeof Chart === "undefined") return;
|
||||
|
||||
function monthLabel(monthStr) {
|
||||
var parts = monthStr.split("-");
|
||||
if (parts.length < 2) return monthStr;
|
||||
var d = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, 1);
|
||||
return d.toLocaleDateString([], { month: "short", year: "2-digit" });
|
||||
}
|
||||
|
||||
var labels = trend.map(function (row) {
|
||||
return monthLabel(row.month);
|
||||
});
|
||||
|
||||
var chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: { duration: 300 },
|
||||
scales: {
|
||||
x: {
|
||||
ticks: { maxTicksLimit: 12, color: "#9a9aad", font: { size: 10 } },
|
||||
grid: { color: "rgba(255,255,255,0.06)" }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { color: "#9a9aad", font: { size: 10 } },
|
||||
grid: { color: "rgba(255,255,255,0.06)" }
|
||||
}
|
||||
},
|
||||
plugins: { legend: { labels: { color: "#ccc", boxWidth: 12 } } }
|
||||
};
|
||||
|
||||
var euroTicks = {
|
||||
ticks: {
|
||||
color: "#9a9aad",
|
||||
font: { size: 10 },
|
||||
callback: function (v) {
|
||||
return "€" + v;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var costRevenueCanvas = document.getElementById("chart-cost-revenue");
|
||||
if (costRevenueCanvas) {
|
||||
new Chart(costRevenueCanvas, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: i18n.cost || "Cost",
|
||||
data: trend.map(function (r) { return (r.cost_cents || 0) / 100; }),
|
||||
backgroundColor: "rgba(229, 57, 53, 0.65)"
|
||||
},
|
||||
{
|
||||
label: i18n.revenue || "Revenue",
|
||||
data: trend.map(function (r) { return (r.revenue_cents || 0) / 100; }),
|
||||
backgroundColor: "rgba(67, 160, 71, 0.65)"
|
||||
},
|
||||
{
|
||||
label: i18n.margin || "Margin",
|
||||
data: trend.map(function (r) { return (r.margin_cents || 0) / 100; }),
|
||||
backgroundColor: "rgba(255, 193, 7, 0.55)"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
scales: {
|
||||
x: chartOptions.scales.x,
|
||||
y: { ...chartOptions.scales.y, ...euroTicks }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var costHourCanvas = document.getElementById("chart-cost-hour");
|
||||
if (costHourCanvas) {
|
||||
new Chart(costHourCanvas, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: i18n.costPerHour || "Cost/hour",
|
||||
data: trend.map(function (r) {
|
||||
return r.cost_per_hour_cents ? r.cost_per_hour_cents / 100 : 0;
|
||||
}),
|
||||
borderColor: "#e53935",
|
||||
backgroundColor: "rgba(229, 57, 53, 0.12)",
|
||||
fill: true,
|
||||
tension: 0.35,
|
||||
pointRadius: 2,
|
||||
borderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
scales: {
|
||||
x: chartOptions.scales.x,
|
||||
y: { ...chartOptions.scales.y, ...euroTicks }
|
||||
},
|
||||
plugins: { legend: { display: false } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var hoursCanvas = document.getElementById("chart-hours");
|
||||
if (hoursCanvas) {
|
||||
new Chart(hoursCanvas, {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: i18n.hours || "Hours",
|
||||
data: trend.map(function (r) { return r.hours || 0; }),
|
||||
borderColor: "#43a047",
|
||||
backgroundColor: "rgba(67, 160, 71, 0.12)",
|
||||
fill: true,
|
||||
tension: 0.35,
|
||||
pointRadius: 2,
|
||||
borderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
plugins: { legend: { display: false } }
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -693,6 +693,13 @@ body.admin-body {
|
||||
background: #3a1515;
|
||||
}
|
||||
|
||||
.admin-device-tab-count {
|
||||
margin-left: 0.35rem;
|
||||
padding: 0 0.35rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.admin-locale-panel {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
@@ -793,6 +800,12 @@ body.admin-body {
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.admin-analytics-preview__status {
|
||||
margin: 0 0 0.75rem;
|
||||
color: #a5f0b8;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admin-filter-count {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
return document.body && document.body.getAttribute("data-ga-id");
|
||||
}
|
||||
|
||||
function isAnalyticsSuppressed() {
|
||||
var meta = document.querySelector("meta[name='mltv-analytics-suppress']");
|
||||
return meta && meta.getAttribute("content") === "1";
|
||||
}
|
||||
|
||||
function loadGoogleAnalytics() {
|
||||
var id = measurementId();
|
||||
if (!id || window.__mltvGaLoaded) return;
|
||||
@@ -68,6 +73,7 @@
|
||||
}
|
||||
|
||||
function applyConsent(consent) {
|
||||
if (isAnalyticsSuppressed()) return;
|
||||
if (consent && consent.analytics) {
|
||||
loadGoogleAnalytics();
|
||||
if (typeof window.mltvSiteAnalyticsStart === "function") {
|
||||
@@ -94,6 +100,10 @@
|
||||
|
||||
if (acceptAll) {
|
||||
acceptAll.addEventListener("click", function () {
|
||||
if (isAnalyticsSuppressed()) {
|
||||
hideBanner(banner);
|
||||
return;
|
||||
}
|
||||
var c = writeConsent({ analytics: true });
|
||||
applyConsent(c);
|
||||
hideBanner(banner);
|
||||
|
||||
@@ -3549,3 +3549,31 @@ a.replay-archive__thumb:hover {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.mltv-preview-badge {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
left: 16px;
|
||||
z-index: 1200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
background: rgba(20, 20, 28, 0.94);
|
||||
border: 1px solid #444;
|
||||
color: #ddd;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.3;
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.mltv-preview-badge__link {
|
||||
color: #ff8a80;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mltv-preview-badge__link:hover {
|
||||
color: #ff5252;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAnalyticsSuppressed() {
|
||||
var meta = document.querySelector("meta[name='mltv-analytics-suppress']");
|
||||
return meta && meta.getAttribute("content") === "1";
|
||||
}
|
||||
|
||||
function hasAnalyticsConsent() {
|
||||
try {
|
||||
var raw = localStorage.getItem(STORAGE_KEY);
|
||||
@@ -104,7 +109,7 @@
|
||||
|
||||
function flush(useBeacon) {
|
||||
flushMovesIntoQueue();
|
||||
if (!queue.length || !hasAnalyticsConsent()) {
|
||||
if (!queue.length || !hasAnalyticsConsent() || isAnalyticsSuppressed()) {
|
||||
queue = [];
|
||||
return;
|
||||
}
|
||||
@@ -236,7 +241,7 @@
|
||||
}
|
||||
|
||||
function captureSnapshot() {
|
||||
if (!hasAnalyticsConsent() || alreadyCapturedToday()) return;
|
||||
if (!hasAnalyticsConsent() || isAnalyticsSuppressed() || alreadyCapturedToday()) return;
|
||||
loadHtml2Canvas(function (html2canvas) {
|
||||
var size = pageSize();
|
||||
html2canvas(document.documentElement, {
|
||||
@@ -281,6 +286,7 @@
|
||||
function start() {
|
||||
if (started) return;
|
||||
if (excludedPath(location.pathname)) return;
|
||||
if (isAnalyticsSuppressed()) return;
|
||||
if (!hasAnalyticsConsent()) return;
|
||||
started = true;
|
||||
maxScroll = scrollPct();
|
||||
|
||||
Reference in New Issue
Block a user