- 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.
201 lines
6.2 KiB
TypeScript
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;
|
|
}
|