'use client' import { useState } from 'react' import { cn } from '@/lib/utils' interface WeekData { week: string rate: number total: number } interface RetentionCurveProps { data: WeekData[] className?: string } export function RetentionCurve({ data, className }: RetentionCurveProps) { const [hoveredIndex, setHoveredIndex] = useState(null) // Filtrer les semaines avec données const weeks = data.filter((w) => w.total > 0) if (weeks.length === 0) { return (

Aucune donnée disponible

) } // Dimensions SVG const width = 500 const height = 120 const padding = { top: 15, right: 30, left: 30, bottom: 25 } const chartWidth = width - padding.left - padding.right const chartHeight = height - padding.top - padding.bottom // Calculer les coordonnées des points const points = weeks.map((w, i) => { // Si 1 seul point, on le centre const x = weeks.length === 1 ? padding.left + chartWidth / 2 : padding.left + (i * chartWidth) / (weeks.length - 1) // Taux entre 0 et 100 const val = Math.max(0, Math.min(100, w.rate)) const y = padding.top + chartHeight * (1 - val / 100) return { x, y, rate: w.rate, week: w.week, total: w.total } }) // Créer le path SVG pour la ligne let linePath = '' let areaPath = '' if (points.length === 1) { // Une seule semaine : ligne pointillée horizontale + point central const yVal = points[0].y linePath = `M ${padding.left} ${yVal} L ${width - padding.right} ${yVal}` } else { // Plusieurs semaines : tracer la courbe ligne linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ') // Area fermée pour le dégradé sous la courbe areaPath = `${linePath} L ${points[points.length - 1].x} ${height - padding.bottom} L ${points[0].x} ${height - padding.bottom} Z` } const activePoint = hoveredIndex !== null ? points[hoveredIndex] : null return (
{/* SVG Container */} {/* Dégradé sous la courbe */} {/* Lueur d'accent */} {/* Lignes de guide d'arrière-plan (Grid) */} {/* Tracé Dégradé Rempli (uniquement si plusieurs points) */} {points.length > 1 && areaPath && ( )} {/* Ligne principale de la courbe */} {/* Guide vertical au survol */} {activePoint && ( )} {/* Points et zones interactives */} {points.map((p, idx) => { const isHovered = hoveredIndex === idx return ( {/* Cercle extérieur (ombre / lueur d'accent si survol) */} {/* Cercle intérieur (le point lui-même) */} {/* Zone invisible large pour faciliter le survol souris/toucher */} setHoveredIndex(idx)} onMouseLeave={() => setHoveredIndex(null)} /> ); })} {/* Labels des semaines sous le graphe (X-Axis) */} {points.map((p, idx) => { const label = p.week.slice(5) // MM-DD const isHovered = hoveredIndex === idx // Pour éviter la surcharge visuelle, on n'affiche pas tous les labels si trop nombreux, sauf si survolé const shouldShowLabel = points.length <= 8 || idx === 0 || idx === points.length - 1 || isHovered if (!shouldShowLabel) return null return ( {label} ) })} {/* Floating Info Tooltip */}
{activePoint ? (
{activePoint.rate}% de succès ({activePoint.total} révs) · sem. {activePoint.week.slice(5)}
) : ( Survolez un point pour les détails )}
) }