diff --git a/TODO.md b/TODO.md index f1eec0e..e1059df 100644 --- a/TODO.md +++ b/TODO.md @@ -2,12 +2,34 @@ ## Idées à developper - [x] Refacto et intégration design : mode sombre et clair sont souvent mal généré par défaut -- [ ] Personnalisation : couleurs +- [x] Personnalisation : couleurs - [ ] Optimisations Perf : requetes DB - [ ] PWA et mode offline --- +## 🖼️ **IMAGE DE FOND PERSONNALISÉE** ✅ TERMINÉ + +### **Fonctionnalités implémentées :** +- [x] **Sélecteur d'images de fond** dans les paramètres généraux +- [x] **Images prédéfinies** : dégradés bleu, violet, coucher de soleil, océan, forêt +- [x] **URL personnalisée** : possibilité d'ajouter une image via URL +- [x] **Aperçu en temps réel** de l'image sélectionnée +- [x] **Application globale** : l'image s'applique sur toutes les pages +- [x] **Optimisation visuelle** : effet de flou et transparence pour la lisibilité +- [x] **Sauvegarde persistante** : préférence sauvegardée en base de données +- [x] **Interface intuitive** : sélection facile avec aperçus visuels + +### **Architecture technique :** +- **Types** : `backgroundImage` ajouté à `ViewPreferences` +- **Service** : `userPreferencesService` mis à jour +- **Actions** : `setBackgroundImage` server action créée +- **Composant** : `BackgroundImageSelector` avec presets et URL personnalisée +- **Contexte** : `BackgroundContext` pour l'application globale +- **Styles** : CSS optimisé pour la lisibilité avec images de fond + +--- + ## 🎨 **REFACTORING THÈME & PERSONNALISATION COULEURS** ### **Phase 1: Nettoyage Architecture Thème** diff --git a/src/actions/preferences.ts b/src/actions/preferences.ts index 425ad37..e9c9a5b 100644 --- a/src/actions/preferences.ts +++ b/src/actions/preferences.ts @@ -32,6 +32,31 @@ export async function updateViewPreferences(updates: Partial): } } +/** + * Met à jour l'image de fond + */ +export async function setBackgroundImage(backgroundImage: string | undefined): Promise<{ + success: boolean; + error?: string; +}> { + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return { success: false, error: 'Non authentifié' }; + } + + await userPreferencesService.updateViewPreferences(session.user.id, { backgroundImage }); + revalidatePath('/'); + return { success: true }; + } catch (error) { + console.error('Erreur setBackgroundImage:', error); + return { + success: false, + error: error instanceof Error ? error.message : 'Erreur inconnue' + }; + } +} + /** * Met à jour les filtres Kanban */ diff --git a/src/app/globals.css b/src/app/globals.css index e0d0cb1..0c638a1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -424,10 +424,45 @@ } body { - background: var(--background); + background-color: var(--background); color: var(--foreground); font-family: var(--font-geist-mono), 'Courier New', monospace; overflow-x: hidden; + transition: background-image 0.3s ease-in-out; +} + +/* Styles pour les images de fond */ +body.has-background-image { + /* Assurer que le contenu reste lisible avec une image de fond */ + position: relative; +} + +body.has-background-image::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.1); + pointer-events: none; + z-index: -1; +} + +/* Améliorer la lisibilité des cartes avec image de fond */ +body.has-background-image .bg-\[var\(--card\)\] { + background-color: color-mix(in srgb, var(--card) 90%, transparent) !important; + backdrop-filter: blur(8px); +} + +body.has-background-image .bg-\[var\(--card\)\]\/30 { + background-color: color-mix(in srgb, var(--card) 20%, transparent) !important; + backdrop-filter: blur(12px); +} + +/* Rendre les conteneurs principaux transparents avec image de fond */ +body.has-background-image .min-h-screen.bg-\[var\(--background\)\] { + background-color: transparent !important; } /* Scrollbar tech style */ @@ -552,3 +587,35 @@ body { -webkit-box-orient: vertical; overflow: hidden; } + +/* Styles pour les sliders */ +.slider::-webkit-slider-thumb { + appearance: none; + height: 16px; + width: 16px; + border-radius: 50%; + background: var(--primary); + cursor: pointer; + border: 2px solid var(--background); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.slider::-webkit-slider-thumb:hover { + background: color-mix(in srgb, var(--primary) 80%, var(--accent) 20%); + transform: scale(1.1); +} + +.slider::-moz-range-thumb { + height: 16px; + width: 16px; + border-radius: 50%; + background: var(--primary); + cursor: pointer; + border: 2px solid var(--background); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.slider::-moz-range-thumb:hover { + background: color-mix(in srgb, var(--primary) 80%, var(--accent) 20%); + transform: scale(1.1); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8048b82..208d79d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/contexts/ThemeContext"; +import { BackgroundProvider } from "@/contexts/BackgroundContext"; import { JiraConfigProvider } from "@/contexts/JiraConfigContext"; import { UserPreferencesProvider } from "@/contexts/UserPreferencesContext"; import { KeyboardShortcutsProvider } from "@/contexts/KeyboardShortcutsContext"; @@ -54,7 +55,9 @@ export default async function RootLayout({ - {children} + + {children} + diff --git a/src/components/daily/DailySection.tsx b/src/components/daily/DailySection.tsx index a00920b..f95f289 100644 --- a/src/components/daily/DailySection.tsx +++ b/src/components/daily/DailySection.tsx @@ -103,7 +103,7 @@ export function DailySection({ onDragEnd={handleDragEnd} id={`daily-dnd-${title.replace(/[^a-zA-Z0-9]/g, '-')}`} > - + {/* Header */}
diff --git a/src/components/daily/PendingTasksSection.tsx b/src/components/daily/PendingTasksSection.tsx index ca477ab..5a06842 100644 --- a/src/components/daily/PendingTasksSection.tsx +++ b/src/components/daily/PendingTasksSection.tsx @@ -121,7 +121,7 @@ export function PendingTasksSection({ const pendingCount = pendingTasks.length; return ( - +
+ ))} +
+ +
+ + {/* URL personnalisée */} + + +
+
+
+

