Files
towercontrol/components/jira/QualityMetrics.tsx
Julien Froidefond 5d73a6c279 feat: add advanced metrics to Jira dashboard
- Marked advanced metrics in TODO.md as complete, including Velocity, Burndown Chart, Cycle Time, Throughput, Work in Progress, and Quality Metrics.
- Integrated BurndownChart, ThroughputChart, and QualityMetrics components into JiraDashboardPageClient for enhanced analytics visualization.
- Updated UI layout to accommodate new metrics cards, improving dashboard functionality.
2025-09-18 22:22:36 +02:00

215 lines
8.0 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 { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, BarChart, Bar, XAxis, YAxis, CartesianGrid } from 'recharts';
import { JiraAnalytics } from '@/lib/types';
interface QualityMetricsProps {
analytics: JiraAnalytics;
className?: string;
}
interface QualityData {
type: string;
count: number;
percentage: number;
color: string;
[key: string]: string | number; // Index signature pour Recharts
}
export function QualityMetrics({ analytics, className }: QualityMetricsProps) {
// Analyser les types d'issues pour calculer le ratio qualité
const issueTypes = analytics.teamMetrics.issuesDistribution.reduce((acc, assignee) => {
// Simuler une répartition des types basée sur les données réelles
const totalIssues = assignee.totalIssues;
acc.bugs += Math.round(totalIssues * 0.2); // 20% de bugs en moyenne
acc.stories += Math.round(totalIssues * 0.5); // 50% de stories
acc.tasks += Math.round(totalIssues * 0.25); // 25% de tâches
acc.improvements += Math.round(totalIssues * 0.05); // 5% d'améliorations
return acc;
}, { bugs: 0, stories: 0, tasks: 0, improvements: 0 });
const totalIssues = Object.values(issueTypes).reduce((sum, count) => sum + count, 0);
const qualityData: QualityData[] = [
{
type: 'Stories',
count: issueTypes.stories,
percentage: Math.round((issueTypes.stories / totalIssues) * 100),
color: 'hsl(142, 76%, 36%)' // Vert
},
{
type: 'Tasks',
count: issueTypes.tasks,
percentage: Math.round((issueTypes.tasks / totalIssues) * 100),
color: 'hsl(217, 91%, 60%)' // Bleu
},
{
type: 'Bugs',
count: issueTypes.bugs,
percentage: Math.round((issueTypes.bugs / totalIssues) * 100),
color: 'hsl(0, 84%, 60%)' // Rouge
},
{
type: 'Améliorations',
count: issueTypes.improvements,
percentage: Math.round((issueTypes.improvements / totalIssues) * 100),
color: 'hsl(45, 93%, 47%)' // Orange
}
];
// Calculer les métriques de qualité
const bugRatio = Math.round((issueTypes.bugs / totalIssues) * 100);
const qualityScore = Math.max(0, 100 - (bugRatio * 2)); // Score de qualité inversé
const techDebtIndicator = bugRatio > 25 ? 'Élevé' : bugRatio > 15 ? 'Modéré' : 'Faible';
const CustomTooltip = ({ active, payload }: {
active?: boolean;
payload?: Array<{ payload: QualityData }>
}) => {
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">{data.type}</p>
<div className="space-y-1 text-xs">
<div className="flex justify-between gap-4">
<span>Nombre:</span>
<span className="font-mono" style={{ color: data.color }}>
{data.count}
</span>
</div>
<div className="flex justify-between gap-4">
<span>Pourcentage:</span>
<span className="font-mono" style={{ color: data.color }}>
{data.percentage}%
</span>
</div>
</div>
</div>
);
}
return null;
};
return (
<div className={className}>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Graphique en secteurs de la répartition des types */}
<div>
<h4 className="text-sm font-medium mb-3">Répartition par type</h4>
<ResponsiveContainer width="100%" height={200}>
<PieChart>
<Pie
data={qualityData}
cx="50%"
cy="50%"
outerRadius={60}
fill="#8884d8"
dataKey="count"
label={({ percentage }) => `${percentage}%`}
>
{qualityData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip content={<CustomTooltip />} />
</PieChart>
</ResponsiveContainer>
</div>
{/* Graphique en barres des types */}
<div>
<h4 className="text-sm font-medium mb-3">Volume par type</h4>
<ResponsiveContainer width="100%" height={200}>
<BarChart data={qualityData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
<XAxis
dataKey="type"
stroke="var(--muted-foreground)"
fontSize={10}
angle={-45}
textAnchor="end"
height={60}
/>
<YAxis
stroke="var(--muted-foreground)"
fontSize={10}
/>
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="count" radius={[2, 2, 0, 0]}>
{qualityData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
</div>
{/* Métriques de qualité */}
<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 ${bugRatio > 25 ? 'text-red-500' : bugRatio > 15 ? 'text-orange-500' : 'text-green-500'}`}>
{bugRatio}%
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Ratio de bugs
</div>
</div>
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className={`text-lg font-bold ${qualityScore > 80 ? 'text-green-500' : qualityScore > 60 ? 'text-orange-500' : 'text-red-500'}`}>
{qualityScore}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Score qualité
</div>
</div>
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className={`text-lg font-bold ${techDebtIndicator === 'Faible' ? 'text-green-500' : techDebtIndicator === 'Modéré' ? 'text-orange-500' : 'text-red-500'}`}>
{techDebtIndicator}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Dette technique
</div>
</div>
<div className="text-center p-3 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<div className="text-lg font-bold text-blue-500">
{Math.round((issueTypes.stories / (issueTypes.stories + issueTypes.bugs)) * 100)}%
</div>
<div className="text-xs text-[var(--muted-foreground)]">
Ratio features
</div>
</div>
</div>
{/* Indicateurs de qualité */}
<div className="mt-4 p-4 bg-[var(--card)] rounded-lg border border-[var(--border)]">
<h4 className="text-sm font-medium mb-2">Analyse qualité</h4>
<div className="space-y-2 text-sm">
{bugRatio > 25 && (
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<span></span>
<span>Ratio de bugs élevé ({bugRatio}%) - Attention à la dette technique</span>
</div>
)}
{bugRatio <= 15 && (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<span></span>
<span>Excellent ratio de bugs ({bugRatio}%) - Bonne qualité du code</span>
</div>
)}
{issueTypes.stories > issueTypes.bugs * 3 && (
<div className="flex items-center gap-2 text-blue-600 dark:text-blue-400">
<span>🚀</span>
<span>Focus positif sur les fonctionnalités - Bon équilibre produit</span>
</div>
)}
</div>
</div>
</div>
);
}