chore: prettier everywhere
This commit is contained in:
@@ -1,8 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { useUserPreferences } from './UserPreferencesContext';
|
||||
import { PRESET_BACKGROUNDS, BACKGROUND_NAMES, TOAST_ICONS, getNextBackground } from '@/lib/ui-config';
|
||||
import {
|
||||
PRESET_BACKGROUNDS,
|
||||
BACKGROUND_NAMES,
|
||||
TOAST_ICONS,
|
||||
getNextBackground,
|
||||
} from '@/lib/ui-config';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useToast } from '@/components/ui/Toast';
|
||||
|
||||
@@ -12,7 +23,9 @@ interface BackgroundContextType {
|
||||
cycleBackground: () => void;
|
||||
}
|
||||
|
||||
const BackgroundContext = createContext<BackgroundContextType | undefined>(undefined);
|
||||
const BackgroundContext = createContext<BackgroundContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
interface BackgroundProviderProps {
|
||||
children: ReactNode;
|
||||
@@ -22,9 +35,9 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
||||
const { preferences, updateViewPreferences } = useUserPreferences();
|
||||
const { data: session } = useSession();
|
||||
const { showToast } = useToast();
|
||||
const [backgroundImage, setBackgroundImageState] = useState<string | undefined>(
|
||||
preferences?.viewPreferences?.backgroundImage
|
||||
);
|
||||
const [backgroundImage, setBackgroundImageState] = useState<
|
||||
string | undefined
|
||||
>(preferences?.viewPreferences?.backgroundImage);
|
||||
const [backgroundBlur, setBackgroundBlurState] = useState<number>(
|
||||
preferences?.viewPreferences?.backgroundBlur || 0
|
||||
);
|
||||
@@ -41,28 +54,41 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
||||
// Sync with preferences (only for authenticated users)
|
||||
useEffect(() => {
|
||||
// Only sync if user is authenticated
|
||||
if (session?.user?.id && preferences?.viewPreferences?.backgroundImage !== backgroundImage) {
|
||||
if (
|
||||
session?.user?.id &&
|
||||
preferences?.viewPreferences?.backgroundImage !== backgroundImage
|
||||
) {
|
||||
setBackgroundImageState(preferences?.viewPreferences?.backgroundImage);
|
||||
}
|
||||
if (preferences?.viewPreferences?.backgroundBlur !== backgroundBlur) {
|
||||
setBackgroundBlurState(preferences?.viewPreferences?.backgroundBlur || 0);
|
||||
}
|
||||
if (preferences?.viewPreferences?.backgroundOpacity !== backgroundOpacity) {
|
||||
setBackgroundOpacityState(preferences?.viewPreferences?.backgroundOpacity || 100);
|
||||
setBackgroundOpacityState(
|
||||
preferences?.viewPreferences?.backgroundOpacity || 100
|
||||
);
|
||||
}
|
||||
}, [preferences?.viewPreferences?.backgroundImage, preferences?.viewPreferences?.backgroundBlur, preferences?.viewPreferences?.backgroundOpacity, backgroundImage, backgroundBlur, backgroundOpacity, session?.user?.id]);
|
||||
}, [
|
||||
preferences?.viewPreferences?.backgroundImage,
|
||||
preferences?.viewPreferences?.backgroundBlur,
|
||||
preferences?.viewPreferences?.backgroundOpacity,
|
||||
backgroundImage,
|
||||
backgroundBlur,
|
||||
backgroundOpacity,
|
||||
session?.user?.id,
|
||||
]);
|
||||
|
||||
// Apply background image to document body
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
const body = document.body;
|
||||
|
||||
|
||||
// Supprimer l'ancien élément de fond s'il existe
|
||||
const existingBackground = document.getElementById('custom-background');
|
||||
if (existingBackground) {
|
||||
existingBackground.remove();
|
||||
}
|
||||
|
||||
|
||||
if (backgroundImage) {
|
||||
console.log('Creating background element for:', backgroundImage);
|
||||
// Créer un élément div pour l'image de fond avec les effets
|
||||
@@ -75,13 +101,18 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
||||
backgroundElement.style.height = '100%';
|
||||
backgroundElement.style.zIndex = '-1';
|
||||
backgroundElement.style.pointerEvents = 'none';
|
||||
|
||||
|
||||
// Vérifier si c'est une URL d'image ou un preset
|
||||
const presetIds = PRESET_BACKGROUNDS.map(preset => preset.id);
|
||||
|
||||
if (backgroundImage && presetIds.includes(backgroundImage as typeof presetIds[number])) {
|
||||
const presetIds = PRESET_BACKGROUNDS.map((preset) => preset.id);
|
||||
|
||||
if (
|
||||
backgroundImage &&
|
||||
presetIds.includes(backgroundImage as (typeof presetIds)[number])
|
||||
) {
|
||||
// Trouver le preset correspondant
|
||||
const preset = PRESET_BACKGROUNDS.find(p => p.id === backgroundImage);
|
||||
const preset = PRESET_BACKGROUNDS.find(
|
||||
(p) => p.id === backgroundImage
|
||||
);
|
||||
if (preset) {
|
||||
// Appliquer le gradient directement
|
||||
backgroundElement.style.background = preset.preview;
|
||||
@@ -93,14 +124,14 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
||||
backgroundElement.style.backgroundImage = `url(${backgroundImage})`;
|
||||
backgroundElement.className = 'custom-background';
|
||||
}
|
||||
|
||||
|
||||
// Appliquer les propriétés communes
|
||||
backgroundElement.style.backgroundSize = 'cover';
|
||||
backgroundElement.style.backgroundPosition = 'center';
|
||||
backgroundElement.style.backgroundRepeat = 'no-repeat';
|
||||
backgroundElement.style.filter = `blur(${backgroundBlur}px)`;
|
||||
backgroundElement.style.opacity = `${backgroundOpacity / 100}`;
|
||||
|
||||
|
||||
// Ajouter l'élément au body
|
||||
body.appendChild(backgroundElement);
|
||||
body.classList.add('has-background-image');
|
||||
@@ -118,24 +149,31 @@ export function BackgroundProvider({ children }: BackgroundProviderProps) {
|
||||
const cycleBackground = () => {
|
||||
const currentBackground = backgroundImage; // Utiliser le state local au lieu des préférences
|
||||
const customImages = preferences?.viewPreferences?.customImages || [];
|
||||
|
||||
const nextBackground = getNextBackground(currentBackground || 'none', customImages);
|
||||
const newBackgroundImage = nextBackground === 'none' ? undefined : nextBackground;
|
||||
|
||||
|
||||
const nextBackground = getNextBackground(
|
||||
currentBackground || 'none',
|
||||
customImages
|
||||
);
|
||||
const newBackgroundImage =
|
||||
nextBackground === 'none' ? undefined : nextBackground;
|
||||
|
||||
setBackgroundImageState(newBackgroundImage);
|
||||
|
||||
|
||||
// Sauvegarder seulement si l'utilisateur est authentifié
|
||||
if (session?.user?.id) {
|
||||
updateViewPreferences({ backgroundImage: newBackgroundImage });
|
||||
}
|
||||
|
||||
|
||||
// Afficher le toast avec le nom du background
|
||||
const backgroundName = BACKGROUND_NAMES[nextBackground] || 'Image personnalisée';
|
||||
const backgroundName =
|
||||
BACKGROUND_NAMES[nextBackground] || 'Image personnalisée';
|
||||
showToast(`Background: ${backgroundName}`, 2000, TOAST_ICONS.background);
|
||||
};
|
||||
|
||||
return (
|
||||
<BackgroundContext.Provider value={{ backgroundImage, setBackgroundImage, cycleBackground }}>
|
||||
<BackgroundContext.Provider
|
||||
value={{ backgroundImage, setBackgroundImage, cycleBackground }}
|
||||
>
|
||||
{children}
|
||||
</BackgroundContext.Provider>
|
||||
);
|
||||
|
||||
@@ -8,20 +8,22 @@ interface JiraConfigContextType {
|
||||
isConfigured: boolean;
|
||||
}
|
||||
|
||||
const JiraConfigContext = createContext<JiraConfigContextType | undefined>(undefined);
|
||||
const JiraConfigContext = createContext<JiraConfigContextType | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
interface JiraConfigProviderProps {
|
||||
children: React.ReactNode;
|
||||
config: JiraConfig;
|
||||
}
|
||||
|
||||
export function JiraConfigProvider({ children, config }: JiraConfigProviderProps) {
|
||||
export function JiraConfigProvider({
|
||||
children,
|
||||
config,
|
||||
}: JiraConfigProviderProps) {
|
||||
// Une config Jira est considérée comme valide si elle a les champs obligatoires
|
||||
const isConfigured = Boolean(
|
||||
config.baseUrl &&
|
||||
config.email &&
|
||||
config.apiToken &&
|
||||
config.enabled
|
||||
config.baseUrl && config.email && config.apiToken && config.enabled
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,12 @@ import { useUserPreferences } from './UserPreferencesContext';
|
||||
import { Task, Tag, TaskStats, TaskStatus } from '@/lib/types';
|
||||
import { CreateTaskData, TaskFilters } from '@/clients/tasks-client';
|
||||
import type { KanbanFilters } from '@/lib/types';
|
||||
import { sortTasks, getSortOption, DEFAULT_SORT, createSortKey } from '@/lib/sort-config';
|
||||
import {
|
||||
sortTasks,
|
||||
getSortOption,
|
||||
DEFAULT_SORT,
|
||||
createSortKey,
|
||||
} from '@/lib/sort-config';
|
||||
|
||||
interface TasksContextType {
|
||||
tasks: Task[]; // Toutes les tâches
|
||||
@@ -17,7 +22,10 @@ interface TasksContextType {
|
||||
syncing: boolean;
|
||||
error: string | null;
|
||||
createTask: (data: CreateTaskData) => Promise<Task | null>;
|
||||
updateTaskOptimistic: (taskId: string, status: TaskStatus) => Promise<Task | null>;
|
||||
updateTaskOptimistic: (
|
||||
taskId: string,
|
||||
status: TaskStatus
|
||||
) => Promise<Task | null>;
|
||||
refreshTasks: () => Promise<void>;
|
||||
setFilters: (filters: TaskFilters) => void;
|
||||
// Kanban filters
|
||||
@@ -41,40 +49,51 @@ interface TasksProviderProps {
|
||||
initialStats?: TaskStats;
|
||||
}
|
||||
|
||||
export function TasksProvider({ children, initialTasks, initialTags, initialStats }: TasksProviderProps) {
|
||||
export function TasksProvider({
|
||||
children,
|
||||
initialTasks,
|
||||
initialTags,
|
||||
initialStats,
|
||||
}: TasksProviderProps) {
|
||||
const tasksState = useTasks(
|
||||
{ limit: 20 },
|
||||
{ tasks: initialTasks, stats: initialStats }
|
||||
);
|
||||
|
||||
const { tags, loading: tagsLoading, error: tagsError } = useTags(initialTags);
|
||||
const { preferences, updateKanbanFilters, updateViewPreferences } = useUserPreferences();
|
||||
const { preferences, updateKanbanFilters, updateViewPreferences } =
|
||||
useUserPreferences();
|
||||
|
||||
// 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',
|
||||
showWithDueDate: preferences.kanbanFilters.showWithDueDate || false,
|
||||
showCompletedLast7Days: preferences.kanbanFilters.showCompletedLast7Days || false,
|
||||
// Filtres Jira
|
||||
showJiraOnly: preferences.kanbanFilters.showJiraOnly || false,
|
||||
hideJiraTasks: preferences.kanbanFilters.hideJiraTasks || false,
|
||||
jiraProjects: preferences.kanbanFilters.jiraProjects || [],
|
||||
jiraTypes: preferences.kanbanFilters.jiraTypes || [],
|
||||
// Filtres TFS
|
||||
showTfsOnly: preferences.kanbanFilters.showTfsOnly || false,
|
||||
hideTfsTasks: preferences.kanbanFilters.hideTfsTasks || false,
|
||||
tfsProjects: preferences.kanbanFilters.tfsProjects || [],
|
||||
// Filtres Manuel
|
||||
showManualOnly: preferences.kanbanFilters.showManualOnly || false,
|
||||
hideManualTasks: preferences.kanbanFilters.hideManualTasks || false
|
||||
}), [preferences]);
|
||||
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',
|
||||
showWithDueDate: preferences.kanbanFilters.showWithDueDate || false,
|
||||
showCompletedLast7Days:
|
||||
preferences.kanbanFilters.showCompletedLast7Days || false,
|
||||
// Filtres Jira
|
||||
showJiraOnly: preferences.kanbanFilters.showJiraOnly || false,
|
||||
hideJiraTasks: preferences.kanbanFilters.hideJiraTasks || false,
|
||||
jiraProjects: preferences.kanbanFilters.jiraProjects || [],
|
||||
jiraTypes: preferences.kanbanFilters.jiraTypes || [],
|
||||
// Filtres TFS
|
||||
showTfsOnly: preferences.kanbanFilters.showTfsOnly || false,
|
||||
hideTfsTasks: preferences.kanbanFilters.hideTfsTasks || false,
|
||||
tfsProjects: preferences.kanbanFilters.tfsProjects || [],
|
||||
// Filtres Manuel
|
||||
showManualOnly: preferences.kanbanFilters.showManualOnly || false,
|
||||
hideManualTasks: preferences.kanbanFilters.hideManualTasks || false,
|
||||
}),
|
||||
[preferences]
|
||||
);
|
||||
|
||||
// Fonction pour mettre à jour les filtres avec persistance
|
||||
const setKanbanFilters = async (newFilters: KanbanFilters) => {
|
||||
@@ -98,68 +117,75 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
|
||||
tfsProjects: newFilters.tfsProjects,
|
||||
// Filtres Manuel
|
||||
showManualOnly: newFilters.showManualOnly,
|
||||
hideManualTasks: newFilters.hideManualTasks
|
||||
hideManualTasks: newFilters.hideManualTasks,
|
||||
};
|
||||
|
||||
|
||||
const viewPreferenceUpdates = {
|
||||
compactView: newFilters.compactView as boolean,
|
||||
swimlanesByTags: newFilters.swimlanesByTags as boolean,
|
||||
swimlanesMode: newFilters.swimlanesMode as 'tags' | 'priority'
|
||||
swimlanesMode: newFilters.swimlanesMode as 'tags' | 'priority',
|
||||
};
|
||||
|
||||
|
||||
// Mettre à jour via UserPreferencesContext
|
||||
await Promise.all([
|
||||
updateKanbanFilters(kanbanFilterUpdates),
|
||||
updateViewPreferences(viewPreferenceUpdates)
|
||||
updateViewPreferences(viewPreferenceUpdates),
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
// 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 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));
|
||||
|
||||
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);
|
||||
|
||||
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]);
|
||||
|
||||
// Calcul du nombre de filtres actifs
|
||||
const activeFiltersCount = useMemo(() => {
|
||||
return (kanbanFilters.tags?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.priorities?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.search ? 1 : 0) +
|
||||
(kanbanFilters.showWithDueDate ? 1 : 0) +
|
||||
(kanbanFilters.showCompletedLast7Days ? 1 : 0) +
|
||||
(kanbanFilters.jiraProjects?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.jiraTypes?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.showJiraOnly ? 1 : 0) +
|
||||
(kanbanFilters.hideJiraTasks ? 1 : 0) +
|
||||
(kanbanFilters.tfsProjects?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.showTfsOnly ? 1 : 0) +
|
||||
(kanbanFilters.hideTfsTasks ? 1 : 0) +
|
||||
(kanbanFilters.showManualOnly ? 1 : 0) +
|
||||
(kanbanFilters.hideManualTasks ? 1 : 0);
|
||||
return (
|
||||
(kanbanFilters.tags?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.priorities?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.search ? 1 : 0) +
|
||||
(kanbanFilters.showWithDueDate ? 1 : 0) +
|
||||
(kanbanFilters.showCompletedLast7Days ? 1 : 0) +
|
||||
(kanbanFilters.jiraProjects?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.jiraTypes?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.showJiraOnly ? 1 : 0) +
|
||||
(kanbanFilters.hideJiraTasks ? 1 : 0) +
|
||||
(kanbanFilters.tfsProjects?.filter(Boolean).length || 0) +
|
||||
(kanbanFilters.showTfsOnly ? 1 : 0) +
|
||||
(kanbanFilters.hideTfsTasks ? 1 : 0) +
|
||||
(kanbanFilters.showManualOnly ? 1 : 0) +
|
||||
(kanbanFilters.hideManualTasks ? 1 : 0)
|
||||
);
|
||||
}, [kanbanFilters]);
|
||||
|
||||
// Filtrage et tri des tâches régulières (pas les épinglées)
|
||||
@@ -169,85 +195,91 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
|
||||
// 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))
|
||||
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)
|
||||
)
|
||||
filtered = filtered.filter((task) =>
|
||||
kanbanFilters.tags!.some((filterTag) => task.tags?.includes(filterTag))
|
||||
);
|
||||
}
|
||||
|
||||
// Filtre par priorités
|
||||
if (kanbanFilters.priorities?.length) {
|
||||
filtered = filtered.filter(task =>
|
||||
filtered = filtered.filter((task) =>
|
||||
kanbanFilters.priorities!.includes(task.priority)
|
||||
);
|
||||
}
|
||||
|
||||
// Filtres spécifiques Jira
|
||||
if (kanbanFilters.showJiraOnly) {
|
||||
filtered = filtered.filter(task => task.source === 'jira');
|
||||
filtered = filtered.filter((task) => task.source === 'jira');
|
||||
} else if (kanbanFilters.hideJiraTasks) {
|
||||
filtered = filtered.filter(task => task.source !== 'jira');
|
||||
filtered = filtered.filter((task) => task.source !== 'jira');
|
||||
}
|
||||
|
||||
// Filtre par projets Jira
|
||||
if (kanbanFilters.jiraProjects?.length) {
|
||||
filtered = filtered.filter(task =>
|
||||
task.source !== 'jira' || kanbanFilters.jiraProjects!.includes(task.jiraProject || '')
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
task.source !== 'jira' ||
|
||||
kanbanFilters.jiraProjects!.includes(task.jiraProject || '')
|
||||
);
|
||||
}
|
||||
|
||||
// Filtre par types Jira
|
||||
if (kanbanFilters.jiraTypes?.length) {
|
||||
filtered = filtered.filter(task =>
|
||||
task.source !== 'jira' || kanbanFilters.jiraTypes!.includes(task.jiraType || '')
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
task.source !== 'jira' ||
|
||||
kanbanFilters.jiraTypes!.includes(task.jiraType || '')
|
||||
);
|
||||
}
|
||||
|
||||
// Filtres spécifiques TFS
|
||||
if (kanbanFilters.showTfsOnly) {
|
||||
filtered = filtered.filter(task => task.source === 'tfs');
|
||||
filtered = filtered.filter((task) => task.source === 'tfs');
|
||||
} else if (kanbanFilters.hideTfsTasks) {
|
||||
filtered = filtered.filter(task => task.source !== 'tfs');
|
||||
filtered = filtered.filter((task) => task.source !== 'tfs');
|
||||
}
|
||||
|
||||
// Filtres spécifiques Manuel
|
||||
if (kanbanFilters.showManualOnly) {
|
||||
filtered = filtered.filter(task => task.source === 'manual');
|
||||
filtered = filtered.filter((task) => task.source === 'manual');
|
||||
} else if (kanbanFilters.hideManualTasks) {
|
||||
filtered = filtered.filter(task => task.source !== 'manual');
|
||||
filtered = filtered.filter((task) => task.source !== 'manual');
|
||||
}
|
||||
|
||||
// Filtre par projets TFS
|
||||
if (kanbanFilters.tfsProjects?.length) {
|
||||
filtered = filtered.filter(task =>
|
||||
task.source !== 'tfs' || kanbanFilters.tfsProjects!.includes(task.tfsProject || '')
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
task.source !== 'tfs' ||
|
||||
kanbanFilters.tfsProjects!.includes(task.tfsProject || '')
|
||||
);
|
||||
}
|
||||
|
||||
// Filtre par date de fin
|
||||
if (kanbanFilters.showWithDueDate) {
|
||||
filtered = filtered.filter(task => task.dueDate != null);
|
||||
filtered = filtered.filter((task) => task.dueDate != null);
|
||||
}
|
||||
|
||||
// Filtre par tâches complétées les 7 derniers jours
|
||||
if (kanbanFilters.showCompletedLast7Days) {
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
|
||||
filtered = filtered.filter(task =>
|
||||
task.status === 'done' &&
|
||||
task.completedAt &&
|
||||
task.completedAt >= sevenDaysAgo
|
||||
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
task.status === 'done' &&
|
||||
task.completedAt &&
|
||||
task.completedAt >= sevenDaysAgo
|
||||
);
|
||||
}
|
||||
|
||||
@@ -255,10 +287,12 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
|
||||
if (kanbanFilters.sortBy) {
|
||||
const sortOption = getSortOption(kanbanFilters.sortBy);
|
||||
if (sortOption) {
|
||||
filtered = sortTasks(filtered, [{
|
||||
field: sortOption.field,
|
||||
direction: sortOption.direction
|
||||
}]);
|
||||
filtered = sortTasks(filtered, [
|
||||
{
|
||||
field: sortOption.field,
|
||||
direction: sortOption.direction,
|
||||
},
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// Tri par défaut (priorité desc + tags asc)
|
||||
@@ -278,7 +312,7 @@ export function TasksProvider({ children, initialTasks, initialTags, initialStat
|
||||
setKanbanFilters,
|
||||
filteredTasks,
|
||||
pinnedTasks,
|
||||
activeFiltersCount
|
||||
activeFiltersCount,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { updateViewPreferences } from '@/actions/preferences';
|
||||
import { useToast } from '@/components/ui/Toast';
|
||||
import { Theme, THEME_CONFIG, getNextDarkTheme, THEME_NAMES, getThemeIcon } from '@/lib/ui-config';
|
||||
import {
|
||||
Theme,
|
||||
THEME_CONFIG,
|
||||
getNextDarkTheme,
|
||||
THEME_NAMES,
|
||||
getThemeIcon,
|
||||
} from '@/lib/ui-config';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
interface ThemeContextType {
|
||||
@@ -22,9 +34,15 @@ interface ThemeProviderProps {
|
||||
userPreferredTheme?: Theme;
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTheme: initialUserPreferredTheme = 'dark' }: ThemeProviderProps) {
|
||||
export function ThemeProvider({
|
||||
children,
|
||||
initialTheme = 'dark',
|
||||
userPreferredTheme: initialUserPreferredTheme = 'dark',
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setThemeState] = useState<Theme>(initialTheme);
|
||||
const [userPreferredTheme, setUserPreferredTheme] = useState<Theme>(initialUserPreferredTheme);
|
||||
const [userPreferredTheme, setUserPreferredTheme] = useState<Theme>(
|
||||
initialUserPreferredTheme
|
||||
);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { showToast } = useToast();
|
||||
const { data: session } = useSession();
|
||||
@@ -48,12 +66,12 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
||||
// Toggle between light and the user's chosen dark theme
|
||||
const newTheme = theme === 'light' ? userPreferredTheme : 'light';
|
||||
setThemeState(newTheme);
|
||||
|
||||
|
||||
// Sauvegarder en base seulement si l'utilisateur est authentifié
|
||||
if (session?.user?.id) {
|
||||
try {
|
||||
const result = await updateViewPreferences({
|
||||
theme: newTheme
|
||||
theme: newTheme,
|
||||
});
|
||||
if (!result.success) {
|
||||
console.error('Erreur lors de la sauvegarde du thème:', result.error);
|
||||
@@ -62,24 +80,24 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
||||
console.error('Erreur lors de la sauvegarde du thème:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Afficher le toast avec le nom du thème
|
||||
showToast(`Thème: ${THEME_NAMES[newTheme]}`, 2000, getThemeIcon(newTheme));
|
||||
};
|
||||
|
||||
const setTheme = async (newTheme: Theme) => {
|
||||
setThemeState(newTheme);
|
||||
|
||||
|
||||
// Si ce n'est pas le thème light, c'est le thème préféré de l'utilisateur
|
||||
if (newTheme !== 'light') {
|
||||
setUserPreferredTheme(newTheme);
|
||||
}
|
||||
|
||||
|
||||
// Sauvegarder en base seulement si l'utilisateur est authentifié
|
||||
if (session?.user?.id) {
|
||||
try {
|
||||
const result = await updateViewPreferences({
|
||||
theme: newTheme
|
||||
theme: newTheme,
|
||||
});
|
||||
if (!result.success) {
|
||||
console.error('Erreur lors de la sauvegarde du thème:', result.error);
|
||||
@@ -88,7 +106,7 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
||||
console.error('Erreur lors de la sauvegarde du thème:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Afficher le toast avec le nom du thème
|
||||
showToast(`Thème: ${THEME_NAMES[newTheme]}`, 2000, getThemeIcon(newTheme));
|
||||
};
|
||||
@@ -99,17 +117,23 @@ export function ThemeProvider({ children, initialTheme = 'dark', userPreferredTh
|
||||
await setTheme(THEME_CONFIG.darkThemes[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Sinon, on passe au thème dark suivant
|
||||
const nextTheme = getNextDarkTheme(theme);
|
||||
await setTheme(nextTheme);
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme, cycleDarkThemes, userPreferredTheme }}>
|
||||
<div className={mounted ? theme : initialTheme}>
|
||||
{children}
|
||||
</div>
|
||||
<ThemeContext.Provider
|
||||
value={{
|
||||
theme,
|
||||
toggleTheme,
|
||||
setTheme,
|
||||
cycleDarkThemes,
|
||||
userPreferredTheme,
|
||||
}}
|
||||
>
|
||||
<div className={mounted ? theme : initialTheme}>{children}</div>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useContext, ReactNode, useState, useCallback, useEffect, useTransition } from 'react';
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
ReactNode,
|
||||
useState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
|
||||
// Types pour les timeouts de debounce
|
||||
declare global {
|
||||
@@ -10,7 +18,13 @@ declare global {
|
||||
columnVisibilitySyncTimeout?: NodeJS.Timeout;
|
||||
}
|
||||
}
|
||||
import { UserPreferences, KanbanFilters, ViewPreferences, ColumnVisibility, TaskStatus } from '@/lib/types';
|
||||
import {
|
||||
UserPreferences,
|
||||
KanbanFilters,
|
||||
ViewPreferences,
|
||||
ColumnVisibility,
|
||||
TaskStatus,
|
||||
} from '@/lib/types';
|
||||
import {
|
||||
updateViewPreferences as updateViewPreferencesAction,
|
||||
updateKanbanFilters as updateKanbanFiltersAction,
|
||||
@@ -18,7 +32,7 @@ import {
|
||||
toggleObjectivesVisibility as toggleObjectivesVisibilityAction,
|
||||
toggleObjectivesCollapse as toggleObjectivesCollapseAction,
|
||||
toggleFontSize as toggleFontSizeAction,
|
||||
toggleColumnVisibility as toggleColumnVisibilityAction
|
||||
toggleColumnVisibility as toggleColumnVisibilityAction,
|
||||
} from '@/actions/preferences';
|
||||
import { useTheme } from './ThemeContext';
|
||||
import { useSession } from 'next-auth/react';
|
||||
@@ -26,25 +40,27 @@ import { useSession } from 'next-auth/react';
|
||||
interface UserPreferencesContextType {
|
||||
preferences: UserPreferences;
|
||||
isPending: boolean;
|
||||
|
||||
|
||||
// Kanban Filters
|
||||
updateKanbanFilters: (updates: Partial<KanbanFilters>) => void;
|
||||
|
||||
// View Preferences
|
||||
|
||||
// View Preferences
|
||||
updateViewPreferences: (updates: Partial<ViewPreferences>) => void;
|
||||
toggleObjectivesVisibility: () => void;
|
||||
toggleObjectivesCollapse: () => void;
|
||||
toggleTheme: () => void;
|
||||
setTheme: (theme: 'light' | 'dark') => void;
|
||||
toggleFontSize: () => void;
|
||||
|
||||
|
||||
// Column Visibility
|
||||
updateColumnVisibility: (updates: Partial<ColumnVisibility>) => void;
|
||||
toggleColumnVisibility: (status: TaskStatus) => void;
|
||||
isColumnVisible: (status: TaskStatus) => boolean;
|
||||
}
|
||||
|
||||
const UserPreferencesContext = createContext<UserPreferencesContextType | null>(null);
|
||||
const UserPreferencesContext = createContext<UserPreferencesContextType | null>(
|
||||
null
|
||||
);
|
||||
|
||||
interface UserPreferencesProviderProps {
|
||||
children: ReactNode;
|
||||
@@ -57,7 +73,7 @@ const defaultPreferences: UserPreferences = {
|
||||
tags: [],
|
||||
priorities: [],
|
||||
showCompleted: false,
|
||||
sortBy: 'priority'
|
||||
sortBy: 'priority',
|
||||
},
|
||||
viewPreferences: {
|
||||
compactView: false,
|
||||
@@ -69,25 +85,30 @@ const defaultPreferences: UserPreferences = {
|
||||
fontSize: 'medium',
|
||||
backgroundImage: undefined,
|
||||
backgroundBlur: 0,
|
||||
backgroundOpacity: 100
|
||||
backgroundOpacity: 100,
|
||||
},
|
||||
columnVisibility: {
|
||||
hiddenStatuses: []
|
||||
hiddenStatuses: [],
|
||||
},
|
||||
jiraConfig: {
|
||||
enabled: false
|
||||
enabled: false,
|
||||
},
|
||||
jiraAutoSync: false,
|
||||
jiraSyncInterval: 'daily',
|
||||
tfsConfig: {
|
||||
enabled: false
|
||||
enabled: false,
|
||||
},
|
||||
tfsAutoSync: false,
|
||||
tfsSyncInterval: 'daily'
|
||||
tfsSyncInterval: 'daily',
|
||||
};
|
||||
|
||||
export function UserPreferencesProvider({ children, initialPreferences }: UserPreferencesProviderProps) {
|
||||
const [preferences, setPreferences] = useState<UserPreferences>(initialPreferences || defaultPreferences);
|
||||
export function UserPreferencesProvider({
|
||||
children,
|
||||
initialPreferences,
|
||||
}: UserPreferencesProviderProps) {
|
||||
const [preferences, setPreferences] = useState<UserPreferences>(
|
||||
initialPreferences || defaultPreferences
|
||||
);
|
||||
const [isPending] = useTransition();
|
||||
const { toggleTheme: themeToggleTheme, setTheme: themeSetTheme } = useTheme();
|
||||
const { status } = useSession();
|
||||
@@ -95,7 +116,7 @@ export function UserPreferencesProvider({ children, initialPreferences }: UserPr
|
||||
// Fonction pour charger les préférences côté client
|
||||
const loadUserPreferences = useCallback(async () => {
|
||||
if (status === 'loading') return; // Attendre que la session soit chargée
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/user-preferences');
|
||||
if (response.ok) {
|
||||
@@ -123,54 +144,69 @@ export function UserPreferencesProvider({ children, initialPreferences }: UserPr
|
||||
// Ne plus synchroniser automatiquement - ThemeContext est la source de vérité
|
||||
|
||||
// === KANBAN FILTERS ===
|
||||
|
||||
const updateKanbanFilters = useCallback((updates: Partial<KanbanFilters>) => {
|
||||
// Optimistic update immédiat
|
||||
setPreferences(prev => ({
|
||||
...prev,
|
||||
kanbanFilters: { ...prev.kanbanFilters, ...updates }
|
||||
}));
|
||||
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
const newFilters = { ...preferences.kanbanFilters, ...updates };
|
||||
localStorage.setItem('kanbanFilters', JSON.stringify(newFilters));
|
||||
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.kanbanFiltersSyncTimeout);
|
||||
window.kanbanFiltersSyncTimeout = setTimeout(() => {
|
||||
updateKanbanFiltersAction(updates).catch(error => {
|
||||
console.error('Erreur lors de la mise à jour des filtres Kanban:', error);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
}, [preferences.kanbanFilters]);
|
||||
|
||||
const updateKanbanFilters = useCallback(
|
||||
(updates: Partial<KanbanFilters>) => {
|
||||
// Optimistic update immédiat
|
||||
setPreferences((prev) => ({
|
||||
...prev,
|
||||
kanbanFilters: { ...prev.kanbanFilters, ...updates },
|
||||
}));
|
||||
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
const newFilters = { ...preferences.kanbanFilters, ...updates };
|
||||
localStorage.setItem('kanbanFilters', JSON.stringify(newFilters));
|
||||
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.kanbanFiltersSyncTimeout);
|
||||
window.kanbanFiltersSyncTimeout = setTimeout(() => {
|
||||
updateKanbanFiltersAction(updates).catch((error) => {
|
||||
console.error(
|
||||
'Erreur lors de la mise à jour des filtres Kanban:',
|
||||
error
|
||||
);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
},
|
||||
[preferences.kanbanFilters]
|
||||
);
|
||||
|
||||
// === VIEW PREFERENCES ===
|
||||
|
||||
const updateViewPreferences = useCallback((updates: Partial<ViewPreferences>) => {
|
||||
// Optimistic update immédiat
|
||||
setPreferences(prev => ({
|
||||
...prev,
|
||||
viewPreferences: { ...prev.viewPreferences, ...updates }
|
||||
}));
|
||||
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
const newViewPrefs = { ...preferences.viewPreferences, ...updates };
|
||||
localStorage.setItem('viewPreferences', JSON.stringify(newViewPrefs));
|
||||
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.viewPreferencesSyncTimeout);
|
||||
window.viewPreferencesSyncTimeout = setTimeout(() => {
|
||||
updateViewPreferencesAction(updates).catch(error => {
|
||||
console.error('Erreur lors de la mise à jour des préférences de vue:', error);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
}, [preferences.viewPreferences]);
|
||||
|
||||
const updateViewPreferences = useCallback(
|
||||
(updates: Partial<ViewPreferences>) => {
|
||||
// Optimistic update immédiat
|
||||
setPreferences((prev) => ({
|
||||
...prev,
|
||||
viewPreferences: { ...prev.viewPreferences, ...updates },
|
||||
}));
|
||||
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
const newViewPrefs = { ...preferences.viewPreferences, ...updates };
|
||||
localStorage.setItem('viewPreferences', JSON.stringify(newViewPrefs));
|
||||
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.viewPreferencesSyncTimeout);
|
||||
window.viewPreferencesSyncTimeout = setTimeout(() => {
|
||||
updateViewPreferencesAction(updates).catch((error) => {
|
||||
console.error(
|
||||
'Erreur lors de la mise à jour des préférences de vue:',
|
||||
error
|
||||
);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
},
|
||||
[preferences.viewPreferences]
|
||||
);
|
||||
|
||||
const toggleObjectivesVisibility = useCallback(() => {
|
||||
// Non-bloquant : utiliser setTimeout pour éviter de bloquer l'UI
|
||||
setTimeout(() => {
|
||||
toggleObjectivesVisibilityAction().catch(error => {
|
||||
console.error('Erreur lors du toggle de la visibilité des objectifs:', error);
|
||||
toggleObjectivesVisibilityAction().catch((error) => {
|
||||
console.error(
|
||||
'Erreur lors du toggle de la visibilité des objectifs:',
|
||||
error
|
||||
);
|
||||
});
|
||||
}, 0);
|
||||
}, []);
|
||||
@@ -178,8 +214,11 @@ export function UserPreferencesProvider({ children, initialPreferences }: UserPr
|
||||
const toggleObjectivesCollapse = useCallback(() => {
|
||||
// Non-bloquant : utiliser setTimeout pour éviter de bloquer l'UI
|
||||
setTimeout(() => {
|
||||
toggleObjectivesCollapseAction().catch(error => {
|
||||
console.error('Erreur lors du toggle du collapse des objectifs:', error);
|
||||
toggleObjectivesCollapseAction().catch((error) => {
|
||||
console.error(
|
||||
'Erreur lors du toggle du collapse des objectifs:',
|
||||
error
|
||||
);
|
||||
});
|
||||
}, 0);
|
||||
}, []);
|
||||
@@ -188,85 +227,118 @@ export function UserPreferencesProvider({ children, initialPreferences }: UserPr
|
||||
themeToggleTheme();
|
||||
}, [themeToggleTheme]);
|
||||
|
||||
const setTheme = useCallback((theme: 'light' | 'dark') => {
|
||||
themeSetTheme(theme);
|
||||
}, [themeSetTheme]);
|
||||
const setTheme = useCallback(
|
||||
(theme: 'light' | 'dark') => {
|
||||
themeSetTheme(theme);
|
||||
},
|
||||
[themeSetTheme]
|
||||
);
|
||||
|
||||
const toggleFontSize = useCallback(() => {
|
||||
// Optimistic update - cycle through font sizes
|
||||
const fontSizes: ('small' | 'medium' | 'large')[] = ['small', 'medium', 'large'];
|
||||
const currentIndex = fontSizes.indexOf(preferences.viewPreferences.fontSize);
|
||||
const fontSizes: ('small' | 'medium' | 'large')[] = [
|
||||
'small',
|
||||
'medium',
|
||||
'large',
|
||||
];
|
||||
const currentIndex = fontSizes.indexOf(
|
||||
preferences.viewPreferences.fontSize
|
||||
);
|
||||
const nextIndex = (currentIndex + 1) % fontSizes.length;
|
||||
const newFontSize = fontSizes[nextIndex];
|
||||
|
||||
setPreferences(prev => ({
|
||||
...prev,
|
||||
viewPreferences: { ...prev.viewPreferences, fontSize: newFontSize }
|
||||
|
||||
setPreferences((prev) => ({
|
||||
...prev,
|
||||
viewPreferences: { ...prev.viewPreferences, fontSize: newFontSize },
|
||||
}));
|
||||
|
||||
|
||||
// Non-bloquant : utiliser setTimeout pour éviter de bloquer l'UI
|
||||
setTimeout(() => {
|
||||
toggleFontSizeAction().catch(error => {
|
||||
toggleFontSizeAction().catch((error) => {
|
||||
console.error('Erreur lors du toggle de la taille de police:', error);
|
||||
});
|
||||
}, 0);
|
||||
}, [preferences.viewPreferences.fontSize]);
|
||||
|
||||
// === COLUMN VISIBILITY ===
|
||||
|
||||
const updateColumnVisibility = useCallback((updates: Partial<ColumnVisibility>) => {
|
||||
// Optimistic update immédiat
|
||||
setPreferences(prev => ({
|
||||
...prev,
|
||||
columnVisibility: { ...prev.columnVisibility, ...updates }
|
||||
}));
|
||||
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
const newColumnVisibility = { ...preferences.columnVisibility, ...updates };
|
||||
localStorage.setItem('columnVisibility', JSON.stringify(newColumnVisibility));
|
||||
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.columnVisibilitySyncTimeout);
|
||||
window.columnVisibilitySyncTimeout = setTimeout(() => {
|
||||
updateColumnVisibilityAction(updates).catch(error => {
|
||||
console.error('Erreur lors de la mise à jour de la visibilité des colonnes:', error);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
}, [preferences.columnVisibility]);
|
||||
|
||||
const toggleColumnVisibility = useCallback((status: TaskStatus) => {
|
||||
const hiddenStatuses = [...preferences.columnVisibility.hiddenStatuses];
|
||||
|
||||
// Optimistic update
|
||||
if (hiddenStatuses.includes(status)) {
|
||||
// Remove from hidden
|
||||
const index = hiddenStatuses.indexOf(status);
|
||||
hiddenStatuses.splice(index, 1);
|
||||
} else {
|
||||
// Add to hidden
|
||||
hiddenStatuses.push(status);
|
||||
}
|
||||
|
||||
setPreferences(prev => ({
|
||||
...prev,
|
||||
columnVisibility: { ...prev.columnVisibility, hiddenStatuses }
|
||||
}));
|
||||
const updateColumnVisibility = useCallback(
|
||||
(updates: Partial<ColumnVisibility>) => {
|
||||
// Optimistic update immédiat
|
||||
setPreferences((prev) => ({
|
||||
...prev,
|
||||
columnVisibility: { ...prev.columnVisibility, ...updates },
|
||||
}));
|
||||
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
localStorage.setItem('columnVisibility', JSON.stringify({ hiddenStatuses }));
|
||||
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.columnVisibilitySyncTimeout);
|
||||
window.columnVisibilitySyncTimeout = setTimeout(() => {
|
||||
toggleColumnVisibilityAction(status).catch(error => {
|
||||
console.error('Erreur lors du toggle de la visibilité des colonnes:', error);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
}, [preferences.columnVisibility.hiddenStatuses]);
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
const newColumnVisibility = {
|
||||
...preferences.columnVisibility,
|
||||
...updates,
|
||||
};
|
||||
localStorage.setItem(
|
||||
'columnVisibility',
|
||||
JSON.stringify(newColumnVisibility)
|
||||
);
|
||||
|
||||
const isColumnVisible = useCallback((status: TaskStatus) => {
|
||||
return !preferences.columnVisibility.hiddenStatuses.includes(status);
|
||||
}, [preferences.columnVisibility.hiddenStatuses]);
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.columnVisibilitySyncTimeout);
|
||||
window.columnVisibilitySyncTimeout = setTimeout(() => {
|
||||
updateColumnVisibilityAction(updates).catch((error) => {
|
||||
console.error(
|
||||
'Erreur lors de la mise à jour de la visibilité des colonnes:',
|
||||
error
|
||||
);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
},
|
||||
[preferences.columnVisibility]
|
||||
);
|
||||
|
||||
const toggleColumnVisibility = useCallback(
|
||||
(status: TaskStatus) => {
|
||||
const hiddenStatuses = [...preferences.columnVisibility.hiddenStatuses];
|
||||
|
||||
// Optimistic update
|
||||
if (hiddenStatuses.includes(status)) {
|
||||
// Remove from hidden
|
||||
const index = hiddenStatuses.indexOf(status);
|
||||
hiddenStatuses.splice(index, 1);
|
||||
} else {
|
||||
// Add to hidden
|
||||
hiddenStatuses.push(status);
|
||||
}
|
||||
|
||||
setPreferences((prev) => ({
|
||||
...prev,
|
||||
columnVisibility: { ...prev.columnVisibility, hiddenStatuses },
|
||||
}));
|
||||
|
||||
// Sauvegarde locale immédiate pour éviter les pertes
|
||||
localStorage.setItem(
|
||||
'columnVisibility',
|
||||
JSON.stringify({ hiddenStatuses })
|
||||
);
|
||||
|
||||
// Sync en arrière-plan avec debounce
|
||||
clearTimeout(window.columnVisibilitySyncTimeout);
|
||||
window.columnVisibilitySyncTimeout = setTimeout(() => {
|
||||
toggleColumnVisibilityAction(status).catch((error) => {
|
||||
console.error(
|
||||
'Erreur lors du toggle de la visibilité des colonnes:',
|
||||
error
|
||||
);
|
||||
});
|
||||
}, 1000); // debounce 1s
|
||||
},
|
||||
[preferences.columnVisibility.hiddenStatuses]
|
||||
);
|
||||
|
||||
const isColumnVisible = useCallback(
|
||||
(status: TaskStatus) => {
|
||||
return !preferences.columnVisibility.hiddenStatuses.includes(status);
|
||||
},
|
||||
[preferences.columnVisibility.hiddenStatuses]
|
||||
);
|
||||
|
||||
const contextValue: UserPreferencesContextType = {
|
||||
preferences,
|
||||
@@ -280,7 +352,7 @@ export function UserPreferencesProvider({ children, initialPreferences }: UserPr
|
||||
toggleFontSize,
|
||||
updateColumnVisibility,
|
||||
toggleColumnVisibility,
|
||||
isColumnVisible
|
||||
isColumnVisible,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -293,7 +365,9 @@ export function UserPreferencesProvider({ children, initialPreferences }: UserPr
|
||||
export function useUserPreferences() {
|
||||
const context = useContext(UserPreferencesContext);
|
||||
if (!context) {
|
||||
throw new Error('useUserPreferences must be used within a UserPreferencesProvider');
|
||||
throw new Error(
|
||||
'useUserPreferences must be used within a UserPreferencesProvider'
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user