Ryan Malloy bec1606c86 Add comprehensive documentation system and tool call tracking
## Documentation System
- Create complete documentation hub at /dashboard/docs with:
  - Getting Started guide with quick setup and troubleshooting
  - Hook Setup Guide with platform-specific configurations
  - API Reference with all endpoints and examples
  - FAQ with searchable questions and categories
- Add responsive design with interactive features
- Update navigation in base template

## Tool Call Tracking
- Add ToolCall model for tracking Claude Code tool usage
- Create /api/tool-calls endpoints for recording and analytics
- Add tool_call hook type with auto-session detection
- Include tool calls in project statistics and recalculation
- Track tool names, parameters, execution time, and success rates

## Project Enhancements
- Add project timeline and statistics pages (fix 404 errors)
- Create recalculation script for fixing zero statistics
- Update project stats to include tool call counts
- Enhance session model with tool call relationships

## Infrastructure
- Switch from requirements.txt to pyproject.toml/uv.lock
- Add data import functionality for claude.json files
- Update database connection to include all new models
- Add comprehensive API documentation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 05:58:27 -06:00

353 lines
13 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Project Statistics</h2>
<a href="/dashboard/projects" class="btn btn-outline-secondary">← Back to Projects</a>
</div>
<div id="project-info" class="card mb-4">
<div class="card-body">
<h5 class="card-title">Loading project information...</h5>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Time Period</h5>
</div>
<div class="card-body">
<select id="dayRange" class="form-select">
<option value="7">Last 7 days</option>
<option value="30" selected>Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="365">Last year</option>
<option value="0">All time</option>
</select>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Summary</h5>
</div>
<div class="card-body" id="summary-stats">
Loading...
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Session Statistics</h5>
</div>
<div class="card-body" id="session-stats">
Loading...
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Activity Breakdown</h5>
</div>
<div class="card-body" id="activity-stats">
Loading...
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Tool Usage</h5>
</div>
<div class="card-body">
<canvas id="toolsChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Language Usage</h5>
</div>
<div class="card-body">
<canvas id="languagesChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-4">
<div class="card">
<div class="card-header">
<h5>Productivity Trends</h5>
</div>
<div class="card-body">
<canvas id="trendsChart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const projectId = {{ project_id }};
let statsData = {};
let charts = {};
async function loadProjectStats() {
try {
const days = document.getElementById('dayRange').value;
const response = await fetch(`/api/projects/${projectId}/stats?days=${days}`);
const data = await response.json();
if (response.ok) {
statsData = data;
displayStats();
updateCharts();
} else {
throw new Error(data.detail || 'Failed to load stats');
}
} catch (error) {
console.error('Error loading stats:', error);
document.getElementById('summary-stats').innerHTML = `
<div class="alert alert-danger">
Failed to load statistics: ${error.message}
</div>
`;
}
}
async function loadProjectInfo() {
try {
const response = await fetch(`/api/projects/${projectId}`);
const project = await response.json();
if (response.ok) {
document.getElementById('project-info').innerHTML = `
<div class="card-body">
<h5 class="card-title">${project.name}</h5>
<p class="card-text">
<strong>Path:</strong> ${project.path}<br>
<strong>Git Repository:</strong> ${project.git_repo || 'Not a git repo'}<br>
<strong>Languages:</strong> ${project.languages ? project.languages.join(', ') : 'Unknown'}
</p>
</div>
`;
}
} catch (error) {
console.error('Error loading project info:', error);
}
}
function displayStats() {
// Summary stats
document.getElementById('summary-stats').innerHTML = `
<div class="row text-center">
<div class="col-md-3">
<h4 class="text-primary">${statsData.session_statistics.total_sessions}</h4>
<small>Sessions</small>
</div>
<div class="col-md-3">
<h4 class="text-success">${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10}h</h4>
<small>Total Time</small>
</div>
<div class="col-md-3">
<h4 class="text-warning">${statsData.activity_statistics.total}</h4>
<small>Activities</small>
</div>
<div class="col-md-3">
<h4 class="text-info">${statsData.conversation_statistics.total}</h4>
<small>Conversations</small>
</div>
</div>
`;
// Session stats
document.getElementById('session-stats').innerHTML = `
<p><strong>Total Sessions:</strong> ${statsData.session_statistics.total_sessions}</p>
<p><strong>Total Time:</strong> ${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10} hours</p>
<p><strong>Average Session:</strong> ${statsData.session_statistics.average_session_length_minutes} minutes</p>
<p><strong>Daily Average Time:</strong> ${statsData.summary.daily_average_time} minutes</p>
<p><strong>Daily Average Sessions:</strong> ${statsData.summary.daily_average_sessions}</p>
`;
// Activity stats
const topTools = Object.entries(statsData.activity_statistics.by_tool)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([tool, count]) => `<li>${tool}: ${count}</li>`)
.join('');
const topLangs = Object.entries(statsData.activity_statistics.by_language)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([lang, count]) => `<li>${lang}: ${count}</li>`)
.join('');
document.getElementById('activity-stats').innerHTML = `
<p><strong>Total Activities:</strong> ${statsData.activity_statistics.total}</p>
<p><strong>Most Used Tool:</strong> ${statsData.summary.most_used_tool || 'None'}</p>
<p><strong>Primary Language:</strong> ${statsData.summary.primary_language || 'None'}</p>
<div class="row mt-3">
<div class="col-md-6">
<strong>Top Tools:</strong>
<ul class="small">${topTools || '<li>No data</li>'}</ul>
</div>
<div class="col-md-6">
<strong>Languages:</strong>
<ul class="small">${topLangs || '<li>No data</li>'}</ul>
</div>
</div>
`;
}
function updateCharts() {
// Destroy existing charts
Object.values(charts).forEach(chart => chart.destroy());
charts = {};
// Tools chart
const toolData = Object.entries(statsData.activity_statistics.by_tool)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
if (toolData.length > 0) {
charts.tools = new Chart(document.getElementById('toolsChart'), {
type: 'doughnut',
data: {
labels: toolData.map(([tool]) => tool),
datasets: [{
data: toolData.map(([, count]) => count),
backgroundColor: [
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
'#FF9F40', '#C9CBCF', '#4BC0C0', '#FF6384', '#36A2EB'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: true
}
});
}
// Languages chart
const langData = Object.entries(statsData.activity_statistics.by_language)
.sort((a, b) => b[1] - a[1]);
if (langData.length > 0) {
charts.languages = new Chart(document.getElementById('languagesChart'), {
type: 'bar',
data: {
labels: langData.map(([lang]) => lang),
datasets: [{
label: 'Activities',
data: langData.map(([, count]) => count),
backgroundColor: '#36A2EB'
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Trends chart
if (statsData.productivity_trends && statsData.productivity_trends.length > 0) {
charts.trends = new Chart(document.getElementById('trendsChart'), {
type: 'line',
data: {
labels: statsData.productivity_trends.map(day => day.date),
datasets: [
{
label: 'Sessions',
data: statsData.productivity_trends.map(day => day.sessions),
borderColor: '#FF6384',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
yAxisID: 'y'
},
{
label: 'Time (minutes)',
data: statsData.productivity_trends.map(day => day.time_minutes),
borderColor: '#36A2EB',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
interaction: {
mode: 'index',
intersect: false,
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Date'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Sessions'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Time (minutes)'
},
grid: {
drawOnChartArea: false,
},
}
}
}
});
}
}
// Event listeners
document.getElementById('dayRange').addEventListener('change', loadProjectStats);
// Load initial data
loadProjectInfo();
loadProjectStats();
</script>
{% endblock %}