Files
towercontrol/components/jira/VelocityChart.tsx
Julien Froidefond 3dd6e0fd1c feat: enhance Jira dashboard with advanced filtering and sprint details
- Updated `TODO.md` to mark several tasks as complete, including anomaly detection and sprint detail integration.
- Enhanced `VelocityChart` to support click events for sprint details, improving user interaction.
- Added `FilterBar` and `AnomalyDetectionPanel` components to `JiraDashboardPageClient` for advanced filtering capabilities.
- Implemented state management for selected sprints and modal display for detailed sprint information.
- Introduced new types for advanced filtering in `types.ts`, expanding the filtering options available in the analytics.
2025-09-19 10:13:48 +02:00

107 lines
3.8 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 { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell } from 'recharts';
import { SprintVelocity } from '@/lib/types';
interface VelocityChartProps {
sprintHistory: SprintVelocity[];
className?: string;
onSprintClick?: (sprint: SprintVelocity) => void;
}
export function VelocityChart({ sprintHistory, className, onSprintClick }: VelocityChartProps) {
// Préparer les données pour le graphique
const chartData = sprintHistory.map(sprint => ({
name: sprint.sprintName,
completed: sprint.completedPoints,
planned: sprint.plannedPoints,
completionRate: sprint.completionRate,
sprintData: sprint // Garder la référence au sprint original
}));
const handleBarClick = (data: unknown) => {
if (onSprintClick && data && typeof data === 'object' && data !== null && 'sprintData' in data) {
const typedData = data as { sprintData: SprintVelocity };
onSprintClick(typedData.sprintData);
}
};
const CustomTooltip = ({ active, payload, label }: {
active?: boolean;
payload?: Array<{ payload: { completed: number; planned: number; completionRate: number } }>;
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">{label}</p>
<div className="space-y-1 text-xs">
<div className="flex justify-between gap-4">
<span>Complétés:</span>
<span className="font-mono text-green-500">{data.completed} pts</span>
</div>
<div className="flex justify-between gap-4">
<span>Planifiés:</span>
<span className="font-mono text-blue-500">{data.planned} pts</span>
</div>
<div className="flex justify-between gap-4">
<span>Taux de réussite:</span>
<span className="font-mono text-orange-500">{data.completionRate}%</span>
</div>
{onSprintClick && (
<div className="border-t border-[var(--border)] pt-2 mt-2">
<p className="text-xs text-[var(--muted-foreground)]">
🖱 Cliquez pour voir les détails
</p>
</div>
)}
</div>
</div>
);
}
return null;
};
return (
<div className={className}>
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={chartData}
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
<XAxis
dataKey="name"
stroke="var(--muted-foreground)"
fontSize={12}
/>
<YAxis
stroke="var(--muted-foreground)"
fontSize={12}
/>
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="planned" fill="var(--muted)" opacity={0.6} radius={[4, 4, 0, 0]} />
<Bar
dataKey="completed"
fill="hsl(142, 76%, 36%)"
radius={[4, 4, 0, 0]}
style={{ cursor: onSprintClick ? 'pointer' : 'default' }}
onClick={handleBarClick}
>
{chartData.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={entry.completionRate >= 80 ? 'hsl(142, 76%, 36%)' :
entry.completionRate >= 60 ? 'hsl(45, 93%, 47%)' :
'hsl(0, 84%, 60%)'}
style={{ cursor: onSprintClick ? 'pointer' : 'default' }}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
);
}