From b5d6967fcd7f1fa3a15af0e6ab393d3426a5e0f4 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sun, 28 Sep 2025 10:14:25 +0200 Subject: [PATCH] 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. --- .cursor/rules/css-variables-theme.mdc | 167 ++++++++++++++++++ TODO.md | 33 +++- src/app/globals.css | 72 ++++---- .../JiraDashboardPageClient.tsx | 4 +- src/app/layout.tsx | 2 +- src/components/backup/BackupTimelineChart.tsx | 36 ++-- .../charts/WeeklyActivityHeatmap.tsx | 31 ++-- .../deadline/CriticalDeadlinesCard.tsx | 16 +- src/components/deadline/DeadlineRiskCard.tsx | 26 +-- .../deadline/DeadlineSummaryCard.tsx | 22 +-- src/components/jira/BurndownChart.tsx | 12 +- src/components/jira/CollaborationMatrix.tsx | 35 ++-- src/components/jira/JiraSync.tsx | 2 +- src/components/jira/PredictabilityMetrics.tsx | 10 +- src/components/jira/QualityMetrics.tsx | 6 +- src/components/jira/ThroughputChart.tsx | 12 +- .../settings/BackupSettingsPageClient.tsx | 25 +-- src/components/settings/JiraConfigForm.tsx | 17 +- src/components/settings/TfsConfigForm.tsx | 19 +- .../settings/index/QuickActions.tsx | 16 +- src/contexts/UserPreferencesContext.tsx | 28 ++- 21 files changed, 404 insertions(+), 187 deletions(-) create mode 100644 .cursor/rules/css-variables-theme.mdc diff --git a/.cursor/rules/css-variables-theme.mdc b/.cursor/rules/css-variables-theme.mdc new file mode 100644 index 0000000..e8beda0 --- /dev/null +++ b/.cursor/rules/css-variables-theme.mdc @@ -0,0 +1,167 @@ +--- +alwaysApply: true +description: CSS Variables theme system best practices +--- + +# CSS Variables Theme System + +## Core Principle: Pure CSS Variables for Theming + +This project uses **CSS Variables exclusively** for theming. No Tailwind `dark:` classes or conditional CSS classes. + +## ✅ Architecture Pattern + +### CSS Structure +```css +:root { + /* Light theme (default values) */ + --background: #f1f5f9; + --foreground: #0f172a; + --primary: #0891b2; + --success: #059669; + --destructive: #dc2626; + --accent: #d97706; + --purple: #8b5cf6; + --yellow: #eab308; + --green: #059669; + --blue: #2563eb; + --gray: #6b7280; + --gray-light: #e5e7eb; +} + +.dark { + /* Dark theme (override values) */ + --background: #1e293b; + --foreground: #f1f5f9; + --primary: #06b6d4; + --success: #10b981; + --destructive: #ef4444; + --accent: #f59e0b; + --purple: #8b5cf6; + --yellow: #eab308; + --green: #10b981; + --blue: #3b82f6; + --gray: #9ca3af; + --gray-light: #374151; +} +``` + +### Theme Application +- **Single source of truth**: [ThemeContext.tsx](mdc:src/contexts/ThemeContext.tsx) applies theme class to `document.documentElement` +- **No duplication**: Theme is applied only once, not in multiple places +- **SSR safe**: Initial theme from server-side preferences + +## ✅ Component Usage Patterns + +### Correct: Using CSS Variables +```tsx +// ✅ GOOD: CSS Variables in className +
+ +// ✅ GOOD: CSS Variables in style prop +
+ +// ✅ GOOD: CSS Variables with color-mix for transparency +
+``` + +### ❌ Forbidden: Tailwind Dark Mode Classes +```tsx +// ❌ BAD: Tailwind dark: classes +
+ +// ❌ BAD: Conditional classes +
+ +// ❌ BAD: Hardcoded colors +
+``` + +## ✅ Color System + +### Semantic Color Tokens +- `--background`: Main background color +- `--foreground`: Main text color +- `--card`: Card/panel background +- `--card-hover`: Card hover state +- `--card-column`: Column background (darker than cards) +- `--border`: Border color +- `--input`: Input field background +- `--primary`: Primary brand color +- `--primary-foreground`: Text on primary background +- `--muted`: Muted text color +- `--muted-foreground`: Secondary text color +- `--accent`: Accent color (orange/amber) +- `--destructive`: Error/danger color (red) +- `--success`: Success color (green) +- `--purple`: Purple accent +- `--yellow`: Yellow accent +- `--green`: Green accent +- `--blue`: Blue accent +- `--gray`: Gray color +- `--gray-light`: Light gray background + +### Color Mixing Patterns +```css +/* Background with transparency */ +background-color: color-mix(in srgb, var(--primary) 10%, transparent); + +/* Border with transparency */ +border-color: color-mix(in srgb, var(--primary) 20%, var(--border)); + +/* Text with opacity */ +color: color-mix(in srgb, var(--destructive) 80%, transparent); +``` + +## ✅ Theme Context Usage + +### ThemeProvider Setup +```tsx +// In layout.tsx + + {children} + +``` + +### Component Usage +```tsx +import { useTheme } from '@/contexts/ThemeContext'; + +function MyComponent() { + const { theme, toggleTheme, setTheme } = useTheme(); + + return ( + + ); +} +``` + +## ✅ Future Extensibility + +This system is designed to support: +- **Custom color themes**: Easy to add new color variables +- **User preferences**: Colors can be dynamically changed +- **Theme presets**: Multiple predefined themes +- **Accessibility**: High contrast modes + +## 🚨 Anti-patterns to Avoid + +1. **Don't mix approaches**: Never use both CSS variables and Tailwind dark: classes +2. **Don't duplicate theme application**: Theme should be applied only in ThemeContext +3. **Don't hardcode colors**: Always use semantic color tokens +4. **Don't use conditional classes**: Use CSS variables instead +5. **Don't forget transparency**: Use `color-mix()` for semi-transparent colors + +## 📁 Key Files + +- [globals.css](mdc:src/app/globals.css) - CSS Variables definitions +- [ThemeContext.tsx](mdc:src/contexts/ThemeContext.tsx) - Theme management +- [UserPreferencesContext.tsx](mdc:src/contexts/UserPreferencesContext.tsx) - Preferences sync +- [layout.tsx](mdc:src/app/layout.tsx) - Theme provider setup + +Remember: **CSS Variables are the single source of truth for theming. Keep it pure and consistent.** \ No newline at end of file diff --git a/TODO.md b/TODO.md index d6e9142..f1eec0e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,40 @@ # TowerControl v2.0 - Gestionnaire de tâches moderne ## Idées à developper +- [x] Refacto et intégration design : mode sombre et clair sont souvent mal généré par défaut - [ ] Personnalisation : couleurs - [ ] Optimisations Perf : requetes DB -- [ ] PWA et mode offline +- [ ] PWA et mode offline + +--- + +## 🎨 **REFACTORING THÈME & PERSONNALISATION COULEURS** + +### **Phase 1: Nettoyage Architecture Thème** +- [x] **Décider de la stratégie** : CSS Variables vs Tailwind Dark Mode vs Hybride +- [x] **Configurer tailwind.config.js** avec `darkMode: 'class'` si nécessaire +- [x] **Supprimer la double application** du thème (layout.tsx + ThemeContext + UserPreferencesContext) +- [x] **Refactorer les CSS variables** : `:root` pour défaut, `.dark/.light` pour override +- [x] **Nettoyer les composants** : supprimer classes `dark:` hardcodées, utiliser uniquement CSS variables +- [ ] **Corriger les problèmes d'hydration** mismatch et flashs de thème +- [ ] **Créer un système de design cohérent** avec tokens de couleur + +### **Phase 2: Système Couleurs Personnalisées** +- [ ] **Étendre le modèle UserPreferences** pour supporter des couleurs personnalisées +- [ ] **Créer un service de gestion** des couleurs personnalisées +- [ ] **Créer une interface de configuration** des couleurs personnalisées +- [ ] **Implémenter le système CSS** pour les couleurs personnalisées dynamiques +- [ ] **Créer un système de presets** de thèmes (Tech Dark, Corporate Light, etc.) +- [ ] **Ajouter la validation des contrastes** pour les couleurs personnalisées +- [ ] **Permettre export/import** des configurations de thème personnalisées + +### **Problèmes identifiés actuellement :** +- ❌ Approche hybride incohérente (CSS Variables + Tailwind `dark:` + classes conditionnelles) +- ❌ Double application du thème (3 endroits différents) +- ❌ Pas de configuration Tailwind pour `darkMode` +- ❌ Hydration mismatch avec flashs +- ❌ CSS Variables mal optimisées (`:root` contient le thème sombre) +- ❌ Couleurs hardcodées dans certains composants --- diff --git a/src/app/globals.css b/src/app/globals.css index 7ea8ed2..35de1c9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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 { diff --git a/src/app/jira-dashboard/JiraDashboardPageClient.tsx b/src/app/jira-dashboard/JiraDashboardPageClient.tsx index fc51a8d..3d70313 100644 --- a/src/app/jira-dashboard/JiraDashboardPageClient.tsx +++ b/src/app/jira-dashboard/JiraDashboardPageClient.tsx @@ -262,7 +262,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }: {error && ( -
+
{error}
@@ -273,7 +273,7 @@ export function JiraDashboardPageClient({ initialJiraConfig, initialAnalytics }: {exportError && ( -
+
⚠️ Erreur d'export: {exportError}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 414a212..976c8c1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -30,7 +30,7 @@ export default async function RootLayout({ const initialPreferences = await userPreferencesService.getAllPreferences(); return ( - + diff --git a/src/components/backup/BackupTimelineChart.tsx b/src/components/backup/BackupTimelineChart.tsx index 2bf7ebc..d6cc326 100644 --- a/src/components/backup/BackupTimelineChart.tsx +++ b/src/components/backup/BackupTimelineChart.tsx @@ -88,7 +88,7 @@ export function BackupTimelineChart({ stats = [], className = '' }: BackupTimeli
@@ -152,58 +152,58 @@ export function BackupTimelineChart({ stats = [], className = '' }: BackupTimeli
{/* Légende claire */} -
-

Légende

+
+

Légende

15
- Manuel seul + Manuel seul
15
- Auto seul + Auto seul
15
- Manuel + Auto + Manuel + Auto
-
15
- Aucune +
15
+ Aucune
-
+
💡 Le badge orange indique le nombre total quand > 1
{/* Statistiques résumées */}
-
-
+
+
{safeStats.reduce((sum, s) => sum + s.manual, 0)}
-
Manuelles
+
Manuelles
-
-
+
+
{safeStats.reduce((sum, s) => sum + s.automatic, 0)}
-
Automatiques
+
Automatiques
-
-
+
+
{safeStats.reduce((sum, s) => sum + s.total, 0)}
-
Total
+
Total
diff --git a/src/components/dashboard/charts/WeeklyActivityHeatmap.tsx b/src/components/dashboard/charts/WeeklyActivityHeatmap.tsx index 8fdb863..2035227 100644 --- a/src/components/dashboard/charts/WeeklyActivityHeatmap.tsx +++ b/src/components/dashboard/charts/WeeklyActivityHeatmap.tsx @@ -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
{data.map((day, index) => { const intensity = getIntensity(day); - const colorClass = getColorClass(intensity); + const colorStyle = getColorStyle(intensity); const totalActivity = day.completed + day.newTasks; return (
{/* Carré de couleur */}
{/* Tooltip au hover */} @@ -87,12 +88,12 @@ export function WeeklyActivityHeatmap({ data, className }: WeeklyActivityHeatmap
Moins
-
-
-
-
-
-
+
+
+
+
+
+
Plus
diff --git a/src/components/deadline/CriticalDeadlinesCard.tsx b/src/components/deadline/CriticalDeadlinesCard.tsx index 7d8a948..1a29a32 100644 --- a/src/components/deadline/CriticalDeadlinesCard.tsx +++ b/src/components/deadline/CriticalDeadlinesCard.tsx @@ -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

Tâches Urgentes

🎉
-

Excellent !

+

Excellent !

Aucune tâche urgente ou critique

@@ -89,7 +89,7 @@ export function CriticalDeadlinesCard({ overdue, critical, warning }: CriticalDe
-
+
{urgentTasks.map((task) => { const urgencyStyle = getUrgencyStyle(task); @@ -157,17 +157,17 @@ export function CriticalDeadlinesCard({ overdue, critical, warning }: CriticalDe
{overdue.length > 0 && ( - + {overdue.length} en retard )} {critical.length > 0 && ( - + {critical.length} critique{critical.length > 1 ? 's' : ''} )} {warning.length > 0 && ( - + {warning.length} attention )} diff --git a/src/components/deadline/DeadlineRiskCard.tsx b/src/components/deadline/DeadlineRiskCard.tsx index cd77c23..9b4e84e 100644 --- a/src/components/deadline/DeadlineRiskCard.tsx +++ b/src/components/deadline/DeadlineRiskCard.tsx @@ -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) { {getRiskIcon(riskAnalysis.riskLevel)}

Niveau de Risque

-
+
{riskAnalysis.riskScore}
@@ -69,11 +69,11 @@ export function DeadlineRiskCard({ metrics }: DeadlineRiskCardProps) {
En retard: - {metrics.summary.overdueCount} + {metrics.summary.overdueCount}
Critique: - {metrics.summary.criticalCount} + {metrics.summary.criticalCount}
diff --git a/src/components/deadline/DeadlineSummaryCard.tsx b/src/components/deadline/DeadlineSummaryCard.tsx index 494aee1..6d13ff9 100644 --- a/src/components/deadline/DeadlineSummaryCard.tsx +++ b/src/components/deadline/DeadlineSummaryCard.tsx @@ -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) => (
-
+
{item.icon}
{item.label}
-
+
{item.count}
@@ -73,7 +73,7 @@ export function DeadlineSummaryCard({ metrics }: DeadlineSummaryCardProps) { {summary.totalWithDeadlines - summary.overdueCount - summary.criticalCount}/{summary.totalWithDeadlines}
-
+
-
- Idéal +
+ Idéal
-
- Réel +
+ Réel
{/* Métriques */}
-
+
{currentSprint.plannedPoints}
@@ -151,7 +151,7 @@ export function BurndownChart({ sprintHistory, className }: BurndownChartProps)
-
+
{currentSprint.completedPoints}
diff --git a/src/components/jira/CollaborationMatrix.tsx b/src/components/jira/CollaborationMatrix.tsx index 1b27e1a..33722bb 100644 --- a/src/components/jira/CollaborationMatrix.tsx +++ b/src/components/jira/CollaborationMatrix.tsx @@ -80,7 +80,7 @@ export function CollaborationMatrix({ analytics, className }: CollaborationMatri if (!isClient) { return (
-
+
); } @@ -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) => Score: {person.collaborationScore} -
+
@@ -132,7 +135,7 @@ const mostIsolated = collaborationData.reduce((max, current) =>
{dep.sharedTickets} tickets -
+
)) @@ -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 (
-
+
{level} {count}
@@ -203,7 +206,7 @@ const mostIsolated = collaborationData.reduce((max, current) => { intensity: 'low' as const, label: 'Faible' } ].map(item => (
-
+
{item.label}
))} @@ -257,25 +260,25 @@ const mostIsolated = collaborationData.reduce((max, current) =>

Recommandations d'équipe

{avgIsolation > 60 && ( -
+
⚠️ Isolation élevée - Encourager le pair programming et les reviews croisées
)} {avgIsolation < 30 && ( -
+
Excellente collaboration - L'équipe travaille bien ensemble
)} {mostIsolated && mostIsolated.isolation > 80 && ( -
+
👥 Attention à {mostIsolated.displayName} - Considérer du mentoring ou du binômage
)} {collaborationData.filter(d => d.dependencies.length === 0).length > 0 && ( -
+
🔗 Quelques membres travaillent en silo - Organiser des sessions de partage
diff --git a/src/components/jira/JiraSync.tsx b/src/components/jira/JiraSync.tsx index 895d198..cfaaffd 100644 --- a/src/components/jira/JiraSync.tsx +++ b/src/components/jira/JiraSync.tsx @@ -150,7 +150,7 @@ export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
-
+

JIRA SYNC

diff --git a/src/components/jira/PredictabilityMetrics.tsx b/src/components/jira/PredictabilityMetrics.tsx index 5567431..27116fd 100644 --- a/src/components/jira/PredictabilityMetrics.tsx +++ b/src/components/jira/PredictabilityMetrics.tsx @@ -205,31 +205,31 @@ export function PredictabilityMetrics({ sprintHistory, className }: Predictabili

Analyse de predictabilité

{averageAccuracy > 80 && ( -
+
Excellente predictabilité - L'équipe estime bien sa capacité
)} {averageAccuracy < 60 && ( -
+
⚠️ Predictabilité faible - Revoir les méthodes d'estimation
)} {averageVariance > 25 && ( -
+
📊 Variance élevée - Considérer des sprints plus courts ou un meilleur découpage
)} {trend > 10 && ( -
+
📈 Tendance positive - L'équipe s'améliore dans ses estimations
)} {trend < -10 && ( -
+
📉 Tendance négative - Attention aux changements récents (équipe, processus)
diff --git a/src/components/jira/QualityMetrics.tsx b/src/components/jira/QualityMetrics.tsx index 7541421..0829af5 100644 --- a/src/components/jira/QualityMetrics.tsx +++ b/src/components/jira/QualityMetrics.tsx @@ -190,19 +190,19 @@ export function QualityMetrics({ analytics, className }: QualityMetricsProps) {

Analyse qualité

{bugRatio > 25 && ( -
+
⚠️ Ratio de bugs élevé ({bugRatio}%) - Attention à la dette technique
)} {bugRatio <= 15 && ( -
+
Excellent ratio de bugs ({bugRatio}%) - Bonne qualité du code
)} {issueTypes.stories > issueTypes.bugs * 3 && ( -
+
🚀 Focus positif sur les fonctionnalités - Bon équilibre produit
diff --git a/src/components/jira/ThroughputChart.tsx b/src/components/jira/ThroughputChart.tsx index 420e18a..c2ed682 100644 --- a/src/components/jira/ThroughputChart.tsx +++ b/src/components/jira/ThroughputChart.tsx @@ -138,16 +138,16 @@ export function ThroughputChart({ sprintHistory, className }: ThroughputChartPro {/* Légende visuelle */}
-
- Points complétés +
+ Points complétés
-
- Throughput +
+ Throughput
-
- Tendance +
+ Tendance
diff --git a/src/components/settings/BackupSettingsPageClient.tsx b/src/components/settings/BackupSettingsPageClient.tsx index 98095ed..984aeb0 100644 --- a/src/components/settings/BackupSettingsPageClient.tsx +++ b/src/components/settings/BackupSettingsPageClient.tsx @@ -271,11 +271,14 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings if (!message) return null; return ( -
+
{message.text}
); @@ -561,11 +564,13 @@ export default function BackupSettingsPageClient({ initialData }: BackupSettings {formatFileSize(backup.size)} - + {backup.type === 'manual' ? 'Manuel' : 'Auto'}
diff --git a/src/components/settings/JiraConfigForm.tsx b/src/components/settings/JiraConfigForm.tsx index b8f9a4b..2e89eac 100644 --- a/src/components/settings/JiraConfigForm.tsx +++ b/src/components/settings/JiraConfigForm.tsx @@ -341,8 +341,8 @@ export function JiraConfigForm() { {validationResult && (
{validationResult.text}
@@ -433,11 +433,14 @@ export function JiraConfigForm() { )} {message && ( -
+
{message.text}
)} diff --git a/src/components/settings/TfsConfigForm.tsx b/src/components/settings/TfsConfigForm.tsx index 87ef1f1..0e6fada 100644 --- a/src/components/settings/TfsConfigForm.tsx +++ b/src/components/settings/TfsConfigForm.tsx @@ -339,16 +339,16 @@ export function TfsConfigForm() { {/* Actions de gestion des données TFS */} {isTfsConfigured && ( -
+
-

+

⚠️ Gestion des données

-

+

Supprimez toutes les tâches TFS synchronisées de la base locale

-

+

Attention: 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 && (

{message.text}
diff --git a/src/components/settings/index/QuickActions.tsx b/src/components/settings/index/QuickActions.tsx index ecd5c05..8d5512d 100644 --- a/src/components/settings/index/QuickActions.tsx +++ b/src/components/settings/index/QuickActions.tsx @@ -43,11 +43,9 @@ export function QuickActions({ Créer une sauvegarde des données

{messages.backup && ( -

+

{messages.backup.text}

)} @@ -72,11 +70,9 @@ export function QuickActions({ Tester la connexion Jira

{messages.jira && ( -

+

{messages.jira.text}

)} diff --git a/src/contexts/UserPreferencesContext.tsx b/src/contexts/UserPreferencesContext.tsx index 4b4de66..cd1b210 100644 --- a/src/contexts/UserPreferencesContext.tsx +++ b/src/contexts/UserPreferencesContext.tsx @@ -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(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 () => {