feat: extend task management with new statuses and centralized configuration
- Added `cancelled` and `freeze` statuses to `TasksResponse`, `HomePageClientProps`, and `useTasks` for comprehensive task tracking. - Updated task forms to dynamically load statuses using `getAllStatuses`, enhancing maintainability and reducing hardcoded values. - Refactored Kanban components to utilize centralized status configuration, improving consistency across the application. - Adjusted visibility toggle and swimlanes to reflect new status options, ensuring a seamless user experience.
This commit is contained in:
@@ -17,6 +17,8 @@ export interface TasksResponse {
|
|||||||
completed: number;
|
completed: number;
|
||||||
inProgress: number;
|
inProgress: number;
|
||||||
todo: number;
|
todo: number;
|
||||||
|
cancelled: number;
|
||||||
|
freeze: number;
|
||||||
completionRate: number;
|
completionRate: number;
|
||||||
};
|
};
|
||||||
count: number;
|
count: number;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ interface HomePageClientProps {
|
|||||||
completed: number;
|
completed: number;
|
||||||
inProgress: number;
|
inProgress: number;
|
||||||
todo: number;
|
todo: number;
|
||||||
|
cancelled: number;
|
||||||
|
freeze: number;
|
||||||
completionRate: number;
|
completionRate: number;
|
||||||
};
|
};
|
||||||
initialTags: (Tag & { usage: number })[];
|
initialTags: (Tag & { usage: number })[];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Input } from '@/components/ui/Input';
|
|||||||
import { TagInput } from '@/components/ui/TagInput';
|
import { TagInput } from '@/components/ui/TagInput';
|
||||||
import { TaskPriority, TaskStatus } from '@/lib/types';
|
import { TaskPriority, TaskStatus } from '@/lib/types';
|
||||||
import { CreateTaskData } from '@/clients/tasks-client';
|
import { CreateTaskData } from '@/clients/tasks-client';
|
||||||
|
import { getAllStatuses } from '@/lib/status-config';
|
||||||
|
|
||||||
interface CreateTaskFormProps {
|
interface CreateTaskFormProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -136,10 +137,11 @@ export function CreateTaskForm({ isOpen, onClose, onSubmit, loading = false }: C
|
|||||||
disabled={loading}
|
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"
|
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"
|
||||||
>
|
>
|
||||||
<option value="todo">À faire</option>
|
{getAllStatuses().map(statusConfig => (
|
||||||
<option value="in_progress">En cours</option>
|
<option key={statusConfig.key} value={statusConfig.key}>
|
||||||
<option value="done">Terminé</option>
|
{statusConfig.label}
|
||||||
<option value="cancelled">Annulé</option>
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Input } from '@/components/ui/Input';
|
|||||||
import { TagInput } from '@/components/ui/TagInput';
|
import { TagInput } from '@/components/ui/TagInput';
|
||||||
import { Task, TaskPriority, TaskStatus } from '@/lib/types';
|
import { Task, TaskPriority, TaskStatus } from '@/lib/types';
|
||||||
import { UpdateTaskData } from '@/clients/tasks-client';
|
import { UpdateTaskData } from '@/clients/tasks-client';
|
||||||
|
import { getAllStatuses } from '@/lib/status-config';
|
||||||
|
|
||||||
interface EditTaskFormProps {
|
interface EditTaskFormProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -148,10 +149,11 @@ export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false
|
|||||||
disabled={loading}
|
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"
|
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"
|
||||||
>
|
>
|
||||||
<option value="todo">À faire</option>
|
{getAllStatuses().map(statusConfig => (
|
||||||
<option value="in_progress">En cours</option>
|
<option key={statusConfig.key} value={statusConfig.key}>
|
||||||
<option value="done">Terminé</option>
|
{statusConfig.label}
|
||||||
<option value="cancelled">Annulé</option>
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { CreateTaskData } from '@/clients/tasks-client';
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
|
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
|
||||||
import { ColumnVisibilityToggle } from './ColumnVisibilityToggle';
|
import { ColumnVisibilityToggle } from './ColumnVisibilityToggle';
|
||||||
|
import { getAllStatuses } from '@/lib/status-config';
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragEndEvent,
|
DragEndEvent,
|
||||||
@@ -58,38 +59,13 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
|
|||||||
return grouped;
|
return grouped;
|
||||||
}, [tasks]);
|
}, [tasks]);
|
||||||
|
|
||||||
// Configuration des colonnes
|
// Configuration des colonnes basée sur la config centralisée
|
||||||
const allColumns: Array<{
|
const allColumns = useMemo(() => {
|
||||||
id: TaskStatus;
|
return getAllStatuses().map(statusConfig => ({
|
||||||
title: string;
|
id: statusConfig.key,
|
||||||
color: string;
|
tasks: tasksByStatus[statusConfig.key] || []
|
||||||
tasks: Task[];
|
}));
|
||||||
}> = [
|
}, [tasksByStatus]);
|
||||||
{
|
|
||||||
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 || []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filtrer les colonnes visibles
|
// Filtrer les colonnes visibles
|
||||||
const visibleColumns = getVisibleStatuses(allColumns);
|
const visibleColumns = getVisibleStatuses(allColumns);
|
||||||
@@ -156,7 +132,6 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
|
|||||||
{/* Toggle de visibilité des colonnes */}
|
{/* Toggle de visibilité des colonnes */}
|
||||||
<div className="px-6 pb-4">
|
<div className="px-6 pb-4">
|
||||||
<ColumnVisibilityToggle
|
<ColumnVisibilityToggle
|
||||||
statuses={allColumns}
|
|
||||||
hiddenStatuses={hiddenStatuses}
|
hiddenStatuses={hiddenStatuses}
|
||||||
onToggleStatus={toggleStatusVisibility}
|
onToggleStatus={toggleStatusVisibility}
|
||||||
/>
|
/>
|
||||||
@@ -168,8 +143,6 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
|
|||||||
<KanbanColumn
|
<KanbanColumn
|
||||||
key={column.id}
|
key={column.id}
|
||||||
id={column.id}
|
id={column.id}
|
||||||
title={column.title}
|
|
||||||
color={column.color}
|
|
||||||
tasks={column.tasks}
|
tasks={column.tasks}
|
||||||
onCreateTask={onCreateTask ? handleCreateTask : undefined}
|
onCreateTask={onCreateTask ? handleCreateTask : undefined}
|
||||||
onDeleteTask={onDeleteTask}
|
onDeleteTask={onDeleteTask}
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ import { Badge } from '@/components/ui/Badge';
|
|||||||
import { CreateTaskData } from '@/clients/tasks-client';
|
import { CreateTaskData } from '@/clients/tasks-client';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useDroppable } from '@dnd-kit/core';
|
import { useDroppable } from '@dnd-kit/core';
|
||||||
|
import { getStatusConfig, getTechStyle, getBadgeVariant } from '@/lib/status-config';
|
||||||
|
|
||||||
interface KanbanColumnProps {
|
interface KanbanColumnProps {
|
||||||
id: TaskStatus;
|
id: TaskStatus;
|
||||||
title: string;
|
|
||||||
color: string;
|
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
||||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||||
@@ -19,52 +18,18 @@ interface KanbanColumnProps {
|
|||||||
compactView?: boolean;
|
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);
|
const [showQuickAdd, setShowQuickAdd] = useState(false);
|
||||||
|
|
||||||
// Configuration de la zone droppable
|
// Configuration de la zone droppable
|
||||||
const { setNodeRef, isOver } = useDroppable({
|
const { setNodeRef, isOver } = useDroppable({
|
||||||
id: id,
|
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];
|
// Récupération de la config du statut
|
||||||
|
const statusConfig = getStatusConfig(id);
|
||||||
// Icônes tech
|
const style = getTechStyle(statusConfig.color);
|
||||||
const techIcons = {
|
const badgeVariant = getBadgeVariant(statusConfig.color);
|
||||||
todo: '⚡',
|
|
||||||
in_progress: '🔄',
|
|
||||||
done: '✓',
|
|
||||||
cancelled: '✕'
|
|
||||||
};
|
|
||||||
|
|
||||||
const badgeVariant = color === 'green' ? 'success' : color === 'blue' ? 'primary' : color === 'red' ? 'danger' : 'default';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-shrink-0 w-80 md:w-1/4 md:flex-1 h-full">
|
<div className="flex-shrink-0 w-80 md:w-1/4 md:flex-1 h-full">
|
||||||
@@ -80,7 +45,7 @@ export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTa
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`w-2 h-2 rounded-full ${style.accent.replace('text-', 'bg-')} animate-pulse`}></div>
|
<div className={`w-2 h-2 rounded-full ${style.accent.replace('text-', 'bg-')} animate-pulse`}></div>
|
||||||
<h3 className={`font-mono text-sm font-bold ${style.accent} uppercase tracking-wider`}>
|
<h3 className={`font-mono text-sm font-bold ${style.accent} uppercase tracking-wider`}>
|
||||||
{title}
|
{statusConfig.label} {statusConfig.icon}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -117,7 +82,7 @@ export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTa
|
|||||||
{tasks.length === 0 && !showQuickAdd ? (
|
{tasks.length === 0 && !showQuickAdd ? (
|
||||||
<div className="text-center py-20">
|
<div className="text-center py-20">
|
||||||
<div className={`w-16 h-16 mx-auto mb-4 rounded-full bg-slate-800 border-2 border-dashed ${style.border} flex items-center justify-center`}>
|
<div className={`w-16 h-16 mx-auto mb-4 rounded-full bg-slate-800 border-2 border-dashed ${style.border} flex items-center justify-center`}>
|
||||||
<span className={`text-2xl ${style.accent} opacity-50`}>{techIcons[id]}</span>
|
<span className={`text-2xl ${style.accent} opacity-50`}>{statusConfig.icon}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-mono text-slate-500 uppercase tracking-wide">NO DATA</p>
|
<p className="text-xs font-mono text-slate-500 uppercase tracking-wide">NO DATA</p>
|
||||||
<div className="mt-2 flex justify-center">
|
<div className="mt-2 flex justify-center">
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { TaskStatus } from '@/lib/types';
|
import { TaskStatus } from '@/lib/types';
|
||||||
|
import { getAllStatuses } from '@/lib/status-config';
|
||||||
|
|
||||||
interface ColumnVisibilityToggleProps {
|
interface ColumnVisibilityToggleProps {
|
||||||
statuses: { id: TaskStatus; title: string; color: string }[];
|
|
||||||
hiddenStatuses: Set<TaskStatus>;
|
hiddenStatuses: Set<TaskStatus>;
|
||||||
onToggleStatus: (status: TaskStatus) => void;
|
onToggleStatus: (status: TaskStatus) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ColumnVisibilityToggle({
|
export function ColumnVisibilityToggle({
|
||||||
statuses,
|
|
||||||
hiddenStatuses,
|
hiddenStatuses,
|
||||||
onToggleStatus,
|
onToggleStatus,
|
||||||
className = ""
|
className = ""
|
||||||
}: ColumnVisibilityToggleProps) {
|
}: ColumnVisibilityToggleProps) {
|
||||||
|
const statuses = getAllStatuses();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center gap-2 ${className}`}>
|
<div className={`flex items-center gap-2 ${className}`}>
|
||||||
<span className="text-sm font-mono font-medium text-slate-400">
|
<span className="text-sm font-mono font-medium text-slate-400">
|
||||||
Colonnes :
|
Colonnes :
|
||||||
</span>
|
</span>
|
||||||
{statuses.map(status => (
|
{statuses.map(statusConfig => (
|
||||||
<button
|
<button
|
||||||
key={status.id}
|
key={statusConfig.key}
|
||||||
onClick={() => onToggleStatus(status.id)}
|
onClick={() => onToggleStatus(statusConfig.key)}
|
||||||
className={`px-3 py-1 rounded-lg text-xs font-mono font-medium transition-colors ${
|
className={`px-3 py-1 rounded-lg text-xs font-mono font-medium transition-colors ${
|
||||||
hiddenStatuses.has(status.id)
|
hiddenStatuses.has(statusConfig.key)
|
||||||
? 'bg-slate-700/50 text-slate-500 hover:bg-slate-700'
|
? 'bg-slate-700/50 text-slate-500 hover:bg-slate-700'
|
||||||
: 'bg-blue-600/20 text-blue-300 border border-blue-500/30 hover:bg-blue-600/30'
|
: 'bg-blue-600/20 text-blue-300 border border-blue-500/30 hover:bg-blue-600/30'
|
||||||
}`}
|
}`}
|
||||||
title={hiddenStatuses.has(status.id) ? `Afficher ${status.title}` : `Masquer ${status.title}`}
|
title={hiddenStatuses.has(statusConfig.key) ? `Afficher ${statusConfig.label}` : `Masquer ${statusConfig.label}`}
|
||||||
>
|
>
|
||||||
{hiddenStatuses.has(status.id) ? '👁️🗨️' : '👁️'} {status.title}
|
{hiddenStatuses.has(statusConfig.key) ? '👁️🗨️' : '👁️'} {statusConfig.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useMemo, useState } from 'react';
|
|||||||
import { useTasksContext } from '@/contexts/TasksContext';
|
import { useTasksContext } from '@/contexts/TasksContext';
|
||||||
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
|
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
|
||||||
import { ColumnVisibilityToggle } from './ColumnVisibilityToggle';
|
import { ColumnVisibilityToggle } from './ColumnVisibilityToggle';
|
||||||
|
import { getAllStatuses } from '@/lib/status-config';
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragEndEvent,
|
DragEndEvent,
|
||||||
@@ -101,12 +102,14 @@ export function SwimlanesBoard({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const statuses: { id: TaskStatus; title: string; color: string }[] = [
|
// Configuration des statuts basée sur la config centralisée
|
||||||
{ id: 'todo', title: 'À faire', color: 'gray' },
|
const statuses = useMemo(() => {
|
||||||
{ id: 'in_progress', title: 'En cours', color: 'blue' },
|
return getAllStatuses().map(statusConfig => ({
|
||||||
{ id: 'done', title: 'Terminé', color: 'green' },
|
id: statusConfig.key,
|
||||||
{ id: 'cancelled', title: 'Annulé', color: 'red' }
|
title: statusConfig.label,
|
||||||
];
|
color: statusConfig.color
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Handlers pour le drag & drop
|
// Handlers pour le drag & drop
|
||||||
const handleDragStart = (event: DragStartEvent) => {
|
const handleDragStart = (event: DragStartEvent) => {
|
||||||
@@ -195,7 +198,6 @@ export function SwimlanesBoard({
|
|||||||
{/* Headers des colonnes avec boutons toggle */}
|
{/* Headers des colonnes avec boutons toggle */}
|
||||||
<div className="flex items-center justify-between px-6 pb-4">
|
<div className="flex items-center justify-between px-6 pb-4">
|
||||||
<ColumnVisibilityToggle
|
<ColumnVisibilityToggle
|
||||||
statuses={statuses}
|
|
||||||
hiddenStatuses={hiddenStatuses}
|
hiddenStatuses={hiddenStatuses}
|
||||||
onToggleStatus={toggleStatusVisibility}
|
onToggleStatus={toggleStatusVisibility}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { TaskStatus } from '@/lib/types';
|
import { TaskStatus } from '@/lib/types';
|
||||||
import { userPreferencesService } from '@/services/user-preferences';
|
import { userPreferencesService } from '@/services/user-preferences';
|
||||||
|
import { getAllStatuses } from '@/lib/status-config';
|
||||||
|
|
||||||
export function useColumnVisibility() {
|
export function useColumnVisibility() {
|
||||||
const [hiddenStatuses, setHiddenStatuses] = useState<Set<TaskStatus>>(new Set());
|
const [hiddenStatuses, setHiddenStatuses] = useState<Set<TaskStatus>>(new Set());
|
||||||
@@ -37,10 +38,15 @@ export function useColumnVisibility() {
|
|||||||
return !hiddenStatuses.has(status);
|
return !hiddenStatuses.has(status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAllAvailableStatuses = () => {
|
||||||
|
return getAllStatuses();
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hiddenStatuses,
|
hiddenStatuses,
|
||||||
toggleStatusVisibility,
|
toggleStatusVisibility,
|
||||||
getVisibleStatuses,
|
getVisibleStatuses,
|
||||||
isStatusVisible
|
isStatusVisible,
|
||||||
|
getAllAvailableStatuses
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ interface UseTasksState {
|
|||||||
completed: number;
|
completed: number;
|
||||||
inProgress: number;
|
inProgress: number;
|
||||||
todo: number;
|
todo: number;
|
||||||
|
cancelled: number;
|
||||||
|
freeze: number;
|
||||||
completionRate: number;
|
completionRate: number;
|
||||||
};
|
};
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -41,6 +43,8 @@ export function useTasks(
|
|||||||
completed: 0,
|
completed: 0,
|
||||||
inProgress: 0,
|
inProgress: 0,
|
||||||
todo: 0,
|
todo: 0,
|
||||||
|
cancelled: 0,
|
||||||
|
freeze: 0,
|
||||||
completionRate: 0
|
completionRate: 0
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -147,6 +151,8 @@ export function useTasks(
|
|||||||
completed: updatedTasks.filter(t => t.status === 'done').length,
|
completed: updatedTasks.filter(t => t.status === 'done').length,
|
||||||
inProgress: updatedTasks.filter(t => t.status === 'in_progress').length,
|
inProgress: updatedTasks.filter(t => t.status === 'in_progress').length,
|
||||||
todo: updatedTasks.filter(t => t.status === 'todo').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
|
completionRate: updatedTasks.length > 0
|
||||||
? Math.round((updatedTasks.filter(t => t.status === 'done').length / updatedTasks.length) * 100)
|
? Math.round((updatedTasks.filter(t => t.status === 'done').length / updatedTasks.length) * 100)
|
||||||
: 0
|
: 0
|
||||||
@@ -188,6 +194,8 @@ export function useTasks(
|
|||||||
completed: currentTasks.filter(t => t.status === 'done').length,
|
completed: currentTasks.filter(t => t.status === 'done').length,
|
||||||
inProgress: currentTasks.filter(t => t.status === 'in_progress').length,
|
inProgress: currentTasks.filter(t => t.status === 'in_progress').length,
|
||||||
todo: currentTasks.filter(t => t.status === 'todo').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
|
completionRate: currentTasks.length > 0
|
||||||
? Math.round((currentTasks.filter(t => t.status === 'done').length / currentTasks.length) * 100)
|
? Math.round((currentTasks.filter(t => t.status === 'done').length / currentTasks.length) * 100)
|
||||||
: 0
|
: 0
|
||||||
|
|||||||
116
lib/status-config.ts
Normal file
116
lib/status-config.ts
Normal file
@@ -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<TaskStatus, StatusConfig> = {
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Types de base pour les tâches
|
// 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 TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
|
||||||
export type TaskSource = 'reminders' | 'jira' | 'manual';
|
export type TaskSource = 'reminders' | 'jira' | 'manual';
|
||||||
|
|
||||||
|
|||||||
@@ -189,12 +189,13 @@ export class TasksService {
|
|||||||
* Récupère les statistiques des tâches
|
* Récupère les statistiques des tâches
|
||||||
*/
|
*/
|
||||||
async getTaskStats() {
|
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(),
|
||||||
prisma.task.count({ where: { status: 'done' } }),
|
prisma.task.count({ where: { status: 'done' } }),
|
||||||
prisma.task.count({ where: { status: 'in_progress' } }),
|
prisma.task.count({ where: { status: 'in_progress' } }),
|
||||||
prisma.task.count({ where: { status: 'todo' } }),
|
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 {
|
return {
|
||||||
@@ -203,6 +204,7 @@ export class TasksService {
|
|||||||
inProgress,
|
inProgress,
|
||||||
todo,
|
todo,
|
||||||
cancelled,
|
cancelled,
|
||||||
|
freeze,
|
||||||
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
|
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ interface TasksContextType {
|
|||||||
completed: number;
|
completed: number;
|
||||||
inProgress: number;
|
inProgress: number;
|
||||||
todo: number;
|
todo: number;
|
||||||
|
cancelled: number;
|
||||||
|
freeze: number;
|
||||||
completionRate: number;
|
completionRate: number;
|
||||||
};
|
};
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user