diff --git a/TODO.md b/TODO.md index 93639a5..78a61c9 100644 --- a/TODO.md +++ b/TODO.md @@ -127,8 +127,11 @@ - [x] Gestion des erreurs et timeouts API ### 3.3 Page d'accueil/dashboard -- [ ] Créer une page d'accueil moderne avec vue d'ensemble -- [ ] Widgets de statistiques (tâches par statut, priorité, etc.) +- [x] Créer une page d'accueil moderne avec vue d'ensemble +- [x] Widgets de statistiques (tâches par statut, priorité, etc.) +- [x] Déplacer kanban vers /kanban et créer nouveau dashboard à la racine +- [x] Actions rapides vers les différentes sections +- [x] Affichage des tâches récentes - [ ] Graphiques de productivité (tâches complétées par jour/semaine) - [ ] Indicateurs de performance personnels diff --git a/components/HomePageClient.tsx b/components/HomePageClient.tsx index 6c68cd0..d82a937 100644 --- a/components/HomePageClient.tsx +++ b/components/HomePageClient.tsx @@ -1,16 +1,13 @@ 'use client'; -import { useState } from 'react'; -import { KanbanBoardContainer } from '@/components/kanban/BoardContainer'; import { Header } from '@/components/ui/Header'; import { TasksProvider, useTasksContext } from '@/contexts/TasksContext'; -import { UserPreferencesProvider, useUserPreferences } from '@/contexts/UserPreferencesContext'; +import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext'; import { Task, Tag, TaskStats, UserPreferences } from '@/lib/types'; import { CreateTaskData } from '@/clients/tasks-client'; -import { CreateTaskForm } from '@/components/forms/CreateTaskForm'; -import { Button } from '@/components/ui/Button'; -import { JiraQuickFilter } from '@/components/kanban/JiraQuickFilter'; -import { FontSizeToggle } from '@/components/ui/FontSizeToggle'; +import { DashboardStats } from '@/components/dashboard/DashboardStats'; +import { QuickActions } from '@/components/dashboard/QuickActions'; +import { RecentTasks } from '@/components/dashboard/RecentTasks'; interface HomePageClientProps { initialTasks: Task[]; @@ -21,163 +18,32 @@ interface HomePageClientProps { function HomePageContent() { - const { stats, syncing, createTask, activeFiltersCount, kanbanFilters, setKanbanFilters } = useTasksContext(); - const { preferences, updateViewPreferences } = useUserPreferences(); - const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const { stats, syncing, createTask, tasks } = useTasksContext(); - // Extraire les préférences du context - const showFilters = preferences.viewPreferences.showFilters; - const showObjectives = preferences.viewPreferences.showObjectives; - const compactView = preferences.viewPreferences.compactView; - const swimlanesByTags = preferences.viewPreferences.swimlanesByTags; - - // Handlers pour les toggles (sauvegarde automatique via le context) - const handleToggleFilters = () => { - updateViewPreferences({ showFilters: !showFilters }); - }; - - const handleToggleObjectives = () => { - updateViewPreferences({ showObjectives: !showObjectives }); - }; - - const handleToggleCompactView = () => { - updateViewPreferences({ compactView: !compactView }); - }; - - const handleToggleSwimlanes = () => { - updateViewPreferences({ swimlanesByTags: !swimlanesByTags }); - }; - - // Handler pour la création de tâche depuis la barre de contrôles + // Handler pour la création de tâche const handleCreateTask = async (data: CreateTaskData) => { await createTask(data); - setIsCreateModalOpen(false); }; return (
- {/* Barre de contrôles de visibilité */} -
-
-
-
-
- - - -
- -
- {/* Raccourcis Jira */} - - - - - - - {/* Font Size Toggle */} - -
- -
- - {/* Bouton d'ajout de tâche */} - -
-
-
- -
- +
+ {/* Statistiques */} + + + {/* Actions rapides */} + + + {/* Tâches récentes */} +
- - {/* Modal de création de tâche */} - setIsCreateModalOpen(false)} - onSubmit={handleCreateTask} - loading={false} - />
); } diff --git a/components/dashboard/DashboardStats.tsx b/components/dashboard/DashboardStats.tsx new file mode 100644 index 0000000..e2f492e --- /dev/null +++ b/components/dashboard/DashboardStats.tsx @@ -0,0 +1,127 @@ +'use client'; + +import { TaskStats } from '@/lib/types'; +import { Card } from '@/components/ui/Card'; + +interface DashboardStatsProps { + stats: TaskStats; +} + +export function DashboardStats({ stats }: DashboardStatsProps) { + const totalTasks = stats.total; + const completionRate = totalTasks > 0 ? Math.round((stats.completed / totalTasks) * 100) : 0; + const inProgressRate = totalTasks > 0 ? Math.round((stats.inProgress / totalTasks) * 100) : 0; + + const statCards = [ + { + title: 'Total Tâches', + value: stats.total, + icon: '📋', + color: 'bg-blue-500', + textColor: 'text-blue-600' + }, + { + title: 'À Faire', + value: stats.todo, + icon: '⏳', + color: 'bg-gray-500', + textColor: 'text-gray-600' + }, + { + title: 'En Cours', + value: stats.inProgress, + icon: '🔄', + color: 'bg-orange-500', + textColor: 'text-orange-600' + }, + { + title: 'Terminées', + value: stats.completed, + icon: '✅', + color: 'bg-green-500', + textColor: 'text-green-600' + } + ]; + + return ( +
+ {statCards.map((stat, index) => ( + +
+
+

+ {stat.title} +

+

+ {stat.value} +

+
+
+ {stat.icon} +
+
+
+ ))} + + {/* Cartes de pourcentage */} + +

Taux de Completion

+
+
+ Terminées + {completionRate}% +
+
+
+
+ +
+ En Cours + {inProgressRate}% +
+
+
+
+
+ + + {/* Insights rapides */} + +

Aperçu Rapide

+
+
+ + + {stats.completed} tâches terminées sur {totalTasks} + +
+
+ + + {stats.inProgress} tâches en cours de traitement + +
+
+ + + {stats.todo} tâches en attente + +
+ {totalTasks > 0 && ( +
+ + Productivité: {completionRate}% de completion + +
+ )} +
+
+
+ ); +} diff --git a/components/dashboard/QuickActions.tsx b/components/dashboard/QuickActions.tsx new file mode 100644 index 0000000..7dcf507 --- /dev/null +++ b/components/dashboard/QuickActions.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/Button'; +import { CreateTaskForm } from '@/components/forms/CreateTaskForm'; +import { CreateTaskData } from '@/clients/tasks-client'; +import Link from 'next/link'; + +interface QuickActionsProps { + onCreateTask: (data: CreateTaskData) => Promise; +} + +export function QuickActions({ onCreateTask }: QuickActionsProps) { + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + + const handleCreateTask = async (data: CreateTaskData) => { + await onCreateTask(data); + setIsCreateModalOpen(false); + }; + + return ( + <> +
+ + + + + + + + + + + + + +
+ + setIsCreateModalOpen(false)} + onSubmit={handleCreateTask} + loading={false} + /> + + ); +} diff --git a/components/dashboard/RecentTasks.tsx b/components/dashboard/RecentTasks.tsx new file mode 100644 index 0000000..f4f20b8 --- /dev/null +++ b/components/dashboard/RecentTasks.tsx @@ -0,0 +1,146 @@ +'use client'; + +import { Task } from '@/lib/types'; +import { Card } from '@/components/ui/Card'; +import { TagDisplay } from '@/components/ui/TagDisplay'; +import { Badge } from '@/components/ui/Badge'; +import { useTasksContext } from '@/contexts/TasksContext'; +import { getPriorityConfig, getPriorityColorHex } from '@/lib/status-config'; +import Link from 'next/link'; + +interface RecentTasksProps { + tasks: Task[]; +} + +export function RecentTasks({ tasks }: RecentTasksProps) { + const { tags: availableTags } = useTasksContext(); + + // Prendre les 5 tâches les plus récentes (créées ou modifiées) + const recentTasks = tasks + .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) + .slice(0, 5); + + const getStatusColor = (status: string) => { + switch (status) { + case 'todo': return 'bg-gray-100 text-gray-800'; + case 'inProgress': return 'bg-orange-100 text-orange-800'; + case 'done': return 'bg-green-100 text-green-800'; + default: return 'bg-gray-100 text-gray-800'; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case 'todo': return 'À faire'; + case 'inProgress': return 'En cours'; + case 'done': return 'Terminé'; + default: return status; + } + }; + + const getPriorityStyle = (priority: string) => { + try { + const config = getPriorityConfig(priority as any); + const hexColor = getPriorityColorHex(config.color); + return { color: hexColor }; + } catch { + return { color: '#6b7280' }; // gray-500 par défaut + } + }; + + return ( + +
+

Tâches Récentes

+ + + +
+ + {recentTasks.length === 0 ? ( +
+ + + +

Aucune tâche disponible

+

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

+
+ ) : ( +
+ {recentTasks.map((task) => ( +
+
+
+
+

{task.title}

+ {task.source === 'jira' && ( + + Jira + + )} +
+ + {task.description && ( +

+ {task.description} +

+ )} + +
+ + {getStatusText(task.status)} + + + {task.priority && ( + + {(() => { + try { + return getPriorityConfig(task.priority as any).label; + } catch { + return task.priority; + } + })()} + + )} + + {task.tags && task.tags.length > 0 && ( +
+ + {task.tags.length > 2 && ( + + +{task.tags.length - 2} + + )} +
+ )} +
+
+ +
+ {new Date(task.updatedAt).toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'short' + })} +
+
+
+ ))} +
+ )} +
+ ); +} diff --git a/components/ui/Header.tsx b/components/ui/Header.tsx index 5ff5e35..3c477d3 100644 --- a/components/ui/Header.tsx +++ b/components/ui/Header.tsx @@ -40,6 +40,12 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s + Dashboard + + Kanban diff --git a/src/app/kanban/KanbanPageClient.tsx b/src/app/kanban/KanbanPageClient.tsx new file mode 100644 index 0000000..43c92b7 --- /dev/null +++ b/src/app/kanban/KanbanPageClient.tsx @@ -0,0 +1,196 @@ +'use client'; + +import { useState } from 'react'; +import { KanbanBoardContainer } from '@/components/kanban/BoardContainer'; +import { Header } from '@/components/ui/Header'; +import { TasksProvider, useTasksContext } from '@/contexts/TasksContext'; +import { UserPreferencesProvider, useUserPreferences } from '@/contexts/UserPreferencesContext'; +import { Task, Tag, TaskStats, UserPreferences } from '@/lib/types'; +import { CreateTaskData } from '@/clients/tasks-client'; +import { CreateTaskForm } from '@/components/forms/CreateTaskForm'; +import { Button } from '@/components/ui/Button'; +import { JiraQuickFilter } from '@/components/kanban/JiraQuickFilter'; +import { FontSizeToggle } from '@/components/ui/FontSizeToggle'; + +interface KanbanPageClientProps { + initialTasks: Task[]; + initialStats: TaskStats; + initialTags: (Tag & { usage: number })[]; + initialPreferences: UserPreferences; +} + +function KanbanPageContent() { + const { stats, syncing, createTask, activeFiltersCount, kanbanFilters, setKanbanFilters } = useTasksContext(); + const { preferences, updateViewPreferences } = useUserPreferences(); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + + // Extraire les préférences du context + const showFilters = preferences.viewPreferences.showFilters; + const showObjectives = preferences.viewPreferences.showObjectives; + const compactView = preferences.viewPreferences.compactView; + const swimlanesByTags = preferences.viewPreferences.swimlanesByTags; + + // Handlers pour les toggles (sauvegarde automatique via le context) + const handleToggleFilters = () => { + updateViewPreferences({ showFilters: !showFilters }); + }; + + const handleToggleObjectives = () => { + updateViewPreferences({ showObjectives: !showObjectives }); + }; + + const handleToggleCompactView = () => { + updateViewPreferences({ compactView: !compactView }); + }; + + const handleToggleSwimlanes = () => { + updateViewPreferences({ swimlanesByTags: !swimlanesByTags }); + }; + + // Handler pour la création de tâche depuis la barre de contrôles + const handleCreateTask = async (data: CreateTaskData) => { + await createTask(data); + setIsCreateModalOpen(false); + }; + + return ( +
+
+ + {/* Barre de contrôles de visibilité */} +
+
+
+
+
+ + + +
+ +
+ {/* Raccourcis Jira */} + + + + + + + {/* Font Size Toggle */} + +
+ +
+ + {/* Bouton d'ajout de tâche */} + +
+
+
+ +
+ +
+ + {/* Modal de création de tâche */} + setIsCreateModalOpen(false)} + onSubmit={handleCreateTask} + loading={false} + /> +
+ ); +} + +export function KanbanPageClient({ initialTasks, initialStats, initialTags, initialPreferences }: KanbanPageClientProps) { + return ( + + + + + + ); +} diff --git a/src/app/kanban/page.tsx b/src/app/kanban/page.tsx new file mode 100644 index 0000000..7a9e051 --- /dev/null +++ b/src/app/kanban/page.tsx @@ -0,0 +1,26 @@ +import { tasksService } from '@/services/tasks'; +import { tagsService } from '@/services/tags'; +import { userPreferencesService } from '@/services/user-preferences'; +import { KanbanPageClient } from './KanbanPageClient'; + +// Force dynamic rendering (no static generation) +export const dynamic = 'force-dynamic'; + +export default async function KanbanPage() { + // SSR - Récupération des données côté serveur + const [initialTasks, initialStats, initialTags, initialPreferences] = await Promise.all([ + tasksService.getTasks(), + tasksService.getTaskStats(), + tagsService.getTags(), + userPreferencesService.getAllPreferences() + ]); + + return ( + + ); +}