125 lines
5.6 KiB
TypeScript
125 lines
5.6 KiB
TypeScript
'use client';
|
|
|
|
import { DailyMetrics } from '@/services/metrics';
|
|
import { parseDate, isToday } from '@/lib/date-utils';
|
|
|
|
interface WeeklyActivityHeatmapProps {
|
|
data: DailyMetrics[];
|
|
className?: string;
|
|
}
|
|
|
|
export function WeeklyActivityHeatmap({ data, className }: WeeklyActivityHeatmapProps) {
|
|
// Calculer l'intensité max pour la normalisation
|
|
const maxActivity = Math.max(...data.map(day => day.completed + day.newTasks));
|
|
|
|
// Obtenir l'intensité relative (0-1)
|
|
const getIntensity = (day: DailyMetrics) => {
|
|
const activity = day.completed + day.newTasks;
|
|
return maxActivity > 0 ? activity / maxActivity : 0;
|
|
};
|
|
|
|
// Obtenir la couleur basée sur l'intensité
|
|
const getColorClass = (intensity: number) => {
|
|
if (intensity === 0) return 'bg-gray-100 dark:bg-gray-800';
|
|
if (intensity < 0.2) return 'bg-green-100 dark:bg-green-900/30';
|
|
if (intensity < 0.4) return 'bg-green-200 dark:bg-green-800/50';
|
|
if (intensity < 0.6) return 'bg-green-300 dark:bg-green-700/70';
|
|
if (intensity < 0.8) return 'bg-green-400 dark:bg-green-600/80';
|
|
return 'bg-green-500 dark:bg-green-500';
|
|
};
|
|
|
|
return (
|
|
<div className={className}>
|
|
<div className="space-y-4">
|
|
{/* Titre */}
|
|
<div className="text-center">
|
|
<h4 className="text-sm font-medium text-[var(--foreground)] mb-2">
|
|
Heatmap d'activité hebdomadaire
|
|
</h4>
|
|
<p className="text-xs text-[var(--muted-foreground)]">
|
|
Intensité basée sur les tâches complétées + nouvelles tâches
|
|
</p>
|
|
</div>
|
|
|
|
{/* Heatmap */}
|
|
<div className="flex justify-center">
|
|
<div className="flex gap-1">
|
|
{data.map((day, index) => {
|
|
const intensity = getIntensity(day);
|
|
const colorClass = getColorClass(intensity);
|
|
const totalActivity = day.completed + day.newTasks;
|
|
|
|
return (
|
|
<div key={index} className="text-center">
|
|
{/* Carré de couleur */}
|
|
<div
|
|
className={`w-8 h-8 rounded ${colorClass} border border-[var(--border)] flex items-center justify-center transition-all hover:scale-110 cursor-help group relative`}
|
|
title={`${day.dayName}: ${totalActivity} activités (${day.completed} complétées, ${day.newTasks} créées)`}
|
|
>
|
|
{/* Tooltip au hover */}
|
|
<div className="opacity-0 group-hover:opacity-100 absolute bottom-10 left-1/2 transform -translate-x-1/2 bg-[var(--card)] border border-[var(--border)] rounded p-2 text-xs whitespace-nowrap z-10 shadow-lg transition-opacity">
|
|
<div className="font-medium">{day.dayName}</div>
|
|
<div className="text-[var(--muted-foreground)]">
|
|
{day.completed} terminées, {day.newTasks} créées
|
|
</div>
|
|
<div className="text-[var(--muted-foreground)]">
|
|
Taux: {day.completionRate.toFixed(1)}%
|
|
</div>
|
|
</div>
|
|
|
|
{/* Indicator si jour actuel */}
|
|
{isToday(parseDate(day.date)) && (
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Label du jour */}
|
|
<div className="text-xs text-[var(--muted-foreground)] mt-1">
|
|
{day.dayName.substring(0, 3)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Légende */}
|
|
<div className="flex items-center justify-center gap-2 text-xs text-[var(--muted-foreground)]">
|
|
<span>Moins</span>
|
|
<div className="flex gap-1">
|
|
<div className="w-3 h-3 bg-gray-100 dark:bg-gray-800 border border-[var(--border)] rounded"></div>
|
|
<div className="w-3 h-3 bg-green-100 dark:bg-green-900/30 border border-[var(--border)] rounded"></div>
|
|
<div className="w-3 h-3 bg-green-200 dark:bg-green-800/50 border border-[var(--border)] rounded"></div>
|
|
<div className="w-3 h-3 bg-green-300 dark:bg-green-700/70 border border-[var(--border)] rounded"></div>
|
|
<div className="w-3 h-3 bg-green-400 dark:bg-green-600/80 border border-[var(--border)] rounded"></div>
|
|
<div className="w-3 h-3 bg-green-500 dark:bg-green-500 border border-[var(--border)] rounded"></div>
|
|
</div>
|
|
<span>Plus</span>
|
|
</div>
|
|
|
|
{/* Stats rapides */}
|
|
<div className="grid grid-cols-3 gap-2 text-center text-xs">
|
|
<div className="p-2 bg-[var(--card)] rounded border">
|
|
<div className="font-medium text-green-600">
|
|
{data.reduce((sum, day) => sum + day.completed, 0)}
|
|
</div>
|
|
<div className="text-[var(--muted-foreground)]">Terminées</div>
|
|
</div>
|
|
<div className="p-2 bg-[var(--card)] rounded border">
|
|
<div className="font-medium text-blue-600">
|
|
{data.reduce((sum, day) => sum + day.newTasks, 0)}
|
|
</div>
|
|
<div className="text-[var(--muted-foreground)]">Créées</div>
|
|
</div>
|
|
<div className="p-2 bg-[var(--card)] rounded border">
|
|
<div className="font-medium text-purple-600">
|
|
{(data.reduce((sum, day) => sum + day.completionRate, 0) / data.length).toFixed(1)}%
|
|
</div>
|
|
<div className="text-[var(--muted-foreground)]">Taux moyen</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|