'use client' import { useMemo, useState } from 'react' import { cn } from '@/lib/utils' import { useLanguage } from '@/lib/i18n' interface HeatmapDay { date: string count: number } interface RevisionHeatmapProps { data: HeatmapDay[] className?: string } function intensityClass(count: number, max: number): string { if (count <= 0) return 'bg-black/[0.06] dark:bg-white/[0.08]' const ratio = count / Math.max(max, 1) if (ratio >= 0.75) return 'bg-brand-accent' if (ratio >= 0.5) return 'bg-brand-accent/70' if (ratio >= 0.25) return 'bg-brand-accent/40' return 'bg-brand-accent/20' } function resolveDateLocale(langCode: string): string { if (langCode === 'fa') return 'fa-IR-u-ca-persian-nu-arabext' return langCode } export function RevisionHeatmap({ data, className }: RevisionHeatmapProps) { const { t, language } = useLanguage() const [hovered, setHovered] = useState<{ label: string; count: number; date: string } | null>(null) const [selected, setSelected] = useState<{ label: string; count: number; date: string } | null>(null) const dateLocale = resolveDateLocale(language ?? 'en') const { cells, maxCount, totalReviews, monthLabels } = useMemo(() => { const map = new Map(data.map((d) => [d.date, d.count])) const now = new Date() // todayUTC est le début de la journée courante à minuit UTC const todayUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())) const cells: { date: string; count: number; label: string }[] = [] const monthLabels: { index: number; label: string }[] = [] let lastMonth = -1 for (let i = 89; i >= 0; i--) { const d = new Date(todayUTC) d.setUTCDate(d.getUTCDate() - i) const key = d.toISOString().slice(0, 10) const count = map.get(key) || 0 const month = d.getUTCMonth() if (month !== lastMonth) { monthLabels.push({ index: 89 - i, label: d.toLocaleDateString(dateLocale, { month: 'short', timeZone: 'UTC' }), }) lastMonth = month } cells.push({ date: key, count, label: d.toLocaleDateString(dateLocale, { weekday: 'long', day: 'numeric', month: 'long', timeZone: 'UTC' }), }) } const maxCount = Math.max(1, ...cells.map((c) => c.count)) const totalReviews = cells.reduce((s, c) => s + c.count, 0) return { cells, maxCount, totalReviews, monthLabels } }, [data, dateLocale]) const pct = (index: number) => `${(index / 90) * 100}%` const activeInfo = hovered || selected return (
{/* En-tête */}

{t('flashcards.heatmapTitle')}

{totalReviews > 0 ? `${totalReviews} révisions · 90 jours` : t('flashcards.heatmapLast90')}
{/* Labels de mois au-dessus de la grille */}
{monthLabels.map((m) => ( {m.label} ))}
{/* Grille pleine largeur */}
{cells.map((cell) => { const isHovered = hovered?.date === cell.date const isSelected = selected?.date === cell.date const reviewText = cell.count > 0 ? `${cell.count} révision${cell.count > 1 ? 's' : ''}` : 'Aucune révision' return (
{/* Info au survol / clic */}
{activeInfo ? (

{activeInfo.count > 0 ? `${activeInfo.count} révision${activeInfo.count > 1 ? 's' : ''}` : 'Aucune révision'} · {activeInfo.label} {selected?.date === activeInfo.date && !hovered && ( sélectionné )}

) : (

Survolez ou cliquez sur un carré pour voir le détail

)} {selected && ( )}
{/* Légende */}
Moins
{['bg-black/[0.06] dark:bg-white/[0.08]', 'bg-brand-accent/20', 'bg-brand-accent/40', 'bg-brand-accent/70', 'bg-brand-accent'].map((cls, i) => (
))}
Plus
) }