- 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.
107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
'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>
|
||
);
|
||
}
|