From ad0b723e009e469012d9e12654a8fb646375d487 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Sat, 4 Oct 2025 11:06:49 +0200 Subject: [PATCH] feat: update TODO.md and refactor Header component - Removed redundant theme handling code from Header component, improving readability and maintainability. - Integrated HeaderMobile and HeaderDesktop components for better responsive design. - Marked the task for repositioning the theme icon in the header as complete in TODO.md. --- TODO.md | 5 +- src/components/ui/Header.tsx | 377 +----------------- src/components/ui/header/HeaderControls.tsx | 59 +++ src/components/ui/header/HeaderDesktop.tsx | 57 +++ src/components/ui/header/HeaderMobile.tsx | 116 ++++++ src/components/ui/header/HeaderNavigation.tsx | 101 +++++ src/components/ui/header/ThemeDropdown.tsx | 74 ++++ 7 files changed, 413 insertions(+), 376 deletions(-) create mode 100644 src/components/ui/header/HeaderControls.tsx create mode 100644 src/components/ui/header/HeaderDesktop.tsx create mode 100644 src/components/ui/header/HeaderMobile.tsx create mode 100644 src/components/ui/header/HeaderNavigation.tsx create mode 100644 src/components/ui/header/ThemeDropdown.tsx diff --git a/TODO.md b/TODO.md index 812ada5..8a1cf8c 100644 --- a/TODO.md +++ b/TODO.md @@ -21,17 +21,16 @@ - [x] **EditModal task couleur calendrier** - Problème de couleur en ajout de taches dans tous les icones calendriers; colmler au thème - [x] **Weekly deux boutons actualiser** - Corriger la duplication des boutons d'actualisation - [x] **Solarized ne doit pas être un soleil** - Corriger l'icône du thème Solarized -- [ ] **Settings : tag icônes actions** - Icônes trop petites dans les actions des tags +- [ ] **Emoji interdit dans UI** - Vérifier et supprimer toutes les emojis dans l'interface, remplacer par lucide-react - [ ] **Settings intégration : icônes** - Problème avec les icônes "Arrêté" et "Actif" : doivent etre les memes - [ ] **Settings backup UI** - Revoir l'UI pour coller au style des intégrations -- [ ] **Emoji interdit dans UI** - Vérifier et supprimer toutes les emojis dans l'interface, remplacer par lib d'icone - [ ] **AlertBanner : hover et bug** - Corriger les problèmes de hover et bugs - [ ] **Deux modales** - Problème de duplication de modales - [ ] **Control panel et select** - Problème avec les contrôles et sélecteurs - [ ] **TaskCard et Kanban transparence** - Appliquer la transparence sur le background et non sur la card - [X] **Recherche Kanban desktop controls** - Ajouter icône et label : "rechercher" pour rapetir - [ ] **Largeur page Kanban** - Réduire légèrement la largeur et revoir toutes les autres pages -- [ ] **Icône thème à gauche du profil** - Repositionner l'icône de thème dans le header +- [x] **Icône thème à gauche du profil** - Repositionner l'icône de thème dans le header - [ ] **Déconnexion trop petit et couleur** - Améliorer le bouton de déconnexion - [ ] **Fond modal trop opaque** - Réduire l'opacité du fond des modales - [ ] **Couleurs thème clair et TFS Jira Kanban** - Harmoniser les couleurs du thème clair diff --git a/src/components/ui/Header.tsx b/src/components/ui/Header.tsx index 1003bb4..bbf0327 100644 --- a/src/components/ui/Header.tsx +++ b/src/components/ui/Header.tsx @@ -1,15 +1,7 @@ 'use client'; -import { useTheme } from '@/contexts/ThemeContext'; -import { useJiraConfig } from '@/contexts/JiraConfigContext'; -import { usePathname } from 'next/navigation'; -import Link from 'next/link'; -import { useState } from 'react'; -import { Theme, THEME_CONFIG, getThemeIcon } from '@/lib/ui-config'; -import { useKeyboardShortcutsModal } from '@/contexts/KeyboardShortcutsContext'; -import { AuthButton } from '@/components/AuthButton'; -import { useSession, signOut } from 'next-auth/react'; -import { Check, X, Menu } from 'lucide-react'; +import { HeaderMobile } from './header/HeaderMobile'; +import { HeaderDesktop } from './header/HeaderDesktop'; interface HeaderProps { title?: string; @@ -18,373 +10,12 @@ interface HeaderProps { } export function Header({ title = "TowerControl", subtitle = "Task Management", syncing = false }: HeaderProps) { - const { theme, setTheme } = useTheme(); - const { isConfigured: isJiraConfigured, config: jiraConfig } = useJiraConfig(); - const pathname = usePathname(); - const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - const [tabletMenuOpen, setTabletMenuOpen] = useState(false); - const [themeDropdownOpen, setThemeDropdownOpen] = useState(false); - const { openModal: openShortcutsModal } = useKeyboardShortcutsModal(); - const { data: session } = useSession(); - - // Liste des thèmes disponibles avec leurs labels et icônes - const themes: { value: Theme; label: string; icon: string }[] = THEME_CONFIG.allThemes.map(themeValue => { - return { - value: themeValue, - label: themeValue.charAt(0).toUpperCase() + themeValue.slice(1).replace('_', ' '), - icon: getThemeIcon(themeValue) - }; - }); - - // Fonction pour déterminer si un lien est actif - const isActiveLink = (href: string) => { - if (href === '/') { - return pathname === '/'; - } - return pathname.startsWith(href); - }; - - // Fonction pour obtenir les classes CSS d'un lien (desktop) - const getLinkClasses = (href: string) => { - const baseClasses = "font-mono text-sm uppercase tracking-wider transition-colors px-3 py-1.5 rounded-md"; - - if (isActiveLink(href)) { - return `${baseClasses} text-[var(--primary)] bg-[var(--primary)]/10 border border-[var(--primary)]/30`; - } - - return `${baseClasses} text-[var(--muted-foreground)] hover:text-[var(--primary)] hover:bg-[var(--card-hover)]`; - }; - - // Fonction pour obtenir les classes CSS d'un lien (mobile) - const getMobileLinkClasses = (href: string) => { - const baseClasses = "font-mono text-sm uppercase tracking-wider transition-colors px-4 py-3 rounded-md block w-full text-left"; - - if (isActiveLink(href)) { - return `${baseClasses} text-[var(--primary)] bg-[var(--primary)]/10 border border-[var(--primary)]/30`; - } - - return `${baseClasses} text-[var(--muted-foreground)] hover:text-[var(--primary)] hover:bg-[var(--card-hover)]`; - }; - - // Liste des liens de navigation - const navLinks = [ - { href: '/', label: 'Dashboard' }, - { href: '/kanban', label: 'Kanban' }, - { href: '/daily', label: 'Daily' }, - { href: '/weekly-manager', label: 'Weekly' }, - ...(isJiraConfigured ? [{ href: '/jira-dashboard', label: `Jira${jiraConfig?.projectKey ? ` (${jiraConfig.projectKey})` : ''}` }] : []), - { href: '/settings', label: 'Settings' } - ]; - return (
- {/* Layout mobile/tablette */} -
-
- {/* Titre et status */} -
-
-
-

