From 8519ec094fa970fb3ae2d52b81439b24d9e55e67 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Tue, 30 Sep 2025 23:34:03 +0200 Subject: [PATCH] feat: add line clamp utility and integrate RecentTaskTimeline component - Added a new CSS utility for line clamping to `globals.css` for better text overflow handling. - Integrated `WelcomeSection` into `HomePageClient` for enhanced user experience. - Replaced `TaskCard` with `RecentTaskTimeline` in `RecentTasks` for improved task visualization. - Updated `ui/index.ts` to export `RecentTaskTimeline` and showcased it in `CardsSection` and `FeedbackSection`. --- src/app/globals.css | 8 + src/components/HomePageClient.tsx | 4 + src/components/dashboard/RecentTasks.tsx | 62 ++--- src/components/dashboard/WelcomeSection.tsx | 213 ++++++++++++++++++ .../ui-showcase/sections/CardsSection.tsx | 49 ++++ .../ui-showcase/sections/FeedbackSection.tsx | 5 +- src/components/ui/RecentTaskTimeline.tsx | 129 +++++++++++ src/components/ui/index.ts | 1 + 8 files changed, 428 insertions(+), 43 deletions(-) create mode 100644 src/components/dashboard/WelcomeSection.tsx create mode 100644 src/components/ui/RecentTaskTimeline.tsx diff --git a/src/app/globals.css b/src/app/globals.css index 23bfa1c..e0d0cb1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -544,3 +544,11 @@ body { .animate-glow { animation: glow 2s ease-in-out infinite; } + +/* Line clamp utilities */ +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/src/components/HomePageClient.tsx b/src/components/HomePageClient.tsx index ef4f1f5..338bf94 100644 --- a/src/components/HomePageClient.tsx +++ b/src/components/HomePageClient.tsx @@ -8,6 +8,7 @@ import { DashboardStats } from '@/components/dashboard/DashboardStats'; import { QuickActions } from '@/components/dashboard/QuickActions'; import { RecentTasks } from '@/components/dashboard/RecentTasks'; import { ProductivityAnalytics } from '@/components/dashboard/ProductivityAnalytics'; +import { WelcomeSection } from '@/components/dashboard/WelcomeSection'; import { ProductivityMetrics } from '@/services/analytics/analytics'; import { DeadlineMetrics } from '@/services/analytics/deadline-analytics'; import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts'; @@ -55,6 +56,9 @@ function HomePageContent({ productivityMetrics, deadlineMetrics }: { />
+ {/* Section de bienvenue */} + + {/* Statistiques */} diff --git a/src/components/dashboard/RecentTasks.tsx b/src/components/dashboard/RecentTasks.tsx index acf58ec..f310ddc 100644 --- a/src/components/dashboard/RecentTasks.tsx +++ b/src/components/dashboard/RecentTasks.tsx @@ -2,7 +2,7 @@ import { Task } from '@/lib/types'; import { Card } from '@/components/ui/Card'; -import { TaskCard } from '@/components/ui'; +import { RecentTaskTimeline } from '@/components/ui/RecentTaskTimeline'; import { useTasksContext } from '@/contexts/TasksContext'; import Link from 'next/link'; @@ -39,47 +39,27 @@ export function RecentTasks({ tasks }: RecentTasksProps) {

Créez votre première tâche pour commencer

) : ( -
+
{recentTasks.map((task) => ( -
- { - // Navigation vers le kanban avec la tâche sélectionnée - window.location.href = `/kanban?taskId=${task.id}`; - }} - /> - - {/* Overlay avec lien vers le kanban */} - -
- - - -
- -
+ { + // Navigation vers le kanban avec la tâche sélectionnée + window.location.href = `/kanban?taskId=${task.id}`; + }} + /> ))}
)} diff --git a/src/components/dashboard/WelcomeSection.tsx b/src/components/dashboard/WelcomeSection.tsx new file mode 100644 index 0000000..4fe1ddb --- /dev/null +++ b/src/components/dashboard/WelcomeSection.tsx @@ -0,0 +1,213 @@ +'use client'; + +import { useSession } from 'next-auth/react'; +import { useState, useEffect } from 'react'; + +const WELCOME_GREETINGS = [ + "Bienvenue", + "Salut", + "Coucou", + "Hello", + "Hey", + "Salutations", + "Bonjour", + "Hola", + "Ciao", + "Yo", +]; + +const WELCOME_MESSAGES = [ + "Prêt à conquérir la journée ? 🚀", + "Votre productivité vous attend ! ⚡", + "C'est parti pour une journée productive ! 💪", + "Organisons ensemble vos tâches ! 📋", + "Votre tableau de bord vous attend ! 🎯", + "Prêt à faire la différence ? ✨", + "Concentrons-nous sur l'essentiel ! 🎯", + "Une nouvelle journée, de nouvelles opportunités ! 🌟", + "Votre succès commence ici ! 🏆", + "Transformons vos objectifs en réalité ! 🎪", + "C'est l'heure de briller ! ⭐", + "Votre efficacité n'attend que vous ! 🔥", + "Organisons votre succès ! 📊", + "Prêt à dépasser vos limites ? 🚀", + "Votre productivité vous remercie ! 🙏", + "C'est parti pour une journée exceptionnelle ! 🌈", + "Votre organisation parfaite vous attend ! 🎨", + "Prêt à accomplir de grandes choses ? 🏅", + "Votre motivation est votre force ! 💎", + "Créons ensemble votre succès ! 🎭", + // Messages humoristiques + "Attention, productivité en approche ! 🚨", + "Votre cerveau va être bien occupé ! 🧠", + "Préparez-vous à être impressionné par vous-même ! 😎", + "Mode super-héros activé ! 🦸‍♂️", + "Votre liste de tâches tremble déjà ! 😱", + "Prêt à faire exploser vos objectifs ? 💥", + "Votre procrastination n'a qu'à bien se tenir ! 😤", + "C'est l'heure de montrer qui est le boss ! 👑", + "Votre café peut attendre, vos tâches non ! ☕", + "Prêt à devenir la légende de la productivité ? 🏆", + "Attention, efficacité maximale détectée ! ⚡", + "Votre motivation est plus forte que le café ! ☕", + "Prêt à faire rougir votre calendrier ? 📅", + "Votre énergie positive est contagieuse ! 😄", + "C'est parti pour une journée épique ! 🎬", + "Votre détermination brille plus que le soleil ! ☀️", + "Prêt à transformer le chaos en ordre ? 🎯", + "Votre focus est plus précis qu'un laser ! 🔴", + "C'est l'heure de montrer vos super-pouvoirs ! 🦸‍♀️", + "Votre organisation va faire des jaloux ! 😏", + "Prêt à devenir le héros de votre propre histoire ? 📚", + "Votre productivité va battre des records ! 🏃‍♂️", + "Attention, génie au travail ! 🧪", + "Votre créativité déborde ! 🎨", + "Prêt à faire trembler vos deadlines ? ⏰", + "Votre énergie positive illumine la pièce ! 💡", + "C'est parti pour une aventure productive ! 🗺️", + "Votre motivation est plus forte que la gravité ! 🌍", + "Prêt à devenir le maître de l'organisation ? 🎭", + "Votre efficacité va faire des étincelles ! ✨" +]; + +const TIME_BASED_MESSAGES = { + morning: [ + "Bonjour ! Une belle journée vous attend ! ☀️", + "Réveillez-vous, c'est l'heure de briller ! 🌅", + "Le matin est le moment parfait pour commencer ! 🌄", + "Une nouvelle journée, de nouvelles possibilités ! 🌞", + "Bonjour ! Votre café vous attend ! ☕", + "Réveillez-vous, les tâches n'attendent pas ! ⏰", + "Bonjour ! Prêt à conquérir le monde ? 🌍", + "Le matin, tout est possible ! 🌅", + "Bonjour ! Votre motivation vous appelle ! 📞", + "Réveillez-vous, c'est l'heure de la productivité ! ⚡" + ], + afternoon: [ + "Bon après-midi ! Continuons sur cette lancée ! 🌤️", + "L'après-midi est parfait pour avancer ! ☀️", + "Encore quelques heures pour accomplir vos objectifs ! ⏰", + "L'énergie de l'après-midi vous porte ! 💪", + "Bon après-midi ! Le momentum continue ! 🚀", + "L'après-midi, c'est l'heure de l'efficacité ! ⚡", + "Bon après-midi ! Votre café de 14h vous attend ! ☕", + "L'après-midi, tout s'accélère ! 🏃‍♂️", + "Bon après-midi ! Prêt pour la deuxième mi-temps ? ⚽", + "L'après-midi, c'est l'heure de briller ! ✨" + ], + evening: [ + "Bonsoir ! Terminons la journée en beauté ! 🌆", + "Le soir est idéal pour finaliser vos tâches ! 🌇", + "Une dernière poussée avant la fin de journée ! 🌃", + "Le crépuscule vous accompagne ! 🌅", + "Bonsoir ! Prêt pour le sprint final ? 🏃‍♀️", + "Le soir, c'est l'heure de la victoire ! 🏆", + "Bonsoir ! Votre récompense vous attend ! 🎁", + "Le soir, on termine en héros ! 🦸‍♂️", + "Bonsoir ! Prêt à clôturer cette journée ? 📝", + "Le soir, c'est l'heure de la satisfaction ! 😌" + ] +}; + +function getTimeBasedMessage(): string { + const hour = new Date().getHours(); + + if (hour >= 5 && hour < 12) { + return TIME_BASED_MESSAGES.morning[Math.floor(Math.random() * TIME_BASED_MESSAGES.morning.length)]; + } else if (hour >= 12 && hour < 18) { + return TIME_BASED_MESSAGES.afternoon[Math.floor(Math.random() * TIME_BASED_MESSAGES.afternoon.length)]; + } else { + return TIME_BASED_MESSAGES.evening[Math.floor(Math.random() * TIME_BASED_MESSAGES.evening.length)]; + } +} + +function getRandomWelcomeMessage(): string { + return WELCOME_MESSAGES[Math.floor(Math.random() * WELCOME_MESSAGES.length)]; +} + +function getRandomGreeting(): string { + return WELCOME_GREETINGS[Math.floor(Math.random() * WELCOME_GREETINGS.length)]; +} + +export function WelcomeSection() { + const { data: session } = useSession(); + const [welcomeMessage, setWelcomeMessage] = useState(''); + const [timeMessage, setTimeMessage] = useState(''); + const [greeting, setGreeting] = useState(''); + + useEffect(() => { + // Générer un message de bienvenue aléatoire + setWelcomeMessage(getRandomWelcomeMessage()); + setTimeMessage(getTimeBasedMessage()); + setGreeting(getRandomGreeting()); + }, []); + + if (!session?.user) { + return null; + } + + const displayName = session.user.name || + `${session.user.firstName || ''} ${session.user.lastName || ''}`.trim() || + session.user.email; + + return ( +
+
+ {/* Avatar */} +
+ {session.user.avatar ? ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Avatar +
+ + + +
+
+ ) : ( +
+ + + +
+ )} +
+ + {/* Message de bienvenue */} +
+

+ {greeting}, {displayName} ! +

+

+ {timeMessage} +

+

+ {welcomeMessage} +

+
+ + {/* Bouton pour changer le message */} +
+ +
+
+
+ ); +} diff --git a/src/components/ui-showcase/sections/CardsSection.tsx b/src/components/ui-showcase/sections/CardsSection.tsx index 8259a22..88e7dae 100644 --- a/src/components/ui-showcase/sections/CardsSection.tsx +++ b/src/components/ui-showcase/sections/CardsSection.tsx @@ -9,6 +9,7 @@ import { MetricCard } from '@/components/ui/MetricCard'; import { AchievementCard } from '@/components/ui/AchievementCard'; import { ChallengeCard } from '@/components/ui/ChallengeCard'; import { SkeletonCard } from '@/components/ui/SkeletonCard'; +import { RecentTaskTimeline } from '@/components/ui/RecentTaskTimeline'; import { AchievementData } from '@/components/ui/AchievementCard'; import { ChallengeData } from '@/components/ui/ChallengeCard'; @@ -224,6 +225,54 @@ export function CardsSection() { />
+ + {/* Recent Task Timeline */} +
+

Recent Task Timeline

+
+ + + + +
+
+ {/* Skeleton Cards */}

Skeleton Cards

diff --git a/src/components/ui-showcase/sections/FeedbackSection.tsx b/src/components/ui-showcase/sections/FeedbackSection.tsx index 5e60da3..b7a894a 100644 --- a/src/components/ui-showcase/sections/FeedbackSection.tsx +++ b/src/components/ui-showcase/sections/FeedbackSection.tsx @@ -66,9 +66,10 @@ export function FeedbackSection() {

Alert Banner

console.log('Dismiss alert:', id)} - onAction={(id, action) => console.log('Alert action:', id, action)} + variant="warning" + onItemClick={(item) => console.log('Alert clicked:', item)} />
diff --git a/src/components/ui/RecentTaskTimeline.tsx b/src/components/ui/RecentTaskTimeline.tsx new file mode 100644 index 0000000..6adcc7c --- /dev/null +++ b/src/components/ui/RecentTaskTimeline.tsx @@ -0,0 +1,129 @@ +import { HTMLAttributes } from 'react'; +import { cn } from '@/lib/utils'; +import { StatusBadge } from './StatusBadge'; +import { TaskStatus } from '@/lib/types'; +import { PriorityBadge } from './PriorityBadge'; +import { TagDisplay } from './TagDisplay'; +import { formatDateForDisplay } from '@/lib/date-utils'; + +interface RecentTaskTimelineProps extends HTMLAttributes { + title: string; + description?: string; + tags?: string[]; + priority?: 'low' | 'medium' | 'high'; + status?: TaskStatus; + dueDate?: Date; + completedAt?: Date; + updatedAt?: Date; + source?: 'manual' | 'jira' | 'tfs' | 'reminders'; + jiraKey?: string; + tfsPullRequestId?: number; + onClick?: () => void; + className?: string; + availableTags?: Array<{ id: string; name: string; color: string }>; +} + +export function RecentTaskTimeline({ + title, + description, + tags = [], + priority, + status = 'todo', + dueDate, + completedAt, + updatedAt, + source = 'manual', + jiraKey, + tfsPullRequestId, + onClick, + className, + availableTags = [], + ...props +}: RecentTaskTimelineProps) { + const getSourceIcon = () => { + switch (source) { + case 'jira': + return
; + case 'tfs': + return
; + case 'reminders': + return
; + default: + return
; + } + }; + + const getTimeInfo = () => { + if (completedAt) { + return `Terminé ${formatDateForDisplay(completedAt)}`; + } + if (dueDate) { + return `Échéance ${formatDateForDisplay(dueDate)}`; + } + if (updatedAt) { + return `Modifié ${formatDateForDisplay(updatedAt)}`; + } + return null; + }; + + return ( +
+ {/* Timeline dot */} +
+ {getSourceIcon()} +
+ + {/* Content */} +
+
+

+ {title} +

+
+ {priority && } + +
+
+ + {description && ( +

+ {description} +

+ )} + + {tags.length > 0 && ( +
+ +
+ )} + +
+
+ {jiraKey && {jiraKey}} + {tfsPullRequestId && PR #{tfsPullRequestId}} +
+ {getTimeInfo()} +
+
+ + {/* Arrow indicator */} +
+ + + +
+
+ ); +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index 1b1d4ed..c29e1d3 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -10,6 +10,7 @@ export { StatCard } from './StatCard'; export { ProgressBar } from './ProgressBar'; export { ActionCard } from './ActionCard'; export { TaskCard } from './TaskCard'; +export { RecentTaskTimeline } from './RecentTaskTimeline'; export { MetricCard } from './MetricCard'; // Composants Kanban