Files
towercontrol/src/components/jira/PredictabilityMetrics.tsx
Julien Froidefond 4152b0bdfc chore: refactor project structure and clean up unused components
- Updated `TODO.md` to reflect new testing tasks and final structure expectations.
- Simplified TypeScript path mappings in `tsconfig.json` for better clarity.
- Revised business logic separation rules in `.cursor/rules` to align with new directory structure.
- Deleted unused client components and services to streamline the codebase.
- Adjusted import paths in scripts to match the new structure.
2025-09-21 10:26:35 +02:00

242 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Cell } from 'recharts';
import { SprintVelocity } from '@/lib/types';
interface PredictabilityMetricsProps {
sprintHistory: SprintVelocity[];
className?: string;
}
interface PredictabilityDataPoint {
sprint: string;
planned: number;
actual: number;
variance: number; // Pourcentage de variance (positif = dépassement, négatif = sous-performance)
accuracy: number; // Pourcentage d'exactitude (100% = parfait)
}
export function PredictabilityMetrics({ sprintHistory, className }: PredictabilityMetricsProps) {
// Calculer les métriques de predictabilité
const predictabilityData: PredictabilityDataPoint[] = sprintHistory.map(sprint => {
const variance = sprint.plannedPoints > 0
? ((sprint.completedPoints - sprint.plannedPoints) / sprint.plannedPoints) * 100
: 0;
const accuracy = sprint.plannedPoints > 0
? Math.max(0, 100 - Math.abs(variance))
: 0;
return {
sprint: sprint.sprintName.replace('Sprint ', ''),
planned: sprint.plannedPoints,
actual: sprint.completedPoints,
variance: Math.round(variance * 10) / 10,
accuracy: Math.round(accuracy * 10) / 10
};
});
// Calculer les statistiques globales
const averageVariance = predictabilityData.length > 0
? predictabilityData.reduce((sum, d) => sum + Math.abs(d.variance), 0) / predictabilityData.length
: 0;
const averageAccuracy = predictabilityData.length > 0
? predictabilityData.reduce((sum, d) => sum + d.accuracy, 0) / predictabilityData.length
: 0;
const consistencyScore = averageVariance < 10 ? 'Excellent' :
averageVariance < 20 ? 'Bon' :
averageVariance < 30 ? 'Moyen' : 'À améliorer';
// Tendance de l'exactitude (en amélioration ou dégradation)
const recentAccuracy = predictabilityData.slice(-2);
const trend = recentAccuracy.length >= 2
? recentAccuracy[1].accuracy - recentAccuracy[0].accuracy
: 0;
const CustomTooltip = ({ active, payload, label }: {
active?: boolean;
payload?: Array<{ payload: PredictabilityDataPoint; value: number; name: string; color: string }>;
label?: string
}) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
return (
<div className="bg-[var(--card)] border border-[var(--border)] rounded-lg p-3 shadow-lg">
<p className="font-medium text-sm mb-2">Sprint {label}</p>
<div className="space-y-1 text-xs">
<div className="flex justify-between gap-4">
<span>Planifié:</span>
<span className="font-mono text-gray-500">{data.planned} pts</span>
</div>
<div className="flex justify-between gap-4">
<span>Réalisé:</span>
<span className="font-mono text-blue-500">{data.actual} pts</span>
</div>
<div className="flex justify-between gap-4">
<span>Variance:</span>
<span className={`font-mono ${data.variance > 0 ? 'text-green-500' : data.variance < 0 ? 'text-red-500' : 'text-gray-500'}`}>
{data.variance > 0 ? '+' : ''}{data.variance}%
</span>
</div>
<div className="flex justify-between gap-4">
<span>Exactitude:</span>
<span className="font-mono text-orange-500">{data.accuracy}%</span>
</div>
</div>
</div>
);
}
return null;
};
return (
<div className={className}>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Graphique de variance */}
<div>
<h4 className="text-sm font-medium mb-3">Variance planifié vs réalisé</h4>
<div style={{ width: '100%', height: '200px' }}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={predictabilityData} margin={{ top: 20, right: 30, left: 20, bottom: 20 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
<XAxis
dataKey="sprint"
stroke="var(--muted-foreground)"
fontSize={10}
/>
<YAxis
stroke="var(--muted-foreground)"
fontSize={10}
label={{ value: '%', angle: 0, position: 'insideLeft' }}
/>
<Tooltip content={<CustomTooltip />} />
<Bar
dataKey="variance"
radius={[2, 2, 2, 2]}
>
{predictabilityData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.variance > 0 ? 'hsl(142, 76%, 36%)' : entry.variance < 0 ? 'hsl(0, 84%, 60%)' : 'hsl(240, 5%, 64%)'}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Graphique d'exactitude */}
<div>
<h4 className="text-sm font-medium mb-3">Évolution de l&apos;exactitude</h4>
<div style={{ width: '100%', height: '200px' }}>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={predictabilityData} margin={{ top: 20, right: 30, left: 20, bottom: 20 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
<XAxis
dataKey="sprint"
stroke="var(--muted-foreground)"
fontSize={10}
/>
<YAxis
stroke="var(--muted-foreground)"
fontSize={10}
domain={[0, 100]}
label={{ value: '%', angle: 0, position: 'insideLeft' }}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="accuracy"
stroke="hsl(45, 93%, 47%)"
strokeWidth={3}
dot={{ fill: 'hsl(45, 93%, 47%)', strokeWidth: 2, r: 4 }}
name="Exactitude"
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
{/* Métriques de predictabilité */}
<div className="mt-6 grid grid-cols-4 gap-4">
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className={`text-lg font-bold ${averageAccuracy > 80 ? 'text-green-500' : averageAccuracy > 60 ? 'text-orange-500' : 'text-red-500'}`}>
{Math.round(averageAccuracy)}%
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Exactitude moyenne
</div>
</div>
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className={`text-lg font-bold ${averageVariance < 10 ? 'text-green-500' : averageVariance < 20 ? 'text-orange-500' : 'text-red-500'}`}>
{Math.round(averageVariance * 10) / 10}%
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Variance moyenne
</div>
</div>
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className={`text-lg font-bold ${consistencyScore === 'Excellent' ? 'text-green-500' : consistencyScore === 'Bon' ? 'text-blue-500' : consistencyScore === 'Moyen' ? 'text-orange-500' : 'text-red-500'}`}>
{consistencyScore}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Consistance
</div>
</div>
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className={`text-lg font-bold ${trend > 5 ? 'text-green-500' : trend < -5 ? 'text-red-500' : 'text-blue-500'}`}>
{trend > 0 ? '↗️' : trend < 0 ? '↘️' : '→'} {Math.abs(Math.round(trend))}%
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Tendance récente
</div>
</div>
</div>
{/* Analyse et recommandations */}
<div className="mt-4 p-4 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<h4 className="text-sm font-medium mb-2">Analyse de predictabilité</h4>
<div className="space-y-2 text-sm">
{averageAccuracy > 80 && (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<span></span>
<span>Excellente predictabilité - L&apos;équipe estime bien sa capacité</span>
</div>
)}
{averageAccuracy < 60 && (
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<span></span>
<span>Predictabilité faible - Revoir les méthodes d&apos;estimation</span>
</div>
)}
{averageVariance > 25 && (
<div className="flex items-center gap-2 text-orange-600 dark:text-orange-400">
<span>📊</span>
<span>Variance élevée - Considérer des sprints plus courts ou un meilleur découpage</span>
</div>
)}
{trend > 10 && (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<span>📈</span>
<span>Tendance positive - L&apos;équipe s&apos;améliore dans ses estimations</span>
</div>
)}
{trend < -10 && (
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<span>📉</span>
<span>Tendance négative - Attention aux changements récents (équipe, processus)</span>
</div>
)}
</div>
</div>
</div>
);
}