- 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`.
184 lines
6.1 KiB
TypeScript
184 lines
6.1 KiB
TypeScript
'use client';
|
|
|
|
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;
|
|
date: Date;
|
|
checkboxes: DailyCheckbox[];
|
|
onAddCheckbox: (text: string, type: DailyCheckboxType) => Promise<void>;
|
|
onToggleCheckbox: (checkboxId: string) => Promise<void>;
|
|
onUpdateCheckbox: (checkboxId: string, text: string, type: DailyCheckboxType, taskId?: string) => Promise<void>;
|
|
onDeleteCheckbox: (checkboxId: string) => Promise<void>;
|
|
onReorderCheckboxes: (date: Date, checkboxIds: string[]) => Promise<void>;
|
|
onToggleAll?: () => Promise<void>;
|
|
saving: boolean;
|
|
refreshing?: boolean;
|
|
}
|
|
|
|
export function DailySection({
|
|
title,
|
|
date,
|
|
checkboxes,
|
|
onAddCheckbox,
|
|
onToggleCheckbox,
|
|
onUpdateCheckbox,
|
|
onDeleteCheckbox,
|
|
onReorderCheckboxes,
|
|
onToggleAll,
|
|
saving,
|
|
refreshing = false
|
|
}: DailySectionProps) {
|
|
const [activeId, setActiveId] = useState<string | null>(null);
|
|
const [items, setItems] = useState(checkboxes);
|
|
const formatShortDate = (date: Date) => {
|
|
return date.toLocaleDateString('fr-FR', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
});
|
|
};
|
|
|
|
// 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 (
|
|
<DndContext
|
|
collisionDetection={closestCenter}
|
|
onDragStart={handleDragStart}
|
|
onDragEnd={handleDragEnd}
|
|
id={`daily-dnd-${title.replace(/[^a-zA-Z0-9]/g, '-')}`}
|
|
>
|
|
<Card className="p-0 flex flex-col h-[600px]">
|
|
{/* Header */}
|
|
<div className="p-4 pb-0">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-bold text-[var(--foreground)] font-mono flex items-center gap-2">
|
|
{title} <span className="text-sm font-normal text-[var(--muted-foreground)]">({formatShortDate(date)})</span>
|
|
{refreshing && (
|
|
<div className="w-4 h-4 border-2 border-[var(--primary)] border-t-transparent rounded-full animate-spin"></div>
|
|
)}
|
|
</h2>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xs text-[var(--muted-foreground)] font-mono">
|
|
{checkboxes.filter(cb => cb.isChecked).length}/{checkboxes.length}
|
|
</span>
|
|
{onToggleAll && checkboxes.length > 0 && (
|
|
<Button
|
|
type="button"
|
|
onClick={onToggleAll}
|
|
variant="ghost"
|
|
size="sm"
|
|
disabled={saving}
|
|
className="text-xs px-2 py-1 h-6"
|
|
title="Tout cocher/décocher"
|
|
>
|
|
✓
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Liste des checkboxes - zone scrollable avec drag & drop */}
|
|
<div className="flex-1 px-4 overflow-y-auto min-h-0">
|
|
<SortableContext items={items.map(item => item.id)} strategy={verticalListSortingStrategy}>
|
|
<div className="space-y-1.5 pb-4">
|
|
{items.map((checkbox) => (
|
|
<DailyCheckboxSortable
|
|
key={checkbox.id}
|
|
checkbox={checkbox}
|
|
onToggle={onToggleCheckbox}
|
|
onUpdate={onUpdateCheckbox}
|
|
onDelete={onDeleteCheckbox}
|
|
saving={saving}
|
|
/>
|
|
))}
|
|
|
|
{items.length === 0 && (
|
|
<div className="text-center py-8 text-[var(--muted-foreground)] text-sm font-mono">
|
|
Aucune tâche pour cette période
|
|
</div>
|
|
)}
|
|
</div>
|
|
</SortableContext>
|
|
</div>
|
|
|
|
{/* Footer - Formulaire d'ajout toujours en bas */}
|
|
<div className="p-4 pt-2 border-t border-[var(--border)]/30 bg-[var(--card)]/50">
|
|
<DailyAddForm
|
|
onAdd={onAddCheckbox}
|
|
disabled={saving}
|
|
/>
|
|
</div>
|
|
</Card>
|
|
|
|
<DragOverlay
|
|
dropAnimation={null}
|
|
style={{
|
|
transformOrigin: '0 0',
|
|
}}
|
|
>
|
|
{activeCheckbox ? (
|
|
<div className="bg-[var(--card)] border border-[var(--border)] rounded-md shadow-xl opacity-95 transform rotate-3 scale-105">
|
|
<div className="pl-4">
|
|
<DailyCheckboxItem
|
|
checkbox={activeCheckbox}
|
|
onToggle={() => Promise.resolve()}
|
|
onUpdate={() => Promise.resolve()}
|
|
onDelete={() => Promise.resolve()}
|
|
saving={false}
|
|
/>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</DragOverlay>
|
|
</DndContext>
|
|
);
|
|
}
|