- Cleaned up theme architecture by consolidating CSS variables and removing redundant theme applications, ensuring a single source of truth for theming. - Implemented a dark mode override and improved color management using CSS variables for better customization. - Updated various components to utilize new color variables, enhancing maintainability and visual consistency across the application. - Added detailed tasks in TODO.md for future enhancements related to user preferences and color customization features.
212 lines
9.1 KiB
TypeScript
212 lines
9.1 KiB
TypeScript
'use client';
|
|
|
|
interface BackupStats {
|
|
date: string;
|
|
manual: number;
|
|
automatic: number;
|
|
total: number;
|
|
}
|
|
|
|
interface BackupTimelineChartProps {
|
|
stats?: BackupStats[];
|
|
className?: string;
|
|
}
|
|
|
|
export function BackupTimelineChart({ stats = [], className = '' }: BackupTimelineChartProps) {
|
|
// Protection contre les stats non-array
|
|
const safeStats = Array.isArray(stats) ? stats : [];
|
|
const error = safeStats.length === 0 ? 'Aucune donnée disponible' : null;
|
|
|
|
// Convertir les stats en map pour accès rapide
|
|
const statsMap = new Map(safeStats.map(s => [s.date, s]));
|
|
|
|
// Générer les 30 derniers jours
|
|
const days = Array.from({ length: 30 }, (_, i) => {
|
|
const date = new Date();
|
|
date.setDate(date.getDate() - (29 - i));
|
|
// Utiliser la date locale pour éviter les décalages UTC
|
|
const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
|
return localDate.toISOString().split('T')[0];
|
|
});
|
|
|
|
// Organiser en semaines (5 semaines de 6 jours + quelques jours)
|
|
const weeks = [];
|
|
for (let i = 0; i < days.length; i += 7) {
|
|
weeks.push(days.slice(i, i + 7));
|
|
}
|
|
|
|
|
|
|
|
const formatDateFull = (dateStr: string) => {
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString('fr-FR', {
|
|
weekday: 'long',
|
|
day: 'numeric',
|
|
month: 'long'
|
|
});
|
|
};
|
|
|
|
if (error) {
|
|
return (
|
|
<div className={`p-4 sm:p-6 ${className}`}>
|
|
<div className="text-gray-500 text-sm text-center py-8">
|
|
{error}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`p-4 sm:p-6 w-full ${className}`}>
|
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
|
💾 Activité de sauvegarde (30 derniers jours)
|
|
</h3>
|
|
|
|
{/* Vue en ligne avec indicateurs clairs */}
|
|
<div className="mb-6">
|
|
{/* En-têtes des jours */}
|
|
<div className="grid grid-cols-7 gap-1 mb-2">
|
|
{['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'].map(day => (
|
|
<div key={day} className="text-xs text-center text-gray-500 font-medium py-1">
|
|
{day}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Grille des jours avec indicateurs visuels */}
|
|
<div className="space-y-1">
|
|
{weeks.map((week, weekIndex) => (
|
|
<div key={weekIndex} className="grid grid-cols-7 gap-1">
|
|
{week.map((day) => {
|
|
const stat = statsMap.get(day) || { date: day, manual: 0, automatic: 0, total: 0 };
|
|
const hasManual = stat.manual > 0;
|
|
const hasAuto = stat.automatic > 0;
|
|
const dayNumber = new Date(day).getDate();
|
|
|
|
return (
|
|
<div key={day} className="group relative">
|
|
<div className={`
|
|
relative h-8 rounded border-2 transition-all duration-200 cursor-pointer flex items-center justify-center text-xs font-medium
|
|
${stat.total === 0
|
|
? 'border-[var(--border)] text-[var(--muted-foreground)]'
|
|
: 'border-transparent'
|
|
}
|
|
`}>
|
|
{/* Jour du mois */}
|
|
<span className={`relative z-10 ${stat.total > 0 ? 'text-white font-bold' : ''}`}>
|
|
{dayNumber}
|
|
</span>
|
|
|
|
{/* Fond selon le type */}
|
|
{stat.total > 0 && (
|
|
<div className={`
|
|
absolute inset-0 rounded
|
|
${hasManual && hasAuto
|
|
? 'bg-gradient-to-br from-blue-500 to-green-500'
|
|
: hasManual
|
|
? 'bg-blue-500'
|
|
: 'bg-green-500'
|
|
}
|
|
`}></div>
|
|
)}
|
|
|
|
{/* Indicateurs visuels pour l'intensité */}
|
|
{stat.total > 0 && stat.total > 1 && (
|
|
<div className="absolute -top-1 -right-1 bg-orange-500 text-white rounded-full w-4 h-4 flex items-center justify-center text-xs font-bold">
|
|
{stat.total > 9 ? '9+' : stat.total}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Tooltip détaillé */}
|
|
<div className="absolute bottom-full mb-2 left-1/2 transform -translate-x-1/2 bg-black text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-20">
|
|
<div className="font-semibold">{formatDateFull(day)}</div>
|
|
{stat.total > 0 ? (
|
|
<div className="mt-1 space-y-1">
|
|
{stat.manual > 0 && (
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-2 h-2 bg-blue-400 rounded-full"></div>
|
|
<span>Manuel: {stat.manual}</span>
|
|
</div>
|
|
)}
|
|
{stat.automatic > 0 && (
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-2 h-2 bg-green-400 rounded-full"></div>
|
|
<span>Auto: {stat.automatic}</span>
|
|
</div>
|
|
)}
|
|
<div className="font-semibold border-t border-gray-600 pt-1">
|
|
Total: {stat.total}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="text-gray-300 mt-1">Aucune sauvegarde</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Légende claire */}
|
|
<div className="mb-6 p-3 rounded-lg" style={{ backgroundColor: 'var(--card-hover)' }}>
|
|
<h4 className="text-sm font-medium mb-3 text-[var(--foreground)]">Légende</h4>
|
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-6 h-6 bg-blue-500 rounded flex items-center justify-center text-white text-xs font-bold">15</div>
|
|
<span className="text-[var(--foreground)]">Manuel seul</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-6 h-6 bg-green-500 rounded flex items-center justify-center text-white text-xs font-bold">15</div>
|
|
<span className="text-[var(--foreground)]">Auto seul</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-6 h-6 bg-gradient-to-br from-blue-500 to-green-500 rounded flex items-center justify-center text-white text-xs font-bold">15</div>
|
|
<span className="text-[var(--foreground)]">Manuel + Auto</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-6 h-6 border-2 rounded flex items-center justify-center text-xs" style={{ backgroundColor: 'var(--gray-light)', borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}>15</div>
|
|
<span className="text-[var(--foreground)]">Aucune</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-3 text-xs text-[var(--muted-foreground)]">
|
|
💡 Le badge orange indique le nombre total quand > 1
|
|
</div>
|
|
</div>
|
|
|
|
{/* Statistiques résumées */}
|
|
<div className="grid grid-cols-3 gap-3 text-center">
|
|
<div className="p-3 rounded-lg" style={{ backgroundColor: 'color-mix(in srgb, var(--blue) 10%, transparent)' }}>
|
|
<div className="text-xl font-bold" style={{ color: 'var(--blue)' }}>
|
|
{safeStats.reduce((sum, s) => sum + s.manual, 0)}
|
|
</div>
|
|
<div className="text-xs font-medium" style={{ color: 'var(--blue)' }}>Manuelles</div>
|
|
</div>
|
|
<div className="p-3 rounded-lg" style={{ backgroundColor: 'color-mix(in srgb, var(--green) 10%, transparent)' }}>
|
|
<div className="text-xl font-bold" style={{ color: 'var(--green)' }}>
|
|
{safeStats.reduce((sum, s) => sum + s.automatic, 0)}
|
|
</div>
|
|
<div className="text-xs font-medium" style={{ color: 'var(--green)' }}>Automatiques</div>
|
|
</div>
|
|
<div className="p-3 rounded-lg" style={{ backgroundColor: 'color-mix(in srgb, var(--purple) 10%, transparent)' }}>
|
|
<div className="text-xl font-bold" style={{ color: 'var(--purple)' }}>
|
|
{safeStats.reduce((sum, s) => sum + s.total, 0)}
|
|
</div>
|
|
<div className="text-xs font-medium" style={{ color: 'var(--purple)' }}>Total</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|