feat: refactor theme management and enhance color customization

- 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.
This commit is contained in:
Julien Froidefond
2025-09-28 10:14:25 +02:00
parent 97770917c1
commit b5d6967fcd
21 changed files with 404 additions and 187 deletions

View File

@@ -1,25 +1,7 @@
@import "tailwindcss";
:root {
/* Dark theme (default) */
--background: #1e293b; /* slate-800 - encore plus clair */
--foreground: #f1f5f9; /* slate-100 */
--card: #334155; /* slate-700 - beaucoup plus clair pour contraste fort */
--card-hover: #475569; /* slate-600 */
--card-column: #0f172a; /* slate-900 - plus foncé que les cartes */
--border: #64748b; /* slate-500 - encore plus clair */
--input: #334155; /* slate-700 - plus clair */
--primary: #06b6d4; /* cyan-500 */
--primary-foreground: #f1f5f9; /* slate-100 */
--muted: #64748b; /* slate-500 */
--muted-foreground: #94a3b8; /* slate-400 */
--accent: #f59e0b; /* amber-500 */
--destructive: #ef4444; /* red-500 */
--success: #10b981; /* emerald-500 */
}
.light {
/* Light theme */
/* Valeurs par défaut (Light theme) */
--background: #f1f5f9; /* slate-100 */
--foreground: #0f172a; /* slate-900 */
--card: #ffffff; /* white */
@@ -34,6 +16,36 @@
--accent: #d97706; /* amber-600 */
--destructive: #dc2626; /* red-600 */
--success: #059669; /* emerald-600 */
--purple: #8b5cf6; /* purple-500 */
--yellow: #eab308; /* yellow-500 */
--green: #059669; /* emerald-600 */
--blue: #2563eb; /* blue-600 */
--gray: #6b7280; /* gray-500 */
--gray-light: #e5e7eb; /* gray-200 */
}
.dark {
/* Dark theme override */
--background: #1e293b; /* slate-800 - encore plus clair */
--foreground: #f1f5f9; /* slate-100 */
--card: #334155; /* slate-700 - beaucoup plus clair pour contraste fort */
--card-hover: #475569; /* slate-600 */
--card-column: #0f172a; /* slate-900 - plus foncé que les cartes */
--border: #64748b; /* slate-500 - encore plus clair */
--input: #334155; /* slate-700 - plus clair */
--primary: #06b6d4; /* cyan-500 */
--primary-foreground: #f1f5f9; /* slate-100 */
--muted: #64748b; /* slate-500 */
--muted-foreground: #94a3b8; /* slate-400 */
--accent: #f59e0b; /* amber-500 */
--destructive: #ef4444; /* red-500 */
--success: #10b981; /* emerald-500 */
--purple: #8b5cf6; /* purple-500 */
--yellow: #eab308; /* yellow-500 */
--green: #10b981; /* emerald-500 */
--blue: #3b82f6; /* blue-500 */
--gray: #9ca3af; /* gray-400 */
--gray-light: #374151; /* gray-700 */
}
@theme inline {
@@ -100,16 +112,16 @@ body {
.outline-card-purple {
@apply p-2.5 rounded-lg border transition-all hover:shadow-sm hover:scale-[1.01];
color: #8b5cf6; /* purple-500 */
background-color: color-mix(in srgb, #8b5cf6 8%, transparent);
border-color: color-mix(in srgb, #8b5cf6 25%, var(--border));
color: var(--purple);
background-color: color-mix(in srgb, var(--purple) 8%, transparent);
border-color: color-mix(in srgb, var(--purple) 25%, var(--border));
}
.outline-card-yellow {
@apply p-2.5 rounded-lg border transition-all hover:shadow-sm hover:scale-[1.01];
color: #eab308; /* yellow-500 */
background-color: color-mix(in srgb, #eab308 8%, transparent);
border-color: color-mix(in srgb, #eab308 25%, var(--border));
color: var(--yellow);
background-color: color-mix(in srgb, var(--yellow) 8%, transparent);
border-color: color-mix(in srgb, var(--yellow) 25%, var(--border));
}
.outline-card-gray {
@@ -143,9 +155,9 @@ body {
.outline-metric-purple {
@apply text-center p-4 rounded-lg border transition-all hover:shadow-sm hover:scale-[1.01];
color: #8b5cf6; /* purple-500 */
background-color: color-mix(in srgb, #8b5cf6 8%, transparent);
border-color: color-mix(in srgb, #8b5cf6 25%, var(--border));
color: var(--purple);
background-color: color-mix(in srgb, var(--purple) 8%, transparent);
border-color: color-mix(in srgb, var(--purple) 25%, var(--border));
}
.outline-metric-gray {
@@ -157,8 +169,8 @@ body {
/* Animations tech */
@keyframes glow {
0%, 100% { box-shadow: 0 0 5px rgba(6, 182, 212, 0.3); }
50% { box-shadow: 0 0 20px rgba(6, 182, 212, 0.6); }
0%, 100% { box-shadow: 0 0 5px var(--primary); }
50% { box-shadow: 0 0 20px var(--primary); }
}
.animate-glow {

View File

@@ -262,7 +262,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{error && (
<Card className="mb-6 border-red-500/20 bg-red-500/10">
<CardContent className="p-4">
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span></span>
<span>{error}</span>
</div>
@@ -273,7 +273,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }:
{exportError && (
<Card className="mb-6 border-orange-500/20 bg-orange-500/10">
<CardContent className="p-4">
<div className="flex items-center gap-2 text-orange-600 dark:text-orange-400">
<div className="flex items-center gap-2" style={{ color: 'var(--accent)' }}>
<span></span>
<span>Erreur d&apos;export: {exportError}</span>
</div>

View File

@@ -30,7 +30,7 @@ export default async function RootLayout({
const initialPreferences = await userPreferencesService.getAllPreferences();
return (
<html lang="en" className={initialPreferences.viewPreferences.theme}>
<html lang="fr">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>

View File

@@ -88,7 +88,7 @@ export function BackupTimelineChart({ stats = [], className = '' }: BackupTimeli
<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
? 'bg-gray-50 dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-400'
? 'border-[var(--border)] text-[var(--muted-foreground)]'
: 'border-transparent'
}
`}>
@@ -152,58 +152,58 @@ export function BackupTimelineChart({ stats = [], className = '' }: BackupTimeli
</div>
{/* Légende claire */}
<div className="mb-6 p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
<h4 className="text-sm font-medium mb-3 text-gray-700 dark:text-gray-300">Légende</h4>
<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-gray-700 dark:text-gray-300">Manuel seul</span>
<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-gray-700 dark:text-gray-300">Auto seul</span>
<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-gray-700 dark:text-gray-300">Manuel + Auto</span>
<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 bg-gray-200 dark:bg-gray-700 border-2 border-gray-300 dark:border-gray-600 rounded flex items-center justify-center text-gray-500 text-xs">15</div>
<span className="text-gray-700 dark:text-gray-300">Aucune</span>
<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-gray-600 dark:text-gray-400">
<div className="mt-3 text-xs text-[var(--muted-foreground)]">
💡 Le badge orange indique le nombre total quand &gt; 1
</div>
</div>
{/* Statistiques résumées */}
<div className="grid grid-cols-3 gap-3 text-center">
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<div className="text-xl font-bold text-blue-600">
<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 text-blue-600 font-medium">Manuelles</div>
<div className="text-xs font-medium" style={{ color: 'var(--blue)' }}>Manuelles</div>
</div>
<div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-lg">
<div className="text-xl font-bold text-green-600">
<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 text-green-600 font-medium">Automatiques</div>
<div className="text-xs font-medium" style={{ color: 'var(--green)' }}>Automatiques</div>
</div>
<div className="p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<div className="text-xl font-bold text-purple-600">
<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 text-purple-600 font-medium">Total</div>
<div className="text-xs font-medium" style={{ color: 'var(--purple)' }}>Total</div>
</div>
</div>
</div>

View File

@@ -19,13 +19,13 @@ export function WeeklyActivityHeatmap({ data, className }: WeeklyActivityHeatmap
};
// 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';
const getColorStyle = (intensity: number) => {
if (intensity === 0) return { backgroundColor: 'var(--gray-light)' };
if (intensity < 0.2) return { backgroundColor: 'color-mix(in srgb, var(--green) 20%, transparent)' };
if (intensity < 0.4) return { backgroundColor: 'color-mix(in srgb, var(--green) 40%, transparent)' };
if (intensity < 0.6) return { backgroundColor: 'color-mix(in srgb, var(--green) 60%, transparent)' };
if (intensity < 0.8) return { backgroundColor: 'color-mix(in srgb, var(--green) 80%, transparent)' };
return { backgroundColor: 'var(--green)' };
};
return (
@@ -46,14 +46,15 @@ export function WeeklyActivityHeatmap({ data, className }: WeeklyActivityHeatmap
<div className="flex gap-1">
{data.map((day, index) => {
const intensity = getIntensity(day);
const colorClass = getColorClass(intensity);
const colorStyle = getColorStyle(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`}
className="w-8 h-8 rounded border border-[var(--border)] flex items-center justify-center transition-all hover:scale-110 cursor-help group relative"
style={colorStyle}
title={`${day.dayName}: ${totalActivity} activités (${day.completed} complétées, ${day.newTasks} créées)`}
>
{/* Tooltip au hover */}
@@ -87,12 +88,12 @@ export function WeeklyActivityHeatmap({ data, className }: WeeklyActivityHeatmap
<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 className="w-3 h-3 border border-[var(--border)] rounded" style={{ backgroundColor: 'var(--gray-light)' }}></div>
<div className="w-3 h-3 border border-[var(--border)] rounded" style={{ backgroundColor: 'color-mix(in srgb, var(--green) 20%, transparent)' }}></div>
<div className="w-3 h-3 border border-[var(--border)] rounded" style={{ backgroundColor: 'color-mix(in srgb, var(--green) 40%, transparent)' }}></div>
<div className="w-3 h-3 border border-[var(--border)] rounded" style={{ backgroundColor: 'color-mix(in srgb, var(--green) 60%, transparent)' }}></div>
<div className="w-3 h-3 border border-[var(--border)] rounded" style={{ backgroundColor: 'color-mix(in srgb, var(--green) 80%, transparent)' }}></div>
<div className="w-3 h-3 border border-[var(--border)] rounded" style={{ backgroundColor: 'var(--green)' }}></div>
</div>
<span>Plus</span>
</div>

View File

@@ -27,7 +27,7 @@ export function CriticalDeadlinesCard({ overdue, critical, warning }: CriticalDe
return {
icon: '🔴',
text: task.daysRemaining === -1 ? 'En retard de 1 jour' : `En retard de ${Math.abs(task.daysRemaining)} jours`,
style: 'text-red-700 bg-red-50/40 border-red-200/60 dark:bg-red-950/20 dark:border-red-800/40 dark:text-red-300'
style: 'border-[var(--destructive)]/60'
};
} else if (task.urgencyLevel === 'critical') {
return {
@@ -35,13 +35,13 @@ export function CriticalDeadlinesCard({ overdue, critical, warning }: CriticalDe
text: task.daysRemaining === 0 ? 'Échéance aujourd\'hui' :
task.daysRemaining === 1 ? 'Échéance demain' :
`Dans ${task.daysRemaining} jours`,
style: 'text-orange-700 bg-orange-50/40 border-orange-200/60 dark:bg-orange-950/20 dark:border-orange-800/40 dark:text-orange-300'
style: 'border-[var(--accent)]/60'
};
} else {
return {
icon: '🟡',
text: `Dans ${task.daysRemaining} jours`,
style: 'text-yellow-700 bg-yellow-50/40 border-yellow-200/60 dark:bg-yellow-950/20 dark:border-yellow-800/40 dark:text-yellow-300'
style: 'border-[var(--yellow)]/60'
};
}
};
@@ -71,7 +71,7 @@ export function CriticalDeadlinesCard({ overdue, critical, warning }: CriticalDe
<h3 className="text-lg font-semibold mb-4">Tâches Urgentes</h3>
<div className="text-center py-8">
<div className="text-4xl mb-2">🎉</div>
<h4 className="text-lg font-medium text-green-600 dark:text-green-400 mb-2">Excellent !</h4>
<h4 className="text-lg font-medium mb-2" style={{ color: 'var(--green)' }}>Excellent !</h4>
<p className="text-sm text-[var(--muted-foreground)]">
Aucune tâche urgente ou critique
</p>
@@ -89,7 +89,7 @@ export function CriticalDeadlinesCard({ overdue, critical, warning }: CriticalDe
</div>
</div>
<div className="space-y-2 max-h-40 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 dark:scrollbar-thumb-gray-600 scrollbar-track-transparent pr-2">
<div className="space-y-2 max-h-40 overflow-y-auto scrollbar-thin scrollbar-track-transparent pr-2" style={{ scrollbarColor: 'var(--muted) transparent' }}>
{urgentTasks.map((task) => {
const urgencyStyle = getUrgencyStyle(task);
@@ -157,17 +157,17 @@ export function CriticalDeadlinesCard({ overdue, critical, warning }: CriticalDe
<div className="pt-3 border-t border-[var(--border)] mt-4">
<div className="flex flex-wrap gap-3 text-xs text-[var(--muted-foreground)] justify-center">
{overdue.length > 0 && (
<span className="text-red-600/80 dark:text-red-400/80 font-medium">
<span className="font-medium" style={{ color: 'var(--destructive)' }}>
{overdue.length} en retard
</span>
)}
{critical.length > 0 && (
<span className="text-orange-600/80 dark:text-orange-400/80 font-medium">
<span className="font-medium" style={{ color: 'var(--accent)' }}>
{critical.length} critique{critical.length > 1 ? 's' : ''}
</span>
)}
{warning.length > 0 && (
<span className="text-yellow-600/80 dark:text-yellow-400/80 font-medium">
<span className="font-medium" style={{ color: 'var(--yellow)' }}>
{warning.length} attention
</span>
)}

View File

@@ -22,21 +22,21 @@ export function DeadlineRiskCard({ metrics }: DeadlineRiskCardProps) {
const getRiskColor = (level: string) => {
switch (level) {
case 'critical': return 'text-red-600 dark:text-red-400';
case 'high': return 'text-orange-600 dark:text-orange-400';
case 'medium': return 'text-yellow-600 dark:text-yellow-400';
case 'low': return 'text-green-600 dark:text-green-400';
default: return 'text-gray-600 dark:text-gray-400';
case 'critical': return { color: 'var(--destructive)' };
case 'high': return { color: 'var(--accent)' };
case 'medium': return { color: 'var(--yellow)' };
case 'low': return { color: 'var(--green)' };
default: return { color: 'var(--muted-foreground)' };
}
};
const getRiskBgColor = (level: string) => {
switch (level) {
case 'critical': return 'bg-red-50/30 border-red-200/50 dark:bg-red-950/20 dark:border-red-800/30';
case 'high': return 'bg-orange-50/30 border-orange-200/50 dark:bg-orange-950/20 dark:border-orange-800/30';
case 'medium': return 'bg-yellow-50/30 border-yellow-200/50 dark:bg-yellow-950/20 dark:border-yellow-800/30';
case 'low': return 'bg-green-50/30 border-green-200/50 dark:bg-green-950/20 dark:border-green-800/30';
default: return 'bg-gray-50/30 border-gray-200/50 dark:bg-gray-950/20 dark:border-gray-800/30';
case 'critical': return { backgroundColor: 'color-mix(in srgb, var(--destructive) 10%, transparent)', borderColor: 'color-mix(in srgb, var(--destructive) 30%, var(--border))' };
case 'high': return { backgroundColor: 'color-mix(in srgb, var(--accent) 10%, transparent)', borderColor: 'color-mix(in srgb, var(--accent) 30%, var(--border))' };
case 'medium': return { backgroundColor: 'color-mix(in srgb, var(--yellow) 10%, transparent)', borderColor: 'color-mix(in srgb, var(--yellow) 30%, var(--border))' };
case 'low': return { backgroundColor: 'color-mix(in srgb, var(--green) 10%, transparent)', borderColor: 'color-mix(in srgb, var(--green) 30%, var(--border))' };
default: return { backgroundColor: 'color-mix(in srgb, var(--muted) 10%, transparent)', borderColor: 'color-mix(in srgb, var(--muted) 30%, var(--border))' };
}
};
@@ -47,7 +47,7 @@ export function DeadlineRiskCard({ metrics }: DeadlineRiskCardProps) {
<span className="text-2xl">{getRiskIcon(riskAnalysis.riskLevel)}</span>
<h3 className="text-lg font-semibold">Niveau de Risque</h3>
</div>
<div className={`text-3xl font-bold ${getRiskColor(riskAnalysis.riskLevel)}`}>
<div className="text-3xl font-bold" style={getRiskColor(riskAnalysis.riskLevel)}>
{riskAnalysis.riskScore}
</div>
</div>
@@ -69,11 +69,11 @@ export function DeadlineRiskCard({ metrics }: DeadlineRiskCardProps) {
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="flex justify-between">
<span className="text-[var(--muted-foreground)]">En retard:</span>
<span className="font-medium text-red-600/80 dark:text-red-400/80">{metrics.summary.overdueCount}</span>
<span className="font-medium" style={{ color: 'var(--destructive)' }}>{metrics.summary.overdueCount}</span>
</div>
<div className="flex justify-between">
<span className="text-[var(--muted-foreground)]">Critique:</span>
<span className="font-medium text-orange-600/80 dark:text-orange-400/80">{metrics.summary.criticalCount}</span>
<span className="font-medium" style={{ color: 'var(--accent)' }}>{metrics.summary.criticalCount}</span>
</div>
</div>

View File

@@ -15,29 +15,29 @@ export function DeadlineSummaryCard({ metrics }: DeadlineSummaryCardProps) {
label: 'En retard',
count: summary.overdueCount,
icon: '⏰',
color: 'text-red-600 dark:text-red-400',
bgColor: 'bg-red-100/50 dark:bg-red-900/30'
color: 'var(--destructive)',
bgColor: 'color-mix(in srgb, var(--destructive) 10%, transparent)'
},
{
label: 'Critique (0-2j)',
count: summary.criticalCount,
icon: '🚨',
color: 'text-orange-600 dark:text-orange-400',
bgColor: 'bg-orange-100/50 dark:bg-orange-900/30'
color: 'var(--accent)',
bgColor: 'color-mix(in srgb, var(--accent) 10%, transparent)'
},
{
label: 'Attention (3-7j)',
count: summary.warningCount,
icon: '⚠️',
color: 'text-yellow-600 dark:text-yellow-400',
bgColor: 'bg-yellow-100/50 dark:bg-yellow-900/30'
color: 'var(--yellow)',
bgColor: 'color-mix(in srgb, var(--yellow) 10%, transparent)'
},
{
label: 'À venir (8-14j)',
count: summary.upcomingCount,
icon: '📅',
color: 'text-blue-600 dark:text-blue-400',
bgColor: 'bg-blue-100/50 dark:bg-blue-900/30'
color: 'var(--blue)',
bgColor: 'color-mix(in srgb, var(--blue) 10%, transparent)'
}
];
@@ -54,12 +54,12 @@ export function DeadlineSummaryCard({ metrics }: DeadlineSummaryCardProps) {
{summaryItems.map((item, index) => (
<div key={index} className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`w-8 h-8 ${item.bgColor} rounded-full flex items-center justify-center text-sm`}>
<div className="w-8 h-8 rounded-full flex items-center justify-center text-sm" style={{ backgroundColor: item.bgColor }}>
{item.icon}
</div>
<span className="text-sm font-medium">{item.label}</span>
</div>
<div className={`text-lg font-bold ${item.color}`}>
<div className="text-lg font-bold" style={{ color: item.color }}>
{item.count}
</div>
</div>
@@ -73,7 +73,7 @@ export function DeadlineSummaryCard({ metrics }: DeadlineSummaryCardProps) {
{summary.totalWithDeadlines - summary.overdueCount - summary.criticalCount}/{summary.totalWithDeadlines}
</span>
</div>
<div className="w-full bg-gray-200/50 dark:bg-gray-700/50 rounded-full h-2 mt-2">
<div className="w-full rounded-full h-2 mt-2" style={{ backgroundColor: 'var(--gray-light)' }}>
<div
className="bg-green-500/80 h-2 rounded-full transition-all duration-300"
style={{

View File

@@ -131,19 +131,19 @@ export function BurndownChart({ sprintHistory, className }: BurndownChartProps)
{/* Légende visuelle */}
<div className="mb-4 flex justify-center gap-6 text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-0.5 bg-green-600 dark:bg-green-500 border-dashed border-t-2 border-green-600 dark:border-green-500"></div>
<span className="text-green-600 dark:text-green-500">Idéal</span>
<div className="w-4 h-0.5 border-dashed border-t-2" style={{ backgroundColor: 'var(--green)', borderColor: 'var(--green)' }}></div>
<span style={{ color: 'var(--green)' }}>Idéal</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-0.5 bg-blue-600 dark:bg-blue-500"></div>
<span className="text-blue-600 dark:text-blue-500">Réel</span>
<div className="w-4 h-0.5" style={{ backgroundColor: 'var(--blue)' }}></div>
<span style={{ color: 'var(--blue)' }}>Réel</span>
</div>
</div>
{/* Métriques */}
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-sm font-medium text-green-500">
<div className="text-sm font-medium" style={{ color: 'var(--green)' }}>
{currentSprint.plannedPoints}
</div>
<div className="text-xs text-[var(--muted-foreground)]">
@@ -151,7 +151,7 @@ export function BurndownChart({ sprintHistory, className }: BurndownChartProps)
</div>
</div>
<div>
<div className="text-sm font-medium text-blue-500">
<div className="text-sm font-medium" style={{ color: 'var(--blue)' }}>
{currentSprint.completedPoints}
</div>
<div className="text-xs text-[var(--muted-foreground)]">

View File

@@ -80,7 +80,7 @@ export function CollaborationMatrix({ analytics, className }: CollaborationMatri
if (!isClient) {
return (
<div className={className}>
<div className="animate-pulse bg-gray-200 dark:bg-gray-700 rounded-lg h-96" />
<div className="animate-pulse rounded-lg h-96" style={{ backgroundColor: 'var(--gray-light)' }} />
</div>
);
}
@@ -96,9 +96,9 @@ const mostIsolated = collaborationData.reduce((max, current) =>
// Couleur d'intensité
const getIntensityColor = (intensity: 'low' | 'medium' | 'high') => {
switch (intensity) {
case 'high': return 'bg-green-600 dark:bg-green-500';
case 'medium': return 'bg-yellow-600 dark:bg-yellow-500';
case 'low': return 'bg-gray-500 dark:bg-gray-400';
case 'high': return { backgroundColor: 'var(--green)' };
case 'medium': return { backgroundColor: 'var(--yellow)' };
case 'low': return { backgroundColor: 'var(--gray)' };
}
};
@@ -117,10 +117,13 @@ const mostIsolated = collaborationData.reduce((max, current) =>
<span className="text-xs text-[var(--muted-foreground)]">
Score: {person.collaborationScore}
</span>
<div className={`w-3 h-3 rounded-full ${
person.isolation < 30 ? 'bg-green-600 dark:bg-green-500' :
person.isolation < 60 ? 'bg-yellow-600 dark:bg-yellow-500' : 'bg-red-600 dark:bg-red-500'
}`} />
<div
className="w-3 h-3 rounded-full"
style={{
backgroundColor: person.isolation < 30 ? 'var(--green)' :
person.isolation < 60 ? 'var(--yellow)' : 'var(--destructive)'
}}
/>
</div>
</div>
<div className="space-y-1">
@@ -132,7 +135,7 @@ const mostIsolated = collaborationData.reduce((max, current) =>
</span>
<div className="flex items-center gap-2 flex-shrink-0">
<span>{dep.sharedTickets} tickets</span>
<div className={`w-2 h-2 rounded-full ${getIntensityColor(dep.intensity)}`} />
<div className="w-2 h-2 rounded-full" style={getIntensityColor(dep.intensity)} />
</div>
</div>
))
@@ -159,11 +162,11 @@ const mostIsolated = collaborationData.reduce((max, current) =>
const ranges = [[0, 30], [30, 50], [50, 70], [70, 100]];
const [min, max] = ranges[index];
const count = collaborationData.filter(d => d.isolation >= min && d.isolation < max).length;
const colors = ['bg-green-600 dark:bg-green-500', 'bg-blue-600 dark:bg-blue-500', 'bg-yellow-600 dark:bg-yellow-500', 'bg-red-600 dark:bg-red-500'];
const colors = ['var(--green)', 'var(--blue)', 'var(--yellow)', 'var(--destructive)'];
return (
<div key={level} className="flex items-center gap-2 text-xs">
<div className={`w-3 h-3 rounded-sm ${colors[index]}`} />
<div className="w-3 h-3 rounded-sm" style={{ backgroundColor: colors[index] }} />
<span className="flex-1 truncate">{level}</span>
<span className="font-mono text-xs">{count}</span>
</div>
@@ -203,7 +206,7 @@ const mostIsolated = collaborationData.reduce((max, current) =>
{ intensity: 'low' as const, label: 'Faible' }
].map(item => (
<div key={item.intensity} className="flex items-center gap-2 text-xs">
<div className={`w-2 h-2 rounded-full ${getIntensityColor(item.intensity)}`} />
<div className="w-2 h-2 rounded-full" style={getIntensityColor(item.intensity)} />
<span>{item.label}</span>
</div>
))}
@@ -257,25 +260,25 @@ const mostIsolated = collaborationData.reduce((max, current) =>
<h4 className="text-sm font-medium mb-2">Recommandations d&apos;équipe</h4>
<div className="space-y-2 text-sm">
{avgIsolation > 60 && (
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span></span>
<span>Isolation élevée - Encourager le pair programming et les reviews croisées</span>
</div>
)}
{avgIsolation < 30 && (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<div className="flex items-center gap-2" style={{ color: 'var(--green)' }}>
<span></span>
<span>Excellente collaboration - L&apos;équipe travaille bien ensemble</span>
</div>
)}
{mostIsolated && mostIsolated.isolation > 80 && (
<div className="flex items-center gap-2 text-orange-600 dark:text-orange-400">
<div className="flex items-center gap-2" style={{ color: 'var(--accent)' }}>
<span>👥</span>
<span>Attention à {mostIsolated.displayName} - Considérer du mentoring ou du binômage</span>
</div>
)}
{collaborationData.filter(d => d.dependencies.length === 0).length > 0 && (
<div className="flex items-center gap-2 text-blue-600 dark:text-blue-400">
<div className="flex items-center gap-2" style={{ color: 'var(--blue)' }}>
<span>🔗</span>
<span>Quelques membres travaillent en silo - Organiser des sessions de partage</span>
</div>

View File

@@ -150,7 +150,7 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-2 h-2 rounded-full bg-blue-500 dark:bg-blue-400 animate-pulse"></div>
<div className="w-2 h-2 rounded-full animate-pulse" style={{ backgroundColor: 'var(--blue)' }}></div>
<h3 className="font-mono text-sm font-bold text-blue-400 uppercase tracking-wider">
JIRA SYNC
</h3>

View File

@@ -205,31 +205,31 @@ export function PredictabilityMetrics({ sprintHistory, className }: Predictabili
<h4 className="text-sm font-medium mb-2">Analyse de predictabilité</h4>
<div className="space-y-2 text-sm">
{averageAccuracy > 80 && (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<div className="flex items-center gap-2" style={{ color: 'var(--green)' }}>
<span></span>
<span>Excellente predictabilité - L&apos;équipe estime bien sa capacité</span>
</div>
)}
{averageAccuracy < 60 && (
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span></span>
<span>Predictabilité faible - Revoir les méthodes d&apos;estimation</span>
</div>
)}
{averageVariance > 25 && (
<div className="flex items-center gap-2 text-orange-600 dark:text-orange-400">
<div className="flex items-center gap-2" style={{ color: 'var(--accent)' }}>
<span>📊</span>
<span>Variance élevée - Considérer des sprints plus courts ou un meilleur découpage</span>
</div>
)}
{trend > 10 && (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<div className="flex items-center gap-2" style={{ color: 'var(--green)' }}>
<span>📈</span>
<span>Tendance positive - L&apos;équipe s&apos;améliore dans ses estimations</span>
</div>
)}
{trend < -10 && (
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span>📉</span>
<span>Tendance négative - Attention aux changements récents (équipe, processus)</span>
</div>

View File

@@ -190,19 +190,19 @@ export function QualityMetrics({ analytics, className }: QualityMetricsProps) {
<h4 className="text-sm font-medium mb-2">Analyse qualité</h4>
<div className="space-y-2 text-sm">
{bugRatio > 25 && (
<div className="flex items-center gap-2 text-red-600 dark:text-red-400">
<div className="flex items-center gap-2" style={{ color: 'var(--destructive)' }}>
<span></span>
<span>Ratio de bugs élevé ({bugRatio}%) - Attention à la dette technique</span>
</div>
)}
{bugRatio <= 15 && (
<div className="flex items-center gap-2 text-green-600 dark:text-green-400">
<div className="flex items-center gap-2" style={{ color: 'var(--green)' }}>
<span></span>
<span>Excellent ratio de bugs ({bugRatio}%) - Bonne qualité du code</span>
</div>
)}
{issueTypes.stories > issueTypes.bugs * 3 && (
<div className="flex items-center gap-2 text-blue-600 dark:text-blue-400">
<div className="flex items-center gap-2" style={{ color: 'var(--blue)' }}>
<span>🚀</span>
<span>Focus positif sur les fonctionnalités - Bon équilibre produit</span>
</div>

View File

@@ -138,16 +138,16 @@ export function ThroughputChart({ sprintHistory, className }: ThroughputChartPro
{/* Légende visuelle */}
<div className="mb-4 flex justify-center gap-6 text-sm">
<div className="flex items-center gap-2">
<div className="w-4 h-3 bg-blue-600 dark:bg-blue-500 rounded-sm"></div>
<span className="text-blue-600 dark:text-blue-500">Points complétés</span>
<div className="w-4 h-3 rounded-sm" style={{ backgroundColor: 'var(--blue)' }}></div>
<span style={{ color: 'var(--blue)' }}>Points complétés</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-0.5 bg-green-600 dark:bg-green-500"></div>
<span className="text-green-600 dark:text-green-500">Throughput</span>
<div className="w-4 h-0.5" style={{ backgroundColor: 'var(--green)' }}></div>
<span style={{ color: 'var(--green)' }}>Throughput</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-0.5 bg-orange-600 dark:bg-orange-500 border-dashed border-t-2 border-orange-600 dark:border-orange-500"></div>
<span className="text-orange-600 dark:text-orange-500">Tendance</span>
<div className="w-4 h-0.5 border-dashed border-t-2" style={{ backgroundColor: 'var(--accent)', borderColor: 'var(--accent)' }}></div>
<span style={{ color: 'var(--accent)' }}>Tendance</span>
</div>
</div>

View File

@@ -271,11 +271,14 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
if (!message) return null;
return (
<div className={`text-xs mt-2 px-2 py-1 rounded transition-all inline-block ${
message.type === 'success'
? 'text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-950/20 border border-green-200 dark:border-green-800/20'
: 'text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-800/20'
}`}>
<div
className="text-xs mt-2 px-2 py-1 rounded transition-all inline-block border"
style={{
color: message.type === 'success' ? 'var(--success)' : 'var(--destructive)',
backgroundColor: message.type === 'success' ? 'color-mix(in srgb, var(--success) 10%, transparent)' : 'color-mix(in srgb, var(--destructive) 10%, transparent)',
borderColor: message.type === 'success' ? 'color-mix(in srgb, var(--success) 20%, var(--border))' : 'color-mix(in srgb, var(--destructive) 20%, var(--border))'
}}
>
{message.text}
</div>
);
@@ -561,11 +564,13 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings
<span className="text-xs text-[var(--muted-foreground)]">
{formatFileSize(backup.size)}
</span>
<span className={`text-xs px-1.5 py-0.5 rounded ${
backup.type === 'manual'
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
: 'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300'
}`}>
<span
className="text-xs px-1.5 py-0.5 rounded"
style={{
color: backup.type === 'manual' ? 'var(--blue)' : 'var(--muted-foreground)',
backgroundColor: backup.type === 'manual' ? 'color-mix(in srgb, var(--blue) 10%, transparent)' : 'color-mix(in srgb, var(--muted) 10%, transparent)'
}}
>
{backup.type === 'manual' ? 'Manuel' : 'Auto'}
</span>
</div>

View File

@@ -341,8 +341,8 @@ export function JiraConfigForm() {
{validationResult && (
<div className={`mt-2 p-2 rounded text-sm ${
validationResult.type === 'success'
? 'bg-green-50 border border-green-200 text-green-800 dark:bg-green-900/20 dark:border-green-800 dark:text-green-200'
: 'bg-red-50 border border-red-200 text-red-800 dark:bg-red-900/20 dark:border-red-800 dark:text-red-200'
? 'border border-[var(--success)]/20'
: 'border border-[var(--destructive)]/20'
}`}>
{validationResult.text}
</div>
@@ -433,11 +433,14 @@ export function JiraConfigForm() {
)}
{message && (
<div className={`p-4 rounded border ${
message.type === 'success'
? 'bg-green-50 border-green-200 text-green-800 dark:bg-green-900/20 dark:border-green-800 dark:text-green-200'
: 'bg-red-50 border-red-200 text-red-800 dark:bg-red-900/20 dark:border-red-800 dark:text-red-200'
}`}>
<div
className="p-4 rounded border"
style={{
color: message.type === 'success' ? 'var(--success)' : 'var(--destructive)',
backgroundColor: message.type === 'success' ? 'color-mix(in srgb, var(--success) 10%, transparent)' : 'color-mix(in srgb, var(--destructive) 10%, transparent)',
borderColor: message.type === 'success' ? 'color-mix(in srgb, var(--success) 20%, var(--border))' : 'color-mix(in srgb, var(--destructive) 20%, var(--border))'
}}
>
{message.text}
</div>
)}

View File

@@ -339,16 +339,16 @@ export function TfsConfigForm() {
{/* Actions de gestion des données TFS */}
{isTfsConfigured && (
<div className="p-4 bg-[var(--card)] rounded border border-orange-200 dark:border-orange-800">
<div className="p-4 bg-[var(--card)] rounded border" style={{ borderColor: 'color-mix(in srgb, var(--accent) 30%, var(--border))', backgroundColor: 'color-mix(in srgb, var(--accent) 5%, var(--card))', color: 'var(--accent)' }}>
<div className="flex items-center justify-between">
<div>
<h3 className="font-medium text-orange-800 dark:text-orange-200">
<h3 className="font-medium" style={{ color: 'var(--accent)' }}>
Gestion des données
</h3>
<p className="text-sm text-orange-600 dark:text-orange-300">
<p className="text-sm" style={{ color: 'var(--accent)' }}>
Supprimez toutes les tâches TFS synchronisées de la base locale
</p>
<p className="text-xs text-orange-500 dark:text-orange-400 mt-1">
<p className="text-xs mt-1" style={{ color: 'var(--accent)' }}>
<strong>Attention:</strong> Cette action est irréversible et
supprimera définitivement toutes les tâches importées depuis
Azure DevOps.
@@ -624,11 +624,12 @@ export function TfsConfigForm() {
{message && (
<div
className={`p-4 rounded border ${
message.type === 'success'
? 'bg-green-50 border-green-200 text-green-800 dark:bg-green-900/20 dark:border-green-800 dark:text-green-200'
: 'bg-red-50 border-red-200 text-red-800 dark:bg-red-900/20 dark:border-red-800 dark:text-red-200'
}`}
className="p-4 rounded border"
style={{
color: message.type === 'success' ? 'var(--success)' : 'var(--destructive)',
backgroundColor: message.type === 'success' ? 'color-mix(in srgb, var(--success) 10%, transparent)' : 'color-mix(in srgb, var(--destructive) 10%, transparent)',
borderColor: message.type === 'success' ? 'color-mix(in srgb, var(--success) 20%, var(--border))' : 'color-mix(in srgb, var(--destructive) 20%, var(--border))'
}}
>
{message.text}
</div>

View File

@@ -43,11 +43,9 @@ export function QuickActions({
Créer une sauvegarde des données
</p>
{messages.backup && (
<p className={`text-xs mt-1 ${
messages.backup.type === 'success'
? 'text-green-600 dark:text-green-400'
: 'text-red-600 dark:text-red-400'
}`}>
<p className="text-xs mt-1" style={{
color: messages.backup.type === 'success' ? 'var(--success)' : 'var(--destructive)'
}}>
{messages.backup.text}
</p>
)}
@@ -72,11 +70,9 @@ export function QuickActions({
Tester la connexion Jira
</p>
{messages.jira && (
<p className={`text-xs mt-1 ${
messages.jira.type === 'success'
? 'text-green-600 dark:text-green-400'
: 'text-red-600 dark:text-red-400'
}`}>
<p className="text-xs mt-1" style={{
color: messages.jira.type === 'success' ? 'var(--success)' : 'var(--destructive)'
}}>
{messages.jira.text}
</p>
)}

View File

@@ -8,11 +8,10 @@ import {
updateColumnVisibility as updateColumnVisibilityAction,
toggleObjectivesVisibility as toggleObjectivesVisibilityAction,
toggleObjectivesCollapse as toggleObjectivesCollapseAction,
toggleTheme as toggleThemeAction,
setTheme as setThemeAction,
toggleFontSize as toggleFontSizeAction,
toggleColumnVisibility as toggleColumnVisibilityAction
} from '@/actions/preferences';
import { useTheme } from './ThemeContext';
interface UserPreferencesContextType {
preferences: UserPreferences;
@@ -77,14 +76,17 @@ const defaultPreferences: UserPreferences = {
export function UserPreferencesProvider({ children, initialPreferences }: UserPreferencesProviderProps) {
const [preferences, setPreferences] = useState<UserPreferences>(initialPreferences || defaultPreferences);
const [isPending, startTransition] = useTransition();
const { theme, toggleTheme: themeToggleTheme, setTheme: themeSetTheme } = useTheme();
// Synchroniser le thème avec le ThemeProvider global (si disponible)
// Synchroniser les préférences avec le thème actuel du ThemeContext
useEffect(() => {
if (typeof window !== 'undefined') {
// Appliquer le thème au document
document.documentElement.className = preferences.viewPreferences.theme;
if (preferences.viewPreferences.theme !== theme) {
setPreferences(prev => ({
...prev,
viewPreferences: { ...prev.viewPreferences, theme }
}));
}
}, [preferences.viewPreferences.theme]);
}, [theme, preferences.viewPreferences.theme]);
// === KANBAN FILTERS ===
@@ -149,16 +151,12 @@ export function UserPreferencesProvider({ children, initialPreferences }: UserPr
}, []);
const toggleTheme = useCallback(() => {
startTransition(async () => {
await toggleThemeAction();
});
}, []);
themeToggleTheme();
}, [themeToggleTheme]);
const setTheme = useCallback((theme: 'light' | 'dark') => {
startTransition(async () => {
await setThemeAction(theme);
});
}, []);
themeSetTheme(theme);
}, [themeSetTheme]);
const toggleFontSize = useCallback(() => {
startTransition(async () => {