← back to Watches
analytics/dashboard.html
613 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analytics Dashboard - Omega Watches</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #333;
padding: 20px;
}
.dashboard {
max-width: 1400px;
margin: 0 auto;
}
.header {
background: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 32px;
margin-bottom: 10px;
color: #1a202c;
}
.header p {
color: #718096;
font-size: 14px;
}
.controls {
display: flex;
gap: 10px;
margin-top: 20px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-secondary {
background: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background: #cbd5e0;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.metric-card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.metric-value {
font-size: 36px;
font-weight: bold;
color: #667eea;
margin-bottom: 8px;
}
.metric-label {
font-size: 14px;
color: #718096;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-change {
font-size: 12px;
margin-top: 8px;
}
.metric-change.positive {
color: #48bb78;
}
.metric-change.negative {
color: #f56565;
}
.chart-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.chart-card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.chart-card h3 {
font-size: 18px;
margin-bottom: 20px;
color: #1a202c;
}
.chart-container {
position: relative;
height: 300px;
}
.table-card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.table-card h3 {
font-size: 18px;
margin-bottom: 20px;
color: #1a202c;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
th {
background: #f7fafc;
font-weight: 600;
color: #4a5568;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
tr:hover {
background: #f7fafc;
}
.status-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.status-active {
background: #c6f6d5;
color: #22543d;
}
.status-inactive {
background: #fed7d7;
color: #742a2a;
}
.loading {
text-align: center;
padding: 40px;
color: white;
font-size: 18px;
}
.error {
background: #fed7d7;
color: #742a2a;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
background: white;
padding: 10px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.tab {
padding: 10px 20px;
border: none;
background: transparent;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: #4a5568;
border-radius: 6px;
transition: all 0.3s;
}
.tab.active {
background: #667eea;
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="dashboard">
<div class="header">
<h1>📊 Analytics Dashboard</h1>
<p>Real-time analytics for Omega Watches Price History</p>
<div class="controls">
<button class="btn btn-primary" onclick="refreshData()">🔄 Refresh</button>
<button class="btn btn-secondary" onclick="exportData()">📥 Export Data</button>
<button class="btn btn-secondary" onclick="window.open('http://45.61.58.125:7600', '_blank')">🌐 Visit Site</button>
<select class="btn btn-secondary" id="timeRange" onchange="changeTimeRange()">
<option value="1">Last Hour</option>
<option value="24" selected>Last 24 Hours</option>
<option value="168">Last 7 Days</option>
<option value="720">Last 30 Days</option>
</select>
</div>
</div>
<div class="tabs">
<button class="tab active" onclick="switchTab('overview')">Overview</button>
<button class="tab" onclick="switchTab('events')">Events</button>
<button class="tab" onclick="switchTab('funnels')">Funnels</button>
<button class="tab" onclick="switchTab('cohorts')">Cohorts</button>
<button class="tab" onclick="switchTab('abtests')">A/B Tests</button>
</div>
<div id="overview-tab" class="tab-content active">
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-value" id="totalEvents">--</div>
<div class="metric-label">Total Events</div>
</div>
<div class="metric-card">
<div class="metric-value" id="uniqueUsers">--</div>
<div class="metric-label">Unique Users</div>
</div>
<div class="metric-card">
<div class="metric-value" id="uniqueSessions">--</div>
<div class="metric-label">Active Sessions</div>
</div>
<div class="metric-card">
<div class="metric-value" id="avgEventsPerUser">--</div>
<div class="metric-label">Avg Events/User</div>
</div>
</div>
<div class="chart-grid">
<div class="chart-card">
<h3>Top Events</h3>
<div class="chart-container">
<canvas id="topEventsChart"></canvas>
</div>
</div>
<div class="chart-card">
<h3>Events Timeline</h3>
<div class="chart-container">
<canvas id="timelineChart"></canvas>
</div>
</div>
</div>
<div class="table-card">
<h3>Recent Events</h3>
<table id="recentEventsTable">
<thead>
<tr>
<th>Event</th>
<th>User ID</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div id="events-tab" class="tab-content">
<div class="chart-card">
<h3>Event Breakdown</h3>
<div class="chart-container" style="height: 400px;">
<canvas id="eventBreakdownChart"></canvas>
</div>
</div>
</div>
<div id="funnels-tab" class="tab-content">
<div class="table-card">
<h3>Funnel Performance</h3>
<div id="funnelData">Loading...</div>
</div>
</div>
<div id="cohorts-tab" class="tab-content">
<div class="chart-card">
<h3>Cohort Analysis</h3>
<div id="cohortData">Loading...</div>
</div>
</div>
<div id="abtests-tab" class="tab-content">
<div class="table-card">
<h3>A/B Test Results</h3>
<div id="abTestData">Loading...</div>
</div>
</div>
</div>
<script>
let dashboardData = null;
let charts = {};
async function loadDashboard() {
try {
const hours = document.getElementById('timeRange').value;
const response = await fetch(`http://45.61.58.125:7650/api/analytics/dashboard?hours=${hours}`);
dashboardData = await response.json();
renderDashboard();
} catch (error) {
console.error('Failed to load dashboard:', error);
}
}
function renderDashboard() {
// Update metrics
document.getElementById('totalEvents').textContent = dashboardData.summary.totalEvents.toLocaleString();
document.getElementById('uniqueUsers').textContent = dashboardData.summary.uniqueUsers.toLocaleString();
document.getElementById('uniqueSessions').textContent = dashboardData.summary.uniqueSessions.toLocaleString();
document.getElementById('avgEventsPerUser').textContent =
(dashboardData.summary.totalEvents / Math.max(1, dashboardData.summary.uniqueUsers)).toFixed(1);
// Render top events chart
renderTopEventsChart();
// Render recent events table
renderRecentEvents();
// Render event breakdown
renderEventBreakdown();
}
function renderTopEventsChart() {
const ctx = document.getElementById('topEventsChart');
if (charts.topEvents) {
charts.topEvents.destroy();
}
charts.topEvents = new Chart(ctx, {
type: 'bar',
data: {
labels: dashboardData.topEvents.map(e => e.event),
datasets: [{
label: 'Event Count',
data: dashboardData.topEvents.map(e => e.count),
backgroundColor: 'rgba(102, 126, 234, 0.8)',
borderColor: 'rgba(102, 126, 234, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
}
function renderEventBreakdown() {
const ctx = document.getElementById('eventBreakdownChart');
if (charts.eventBreakdown) {
charts.eventBreakdown.destroy();
}
const eventCounts = Object.entries(dashboardData.summary.eventCounts)
.sort(([, a], [, b]) => b - a)
.slice(0, 15);
charts.eventBreakdown = new Chart(ctx, {
type: 'doughnut',
data: {
labels: eventCounts.map(([event]) => event),
datasets: [{
data: eventCounts.map(([, count]) => count),
backgroundColor: [
'#667eea', '#764ba2', '#f093fb', '#4facfe',
'#43e97b', '#fa709a', '#fee140', '#30cfd0',
'#a8edea', '#fed6e3', '#c3cfe2', '#ffecd2'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right'
}
}
}
});
}
function renderRecentEvents() {
const tbody = document.querySelector('#recentEventsTable tbody');
tbody.innerHTML = '';
dashboardData.recentEvents.forEach(event => {
const row = tbody.insertRow();
row.insertCell(0).textContent = event.eventName;
row.insertCell(1).textContent = event.userId.substring(0, 12) + '...';
row.insertCell(2).textContent = new Date(event.timestamp).toLocaleString();
});
}
function switchTab(tabName) {
// Update tab buttons
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
event.target.classList.add('active');
// Update tab content
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.getElementById(`${tabName}-tab`).classList.add('active');
// Load specific tab data
if (tabName === 'funnels') {
loadFunnelData();
} else if (tabName === 'cohorts') {
loadCohortData();
} else if (tabName === 'abtests') {
loadABTestData();
}
}
async function loadFunnelData() {
const container = document.getElementById('funnelData');
container.innerHTML = 'Loading funnel data...';
try {
const funnels = ['watch_discovery', 'price_research', 'collection_analysis', 'investment_research'];
let html = '';
for (const funnelId of funnels) {
const response = await fetch(`http://45.61.58.125:7650/api/analytics/funnels/${funnelId}/conversions?days=30`);
const data = await response.json();
html += `<h4>${funnelId.replace(/_/g, ' ').toUpperCase()}</h4>`;
html += '<table><thead><tr><th>Step</th><th>Users</th><th>Conversion Rate</th><th>Drop-off</th></tr></thead><tbody>';
data.conversions.forEach(step => {
html += `<tr>
<td>${step.step}</td>
<td>${step.users}</td>
<td>${step.conversionRate}%</td>
<td>${step.dropoffRate}%</td>
</tr>`;
});
html += '</tbody></table><br>';
}
container.innerHTML = html;
} catch (error) {
container.innerHTML = 'Error loading funnel data';
}
}
async function loadCohortData() {
const container = document.getElementById('cohortData');
container.innerHTML = 'Loading cohort data...';
try {
const response = await fetch('http://45.61.58.125:7650/api/analytics/cohorts/summary');
const data = await response.json();
let html = '<table><thead><tr><th>Cohort Month</th><th>Users</th><th>Avg Visits</th><th>Avg Actions</th><th>Engagement Score</th></tr></thead><tbody>';
data.cohorts.forEach(cohort => {
html += `<tr>
<td>${cohort.month}</td>
<td>${cohort.users}</td>
<td>${cohort.avgVisitsPerUser}</td>
<td>${cohort.avgActionsPerUser}</td>
<td>${cohort.avgEngagementScore}</td>
</tr>`;
});
html += '</tbody></table>';
container.innerHTML = html;
} catch (error) {
container.innerHTML = 'Error loading cohort data';
}
}
async function loadABTestData() {
const container = document.getElementById('abTestData');
container.innerHTML = 'Loading A/B test data...';
try {
const experiments = ['price_chart_layout', 'watch_card_design', 'cta_button_color', 'search_algorithm'];
let html = '';
for (const experimentId of experiments) {
const response = await fetch(`http://45.61.58.125:7650/api/analytics/ab-tests/${experimentId}/results`);
const data = await response.json();
html += `<h4>${experimentId.replace(/_/g, ' ').toUpperCase()}</h4>`;
html += '<table><thead><tr><th>Variant</th><th>Users</th><th>Goals</th><th>Metrics</th></tr></thead><tbody>';
data.variants.forEach(variant => {
html += `<tr>
<td>${variant.variantId}</td>
<td>${variant.users}</td>
<td>${JSON.stringify(variant.goals)}</td>
<td>${JSON.stringify(variant.metrics)}</td>
</tr>`;
});
html += '</tbody></table><br>';
}
container.innerHTML = html;
} catch (error) {
container.innerHTML = 'Error loading A/B test data';
}
}
function refreshData() {
loadDashboard();
}
function changeTimeRange() {
loadDashboard();
}
async function exportData() {
window.open('http://45.61.58.125:7650/api/analytics/export', '_blank');
}
// Load dashboard on page load
loadDashboard();
// Auto-refresh every 30 seconds
setInterval(loadDashboard, 30000);
</script>
</body>
</html>