Online Team Sprint Performance Tracker

Team Sprint Performance Tracker

Analyze your team's sprint data to improve velocity and predictability.

Log a Completed Sprint

Sprint History

No data to display. Please log sprints first.

'; return; } // Calculate stats const totalSprints = appData.sprints.length; const totalCompleted = appData.sprints.reduce((sum, s) => sum + s.completed, 0); const totalCommitted = appData.sprints.reduce((sum, s) => sum + s.committed, 0); const avgVelocity = totalSprints > 0 ? (totalCompleted / totalSprints).toFixed(1) : 0; const sayDoRatio = totalCommitted > 0 ? ((totalCompleted / totalCommitted) * 100).toFixed(0) : 0; statsAvgVelocity.textContent = avgVelocity; statsSayDo.textContent = `${sayDoRatio}%`; statsTotalSprints.textContent = totalSprints; // Render chart renderPerformanceChart(); }; const renderPerformanceChart = () => { const chartContainer = document.getElementById('performance-chart').closest('div'); chartContainer.innerHTML = ''; // Clear previous chart and any messages const ctx = document.getElementById('performance-chart').getContext('2d'); const labels = appData.sprints.map(s => s.name); const committedData = appData.sprints.map(s => s.committed); const completedData = appData.sprints.map(s => s.completed); if (performanceChartInstance) { performanceChartInstance.destroy(); } performanceChartInstance = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [ { label: 'Points Committed', data: committedData, borderColor: '#a5b4fc', backgroundColor: 'rgba(165, 180, 252, 0.1)', fill: false, tension: 0.1, borderDash: [5, 5] }, { label: 'Points Completed', data: completedData, borderColor: '#4f46e5', backgroundColor: 'rgba(79, 70, 229, 0.1)', fill: true, tension: 0.1 } ] }, options: { responsive: true, scales: { y: { beginAtZero: true } }, plugins: { tooltip: { mode: 'index', intersect: false } } } }); }; // --- TAB 3: INSIGHTS --- generateInsightsBtn.addEventListener('click', async () => { if (appData.sprints.length < 4) { showMessage('Please log at least 4 sprints for a meaningful analysis.'); return; } insightsLoader.classList.remove('hidden'); insightsContent.innerHTML = ''; const dataSummary = appData.sprints.map(s => `In sprint "${s.name}", the team committed to ${s.committed} points and completed ${s.completed} points.` ).join('\n'); const prompt = `An agile team has the following sprint history:\n\n${dataSummary}\n\nBased on this data, analyze their performance. Identify trends in their velocity (completed points) and predictability (completion vs. commitment). Provide 2-3 concise, actionable insights to help them improve their planning and execution. Frame the advice constructively.`; const insights = await callGeminiAPI(prompt); insightsLoader.classList.add('hidden'); if (insights) { insightsContent.innerHTML = insights.replace(/\n/g, '
'); } else { insightsContent.innerHTML = '

Could not generate insights at this time. Please try again later.

'; } }); // --- PDF DOWNLOAD --- downloadPdfBtn.addEventListener('click', () => { showMessage('Preparing PDF...', false); const { jsPDF } = window.jspdf; const element = document.getElementById('pdf-export-area'); const originalAnimation = performanceChartInstance.options.animation; performanceChartInstance.options.animation = false; performanceChartInstance.update(); html2canvas(element, { scale: 2, useCORS: true }).then(canvas => { performanceChartInstance.options.animation = originalAnimation; performanceChartInstance.update(); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF({ orientation: 'portrait', unit: 'in', format: 'letter' }); const pdfWidth = pdf.internal.pageSize.getWidth() - 1; const pdfHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, 'PNG', 0.5, 0.5, pdfWidth, pdfHeight); pdf.save('Team_Sprint_Performance.pdf'); showMessage('PDF downloaded successfully!', false); }).catch(err => { console.error("PDF Generation Error:", err); showMessage("An error occurred during PDF generation."); performanceChartInstance.options.animation = originalAnimation; performanceChartInstance.update(); }); }); // --- INITIALIZATION --- const initializeApp = () => { loadData(); switchTab('tab1'); }; initializeApp(); });
Scroll to Top