From 618b2e9e5ce954d77cc05fe3684fb2d1254aa27f Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Thu, 18 Sep 2025 14:56:05 +0200 Subject: [PATCH] feat: implement drag-and-drop reordering for daily checkboxes - Added DnD functionality to `DailySection` for reordering checkboxes using `@dnd-kit/core` and `@dnd-kit/sortable`. - Introduced `onReorderCheckboxes` prop to handle server updates after reordering. - Updated `useDaily` hook to streamline error handling during reordering. - Cleaned up `Header` component by removing unnecessary syncing text. - Adjusted `DailyPageClient` to pass reorder function to `DailySection`. --- components/daily/DailyCheckboxSortable.tsx | 78 +++++++++ components/daily/DailySection.tsx | 186 +++++++++++++++------ components/ui/Header.tsx | 2 +- hooks/useDaily.ts | 7 +- src/app/daily/DailyPageClient.tsx | 7 + 5 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 components/daily/DailyCheckboxSortable.tsx diff --git a/components/daily/DailyCheckboxSortable.tsx b/components/daily/DailyCheckboxSortable.tsx new file mode 100644 index 0000000..889d7f9 --- /dev/null +++ b/components/daily/DailyCheckboxSortable.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { DailyCheckbox, DailyCheckboxType } from '@/lib/types'; +import { DailyCheckboxItem } from './DailyCheckboxItem'; + +interface DailyCheckboxSortableProps { + checkbox: DailyCheckbox; + onToggle: (checkboxId: string) => Promise; + onUpdate: (checkboxId: string, text: string, type: DailyCheckboxType, taskId?: string) => Promise; + onDelete: (checkboxId: string) => Promise; + saving?: boolean; +} + +export function DailyCheckboxSortable({ + checkbox, + onToggle, + onUpdate, + onDelete, + saving = false +}: DailyCheckboxSortableProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ + id: checkbox.id, + }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.5 : 1, + }; + + return ( +
+
+ {/* Handle de drag */} +
+
+
+
+
+
+
+ + {/* Checkbox item avec padding left pour le handle */} +
+ +
+
+
+ ); +} diff --git a/components/daily/DailySection.tsx b/components/daily/DailySection.tsx index 18cd1ce..2615067 100644 --- a/components/daily/DailySection.tsx +++ b/components/daily/DailySection.tsx @@ -3,8 +3,13 @@ import { DailyCheckbox, DailyCheckboxType } from '@/lib/types'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; +import { DailyCheckboxSortable } from './DailyCheckboxSortable'; import { DailyCheckboxItem } from './DailyCheckboxItem'; import { DailyAddForm } from './DailyAddForm'; +import { DndContext, closestCenter, DragEndEvent, DragOverlay, DragStartEvent } from '@dnd-kit/core'; +import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable'; +import { useState } from 'react'; +import React from 'react'; interface DailySectionProps { title: string; @@ -14,6 +19,7 @@ interface DailySectionProps { onToggleCheckbox: (checkboxId: string) => Promise; onUpdateCheckbox: (checkboxId: string, text: string, type: DailyCheckboxType, taskId?: string) => Promise; onDeleteCheckbox: (checkboxId: string) => Promise; + onReorderCheckboxes: (date: Date, checkboxIds: string[]) => Promise; onToggleAll?: () => Promise; saving: boolean; refreshing?: boolean; @@ -27,10 +33,13 @@ export function DailySection({ onToggleCheckbox, onUpdateCheckbox, onDeleteCheckbox, + onReorderCheckboxes, onToggleAll, saving, refreshing = false }: DailySectionProps) { + const [activeId, setActiveId] = useState(null); + const [items, setItems] = useState(checkboxes); const formatShortDate = (date: Date) => { return date.toLocaleDateString('fr-FR', { day: '2-digit', @@ -39,67 +48,136 @@ export function DailySection({ }); }; + // Mettre à jour les items quand les checkboxes changent + React.useEffect(() => { + setItems(checkboxes); + }, [checkboxes]); + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(event.active.id as string); + }; + + const handleDragEnd = async (event: DragEndEvent) => { + const { active, over } = event; + setActiveId(null); + + if (!over || active.id === over.id) { + return; + } + + const oldIndex = items.findIndex((item) => item.id === active.id); + const newIndex = items.findIndex((item) => item.id === over.id); + + if (oldIndex !== -1 && newIndex !== -1) { + // Mise à jour optimiste + const newItems = arrayMove(items, oldIndex, newIndex); + setItems(newItems); + + // Envoyer l'ordre au serveur + const checkboxIds = newItems.map(item => item.id); + try { + await onReorderCheckboxes(date, checkboxIds); + } catch (error) { + // Rollback en cas d'erreur + setItems(checkboxes); + console.error('Erreur lors du réordonnancement:', error); + } + } + }; + + const activeCheckbox = activeId ? items.find(item => item.id === activeId) : null; + return ( - - {/* Header */} -
-
-

- {title} ({formatShortDate(date)}) - {refreshing && ( -
- )} -

-
- - {checkboxes.filter(cb => cb.isChecked).length}/{checkboxes.length} - - {onToggleAll && checkboxes.length > 0 && ( - - )} + + + {/* Header */} +
+
+

+ {title} ({formatShortDate(date)}) + {refreshing && ( +
+ )} +

+
+ + {checkboxes.filter(cb => cb.isChecked).length}/{checkboxes.length} + + {onToggleAll && checkboxes.length > 0 && ( + + )} +
-
- {/* Liste des checkboxes - zone scrollable */} -
-
- {checkboxes.map((checkbox) => ( - - ))} + {/* Liste des checkboxes - zone scrollable avec drag & drop */} +
+ item.id)} strategy={verticalListSortingStrategy}> +
+ {items.map((checkbox) => ( + + ))} - {checkboxes.length === 0 && ( -
- Aucune tâche pour cette période + {items.length === 0 && ( +
+ Aucune tâche pour cette période +
+ )}
- )} +
-
- {/* Footer - Formulaire d'ajout toujours en bas */} -
- -
- + {/* Footer - Formulaire d'ajout toujours en bas */} +
+ +
+ + + + {activeCheckbox ? ( +
+
+ Promise.resolve()} + onUpdate={() => Promise.resolve()} + onDelete={() => Promise.resolve()} + saving={false} + /> +
+
+ ) : null} +
+ ); } diff --git a/components/ui/Header.tsx b/components/ui/Header.tsx index a14c7dd..e4d9066 100644 --- a/components/ui/Header.tsx +++ b/components/ui/Header.tsx @@ -51,7 +51,7 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s {title}

