Ryan Malloy 44ed9936b7 Initial commit: Claude Code Project Tracker
Add comprehensive development intelligence system that tracks:
- Development sessions with automatic start/stop
- Full conversation history with semantic search
- Tool usage and file operation analytics
- Think time and engagement analysis
- Git activity correlation
- Learning pattern recognition
- Productivity insights and metrics

Features:
- FastAPI backend with SQLite database
- Modern web dashboard with interactive charts
- Claude Code hook integration for automatic tracking
- Comprehensive test suite with 100+ tests
- Complete API documentation (OpenAPI/Swagger)
- Privacy-first design with local data storage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 02:59:21 -06:00

390 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Analytics - Claude Code Project Tracker{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>
<i class="fas fa-chart-line me-2"></i>
Development Analytics
</h1>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-primary active" onclick="setTimePeriod(30)">
30 Days
</button>
<button type="button" class="btn btn-outline-primary" onclick="setTimePeriod(7)">
7 Days
</button>
<button type="button" class="btn btn-outline-primary" onclick="setTimePeriod(90)">
90 Days
</button>
</div>
</div>
</div>
</div>
<!-- Productivity Metrics -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-brain me-2"></i>
Engagement Analysis
</h6>
</div>
<div class="card-body text-center">
<div class="display-6 text-primary mb-2" id="engagement-metric">-</div>
<p class="text-muted">Engagement Score</p>
<canvas id="engagementChart" height="150"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-clock me-2"></i>
Time Analysis
</h6>
</div>
<div class="card-body">
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Average Session</span>
<strong id="avg-session">-</strong>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Think Time</span>
<strong id="think-time-metric">-</strong>
</div>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between">
<span>Files per Session</span>
<strong id="files-per-session">-</strong>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-graduation-cap me-2"></i>
Learning Insights
</h6>
</div>
<div class="card-body" id="learning-insights">
<div class="text-center py-3">
<div class="spinner-border spinner-border-sm text-primary"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Development Patterns -->
<div class="row mb-4">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-chart-area me-2"></i>
Development Patterns
</h6>
</div>
<div class="card-body">
<canvas id="patternsChart" height="100"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-user-clock me-2"></i>
Working Hours
</h6>
</div>
<div class="card-body">
<canvas id="hoursChart" height="150"></canvas>
</div>
</div>
</div>
</div>
<!-- Tool Usage & Git Activity -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-tools me-2"></i>
Tool Usage Statistics
</h6>
</div>
<div class="card-body">
<div id="tool-stats">
<div class="text-center py-3">
<div class="spinner-border spinner-border-sm text-primary"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fab fa-git-alt me-2"></i>
Git Activity
</h6>
</div>
<div class="card-body">
<div id="git-stats">
<div class="text-center py-3">
<div class="spinner-border spinner-border-sm text-primary"></div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentTimePeriod = 30;
let engagementChart = null;
let patternsChart = null;
let hoursChart = null;
document.addEventListener('DOMContentLoaded', function() {
loadAnalyticsData();
});
function setTimePeriod(days) {
currentTimePeriod = days;
// Update active button
document.querySelectorAll('.btn-group .btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
loadAnalyticsData();
}
async function loadAnalyticsData() {
try {
const [
productivityMetrics,
developmentPatterns,
learningInsights,
toolUsageStats,
gitStats
] = await Promise.all([
apiClient.getProductivityMetrics(null, currentTimePeriod),
apiClient.getDevelopmentPatterns(null, currentTimePeriod),
apiClient.getLearningInsights(null, currentTimePeriod),
apiClient.getToolUsageStats(null, null, currentTimePeriod),
apiClient.getGitActivityStats(null, currentTimePeriod)
]);
updateProductivityMetrics(productivityMetrics);
updateDevelopmentPatterns(developmentPatterns);
updateLearningInsights(learningInsights);
updateToolStats(toolUsageStats);
updateGitStats(gitStats);
} catch (error) {
console.error('Failed to load analytics data:', error);
}
}
function updateProductivityMetrics(metrics) {
document.getElementById('engagement-metric').textContent =
metrics.engagement_score?.toFixed(0) || '-';
document.getElementById('avg-session').textContent =
ApiUtils.formatDuration(metrics.average_session_length || 0);
document.getElementById('think-time-metric').textContent =
`${(metrics.think_time_average || 0).toFixed(1)}s`;
document.getElementById('files-per-session').textContent =
(metrics.files_per_session || 0).toFixed(1);
}
function updateDevelopmentPatterns(patterns) {
if (patterns.working_hours) {
updateWorkingHoursChart(patterns.working_hours);
}
if (patterns.productivity_trends) {
updatePatternsChart(patterns);
}
}
function updateWorkingHoursChart(workingHours) {
const ctx = document.getElementById('hoursChart').getContext('2d');
if (hoursChart) {
hoursChart.destroy();
}
const hours = Array.from({length: 24}, (_, i) => i);
const data = hours.map(hour => workingHours.distribution[hour] || 0);
hoursChart = new Chart(ctx, {
type: 'bar',
data: {
labels: hours.map(h => `${h}:00`),
datasets: [{
data: data,
backgroundColor: '#007bff',
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
x: {
ticks: { maxTicksLimit: 8 }
},
y: {
beginAtZero: true
}
}
}
});
}
function updateLearningInsights(insights) {
const container = document.getElementById('learning-insights');
if (insights.message) {
container.innerHTML = `
<div class="text-center text-muted">
<i class="fas fa-info-circle mb-2"></i>
<p class="small">${insights.message}</p>
</div>
`;
return;
}
const topTopics = Object.entries(insights.learning_topics?.frequency || {})
.sort(([,a], [,b]) => b - a)
.slice(0, 3);
const insightsHtml = `
<div class="mb-3">
<small class="text-muted">Most Discussed Topics</small>
${topTopics.map(([topic, count]) => `
<div class="d-flex justify-content-between align-items-center mt-1">
<span class="small">${topic}</span>
<span class="badge bg-primary">${count}</span>
</div>
`).join('')}
</div>
<div class="text-center">
<div class="fw-bold text-success">${insights.insights?.diverse_learning || 0}</div>
<small class="text-muted">Topics Explored</small>
</div>
`;
container.innerHTML = insightsHtml;
}
function updateToolStats(toolStats) {
const container = document.getElementById('tool-stats');
if (!toolStats.length) {
container.innerHTML = `
<div class="text-center text-muted py-3">
<i class="fas fa-tools mb-2"></i>
<p class="small">No tool usage data</p>
</div>
`;
return;
}
const topTools = toolStats.slice(0, 5);
const maxUsage = Math.max(...topTools.map(t => t.usage_count));
const toolsHtml = topTools.map(tool => {
const percentage = (tool.usage_count / maxUsage) * 100;
return `
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="small">
<i class="${ApiUtils.getToolIcon(tool.tool_name)} me-1"></i>
${tool.tool_name}
</span>
<span class="badge bg-${ApiUtils.getToolColor(tool.tool_name)}">${tool.usage_count}</span>
</div>
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-${ApiUtils.getToolColor(tool.tool_name)}"
style="width: ${percentage}%"></div>
</div>
</div>
`;
}).join('');
container.innerHTML = toolsHtml;
}
function updateGitStats(gitStats) {
const container = document.getElementById('git-stats');
if (!gitStats.total_operations) {
container.innerHTML = `
<div class="text-center text-muted py-3">
<i class="fab fa-git-alt mb-2"></i>
<p class="small">No git activity data</p>
</div>
`;
return;
}
const statsHtml = `
<div class="row g-3 text-center">
<div class="col-6">
<div class="fw-bold text-primary">${gitStats.total_operations}</div>
<small class="text-muted">Operations</small>
</div>
<div class="col-6">
<div class="fw-bold text-success">${gitStats.success_rate || 0}%</div>
<small class="text-muted">Success Rate</small>
</div>
</div>
<hr>
<div class="mb-2">
<small class="text-muted">Operation Types</small>
</div>
${Object.entries(gitStats.operations_by_type || {}).map(([type, count]) => `
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="small">${type}</span>
<span class="badge bg-secondary">${count}</span>
</div>
`).join('')}
`;
container.innerHTML = statsHtml;
}
</script>
{% endblock %}