Files
towercontrol/src/contexts/TasksContext.tsx
Julien Froidefond c8cacf1714 feat: add sorting functionality to KanbanFilters
- Enhanced `KanbanFilters` to include sorting options, allowing users to sort tasks dynamically.
- Implemented dropdown for sorting with options rendered via portal to manage z-index issues.
- Updated `TasksContext` to handle sorting preferences and apply sorting logic to both pinned and regular tasks.
- Added `sortBy` property to `KanbanFilters` and user preferences for persistent sorting settings.
2025-09-15 08:32:01 +02:00

201 lines
6.2 KiB
TypeScript

'use client';
import { createContext, useContext, ReactNode, useState, useMemo, useEffect } from 'react';
import { useTasks } from '@/hooks/useTasks';
import { useTags } from '@/hooks/useTags';
import { userPreferencesService } from '@/services/user-preferences';
import { Task, Tag } from '@/lib/types';
import { CreateTaskData, UpdateTaskData, TaskFilters } from '@/clients/tasks-client';
import { KanbanFilters } from '@/components/kanban/KanbanFilters';
import { sortTasks, getSortOption, DEFAULT_SORT, createSortKey } from '@/lib/sort-config';
interface TasksContextType {
tasks: Task[];
stats: {
total: number;
completed: number;
inProgress: number;
todo: number;
cancelled: number;
freeze: number;
completionRate: number;
};
loading: boolean;
syncing: boolean;
error: string | null;
createTask: (data: CreateTaskData) => Promise<Task | null>;
updateTask: (data: UpdateTaskData) => Promise<Task | null>;
updateTaskOptimistic: (data: UpdateTaskData) => Promise<Task | null>;
deleteTask: (taskId: string) => Promise<void>;
refreshTasks: () => Promise<void>;
setFilters: (filters: TaskFilters) => void;
// Kanban filters
kanbanFilters: KanbanFilters;
setKanbanFilters: (filters: KanbanFilters) => void;
filteredTasks: Task[];
pinnedTasks: Task[]; // Tâches avec tags épinglés (objectifs)
// Tags
tags: Tag[];
tagsLoading: boolean;
tagsError: string | null;
}
const TasksContext = createContext<TasksContextType | null>(null);
interface TasksProviderProps {
children: ReactNode;
initialTasks: Task[];
initialStats: TasksContextType['stats'];
initialTags?: (Tag & { usage: number })[];
}
export function TasksProvider({ children, initialTasks, initialStats, initialTags }: TasksProviderProps) {
const tasksState = useTasks(
{ limit: 20 },
{ tasks: initialTasks, stats: initialStats }
);
const { tags, loading: tagsLoading, error: tagsError } = useTags(initialTags);
// État des filtres Kanban avec persistance
const [kanbanFilters, setKanbanFilters] = useState<KanbanFilters>({});
// Charger les préférences au montage
useEffect(() => {
const savedFilters = userPreferencesService.getKanbanFilters();
const savedViewPrefs = userPreferencesService.getViewPreferences();
setKanbanFilters({
search: savedFilters.search,
tags: savedFilters.tags,
priorities: savedFilters.priorities,
showCompleted: savedFilters.showCompleted,
sortBy: savedFilters.sortBy || createSortKey('priority', 'desc'), // Tri par défaut
compactView: savedViewPrefs.compactView,
swimlanesByTags: savedViewPrefs.swimlanesByTags
});
}, []);
// Fonction pour mettre à jour les filtres avec persistance
const updateKanbanFilters = (newFilters: KanbanFilters) => {
setKanbanFilters(newFilters);
// Sauvegarder les filtres
userPreferencesService.saveKanbanFilters({
search: newFilters.search,
tags: newFilters.tags,
priorities: newFilters.priorities,
showCompleted: newFilters.showCompleted,
sortBy: newFilters.sortBy
});
// Sauvegarder les préférences de vue
userPreferencesService.saveViewPreferences({
compactView: newFilters.compactView || false,
swimlanesByTags: newFilters.swimlanesByTags || false,
showObjectives: true // Toujours visible pour l'instant
});
};
// Séparer les tâches épinglées (objectifs) des autres et les trier
const { pinnedTasks, regularTasks } = useMemo(() => {
const pinnedTagNames = tags.filter(tag => tag.isPinned).map(tag => tag.name);
const pinned: Task[] = [];
const regular: Task[] = [];
tasksState.tasks.forEach(task => {
const hasPinnedTag = task.tags?.some(tagName => pinnedTagNames.includes(tagName));
if (hasPinnedTag) {
pinned.push(task);
} else {
regular.push(task);
}
});
// Trier les tâches épinglées avec le même tri que les autres
const sortedPinned = kanbanFilters.sortBy ?
(() => {
const sortOption = getSortOption(kanbanFilters.sortBy);
return sortOption ?
sortTasks(pinned, [{ field: sortOption.field, direction: sortOption.direction }]) :
sortTasks(pinned, DEFAULT_SORT);
})() :
sortTasks(pinned, DEFAULT_SORT);
return { pinnedTasks: sortedPinned, regularTasks: regular };
}, [tasksState.tasks, tags, kanbanFilters.sortBy]);
// Filtrage et tri des tâches régulières (pas les épinglées)
const filteredTasks = useMemo(() => {
let filtered = regularTasks;
// Filtre par recherche
if (kanbanFilters.search) {
const searchLower = kanbanFilters.search.toLowerCase();
filtered = filtered.filter(task =>
task.title.toLowerCase().includes(searchLower) ||
task.description?.toLowerCase().includes(searchLower) ||
task.tags?.some(tag => tag.toLowerCase().includes(searchLower))
);
}
// Filtre par tags
if (kanbanFilters.tags?.length) {
filtered = filtered.filter(task =>
kanbanFilters.tags!.some(filterTag =>
task.tags?.includes(filterTag)
)
);
}
// Filtre par priorités
if (kanbanFilters.priorities?.length) {
filtered = filtered.filter(task =>
kanbanFilters.priorities!.includes(task.priority)
);
}
// Tri des tâches
if (kanbanFilters.sortBy) {
const sortOption = getSortOption(kanbanFilters.sortBy);
if (sortOption) {
filtered = sortTasks(filtered, [{
field: sortOption.field,
direction: sortOption.direction
}]);
}
} else {
// Tri par défaut (priorité desc + tags asc)
filtered = sortTasks(filtered, DEFAULT_SORT);
}
return filtered;
}, [regularTasks, kanbanFilters]);
const contextValue: TasksContextType = {
...tasksState,
tags,
tagsLoading,
tagsError,
kanbanFilters,
setKanbanFilters: updateKanbanFilters,
filteredTasks,
pinnedTasks
};
return (
<TasksContext.Provider value={contextValue}>
{children}
</TasksContext.Provider>
);
}
export function useTasksContext() {
const context = useContext(TasksContext);
if (!context) {
throw new Error('useTasksContext must be used within a TasksProvider');
}
return context;
}