- {subtitle} {syncing && '• Synchronisation...'} + {subtitle}

diff --git a/hooks/useDaily.ts b/hooks/useDaily.ts index 289ac6c..d547fba 100644 --- a/hooks/useDaily.ts +++ b/hooks/useDaily.ts @@ -329,10 +329,7 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD const dailyId = data.date.toISOString().split('T')[0]; const result = await reorderCheckboxesAction(dailyId, data.checkboxIds); - if (result.success) { - // Rafraîchir pour obtenir l'ordre correct - await refreshDaily(); - } else { + if (!result.success) { setError(result.error || 'Erreur lors du réordonnancement'); } } catch (err) { @@ -341,7 +338,7 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD } finally { setSaving(false); } - }, [refreshDaily]); + }, []); const goToPreviousDay = useCallback(async (): Promise => { const previousDay = new Date(currentDate); diff --git a/src/app/daily/DailyPageClient.tsx b/src/app/daily/DailyPageClient.tsx index d4f7a3d..e7ebc89 100644 --- a/src/app/daily/DailyPageClient.tsx +++ b/src/app/daily/DailyPageClient.tsx @@ -36,6 +36,7 @@ export function DailyPageClient({ toggleAllYesterday, updateCheckbox, deleteCheckbox, + reorderCheckboxes, goToPreviousDay, goToNextDay, goToToday, @@ -93,6 +94,10 @@ export function DailyPageClient({ }); }; + const handleReorderCheckboxes = async (date: Date, checkboxIds: string[]) => { + await reorderCheckboxes({ date, checkboxIds }); + }; + const getYesterdayDate = () => { const yesterday = new Date(currentDate); yesterday.setDate(yesterday.getDate() - 1); @@ -220,6 +225,7 @@ export function DailyPageClient({ onToggleCheckbox={handleToggleCheckbox} onUpdateCheckbox={handleUpdateCheckbox} onDeleteCheckbox={handleDeleteCheckbox} + onReorderCheckboxes={handleReorderCheckboxes} onToggleAll={toggleAllYesterday} saving={saving} refreshing={refreshing} @@ -234,6 +240,7 @@ export function DailyPageClient({ onToggleCheckbox={handleToggleCheckbox} onUpdateCheckbox={handleUpdateCheckbox} onDeleteCheckbox={handleDeleteCheckbox} + onReorderCheckboxes={handleReorderCheckboxes} onToggleAll={toggleAllToday} saving={saving} refreshing={refreshing}