refactor: userpreferences are now in the DB
This commit is contained in:
28
src/app/api/user-preferences/column-visibility/route.ts
Normal file
28
src/app/api/user-preferences/column-visibility/route.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { userPreferencesService } from '@/services/user-preferences';
|
||||
|
||||
/**
|
||||
* PATCH /api/user-preferences/column-visibility - Met à jour partiellement la visibilité des colonnes
|
||||
*/
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const updates = await request.json();
|
||||
|
||||
const current = await userPreferencesService.getColumnVisibility();
|
||||
await userPreferencesService.saveColumnVisibility({ ...current, ...updates });
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Visibilité des colonnes mise à jour avec succès'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour de la visibilité des colonnes:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Erreur lors de la mise à jour de la visibilité des colonnes'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
76
src/app/api/user-preferences/kanban-filters/route.ts
Normal file
76
src/app/api/user-preferences/kanban-filters/route.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { userPreferencesService } from '@/services/user-preferences';
|
||||
|
||||
/**
|
||||
* GET /api/user-preferences/kanban-filters - Récupère les filtres Kanban
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
const filters = await userPreferencesService.getKanbanFilters();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: filters
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des filtres Kanban:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Erreur lors de la récupération des filtres Kanban'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/user-preferences/kanban-filters - Met à jour les filtres Kanban
|
||||
*/
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const filters = await request.json();
|
||||
|
||||
await userPreferencesService.saveKanbanFilters(filters);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Filtres Kanban sauvegardés avec succès'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la sauvegarde des filtres Kanban:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Erreur lors de la sauvegarde des filtres Kanban'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/user-preferences/kanban-filters - Met à jour partiellement les filtres Kanban
|
||||
*/
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const updates = await request.json();
|
||||
|
||||
const current = await userPreferencesService.getKanbanFilters();
|
||||
await userPreferencesService.saveKanbanFilters({ ...current, ...updates });
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Filtres Kanban mis à jour avec succès'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour des filtres Kanban:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Erreur lors de la mise à jour des filtres Kanban'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
50
src/app/api/user-preferences/route.ts
Normal file
50
src/app/api/user-preferences/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { userPreferencesService } from '@/services/user-preferences';
|
||||
|
||||
/**
|
||||
* GET /api/user-preferences - Récupère toutes les préférences utilisateur
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
const preferences = await userPreferencesService.getAllPreferences();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: preferences
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des préférences:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Erreur lors de la récupération des préférences'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/user-preferences - Met à jour toutes les préférences utilisateur
|
||||
*/
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const preferences = await request.json();
|
||||
|
||||
await userPreferencesService.saveAllPreferences(preferences);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Préférences sauvegardées avec succès'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la sauvegarde des préférences:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Erreur lors de la sauvegarde des préférences'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
28
src/app/api/user-preferences/view-preferences/route.ts
Normal file
28
src/app/api/user-preferences/view-preferences/route.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { userPreferencesService } from '@/services/user-preferences';
|
||||
|
||||
/**
|
||||
* PATCH /api/user-preferences/view-preferences - Met à jour partiellement les préférences de vue
|
||||
*/
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const updates = await request.json();
|
||||
|
||||
const current = await userPreferencesService.getViewPreferences();
|
||||
await userPreferencesService.saveViewPreferences({ ...current, ...updates });
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Préférences de vue mises à jour avec succès'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour des préférences de vue:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Erreur lors de la mise à jour des préférences de vue'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { tasksService } from '@/services/tasks';
|
||||
import { tagsService } from '@/services/tags';
|
||||
import { userPreferencesService } from '@/services/user-preferences';
|
||||
import { HomePageClient } from '@/components/HomePageClient';
|
||||
|
||||
// Force dynamic rendering (no static generation)
|
||||
@@ -7,10 +8,11 @@ export const dynamic = 'force-dynamic';
|
||||
|
||||
export default async function HomePage() {
|
||||
// SSR - Récupération des données côté serveur
|
||||
const [initialTasks, initialStats, initialTags] = await Promise.all([
|
||||
const [initialTasks, initialStats, initialTags, initialPreferences] = await Promise.all([
|
||||
tasksService.getTasks(),
|
||||
tasksService.getTaskStats(),
|
||||
tagsService.getTags()
|
||||
tagsService.getTags(),
|
||||
userPreferencesService.getAllPreferences()
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -18,6 +20,7 @@ export default async function HomePage() {
|
||||
initialTasks={initialTasks}
|
||||
initialStats={initialStats}
|
||||
initialTags={initialTags}
|
||||
initialPreferences={initialPreferences}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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