'use client';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Cell } from 'recharts';
import { SprintVelocity } from '@/lib/types';
import { Card, CardContent, CardHeader } from '@/components/ui/Card';
interface SprintComparisonProps {
sprintHistory: SprintVelocity[];
className?: string;
}
interface ComparisonMetrics {
velocityTrend: 'improving' | 'declining' | 'stable';
avgCompletion: number;
consistency: 'high' | 'medium' | 'low';
bestSprint: SprintVelocity;
worstSprint: SprintVelocity;
predictions: {
nextSprintEstimate: number;
confidenceLevel: 'high' | 'medium' | 'low';
};
}
export function SprintComparison({ sprintHistory, className }: SprintComparisonProps) {
// Analyser les tendances
const metrics = analyzeSprintTrends(sprintHistory);
// Données pour les graphiques
const comparisonData = sprintHistory.map(sprint => ({
name: sprint.sprintName.replace('Sprint ', ''),
completion: sprint.completionRate,
velocity: sprint.completedPoints,
planned: sprint.plannedPoints,
variance: sprint.plannedPoints > 0
? ((sprint.completedPoints - sprint.plannedPoints) / sprint.plannedPoints) * 100
: 0
}));
const CustomTooltip = ({ active, payload, label }: {
active?: boolean;
payload?: Array<{ value: number; name: string; color: string }>;
label?: string
}) => {
if (active && payload && payload.length) {
return (
Sprint {label}
{payload.map((item, index) => (
{item.name}:
{typeof item.value === 'number' ?
(item.name.includes('%') ? `${item.value}%` : item.value) :
item.value
}
))}
);
}
return null;
};
return (
{/* Graphique de comparaison des taux de complétion */}
Évolution des taux de complétion
} />
{/* Graphique de comparaison des vélocités */}
Comparaison planifié vs réalisé
} />
{/* Métriques de comparaison */}
{metrics.velocityTrend === 'improving' ? '📈' :
metrics.velocityTrend === 'declining' ? '📉' : '➡️'}
Tendance générale
80 ? 'text-green-500' :
metrics.avgCompletion > 60 ? 'text-orange-500' : 'text-red-500'
}`}>
{Math.round(metrics.avgCompletion)}%
Complétion moyenne
{metrics.consistency === 'high' ? 'Haute' :
metrics.consistency === 'medium' ? 'Moyenne' : 'Faible'}
Consistance
{metrics.predictions.nextSprintEstimate}
Prédiction suivante
{/* Insights et recommandations */}
🏆 Meilleur sprint
Sprint:
{metrics.bestSprint.sprintName}
Points complétés:
{metrics.bestSprint.completedPoints}
Taux de complétion:
{metrics.bestSprint.completionRate}%
📉 Sprint à améliorer
Sprint:
{metrics.worstSprint.sprintName}
Points complétés:
{metrics.worstSprint.completedPoints}
Taux de complétion:
{metrics.worstSprint.completionRate}%
{/* Recommandations */}
💡 Recommandations
{getRecommendations(metrics).map((recommendation, index) => (
•
{recommendation}
))}
);
}
/**
* Analyse les tendances des sprints
*/
function analyzeSprintTrends(sprintHistory: SprintVelocity[]): ComparisonMetrics {
if (sprintHistory.length === 0) {
return {
velocityTrend: 'stable',
avgCompletion: 0,
consistency: 'low',
bestSprint: sprintHistory[0],
worstSprint: sprintHistory[0],
predictions: { nextSprintEstimate: 0, confidenceLevel: 'low' }
};
}
// Tendance de vélocité (comparer premiers vs derniers sprints)
const firstHalf = sprintHistory.slice(0, Math.ceil(sprintHistory.length / 2));
const secondHalf = sprintHistory.slice(Math.floor(sprintHistory.length / 2));
const firstHalfAvg = firstHalf.reduce((sum, s) => sum + s.completedPoints, 0) / firstHalf.length;
const secondHalfAvg = secondHalf.reduce((sum, s) => sum + s.completedPoints, 0) / secondHalf.length;
const improvementRate = (secondHalfAvg - firstHalfAvg) / firstHalfAvg * 100;
const velocityTrend: 'improving' | 'declining' | 'stable' =
improvementRate > 10 ? 'improving' :
improvementRate < -10 ? 'declining' : 'stable';
// Complétion moyenne
const avgCompletion = sprintHistory.reduce((sum, s) => sum + s.completionRate, 0) / sprintHistory.length;
// Consistance (variance des taux de complétion)
const completionRates = sprintHistory.map(s => s.completionRate);
const variance = completionRates.reduce((sum, rate) => sum + Math.pow(rate - avgCompletion, 2), 0) / completionRates.length;
const standardDeviation = Math.sqrt(variance);
const consistency: 'high' | 'medium' | 'low' =
standardDeviation < 10 ? 'high' :
standardDeviation < 20 ? 'medium' : 'low';
// Meilleur et pire sprint
const bestSprint = sprintHistory.reduce((best, current) =>
current.completionRate > best.completionRate ? current : best);
const worstSprint = sprintHistory.reduce((worst, current) =>
current.completionRate < worst.completionRate ? current : worst);
// Prédiction pour le prochain sprint
const recentSprints = sprintHistory.slice(-3); // 3 derniers sprints
const recentAvg = recentSprints.reduce((sum, s) => sum + s.completedPoints, 0) / recentSprints.length;
const nextSprintEstimate = Math.round(recentAvg);
const confidenceLevel: 'high' | 'medium' | 'low' =
consistency === 'high' && velocityTrend !== 'declining' ? 'high' :
consistency === 'medium' ? 'medium' : 'low';
return {
velocityTrend,
avgCompletion,
consistency,
bestSprint,
worstSprint,
predictions: { nextSprintEstimate, confidenceLevel }
};
}
/**
* Génère des recommandations basées sur l'analyse
*/
function getRecommendations(metrics: ComparisonMetrics): string[] {
const recommendations: string[] = [];
if (metrics.velocityTrend === 'declining') {
recommendations.push("Tendance en baisse détectée - Identifier les blockers récurrents");
recommendations.push("Revoir les estimations ou la complexité des tâches récentes");
} else if (metrics.velocityTrend === 'improving') {
recommendations.push("Excellente progression ! Maintenir les bonnes pratiques actuelles");
}
if (metrics.avgCompletion < 60) {
recommendations.push("Taux de complétion faible - Considérer des sprints plus courts ou moins ambitieux");
} else if (metrics.avgCompletion > 90) {
recommendations.push("Taux de complétion très élevé - L'équipe pourrait prendre plus d'engagements");
}
if (metrics.consistency === 'low') {
recommendations.push("Consistance faible - Améliorer la prévisibilité des estimations");
recommendations.push("Organiser des rétrospectives pour identifier les causes de variabilité");
}
if (recommendations.length === 0) {
recommendations.push("Performance stable et prévisible - Continuer sur cette lancée !");
}
return recommendations;
}