- {title} -

-

- {subtitle} -

-
-
- - {/* Controls mobile/tablette */} -
- {/* Keyboard Shortcuts */} - - - {/* Theme Dropdown */} -
- - - {themeDropdownOpen && ( - <> - {/* Backdrop */} -
setThemeDropdownOpen(false)} - /> - {/* Dropdown */} -
-
- {themes.map((themeOption) => ( - - ))} -
-
- - )} -
- - {/* Menu burger */} - -
- -
- - {/* Auth controls à droite mobile - dans la ligne principale */} -
- -
-
- - {/* Ligne Auth séparée sur très petits écrans */} -
- -
- - {/* Layout desktop - une seule ligne comme avant */} -
- {/* Titre et status */} -
-
-
-
-

- {title} - {title} -

-

- {subtitle} -

-
-
- - {/* Navigation desktop */} - -
- - {/* Contrôles à droite */} -
- -
- -
- + +
- - {/* Menu mobile/tablette en modal */} - {mobileMenuOpen && ( -
- {/* Backdrop */} -
setMobileMenuOpen(false)} - /> - - {/* Modal content */} -
- -
-
- )}
); } diff --git a/src/components/ui/header/HeaderControls.tsx b/src/components/ui/header/HeaderControls.tsx new file mode 100644 index 0000000..67c67ac --- /dev/null +++ b/src/components/ui/header/HeaderControls.tsx @@ -0,0 +1,59 @@ +'use client'; + +import { useState } from 'react'; +import { ThemeDropdown } from './ThemeDropdown'; +import { HeaderNavigation } from './HeaderNavigation'; + +interface HeaderControlsProps { + variant: 'desktop' | 'mobile'; + className?: string; +} + +export function HeaderControls({ variant, className = '' }: HeaderControlsProps) { + const [tabletMenuOpen, setTabletMenuOpen] = useState(false); + + if (variant === 'mobile') { + return ( +
+ {/* Theme Dropdown */} + +
+ ); + } + + // Desktop version + return ( +
+ {/* Navigation */} + + + {/* Plus de navigation pour écrans moyens */} +
+ + + {tabletMenuOpen && ( + <> + {/* Backdrop */} +
setTabletMenuOpen(false)} + /> + {/* Menu items */} +
+ setTabletMenuOpen(false)} + /> +
+ + )} +
+
+ ); +} diff --git a/src/components/ui/header/HeaderDesktop.tsx b/src/components/ui/header/HeaderDesktop.tsx new file mode 100644 index 0000000..f5f8b7b --- /dev/null +++ b/src/components/ui/header/HeaderDesktop.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { AuthButton } from '@/components/AuthButton'; +import { HeaderControls } from './HeaderControls'; +import { ThemeDropdown } from './ThemeDropdown'; +import { useKeyboardShortcutsModal } from '@/contexts/KeyboardShortcutsContext'; + +interface HeaderDesktopProps { + title: string; + subtitle: string; + syncing: boolean; +} + +export function HeaderDesktop({ title, subtitle, syncing }: HeaderDesktopProps) { + const { openModal: openShortcutsModal } = useKeyboardShortcutsModal(); + + return ( +
+ {/* Titre et status */} +
+
+
+
+

+ {title} + {title} +

+

+ {subtitle} +

+
+
+ + {/* Navigation desktop */} + +
+ + {/* Contrôles à droite */} +
+ {/* Keyboard Shortcuts desktop */} + + + +
+
+ ); +} diff --git a/src/components/ui/header/HeaderMobile.tsx b/src/components/ui/header/HeaderMobile.tsx new file mode 100644 index 0000000..f69365a --- /dev/null +++ b/src/components/ui/header/HeaderMobile.tsx @@ -0,0 +1,116 @@ +'use client'; + +import { useState } from 'react'; +import { X, Menu } from 'lucide-react'; +import { signOut } from 'next-auth/react'; +import Link from 'next/link'; +import { HeaderControls } from './HeaderControls'; +import { HeaderNavigation } from './HeaderNavigation'; +import { AuthButton } from '@/components/AuthButton'; + +interface HeaderMobileProps { + title: string; + subtitle: string; + syncing: boolean; +} + +export function HeaderMobile({ title, subtitle, syncing }: HeaderMobileProps) { + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + + // Fonction pour obtenir les classes CSS d'un lien (mobile) + const getMobileLinkClasses = (href: string) => { + const baseClasses = "font-mono text-sm uppercase tracking-wider transition-colors px-4 py-3 rounded-md block w-full text-left"; + + // Simplifier pour éviter la duplication de logique + return `${baseClasses} text-[var(--muted-foreground)] hover:text-[var(--primary)] hover:bg-[var(--card-hover)]`; + }; + + return ( + <> + {/* Layout mobile/tablette */} +
+
+ {/* Titre et status */} +
+
+
+

+ {title} +

+

+ {subtitle} +

+
+
+ + {/* Controls mobile/tablette */} + + + {/* Menu burger */} + +
+ + {/* Auth controls à droite mobile - dans la ligne principale */} +
+ +
+
+ + {/* Ligne Auth séparée sur très petits écrans */} +
+ +
+ + {/* Menu mobile/tablette en modal */} + {mobileMenuOpen && ( +
+ {/* Backdrop */} +
setMobileMenuOpen(false)} + /> + + {/* Modal content */} +
+ +
+
+ )} + + ); +} diff --git a/src/components/ui/header/HeaderNavigation.tsx b/src/components/ui/header/HeaderNavigation.tsx new file mode 100644 index 0000000..09a69fa --- /dev/null +++ b/src/components/ui/header/HeaderNavigation.tsx @@ -0,0 +1,101 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useJiraConfig } from '@/contexts/JiraConfigContext'; + +interface HeaderNavigationProps { + variant: 'desktop' | 'mobile'; + className?: string; + onLinkClick?: () => void; +} + +export function HeaderNavigation({ variant, className = '', onLinkClick }: HeaderNavigationProps) { + const { isConfigured: isJiraConfigured, config: jiraConfig } = useJiraConfig(); + const pathname = usePathname(); + + // Liste des liens de navigation + const navLinks = [ + { href: '/', label: 'Dashboard' }, + { href: '/kanban', label: 'Kanban' }, + { href: '/daily', label: 'Daily' }, + { href: '/weekly-manager', label: 'Weekly' }, + ...(isJiraConfigured ? [{ href: '/jira-dashboard', label: `Jira${jiraConfig?.projectKey ? ` (${jiraConfig.projectKey})` : ''}` }] : []), + { href: '/settings', label: 'Settings' } + ]; + + // Fonction pour déterminer si un lien est actif + const isActiveLink = (href: string) => { + if (href === '/') { + return pathname === '/'; + } + return pathname.startsWith(href); + }; + + // Fonction pour obtenir les classes CSS d'un lien (desktop) + const getLinkClasses = (href: string) => { + const baseClasses = "font-mono text-sm uppercase tracking-wider transition-colors px-3 py-1.5 rounded-md"; + + if (isActiveLink(href)) { + return `${baseClasses} text-[var(--primary)] bg-[var(--primary)]/10 border border-[var(--primary)]/30`; + } + + return `${baseClasses} text-[var(--muted-foreground)] hover:text-[var(--primary)] hover:bg-[var(--card-hover)]`; + }; + + // Fonction pour obtenir les classes CSS d'un lien (mobile) + const getMobileLinkClasses = (href: string) => { + const baseClasses = "font-mono text-sm uppercase tracking-wider transition-colors px-4 py-3 rounded-md block w-full text-left"; + + if (isActiveLink(href)) { + return `${baseClasses} text-[var(--primary)] bg-[var(--primary)]/10 border border-[var(--primary)]/30`; + } + + return `${baseClasses} text-[var(--muted-foreground)] hover:text-[var(--primary)] hover:bg-[var(--card-hover"]`; + }; + + if (variant === 'mobile') { + return ( + + ); + } + + // Desktop version + return ( + + ); +} diff --git a/src/components/ui/header/ThemeDropdown.tsx b/src/components/ui/header/ThemeDropdown.tsx new file mode 100644 index 0000000..b38060c --- /dev/null +++ b/src/components/ui/header/ThemeDropdown.tsx @@ -0,0 +1,74 @@ +'use client'; + +import { useState } from 'react'; +import { Check } from 'lucide-react'; +import { useTheme } from '@/contexts/ThemeContext'; +import { Theme, THEME_CONFIG, getThemeIcon } from '@/lib/ui-config'; + +interface ThemeDropdownProps { + variant: 'desktop' | 'mobile'; + className?: string; +} + +export function ThemeDropdown({ variant, className = '' }: ThemeDropdownProps) { + const { theme, setTheme } = useTheme(); + const [themeDropdownOpen, setThemeDropdownOpen] = useState(false); + + // Liste des thèmes disponibles avec leurs labels et icônes + const themes: { value: Theme; label: string; icon: string }[] = THEME_CONFIG.allThemes.map(themeValue => { + return { + value: themeValue, + label: themeValue.charAt(0).toUpperCase() + themeValue.slice(1).replace('_', ' '), + icon: getThemeIcon(themeValue) + }; + }); + + const buttonSize = variant === 'mobile' ? 'p-2' : 'p-1'; + + return ( +
+ + + {themeDropdownOpen && ( + <> + {/* Backdrop */} +
setThemeDropdownOpen(false)} + /> + {/* Dropdown */} +
+
+ {themes.map((themeOption) => ( + + ))} +
+
+ + )} +
+ ); +}