refactor: userpreferences are now in the DB
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, ReactNode, useState, useMemo, useEffect } from 'react';
|
||||
import { createContext, useContext, ReactNode, useMemo } from 'react';
|
||||
import { useTasks } from '@/hooks/useTasks';
|
||||
import { useTags } from '@/hooks/useTags';
|
||||
import { userPreferencesService } from '@/services/user-preferences';
|
||||
import { useUserPreferences } from './UserPreferencesContext';
|
||||
import { Task, Tag, TaskStats } from '@/lib/types';
|
||||
import { CreateTaskData, UpdateTaskData, TaskFilters } from '@/clients/tasks-client';
|
||||
import { KanbanFilters } from '@/components/kanban/KanbanFilters';
|
||||
@@ -49,48 +49,42 @@ export function TasksProvider({ children, initialTasks, initialStats, initialTag
|
||||
);
|
||||
|
||||
const { tags, loading: tagsLoading, error: tagsError } = useTags(initialTags);
|
||||
|
||||
// État des filtres Kanban avec persistance
|
||||
const [kanbanFilters, setKanbanFilters] = useState<KanbanFilters>({});
|
||||
const { preferences, updateKanbanFilters, updateViewPreferences } = useUserPreferences();
|
||||
|
||||
// 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,
|
||||
swimlanesMode: savedViewPrefs.swimlanesMode || 'tags'
|
||||
});
|
||||
}, []);
|
||||
// Construire l'objet KanbanFilters à partir des préférences
|
||||
const kanbanFilters: KanbanFilters = useMemo(() => ({
|
||||
search: preferences.kanbanFilters.search || '',
|
||||
tags: preferences.kanbanFilters.tags || [],
|
||||
priorities: preferences.kanbanFilters.priorities || [],
|
||||
showCompleted: preferences.kanbanFilters.showCompleted ?? true,
|
||||
sortBy: preferences.kanbanFilters.sortBy || createSortKey('priority', 'desc'),
|
||||
compactView: preferences.viewPreferences.compactView || false,
|
||||
swimlanesByTags: preferences.viewPreferences.swimlanesByTags || false,
|
||||
swimlanesMode: preferences.viewPreferences.swimlanesMode || 'tags'
|
||||
}), [preferences]);
|
||||
|
||||
// Fonction pour mettre à jour les filtres avec persistance
|
||||
const updateKanbanFilters = (newFilters: KanbanFilters) => {
|
||||
setKanbanFilters(newFilters);
|
||||
|
||||
// Sauvegarder les filtres
|
||||
userPreferencesService.saveKanbanFilters({
|
||||
const setKanbanFilters = async (newFilters: KanbanFilters) => {
|
||||
// Séparer les vrais filtres des préférences de vue
|
||||
const kanbanFilterUpdates = {
|
||||
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,
|
||||
swimlanesMode: newFilters.swimlanesMode || 'tags',
|
||||
showObjectives: true, // Toujours visible pour l'instant
|
||||
showFilters: true // Toujours visible pour l'instant
|
||||
});
|
||||
const viewPreferenceUpdates = {
|
||||
compactView: newFilters.compactView,
|
||||
swimlanesByTags: newFilters.swimlanesByTags,
|
||||
swimlanesMode: newFilters.swimlanesMode
|
||||
};
|
||||
|
||||
// Mettre à jour via UserPreferencesContext
|
||||
await Promise.all([
|
||||
updateKanbanFilters(kanbanFilterUpdates),
|
||||
updateViewPreferences(viewPreferenceUpdates)
|
||||
]);
|
||||
};
|
||||
|
||||
// Séparer les tâches épinglées (objectifs) des autres et les trier
|
||||
@@ -176,7 +170,7 @@ export function TasksProvider({ children, initialTasks, initialStats, initialTag
|
||||
tagsLoading,
|
||||
tagsError,
|
||||
kanbanFilters,
|
||||
setKanbanFilters: updateKanbanFilters,
|
||||
setKanbanFilters,
|
||||
filteredTasks,
|
||||
pinnedTasks
|
||||
};
|
||||
|
||||
@@ -14,34 +14,22 @@ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
interface ThemeProviderProps {
|
||||
children: ReactNode;
|
||||
initialTheme?: Theme;
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children }: ThemeProviderProps) {
|
||||
const [theme, setThemeState] = useState<Theme>('dark');
|
||||
export function ThemeProvider({ children, initialTheme = 'dark' }: ThemeProviderProps) {
|
||||
const [theme, setThemeState] = useState<Theme>(initialTheme);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// Hydration safe initialization
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
// Check localStorage first
|
||||
const savedTheme = localStorage.getItem('theme') as Theme | null;
|
||||
if (savedTheme) {
|
||||
setThemeState(savedTheme);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to system preference
|
||||
if (window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||||
setThemeState('light');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Apply theme class to document
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
document.documentElement.className = theme;
|
||||
localStorage.setItem('theme', theme);
|
||||
}
|
||||
}, [theme, mounted]);
|
||||
|
||||
@@ -55,7 +43,7 @@ export function ThemeProvider({ children }: ThemeProviderProps) {
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
|
||||
<div className={mounted ? theme : 'dark'}>
|
||||
<div className={mounted ? theme : initialTheme}>
|
||||
{children}
|
||||
</div>
|
||||
</ThemeContext.Provider>
|
||||
|
||||
150
src/contexts/UserPreferencesContext.tsx
Normal file
150
src/contexts/UserPreferencesContext.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, ReactNode, useState, useCallback, useEffect } from 'react';
|
||||
import { userPreferencesClient } from '@/clients/user-preferences-client';
|
||||
import { UserPreferences, KanbanFilters, ViewPreferences, ColumnVisibility, TaskStatus } from '@/lib/types';
|
||||
|
||||
interface UserPreferencesContextType {
|
||||
preferences: UserPreferences;
|
||||
|
||||
// Kanban Filters
|
||||
updateKanbanFilters: (updates: Partial<KanbanFilters>) => Promise<void>;
|
||||
|
||||
// View Preferences
|
||||
updateViewPreferences: (updates: Partial<ViewPreferences>) => Promise<void>;
|
||||
toggleObjectivesVisibility: () => Promise<void>;
|
||||
toggleObjectivesCollapse: () => Promise<void>;
|
||||
toggleTheme: () => Promise<void>;
|
||||
setTheme: (theme: 'light' | 'dark') => Promise<void>;
|
||||
|
||||
// Column Visibility
|
||||
updateColumnVisibility: (updates: Partial<ColumnVisibility>) => Promise<void>;
|
||||
toggleColumnVisibility: (status: TaskStatus) => Promise<void>;
|
||||
isColumnVisible: (status: TaskStatus) => boolean;
|
||||
}
|
||||
|
||||
const UserPreferencesContext = createContext<UserPreferencesContextType | null>(null);
|
||||
|
||||
interface UserPreferencesProviderProps {
|
||||
children: ReactNode;
|
||||
initialPreferences: UserPreferences;
|
||||
}
|
||||
|
||||
export function UserPreferencesProvider({ children, initialPreferences }: UserPreferencesProviderProps) {
|
||||
const [preferences, setPreferences] = useState<UserPreferences>(initialPreferences);
|
||||
|
||||
// Synchroniser le thème avec le ThemeProvider global (si disponible)
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// Appliquer le thème au document
|
||||
document.documentElement.className = preferences.viewPreferences.theme;
|
||||
}
|
||||
}, [preferences.viewPreferences.theme]);
|
||||
|
||||
// === KANBAN FILTERS ===
|
||||
|
||||
const updateKanbanFilters = useCallback(async (updates: Partial<KanbanFilters>) => {
|
||||
const newFilters = { ...preferences.kanbanFilters, ...updates };
|
||||
setPreferences(prev => ({ ...prev, kanbanFilters: newFilters }));
|
||||
|
||||
try {
|
||||
await userPreferencesClient.updateKanbanFilters(updates);
|
||||
} catch (error) {
|
||||
console.warn('Erreur lors de la sauvegarde des filtres Kanban:', error);
|
||||
// Revert optimistic update on error
|
||||
setPreferences(prev => ({ ...prev, kanbanFilters: preferences.kanbanFilters }));
|
||||
}
|
||||
}, [preferences.kanbanFilters]);
|
||||
|
||||
// === VIEW PREFERENCES ===
|
||||
|
||||
const updateViewPreferences = useCallback(async (updates: Partial<ViewPreferences>) => {
|
||||
const newPreferences = { ...preferences.viewPreferences, ...updates };
|
||||
setPreferences(prev => ({ ...prev, viewPreferences: newPreferences }));
|
||||
|
||||
try {
|
||||
await userPreferencesClient.updateViewPreferences(updates);
|
||||
} catch (error) {
|
||||
console.warn('Erreur lors de la sauvegarde des préférences de vue:', error);
|
||||
// Revert optimistic update on error
|
||||
setPreferences(prev => ({ ...prev, viewPreferences: preferences.viewPreferences }));
|
||||
}
|
||||
}, [preferences.viewPreferences]);
|
||||
|
||||
const toggleObjectivesVisibility = useCallback(async () => {
|
||||
const newValue = !preferences.viewPreferences.showObjectives;
|
||||
await updateViewPreferences({ showObjectives: newValue });
|
||||
}, [preferences.viewPreferences.showObjectives, updateViewPreferences]);
|
||||
|
||||
const toggleObjectivesCollapse = useCallback(async () => {
|
||||
const newValue = !preferences.viewPreferences.objectivesCollapsed;
|
||||
await updateViewPreferences({ objectivesCollapsed: newValue });
|
||||
}, [preferences.viewPreferences.objectivesCollapsed, updateViewPreferences]);
|
||||
|
||||
const toggleTheme = useCallback(async () => {
|
||||
const newTheme = preferences.viewPreferences.theme === 'dark' ? 'light' : 'dark';
|
||||
await updateViewPreferences({ theme: newTheme });
|
||||
}, [preferences.viewPreferences.theme, updateViewPreferences]);
|
||||
|
||||
const setTheme = useCallback(async (theme: 'light' | 'dark') => {
|
||||
await updateViewPreferences({ theme });
|
||||
}, [updateViewPreferences]);
|
||||
|
||||
// === COLUMN VISIBILITY ===
|
||||
|
||||
const updateColumnVisibility = useCallback(async (updates: Partial<ColumnVisibility>) => {
|
||||
const newVisibility = { ...preferences.columnVisibility, ...updates };
|
||||
setPreferences(prev => ({ ...prev, columnVisibility: newVisibility }));
|
||||
|
||||
try {
|
||||
await userPreferencesClient.updateColumnVisibility(updates);
|
||||
} catch (error) {
|
||||
console.warn('Erreur lors de la sauvegarde de la visibilité des colonnes:', error);
|
||||
// Revert optimistic update on error
|
||||
setPreferences(prev => ({ ...prev, columnVisibility: preferences.columnVisibility }));
|
||||
}
|
||||
}, [preferences.columnVisibility]);
|
||||
|
||||
const toggleColumnVisibility = useCallback(async (status: TaskStatus) => {
|
||||
const hiddenStatuses = new Set(preferences.columnVisibility.hiddenStatuses);
|
||||
|
||||
if (hiddenStatuses.has(status)) {
|
||||
hiddenStatuses.delete(status);
|
||||
} else {
|
||||
hiddenStatuses.add(status);
|
||||
}
|
||||
|
||||
await updateColumnVisibility({ hiddenStatuses: Array.from(hiddenStatuses) });
|
||||
}, [preferences.columnVisibility.hiddenStatuses, updateColumnVisibility]);
|
||||
|
||||
const isColumnVisible = useCallback((status: TaskStatus) => {
|
||||
return !preferences.columnVisibility.hiddenStatuses.includes(status);
|
||||
}, [preferences.columnVisibility.hiddenStatuses]);
|
||||
|
||||
const contextValue: UserPreferencesContextType = {
|
||||
preferences,
|
||||
updateKanbanFilters,
|
||||
updateViewPreferences,
|
||||
toggleObjectivesVisibility,
|
||||
toggleObjectivesCollapse,
|
||||
toggleTheme,
|
||||
setTheme,
|
||||
updateColumnVisibility,
|
||||
toggleColumnVisibility,
|
||||
isColumnVisible
|
||||
};
|
||||
|
||||
return (
|
||||
<UserPreferencesContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</UserPreferencesContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useUserPreferences() {
|
||||
const context = useContext(UserPreferencesContext);
|
||||
if (!context) {
|
||||
throw new Error('useUserPreferences must be used within a UserPreferencesProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user