refactor: userpreferences are now in the DB

This commit is contained in:
Julien Froidefond
2025-09-17 08:30:36 +02:00
parent 4f137455f4
commit 14d300c682
24 changed files with 763 additions and 404 deletions

View File

@@ -1,12 +1,12 @@
'use client';
import { useState, useEffect } from 'react';
import { useState } from 'react';
import { KanbanBoardContainer } from '@/components/kanban/BoardContainer';
import { Header } from '@/components/ui/Header';
import { TasksProvider, useTasksContext } from '@/contexts/TasksContext';
import { Task, Tag, TaskStats } from '@/lib/types';
import { UserPreferencesProvider, useUserPreferences } from '@/contexts/UserPreferencesContext';
import { Task, Tag, TaskStats, UserPreferences } from '@/lib/types';
import { CreateTaskData } from '@/clients/tasks-client';
import { userPreferencesService } from '@/services/user-preferences';
import { CreateTaskForm } from '@/components/forms/CreateTaskForm';
import { Button } from '@/components/ui/Button';
@@ -14,32 +14,26 @@ interface HomePageClientProps {
initialTasks: Task[];
initialStats: TaskStats;
initialTags: (Tag & { usage: number })[];
initialPreferences: UserPreferences;
}
function HomePageContent() {
const { stats, syncing, createTask } = useTasksContext();
const [showFilters, setShowFilters] = useState(true);
const [showObjectives, setShowObjectives] = useState(true);
const { preferences, updateViewPreferences } = useUserPreferences();
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
// Charger les préférences depuis le service
useEffect(() => {
const viewPreferences = userPreferencesService.getViewPreferences();
setShowFilters(viewPreferences.showFilters);
setShowObjectives(viewPreferences.showObjectives);
}, []);
// Extraire les préférences du context
const showFilters = preferences.viewPreferences.showFilters;
const showObjectives = preferences.viewPreferences.showObjectives;
// Sauvegarder les préférences via le service
// Handlers pour les toggles (sauvegarde automatique via le context)
const handleToggleFilters = () => {
const newValue = !showFilters;
setShowFilters(newValue);
userPreferencesService.updateViewPreferences({ showFilters: newValue });
updateViewPreferences({ showFilters: !showFilters });
};
const handleToggleObjectives = () => {
const newValue = !showObjectives;
setShowObjectives(newValue);
userPreferencesService.updateViewPreferences({ showObjectives: newValue });
updateViewPreferences({ showObjectives: !showObjectives });
};
// Handler pour la création de tâche depuis la barre de contrôles
@@ -127,14 +121,16 @@ function HomePageContent() {
);
}
export function HomePageClient({ initialTasks, initialStats, initialTags }: HomePageClientProps) {
export function HomePageClient({ initialTasks, initialStats, initialTags, initialPreferences }: HomePageClientProps) {
return (
<TasksProvider
initialTasks={initialTasks}
initialStats={initialStats}
initialTags={initialTags}
>
<HomePageContent />
</TasksProvider>
<UserPreferencesProvider initialPreferences={initialPreferences}>
<TasksProvider
initialTasks={initialTasks}
initialStats={initialStats}
initialTags={initialTags}
>
<HomePageContent />
</TasksProvider>
</UserPreferencesProvider>
);
}

View File

@@ -4,16 +4,14 @@ import { Task, TaskStatus } from '@/lib/types';
import { KanbanColumn } from './Column';
import { CreateTaskData } from '@/clients/tasks-client';
import { useMemo, useState } from 'react';
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { useDragAndDrop } from '@/hooks/useDragAndDrop';
import { getAllStatuses } from '@/lib/status-config';
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
PointerSensor,
useSensor,
useSensors,
DragStartEvent
} from '@dnd-kit/core';
import { TaskCard } from './TaskCard';
@@ -30,18 +28,8 @@ interface KanbanBoardProps {
export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, onUpdateStatus, compactView = false, visibleStatuses }: KanbanBoardProps) {
const [activeTask, setActiveTask] = useState<Task | null>(null);
// Gestion de la visibilité des colonnes (utilise les props si disponibles)
const { getVisibleStatuses } = useColumnVisibility();
// Configuration des capteurs pour le drag & drop
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8, // Évite les clics accidentels
},
})
);
const { isColumnVisible } = useUserPreferences();
const { isMounted, sensors } = useDragAndDrop();
// Organiser les tâches par statut
const tasksByStatus = useMemo(() => {
const grouped = tasks.reduce((acc, task) => {
@@ -66,7 +54,7 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
// Filtrer les colonnes visibles
const visibleColumns = visibleStatuses ?
allColumns.filter(column => visibleStatuses.includes(column.id)) :
getVisibleStatuses(allColumns);
allColumns.filter(column => isColumnVisible(column.id));
// Gestion du début du drag
@@ -94,17 +82,10 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
await onUpdateStatus(taskId, newStatus);
};
return (
<DndContext
id="kanban-board"
sensors={sensors}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className="h-full flex flex-col bg-[var(--background)]">
{/* Espacement supérieur */}
<div className="pt-4"></div>
const content = (
<div className="h-full flex flex-col bg-[var(--background)]">
{/* Espacement supérieur */}
<div className="pt-4"></div>
{/* Board tech dark */}
<div className="flex-1 flex gap-6 overflow-x-auto p-6">
@@ -121,9 +102,22 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
/>
))}
</div>
</div>
);
</div>
if (!isMounted) {
return content;
}
return (
<DndContext
id="kanban-board"
sensors={sensors}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
{content}
{/* Overlay pour le drag & drop */}
<DragOverlay>
{activeTask ? (

View File

@@ -8,9 +8,9 @@ import { ObjectivesBoard } from './ObjectivesBoard';
import { KanbanFilters } from './KanbanFilters';
import { EditTaskForm } from '@/components/forms/EditTaskForm';
import { useTasksContext } from '@/contexts/TasksContext';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { Task, TaskStatus } from '@/lib/types';
import { UpdateTaskData, CreateTaskData } from '@/clients/tasks-client';
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
import { getAllStatuses } from '@/lib/status-config';
interface KanbanBoardContainerProps {
@@ -20,7 +20,7 @@ interface KanbanBoardContainerProps {
export function KanbanBoardContainer({
showFilters = true,
showObjectives = true
showObjectives = true
}: KanbanBoardContainerProps = {}) {
const {
filteredTasks,
@@ -35,9 +35,10 @@ export function KanbanBoardContainer({
tags
} = useTasksContext();
const { hiddenStatuses, toggleStatusVisibility, getVisibleStatuses } = useColumnVisibility();
const { preferences, toggleColumnVisibility, isColumnVisible } = useUserPreferences();
const allStatuses = getAllStatuses();
const visibleStatuses = getVisibleStatuses(allStatuses.map(s => ({ id: s.key }))).map(s => s.id);
const visibleStatuses = allStatuses.filter(status => isColumnVisible(status.key)).map(s => s.key);
const [editingTask, setEditingTask] = useState<Task | null>(null);
const handleEditTask = (task: Task) => {
@@ -79,8 +80,8 @@ export function KanbanBoardContainer({
<KanbanFilters
filters={kanbanFilters}
onFiltersChange={setKanbanFilters}
hiddenStatuses={hiddenStatuses}
onToggleStatusVisibility={toggleStatusVisibility}
hiddenStatuses={new Set(preferences.columnVisibility.hiddenStatuses)}
onToggleStatusVisibility={toggleColumnVisibility}
/>
)}

View File

@@ -8,7 +8,7 @@ import { Input } from '@/components/ui/Input';
import { useTasksContext } from '@/contexts/TasksContext';
import { getAllPriorities, getPriorityColorHex } from '@/lib/status-config';
import { SORT_OPTIONS } from '@/lib/sort-config';
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { ColumnVisibilityToggle } from './ColumnVisibilityToggle';
export interface KanbanFilters {
@@ -32,11 +32,11 @@ interface KanbanFiltersProps {
export function KanbanFilters({ filters, onFiltersChange, hiddenStatuses: propsHiddenStatuses, onToggleStatusVisibility }: KanbanFiltersProps) {
const { tags: availableTags, regularTasks } = useTasksContext();
const { hiddenStatuses: localHiddenStatuses, toggleStatusVisibility: localToggleStatusVisibility } = useColumnVisibility();
const { preferences, toggleColumnVisibility } = useUserPreferences();
// Utiliser les props si disponibles, sinon utiliser l'état local
const hiddenStatuses = propsHiddenStatuses || localHiddenStatuses;
const toggleStatusVisibility = onToggleStatusVisibility || localToggleStatusVisibility;
// Utiliser les props si disponibles, sinon utiliser le context
const hiddenStatuses = propsHiddenStatuses || new Set(preferences.columnVisibility.hiddenStatuses);
const toggleStatusVisibility = onToggleStatusVisibility || toggleColumnVisibility;
const [isExpanded, setIsExpanded] = useState(false);
const [isSortExpanded, setIsSortExpanded] = useState(false);
const [isSwimlaneModeExpanded, setIsSwimlaneModeExpanded] = useState(false);

View File

@@ -1,7 +1,8 @@
'use client';
import { useState } from 'react';
import { useObjectivesCollapse } from '@/hooks/useObjectivesCollapse';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { useDragAndDrop } from '@/hooks/useDragAndDrop';
import { Task, TaskStatus } from '@/lib/types';
import { TaskCard } from './TaskCard';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
@@ -10,10 +11,7 @@ import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
PointerSensor,
useSensor,
useSensors,
DragStartEvent
} from '@dnd-kit/core';
import {
SortableContext,
@@ -105,18 +103,11 @@ export function ObjectivesBoard({
compactView = false,
pinnedTagName = "Objectifs"
}: ObjectivesBoardProps) {
const { isCollapsed, toggleCollapse } = useObjectivesCollapse();
const { preferences, toggleObjectivesCollapse } = useUserPreferences();
const isCollapsed = preferences.viewPreferences.objectivesCollapsed;
const { isMounted, sensors } = useDragAndDrop();
const [activeTask, setActiveTask] = useState<Task | null>(null);
// Configuration des sensors pour le drag & drop
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8, // Évite les clics accidentels
},
})
);
// Handlers pour le drag & drop
const handleDragStart = (event: DragStartEvent) => {
const task = tasks.find(t => t.id === event.active.id);
@@ -143,20 +134,14 @@ export function ObjectivesBoard({
return null; // Ne rien afficher s'il n'y a pas d'objectifs
}
return (
<DndContext
id="objectives-board"
sensors={sensors}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className="bg-[var(--card)]/30 border-b border-[var(--accent)]/30">
<div className="container mx-auto px-6 py-4">
const content = (
<div className="bg-[var(--card)]/30 border-b border-[var(--accent)]/30">
<div className="container mx-auto px-6 py-4">
<Card variant="column" className="border-[var(--accent)]/30 shadow-[var(--accent)]/10">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<button
onClick={toggleCollapse}
onClick={toggleObjectivesCollapse}
className="flex items-center gap-3 hover:bg-[var(--accent)]/20 rounded-lg p-2 -m-2 transition-colors group"
>
<div className="w-3 h-3 bg-[var(--accent)] rounded-full animate-pulse shadow-[var(--accent)]/50 shadow-lg"></div>
@@ -189,7 +174,7 @@ export function ObjectivesBoard({
{/* Bouton collapse séparé pour mobile */}
<button
onClick={toggleCollapse}
onClick={toggleObjectivesCollapse}
className="lg:hidden p-1 hover:bg-[var(--accent)]/20 rounded transition-colors"
aria-label={isCollapsed ? "Développer" : "Réduire"}
>
@@ -261,7 +246,21 @@ export function ObjectivesBoard({
</Card>
</div>
</div>
);
if (!isMounted) {
return content;
}
return (
<DndContext
id="objectives-board"
sensors={sensors}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
{content}
{/* Overlay pour le drag & drop */}
<DragOverlay>
{activeTask ? (

View File

@@ -5,17 +5,15 @@ import { TaskCard } from './TaskCard';
import { QuickAddTask } from './QuickAddTask';
import { CreateTaskData } from '@/clients/tasks-client';
import { useState } from 'react';
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { useDragAndDrop } from '@/hooks/useDragAndDrop';
import { getAllStatuses } from '@/lib/status-config';
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
closestCenter,
PointerSensor,
useSensor,
useSensors,
closestCenter
} from '@dnd-kit/core';
import {
SortableContext,
@@ -143,19 +141,11 @@ export function SwimlanesBase({
const [showQuickAdd, setShowQuickAdd] = useState<{ [key: string]: boolean }>({});
// Gestion de la visibilité des colonnes
const { getVisibleStatuses } = useColumnVisibility();
const { isColumnVisible } = useUserPreferences();
const { isMounted, sensors } = useDragAndDrop();
const allStatuses = getAllStatuses();
const statusesToShow = visibleStatuses ||
getVisibleStatuses(allStatuses.map(s => ({ id: s.key }))).map(s => s.id);
// Configuration des sensors pour le drag & drop
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
})
);
allStatuses.filter(status => isColumnVisible(status.key)).map(s => s.key);
// Handlers pour le drag & drop
const handleDragStart = (event: DragStartEvent) => {
@@ -203,16 +193,10 @@ export function SwimlanesBase({
setShowQuickAdd(prev => ({ ...prev, [columnId]: !prev[columnId] }));
};
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className="flex flex-col h-full bg-[var(--background)]">
{/* Espacement supérieur */}
<div className="flex-shrink-0 py-2"></div>
const content = (
<div className="flex flex-col h-full bg-[var(--background)]">
{/* Espacement supérieur */}
<div className="flex-shrink-0 py-2"></div>
{/* Headers des colonnes visibles */}
@@ -305,7 +289,21 @@ export function SwimlanesBase({
</div>
</div>
</div>
);
if (!isMounted) {
return content;
}
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
{content}
{/* Drag overlay */}
<DragOverlay>
{activeTask && (
@@ -315,7 +313,6 @@ export function SwimlanesBase({
/>
)}
</DragOverlay>
</DndContext>
);
}