ornstein-saber-eval / design_analytics_dashboard.html
KyleHessling1's picture
Initial eval upload: thinking-on full bench, tool-calling, report
ed47c9c verified
Raw
History Blame Contribute Delete
24 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Analytics Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #1f2937;
}
.container {
display: flex;
min-height: 100vh;
}
/* Sidebar Styles */
.sidebar {
width: 250px;
background-color: #fff;
border-right: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
z-index: 100;
}
.workspace-switcher {
padding: 16px;
border-bottom: 1px solid #e5e7eb;
}
.workspace-select {
width: 100%;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background-color: #fff;
font-size: 14px;
cursor: pointer;
}
.nav-menu {
padding: 16px;
flex: 1;
}
.nav-item {
display: flex;
align-items: center;
padding: 10px 12px;
margin-bottom: 4px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.nav-item:hover {
background-color: #f3f4f6;
}
.nav-item.active {
background-color: #ecfdf5;
color: #059669;
}
.nav-icon {
width: 20px;
height: 20px;
margin-right: 12px;
}
.nav-text {
font-size: 14px;
}
.user-profile {
padding: 16px;
border-top: 1px solid #e5e7eb;
display: flex;
align-items: center;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #10b981;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 12px;
}
.user-info {
font-size: 14px;
}
.user-name {
font-weight: 500;
}
.user-email {
color: #6b7280;
font-size: 12px;
}
/* Main Content Area */
.main-content {
flex: 1;
margin-left: 250px;
}
.topbar {
background-color: #fff;
border-bottom: 1px solid #e5e7eb;
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 90;
}
.search-container {
position: relative;
}
.search-input {
padding: 8px 12px 8px 36px;
border: 1px solid #d1d5db;
border-radius: 6px;
width: 300px;
font-size: 14px;
}
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #6b7280;
}
.date-range {
display: flex;
align-items: center;
gap: 8px;
}
.date-input {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
}
.notifications {
position: relative;
cursor: pointer;
}
.notification-dot {
position: absolute;
top: -2px;
right: -2px;
width: 8px;
height: 8px;
background-color: #ef4444;
border-radius: 50%;
}
.content {
padding: 24px;
}
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.kpi-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.kpi-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.kpi-title {
font-size: 14px;
color: #6b7280;
}
.kpi-value {
font-size: 24px;
font-weight: 600;
}
.kpi-change {
font-size: 12px;
margin-top: 4px;
}
.kpi-change.positive {
color: #10b981;
}
.kpi-change.negative {
color: #ef4444;
}
.charts-container {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 24px;
margin-bottom: 32px;
}
.chart-card {
background-color: #fff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.table-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.table-header {
padding: 16px 24px;
border-bottom: 1px solid #e5e7eb;
}
.table-title {
font-size: 16px;
font-weight: 500;
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: 12px 24px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.data-table th {
background-color: #f9fafb;
font-weight: 500;
cursor: pointer;
}
.data-table tr:hover {
background-color: #f9fafb;
}
.status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.status.completed {
background-color: #ecfdf5;
color: #059669;
}
.status.pending {
background-color: #fef3c7;
color: #d97706;
}
.status.failed {
background-color: #fef2f2;
color: #dc2626;
}
</style>
</head>
<body>
<div class="container">
<aside class="sidebar">
<div class="workspace-switcher">
<select class="workspace-select" id="workspaceSelect">
<option value="acme">Acme Corp</option>
<option value="globex">Globex Inc</option>
<option value="initech">Initech</option>
</select>
</div>
<nav class="nav-menu">
<div class="nav-item active">
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</svg>
<span class="nav-text">Dashboard</span>
</div>
<div class="nav-item">
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span class="nav-text">Analytics</span>
</div>
<div class="nav-item">
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"></path>
</svg>
<span class="nav-text">Customers</span>
</div>
<div class="nav-item">
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<span class="nav-text">Reports</span>
</div>
<div class="nav-item">
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
</svg>
<span class="nav-text">Settings</span>
</div>
<div class="nav-item">
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
</svg>
<span class="nav-text">Integrations</span>
</div>
</nav>
<div class="user-profile">
<div class="avatar">JS</div>
<div class="user-info">
<div class="user-name">Jane Smith</div>
<div class="user-email">jane.smith@acmecorp.com</div>
</div>
</div>
</aside>
<main class="main-content">
<header class="topbar">
<div class="search-container">
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input type="text" class="search-input" placeholder="Search anything...">
</div>
<div class="date-range">
<input type="date" class="date-input" value="2024-01-01">
<span>to</span>
<input type="date" class="date-input" value="2024-01-31">
</div>
<div class="notifications">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
<path d="M13.73 21a2 2 0 01-3.46 0"></path>
</svg>
<span class="notification-dot"></span>
</div>
</header>
<div class="content">
<div class="kpi-grid">
<div class="kpi-card">
<div class="kpi-header">
<span class="kpi-title">Revenue</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2">
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
<polyline points="17 6 23 6 23 12"></polyline>
</svg>
</div>
<div class="kpi-value">$124,500</div>
<div class="kpi-change positive">+12% from last month</div>
</div>
<div class="kpi-card">
<div class="kpi-header">
<span class="kpi-title">Active Users</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2">
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
<polyline points="17 6 23 6 23 12"></polyline>
</svg>
</div>
<div class="kpi-value">8,234</div>
<div class="kpi-change positive">+5% from last month</div>
</div>
<div class="kpi-card">
<div class="kpi-header">
<span class="kpi-title">Conversion Rate</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2">
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
<polyline points="17 6 23 6 23 12"></polyline>
</svg>
</div>
<div class="kpi-value">3.2%</div>
<div class="kpi-change positive">+0.4% from last month</div>
</div>
<div class="kpi-card">
<div class="kpi-header">
<span class="kpi-title">Churn Rate</span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ef4444" stroke-width="2">
<polyline points="23 18 13.5 8.5 8.5 13.5 1 6"></polyline>
<polyline points="17 18 23 18 23 12"></polyline>
</svg>
</div>
<div class="kpi-value">2.1%</div>
<div class="kpi-change negative">-0.3% from last month</div>
</div>
</div>
<div class="charts-container">
<div class="chart-card">
<h3>Weekly Active Users</h3>
<svg width="100%" height="200" id="lineChart"></svg>
</div>
<div class="chart-card">
<h3>Traffic Sources</h3>
<svg width="200" height="200" id="donutChart"></svg>
</div>
</div>
<div class="table-card">
<div class="table-header">
<h3 class="table-title">Recent Transactions</h3>
</div>
<table class="data-table" id="transactionsTable">
<thead>
<tr>
<th>User</th>
<th>Amount</th>
<th>Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>John Doe</td>
<td>$1,250.00</td>
<td>2024-01-28</td>
<td><span class="status completed">Completed</span></td>
</tr>
<tr>
<td>Jane Smith</td>
<td>$875.00</td>
<td>2024-01-27</td>
<td><span class="status completed">Completed</span></td>
</tr>
<tr>
<td>Mike Johnson</td>
<td>$2,100.00</td>
<td>2024-01-26</td>
<td><span class="status pending">Pending</span></td>
</tr>
<tr>
<td>Sarah Wilson</td>
<td>$450.00</td>
<td>2024-01-25</td>
<td><span class="status completed">Completed</span></td>
</tr>
<tr>
<td>David Brown</td>
<td>$1,800.00</td>
<td>2024-01-24</td>
<td><span class="status failed">Failed</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
<script>
// Sparkline data
const revenueData = [100, 120, 115, 130, 125, 140, 135, 150, 145, 160, 155, 170];
const usersData = [80, 85, 82, 88, 86, 90, 87, 92, 89, 95, 91, 96];
const conversionData = [2.5, 2.8, 3.1, 3.0, 3.2, 3.1, 3.3, 3.2, 3.4, 3.3, 3.5, 3.2];
const churnData = [2.5, 2.4, 2.3, 2.2, 2.1, 2.0, 2.1, 2.0, 1.9, 2.0, 1.9, 2.1];
// Function to create SVG sparkline
function createSparkline(data, color) {
const width = 120;
const height = 40;
const padding = 5;
const maxValue = Math.max(...data);
const minValue = Math.min(...data);
const range = maxValue - minValue || 1;
const points = data.map((value, index) => {
const x = padding + (index / (data.length - 1)) * (width - 2 * padding);
const y = height - padding - ((value - minValue) / range) * (height - 2 * padding);
return `${x},${y}`;
});
const path = `M${points.join(' L')}`;
return `
<svg width="${width}" height="${height}">
<path d="${path}" fill="none" stroke="${color}" stroke-width="2"/>
</svg>
`;
}
// Update KPI cards with sparklines
document.querySelectorAll('.kpi-card').forEach((card, index) => {
const data = [revenueData, usersData, conversionData, churnData][index];
const color = index === 3 ? '#ef4444' : '#10b981';
card.innerHTML += createSparkline(data, color);
});
// Line chart data
const weeklyActiveUsers = [
{ week: 'Week 1', users: 1200 },
{ week: 'Week 2', users: 1350 },
{ week: 'Week 3', users: 1100 },
{ week: 'Week 4', users: 1450 },
{ week: 'Week 5', users: 1300 },
{ week: 'Week 6', users: 1500 },
{ week: 'Week 7', users: 1250 },
{ week: 'Week 8', users: 1400 },
{ week: 'Week 9', users: 1350 },
{ week: 'Week 10', users: 1550 },
{ week: 'Week 11', users: 1200 },
{ week: 'Week 12', users: 1600 }
];
// Create line chart
function createLineChart() {
const svg = document.getElementById('lineChart');
const width = 600;
const height = 200;
const padding = { top: 20, right: 20, bottom: 30, left: 40 };
const maxValue = Math.max(...weeklyActiveUsers.map(d => d.users));
const minValue = Math.min(...weeklyActiveUsers.map(d => d.users));
const range = maxValue - minValue || 1;
const points = weeklyActiveUsers.map((data, index) => {
const x = padding.left + (index / (weeklyActiveUsers.length - 1)) * (width - padding.left - padding.right);
const y = height - padding.bottom - ((data.users - minValue) / range) * (height - padding.top - padding.bottom);
return { x, y, data };
});
const path = `M${points.map(p => `${p.x},${p.y}`).join(' L')}`;
svg.innerHTML = `
<path d="${path}" fill="none" stroke="#10b981" stroke-width="2"/>
${points.map(p => `
<circle cx="${p.x}" cy="${p.y}" r="3" fill="#10b981"/>
<text x="${p.x}" y="${p.y + 20}" text-anchor="middle" font-size="10">${p.data.week}</text>
`).join('')}
`;
}
createLineChart();
// Donut chart data
const trafficSources = [
{ name: 'Organic', percentage: 45 },
{ name: 'Paid', percentage: 30 },
{ name: 'Social', percentage: 15 },
{ name: 'Direct', percentage: 10 }
];
// Create donut chart
function createDonutChart() {
const svg = document.getElementById('donutChart');
const width = 200;
const height = 200;
const radius = 80;
const innerRadius = 60;
let cumulativePercentage = 0;
svg.innerHTML = trafficSources.map((source, index) => {
const startAngle = (cumulativePercentage * 3.6) * Math.PI / 180;
const endAngle = (cumulativePercentage + source.percentage) * 3.6 * Math.PI / 180;
cumulativePercentage += source.percentage;
const x1 = width / 2 + radius * Math.cos(startAngle);
const y1 = height / 2 + radius * Math.sin(startAngle);
const x2 = width / 2 + radius * Math.cos(endAngle);
const y2 = height / 2 + radius * Math.sin(endAngle);
const largeArc = source.percentage > 50 ? 1 : 0;
return `
<path d="M ${width / 2} ${height / 2} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z"
fill="#10b981" fill-opacity="0.7"/>
<text x="${(width / 2 + x1 + x2) / 2}" y="${(height / 2 + y1 + y2) / 2}"
text-anchor="middle" font-size="12" fill="white">${source.name}</text>
`;
}).join('');
}
createDonutChart();
// Sortable table
const table = document.getElementById('transactionsTable');
const headers = table.querySelectorAll('th');
headers.forEach((header, index) => {
header.addEventListener('click', () => {
const rows = Array.from(table.querySelectorAll('tbody tr'));
const isAscending = header.classList.contains('ascending');
const sortIndex = index;
rows.sort((a, b) => {
const aValue = a.cells[sortIndex].textContent.trim();
const bValue = b.cells[sortIndex].textContent.trim();
const numericA = parseFloat(aValue.replace(/[^0-9.-]/g, ''));
const numericB = parseFloat(bValue.replace(/[^0-9.-]/g, ''));
if (!isNaN(numericA) && !isNaN(numericB)) {
return isAscending ? numericA - numericB : numericB - numericA;
} else {
return isAscending ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
}
});
rows.forEach(row => table.querySelector('tbody').appendChild(row));
header.classList.toggle('ascending');
});
});
</script>
</body>
</html>