diff --git a/clients/tasks-client.ts b/clients/tasks-client.ts index 5697461..8978e25 100644 --- a/clients/tasks-client.ts +++ b/clients/tasks-client.ts @@ -17,6 +17,8 @@ export interface TasksResponse { completed: number; inProgress: number; todo: number; + cancelled: number; + freeze: number; completionRate: number; }; count: number; diff --git a/components/HomePageClient.tsx b/components/HomePageClient.tsx index 78efd74..eb35fc4 100644 --- a/components/HomePageClient.tsx +++ b/components/HomePageClient.tsx @@ -12,6 +12,8 @@ interface HomePageClientProps { completed: number; inProgress: number; todo: number; + cancelled: number; + freeze: number; completionRate: number; }; initialTags: (Tag & { usage: number })[]; diff --git a/components/forms/CreateTaskForm.tsx b/components/forms/CreateTaskForm.tsx index 1fef417..5b4ad77 100644 --- a/components/forms/CreateTaskForm.tsx +++ b/components/forms/CreateTaskForm.tsx @@ -7,6 +7,7 @@ import { Input } from '@/components/ui/Input'; import { TagInput } from '@/components/ui/TagInput'; import { TaskPriority, TaskStatus } from '@/lib/types'; import { CreateTaskData } from '@/clients/tasks-client'; +import { getAllStatuses } from '@/lib/status-config'; interface CreateTaskFormProps { isOpen: boolean; @@ -136,10 +137,11 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C disabled={loading} className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm" > - - - - + {getAllStatuses().map(statusConfig => ( + + ))} diff --git a/components/forms/EditTaskForm.tsx b/components/forms/EditTaskForm.tsx index 85e6d18..8ed25bf 100644 --- a/components/forms/EditTaskForm.tsx +++ b/components/forms/EditTaskForm.tsx @@ -7,6 +7,7 @@ import { Input } from '@/components/ui/Input'; import { TagInput } from '@/components/ui/TagInput'; import { Task, TaskPriority, TaskStatus } from '@/lib/types'; import { UpdateTaskData } from '@/clients/tasks-client'; +import { getAllStatuses } from '@/lib/status-config'; interface EditTaskFormProps { isOpen: boolean; @@ -148,10 +149,11 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false disabled={loading} className="w-full px-3 py-2 bg-slate-800/50 border border-slate-700/50 rounded-lg text-slate-100 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-500/50 hover:border-slate-600/50 transition-all duration-200 backdrop-blur-sm" > - - - - + {getAllStatuses().map(statusConfig => ( + + ))} diff --git a/components/kanban/Board.tsx b/components/kanban/Board.tsx index 2b70bb0..0d1c899 100644 --- a/components/kanban/Board.tsx +++ b/components/kanban/Board.tsx @@ -8,6 +8,7 @@ import { CreateTaskData } from '@/clients/tasks-client'; import { useMemo, useState } from 'react'; import { useColumnVisibility } from '@/hooks/useColumnVisibility'; import { ColumnVisibilityToggle } from './ColumnVisibilityToggle'; +import { getAllStatuses } from '@/lib/status-config'; import { DndContext, DragEndEvent, @@ -58,38 +59,13 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU return grouped; }, [tasks]); - // Configuration des colonnes - const allColumns: Array<{ - id: TaskStatus; - title: string; - color: string; - tasks: Task[]; - }> = [ - { - id: 'todo', - title: 'À faire', - color: 'gray', - tasks: tasksByStatus.todo || [] - }, - { - id: 'in_progress', - title: 'En cours', - color: 'blue', - tasks: tasksByStatus.in_progress || [] - }, - { - id: 'done', - title: 'Terminé', - color: 'green', - tasks: tasksByStatus.done || [] - }, - { - id: 'cancelled', - title: 'Annulé', - color: 'red', - tasks: tasksByStatus.cancelled || [] - } - ]; + // Configuration des colonnes basée sur la config centralisée + const allColumns = useMemo(() => { + return getAllStatuses().map(statusConfig => ({ + id: statusConfig.key, + tasks: tasksByStatus[statusConfig.key] || [] + })); + }, [tasksByStatus]); // Filtrer les colonnes visibles const visibleColumns = getVisibleStatuses(allColumns); @@ -156,7 +132,6 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU {/* Toggle de visibilité des colonnes */}
@@ -168,8 +143,6 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU Promise; onDeleteTask?: (taskId: string) => Promise; @@ -19,52 +18,18 @@ interface KanbanColumnProps { compactView?: boolean; } -export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, compactView = false }: KanbanColumnProps) { +export function KanbanColumn({ id, tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, compactView = false }: KanbanColumnProps) { const [showQuickAdd, setShowQuickAdd] = useState(false); // Configuration de la zone droppable const { setNodeRef, isOver } = useDroppable({ id: id, }); - // Couleurs tech/cyberpunk - const techStyles = { - gray: { - border: 'border-slate-700', - glow: 'shadow-slate-500/20', - accent: 'text-slate-400', - badge: 'bg-slate-800 text-slate-300 border border-slate-600' - }, - blue: { - border: 'border-cyan-500/30', - glow: 'shadow-cyan-500/20', - accent: 'text-cyan-400', - badge: 'bg-cyan-950 text-cyan-300 border border-cyan-500/30' - }, - green: { - border: 'border-emerald-500/30', - glow: 'shadow-emerald-500/20', - accent: 'text-emerald-400', - badge: 'bg-emerald-950 text-emerald-300 border border-emerald-500/30' - }, - red: { - border: 'border-red-500/30', - glow: 'shadow-red-500/20', - accent: 'text-red-400', - badge: 'bg-red-950 text-red-300 border border-red-500/30' - } - }; - const style = techStyles[color as keyof typeof techStyles]; - - // Icônes tech - const techIcons = { - todo: '⚡', - in_progress: '🔄', - done: '✓', - cancelled: '✕' - }; - - const badgeVariant = color === 'green' ? 'success' : color === 'blue' ? 'primary' : color === 'red' ? 'danger' : 'default'; + // Récupération de la config du statut + const statusConfig = getStatusConfig(id); + const style = getTechStyle(statusConfig.color); + const badgeVariant = getBadgeVariant(statusConfig.color); return (
@@ -80,7 +45,7 @@ export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTa

- {title} + {statusConfig.label} {statusConfig.icon}

@@ -117,7 +82,7 @@ export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTa {tasks.length === 0 && !showQuickAdd ? (
- {techIcons[id]} + {statusConfig.icon}

NO DATA

diff --git a/components/kanban/ColumnVisibilityToggle.tsx b/components/kanban/ColumnVisibilityToggle.tsx index fc25c29..0b6ebb1 100644 --- a/components/kanban/ColumnVisibilityToggle.tsx +++ b/components/kanban/ColumnVisibilityToggle.tsx @@ -1,37 +1,38 @@ 'use client'; import { TaskStatus } from '@/lib/types'; +import { getAllStatuses } from '@/lib/status-config'; interface ColumnVisibilityToggleProps { - statuses: { id: TaskStatus; title: string; color: string }[]; hiddenStatuses: Set; onToggleStatus: (status: TaskStatus) => void; className?: string; } export function ColumnVisibilityToggle({ - statuses, hiddenStatuses, onToggleStatus, className = "" }: ColumnVisibilityToggleProps) { + const statuses = getAllStatuses(); + return (
Colonnes : - {statuses.map(status => ( + {statuses.map(statusConfig => ( ))}
diff --git a/components/kanban/SwimlanesBoard.tsx b/components/kanban/SwimlanesBoard.tsx index 56e2dc2..1bbc475 100644 --- a/components/kanban/SwimlanesBoard.tsx +++ b/components/kanban/SwimlanesBoard.tsx @@ -6,6 +6,7 @@ import { useMemo, useState } from 'react'; import { useTasksContext } from '@/contexts/TasksContext'; import { useColumnVisibility } from '@/hooks/useColumnVisibility'; import { ColumnVisibilityToggle } from './ColumnVisibilityToggle'; +import { getAllStatuses } from '@/lib/status-config'; import { DndContext, DragEndEvent, @@ -101,12 +102,14 @@ export function SwimlanesBoard({ }) ); - const statuses: { id: TaskStatus; title: string; color: string }[] = [ - { id: 'todo', title: 'À faire', color: 'gray' }, - { id: 'in_progress', title: 'En cours', color: 'blue' }, - { id: 'done', title: 'Terminé', color: 'green' }, - { id: 'cancelled', title: 'Annulé', color: 'red' } - ]; + // Configuration des statuts basée sur la config centralisée + const statuses = useMemo(() => { + return getAllStatuses().map(statusConfig => ({ + id: statusConfig.key, + title: statusConfig.label, + color: statusConfig.color + })); + }, []); // Handlers pour le drag & drop const handleDragStart = (event: DragStartEvent) => { @@ -195,7 +198,6 @@ export function SwimlanesBoard({ {/* Headers des colonnes avec boutons toggle */}
diff --git a/hooks/useColumnVisibility.ts b/hooks/useColumnVisibility.ts index e621de6..e25175c 100644 --- a/hooks/useColumnVisibility.ts +++ b/hooks/useColumnVisibility.ts @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { TaskStatus } from '@/lib/types'; import { userPreferencesService } from '@/services/user-preferences'; +import { getAllStatuses } from '@/lib/status-config'; export function useColumnVisibility() { const [hiddenStatuses, setHiddenStatuses] = useState>(new Set()); @@ -37,10 +38,15 @@ export function useColumnVisibility() { return !hiddenStatuses.has(status); }; + const getAllAvailableStatuses = () => { + return getAllStatuses(); + }; + return { hiddenStatuses, toggleStatusVisibility, getVisibleStatuses, - isStatusVisible + isStatusVisible, + getAllAvailableStatuses }; } diff --git a/hooks/useTasks.ts b/hooks/useTasks.ts index 543a186..67fca2c 100644 --- a/hooks/useTasks.ts +++ b/hooks/useTasks.ts @@ -11,6 +11,8 @@ interface UseTasksState { completed: number; inProgress: number; todo: number; + cancelled: number; + freeze: number; completionRate: number; }; loading: boolean; @@ -41,6 +43,8 @@ export function useTasks( completed: 0, inProgress: 0, todo: 0, + cancelled: 0, + freeze: 0, completionRate: 0 }, loading: false, @@ -147,6 +151,8 @@ export function useTasks( completed: updatedTasks.filter(t => t.status === 'done').length, inProgress: updatedTasks.filter(t => t.status === 'in_progress').length, todo: updatedTasks.filter(t => t.status === 'todo').length, + cancelled: updatedTasks.filter(t => t.status === 'cancelled').length, + freeze: updatedTasks.filter(t => t.status === 'freeze').length, completionRate: updatedTasks.length > 0 ? Math.round((updatedTasks.filter(t => t.status === 'done').length / updatedTasks.length) * 100) : 0 @@ -188,6 +194,8 @@ export function useTasks( completed: currentTasks.filter(t => t.status === 'done').length, inProgress: currentTasks.filter(t => t.status === 'in_progress').length, todo: currentTasks.filter(t => t.status === 'todo').length, + cancelled: currentTasks.filter(t => t.status === 'cancelled').length, + freeze: currentTasks.filter(t => t.status === 'freeze').length, completionRate: currentTasks.length > 0 ? Math.round((currentTasks.filter(t => t.status === 'done').length / currentTasks.length) * 100) : 0 diff --git a/lib/status-config.ts b/lib/status-config.ts new file mode 100644 index 0000000..7d4713f --- /dev/null +++ b/lib/status-config.ts @@ -0,0 +1,116 @@ +import { TaskStatus } from './types'; + +export interface StatusConfig { + key: TaskStatus; + label: string; + icon: string; + color: 'gray' | 'blue' | 'green' | 'red' | 'purple'; + order: number; +} + +export const STATUS_CONFIG: Record = { + todo: { + key: 'todo', + label: 'À faire', + icon: '⚡', + color: 'gray', + order: 1 + }, + in_progress: { + key: 'in_progress', + label: 'En cours', + icon: '🔄', + color: 'blue', + order: 2 + }, + freeze: { + key: 'freeze', + label: 'Gelé', + icon: '🧊', + color: 'purple', + order: 3 + }, + done: { + key: 'done', + label: 'Terminé', + icon: '✓', + color: 'green', + order: 4 + }, + cancelled: { + key: 'cancelled', + label: 'Annulé', + icon: '✕', + color: 'red', + order: 5 + } +} as const; + +// Utilitaires pour récupérer facilement les infos +export const getStatusConfig = (status: TaskStatus): StatusConfig => { + return STATUS_CONFIG[status]; +}; + +export const getAllStatuses = (): StatusConfig[] => { + return Object.values(STATUS_CONFIG).sort((a, b) => a.order - b.order); +}; + +export const getStatusLabel = (status: TaskStatus): string => { + return STATUS_CONFIG[status].label; +}; + +export const getStatusIcon = (status: TaskStatus): string => { + return STATUS_CONFIG[status].icon; +}; + +export const getStatusColor = (status: TaskStatus): StatusConfig['color'] => { + return STATUS_CONFIG[status].color; +}; + +// Configuration des couleurs tech/cyberpunk +export const TECH_STYLES = { + gray: { + border: 'border-slate-700', + glow: 'shadow-slate-500/20', + accent: 'text-slate-400', + badge: 'bg-slate-800 text-slate-300 border border-slate-600' + }, + blue: { + border: 'border-cyan-500/30', + glow: 'shadow-cyan-500/20', + accent: 'text-cyan-400', + badge: 'bg-cyan-950 text-cyan-300 border border-cyan-500/30' + }, + green: { + border: 'border-emerald-500/30', + glow: 'shadow-emerald-500/20', + accent: 'text-emerald-400', + badge: 'bg-emerald-950 text-emerald-300 border border-emerald-500/30' + }, + red: { + border: 'border-red-500/30', + glow: 'shadow-red-500/20', + accent: 'text-red-400', + badge: 'bg-red-950 text-red-300 border border-red-500/30' + }, + purple: { + border: 'border-purple-500/30', + glow: 'shadow-purple-500/20', + accent: 'text-purple-400', + badge: 'bg-purple-950 text-purple-300 border border-purple-500/30' + } +} as const; + +export const getTechStyle = (color: StatusConfig['color']) => { + return TECH_STYLES[color]; +}; + +export const getBadgeVariant = (color: StatusConfig['color']): 'success' | 'primary' | 'danger' | 'default' => { + switch (color) { + case 'green': return 'success'; + case 'blue': + case 'purple': return 'primary'; + case 'red': return 'danger'; + default: return 'default'; + } +}; diff --git a/lib/types.ts b/lib/types.ts index b2c1efc..49e3912 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,5 +1,6 @@ // Types de base pour les tâches -export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'cancelled'; +// Note: TaskStatus est maintenant géré par la configuration centralisée dans lib/status-config.ts +export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'cancelled' | 'freeze'; export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'; export type TaskSource = 'reminders' | 'jira' | 'manual'; diff --git a/services/tasks.ts b/services/tasks.ts index 4943ddd..85967b4 100644 --- a/services/tasks.ts +++ b/services/tasks.ts @@ -189,12 +189,13 @@ export class TasksService { * Récupère les statistiques des tâches */ async getTaskStats() { - const [total, completed, inProgress, todo, cancelled] = await Promise.all([ + const [total, completed, inProgress, todo, cancelled, freeze] = await Promise.all([ prisma.task.count(), prisma.task.count({ where: { status: 'done' } }), prisma.task.count({ where: { status: 'in_progress' } }), prisma.task.count({ where: { status: 'todo' } }), - prisma.task.count({ where: { status: 'cancelled' } }) + prisma.task.count({ where: { status: 'cancelled' } }), + prisma.task.count({ where: { status: 'freeze' } }) ]); return { @@ -203,6 +204,7 @@ export class TasksService { inProgress, todo, cancelled, + freeze, completionRate: total > 0 ? Math.round((completed / total) * 100) : 0 }; } diff --git a/src/contexts/TasksContext.tsx b/src/contexts/TasksContext.tsx index 8f88049..3f8303c 100644 --- a/src/contexts/TasksContext.tsx +++ b/src/contexts/TasksContext.tsx @@ -15,6 +15,8 @@ interface TasksContextType { completed: number; inProgress: number; todo: number; + cancelled: number; + freeze: number; completionRate: number; }; loading: boolean;