feat: implement optimistic UI for checkbox toggling in DailyCheckboxItem
- Added optimistic state handling in `DailyCheckboxItem` for immediate feedback on checkbox toggles, improving user experience. - Updated `useDaily` hook to handle checkbox state updates without blocking UI, ensuring smoother interactions. - Enhanced error handling to rollback state on toggle failures, maintaining data integrity.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { DailyCheckbox, DailyCheckboxType } from '@/lib/types';
|
import { DailyCheckbox, DailyCheckboxType } from '@/lib/types';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
@@ -24,6 +24,36 @@ export function DailyCheckboxItem({
|
|||||||
const [inlineEditingId, setInlineEditingId] = useState<string | null>(null);
|
const [inlineEditingId, setInlineEditingId] = useState<string | null>(null);
|
||||||
const [inlineEditingText, setInlineEditingText] = useState('');
|
const [inlineEditingText, setInlineEditingText] = useState('');
|
||||||
const [editingCheckbox, setEditingCheckbox] = useState<DailyCheckbox | null>(null);
|
const [editingCheckbox, setEditingCheckbox] = useState<DailyCheckbox | null>(null);
|
||||||
|
const [optimisticChecked, setOptimisticChecked] = useState<boolean | null>(null);
|
||||||
|
|
||||||
|
// État optimiste local pour une réponse immédiate
|
||||||
|
const isChecked = optimisticChecked !== null ? optimisticChecked : checkbox.isChecked;
|
||||||
|
|
||||||
|
// Synchroniser l'état optimiste avec les changements externes
|
||||||
|
useEffect(() => {
|
||||||
|
if (optimisticChecked !== null && optimisticChecked === checkbox.isChecked) {
|
||||||
|
// L'état serveur a été mis à jour, on peut reset l'optimiste
|
||||||
|
setOptimisticChecked(null);
|
||||||
|
}
|
||||||
|
}, [checkbox.isChecked, optimisticChecked]);
|
||||||
|
|
||||||
|
// Handler optimiste pour le toggle
|
||||||
|
const handleOptimisticToggle = async () => {
|
||||||
|
const newCheckedState = !isChecked;
|
||||||
|
|
||||||
|
// Mise à jour optimiste immédiate
|
||||||
|
setOptimisticChecked(newCheckedState);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await onToggle(checkbox.id);
|
||||||
|
// Reset l'état optimiste après succès
|
||||||
|
setOptimisticChecked(null);
|
||||||
|
} catch (error) {
|
||||||
|
// Rollback en cas d'erreur
|
||||||
|
setOptimisticChecked(null);
|
||||||
|
console.error('Erreur lors du toggle:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Édition inline simple
|
// Édition inline simple
|
||||||
const handleStartInlineEdit = () => {
|
const handleStartInlineEdit = () => {
|
||||||
@@ -82,8 +112,8 @@ export function DailyCheckboxItem({
|
|||||||
{/* Checkbox */}
|
{/* Checkbox */}
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={checkbox.isChecked}
|
checked={isChecked}
|
||||||
onChange={() => onToggle(checkbox.id)}
|
onChange={handleOptimisticToggle}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="w-4 h-4 md:w-3.5 md:h-3.5 rounded border border-[var(--border)] text-[var(--primary)] focus:ring-[var(--primary)]/20 focus:ring-1"
|
className="w-4 h-4 md:w-3.5 md:h-3.5 rounded border border-[var(--border)] text-[var(--primary)] focus:ring-[var(--primary)]/20 focus:ring-1"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -209,7 +209,6 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD
|
|||||||
if (!dailyView) return Promise.resolve();
|
if (!dailyView) return Promise.resolve();
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
startTransition(async () => {
|
|
||||||
// Trouver la checkbox dans yesterday ou today
|
// Trouver la checkbox dans yesterday ou today
|
||||||
let checkbox = dailyView.yesterday.find(cb => cb.id === checkboxId);
|
let checkbox = dailyView.yesterday.find(cb => cb.id === checkboxId);
|
||||||
if (!checkbox) {
|
if (!checkbox) {
|
||||||
@@ -235,22 +234,22 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD
|
|||||||
)
|
)
|
||||||
} : null);
|
} : null);
|
||||||
|
|
||||||
try {
|
// Appel serveur en arrière-plan (sans startTransition)
|
||||||
const result = await toggleCheckboxAction(checkboxId);
|
toggleCheckboxAction(checkboxId)
|
||||||
|
.then(result => {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
// Rollback en cas d'erreur
|
// Rollback en cas d'erreur
|
||||||
setDailyView(previousDailyView);
|
setDailyView(previousDailyView);
|
||||||
setError(result.error || 'Erreur lors de la mise à jour de la checkbox');
|
setError(result.error || 'Erreur lors de la mise à jour de la checkbox');
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
} catch (err) {
|
})
|
||||||
|
.catch(err => {
|
||||||
// Rollback en cas d'erreur
|
// Rollback en cas d'erreur
|
||||||
setDailyView(previousDailyView);
|
setDailyView(previousDailyView);
|
||||||
setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour de la checkbox');
|
setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour de la checkbox');
|
||||||
console.error('Erreur toggleCheckbox:', err);
|
console.error('Erreur toggleCheckbox:', err);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [dailyView]);
|
}, [dailyView]);
|
||||||
|
|||||||
Reference in New Issue
Block a user