URL personnalisée

+

+ Ajoutez votre propre image de fond +

+
+ +
+ + {showCustomInput && ( +
+
+ setCustomUrl(e.target.value)} + className="flex-1" + /> + +
+

+ Entrez l'URL d'une image (JPG, PNG, GIF, WebP) +

+
+ )} + + {/* Bouton pour supprimer l'image personnalisée */} + {currentBackground && !PRESET_BACKGROUNDS.find(p => p.id === currentBackground) && ( + + )} +
+
+
+ + {/* Options avancées */} + {currentBackground && ( + + +
+
+
+

Options avancées

+

+ Personnalisez l'effet de l'image de fond +

+
+ +
+ + {showAdvancedOptions && ( +
+ {/* Contrôle du blur */} +
+
+ + + {backgroundBlur}px + +
+ handleBlurChange(Number(e.target.value))} + className="w-full h-2 bg-[var(--border)] rounded-lg appearance-none cursor-pointer slider" + /> +
+ Aucun + Très flou +
+
+ + {/* Contrôle de l'opacité */} +
+
+ + + {backgroundOpacity}% + +
+ handleOpacityChange(Number(e.target.value))} + className="w-full h-2 bg-[var(--border)] rounded-lg appearance-none cursor-pointer slider" + /> +
+ Très transparent + Opaque +
+
+ + {/* Aperçu en temps réel */} +
+ +
p.id === currentBackground) + ? `url(${currentBackground})` + : PRESET_BACKGROUNDS.find(p => p.id === currentBackground)?.preview || 'var(--background)', + backgroundSize: 'cover', + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + filter: `blur(${backgroundBlur}px)`, + opacity: backgroundOpacity / 100 + }} + /> +
+
+ )} +
+ + + )} + + {/* Note sur les performances */} + + +
+
💡
+
+

+ Conseil pour les performances +

+

+ Utilisez des images optimisées pour de meilleures performances. + Les images trop lourdes peuvent ralentir l'interface. +

+
+
+
+
+
+ ); +} diff --git a/src/components/settings/GeneralSettingsPageClient.tsx b/src/components/settings/GeneralSettingsPageClient.tsx index 9923b22..9787419 100644 --- a/src/components/settings/GeneralSettingsPageClient.tsx +++ b/src/components/settings/GeneralSettingsPageClient.tsx @@ -6,6 +6,7 @@ import { Header } from '@/components/ui/Header'; import { Card, CardContent } from '@/components/ui/Card'; import { TagsManagement } from './tags/TagsManagement'; import { ThemeSelector } from '@/components/ThemeSelector'; +import { BackgroundImageSelector } from './BackgroundImageSelector'; import Link from 'next/link'; interface GeneralSettingsPageClientProps { @@ -49,13 +50,22 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl
{/* Sélection de thème */} -
- -
+ + + + + + + {/* Sélection d'image de fond */} + + + + + {/* UI Showcase */} - - + +

@@ -83,7 +93,7 @@ export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageCl /> {/* Note développement futur */} - +

diff --git a/src/components/settings/tags/TagsGrid.tsx b/src/components/settings/tags/TagsGrid.tsx index dfcbc56..780f1ca 100644 --- a/src/components/settings/tags/TagsGrid.tsx +++ b/src/components/settings/tags/TagsGrid.tsx @@ -1,6 +1,7 @@ 'use client'; import { Button } from '@/components/ui/Button'; +import { Card } from '@/components/ui/Card'; import { Tag } from '@/lib/types'; interface TagsGridProps { @@ -41,16 +42,17 @@ export function TagsGrid({ const usage = tag.usage || 0; const isUnused = usage === 0; return ( -

- {/* Header du tag */} -
+
+ {/* Header du tag */} +
)}
-
+
+ ); })}
diff --git a/src/components/settings/tags/TagsManagement.tsx b/src/components/settings/tags/TagsManagement.tsx index 88e3154..51e4c72 100644 --- a/src/components/settings/tags/TagsManagement.tsx +++ b/src/components/settings/tags/TagsManagement.tsx @@ -95,7 +95,7 @@ export function TagsManagement({ tags, onRefreshTags, onDeleteTag }: TagsManagem return ( <> - +
diff --git a/src/components/ui/Calendar.tsx b/src/components/ui/Calendar.tsx index 98988cb..3dcc5e4 100644 --- a/src/components/ui/Calendar.tsx +++ b/src/components/ui/Calendar.tsx @@ -111,7 +111,7 @@ export function Calendar({ const weekDays = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']; return ( - + {/* Header avec navigation */}