feat: complete daily checkbox actions and cleanup

- Marked all daily checkbox actions as complete in TODO.md.
- Removed outdated mutation methods from `daily-client.ts`, now managed by server actions in `actions/daily.ts`.
- Deleted unused API routes for checkbox management, streamlining the codebase.
- Updated `useDaily.ts` to utilize server actions with `useTransition`, enhancing performance and user experience.
This commit is contained in:
Julien Froidefond
2025-09-18 13:20:44 +02:00
parent 6135fd8cb1
commit 3ce7af043c
6 changed files with 413 additions and 387 deletions

View File

@@ -1,8 +1,15 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useTransition } from 'react';
import { dailyClient, DailyHistoryFilters, DailySearchFilters, ReorderCheckboxesData } from '@/clients/daily-client';
import { DailyView, DailyCheckbox, UpdateDailyCheckboxData, DailyCheckboxType } from '@/lib/types';
import {
toggleCheckbox as toggleCheckboxAction,
addTodayCheckbox as addTodayCheckboxAction,
addYesterdayCheckbox as addYesterdayCheckboxAction,
updateCheckbox as updateCheckboxAction,
deleteCheckbox as deleteCheckboxAction
} from '@/actions/daily';
interface UseDailyState {
dailyView: DailyView | null;
@@ -10,6 +17,7 @@ interface UseDailyState {
refreshing: boolean; // Pour les refresh silencieux
error: string | null;
saving: boolean; // Pour indiquer les opérations en cours
isPending: boolean; // Pour indiquer les server actions en cours
}
interface UseDailyActions {
@@ -39,6 +47,7 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD
const [refreshing, setRefreshing] = useState(false); // Pour les refresh silencieux
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [isPending, startTransition] = useTransition();
const refreshDaily = useCallback(async () => {
try {
@@ -69,215 +78,179 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD
}
}, [currentDate]);
const addTodayCheckbox = useCallback(async (text: string, type?: DailyCheckboxType, taskId?: string): Promise<DailyCheckbox | null> => {
if (!dailyView) return null;
const addTodayCheckbox = useCallback((text: string, type?: DailyCheckboxType, taskId?: string): Promise<DailyCheckbox | null> => {
if (!dailyView) return Promise.resolve(null);
// Créer une checkbox temporaire pour l'affichage optimiste
const tempCheckbox: DailyCheckbox = {
id: `temp-${Date.now()}`, // ID temporaire
date: currentDate,
text,
isChecked: false,
type: type || 'task', // Utilise le type fourni ou 'task' par défaut
order: dailyView.today.length, // Ordre temporaire
taskId,
task: undefined,
createdAt: new Date(),
updatedAt: new Date()
};
const previousDailyView = dailyView;
// Mise à jour optimiste IMMÉDIATE
setDailyView(prev => prev ? {
...prev,
today: [...prev.today, tempCheckbox]
} : null);
try {
// Appel API en arrière-plan
const newCheckbox = await dailyClient.addCheckbox({
date: currentDate,
text,
type: type || 'task',
taskId
return new Promise((resolve) => {
startTransition(async () => {
try {
setError(null);
const result = await addTodayCheckboxAction(text, taskId);
if (result.success && result.data) {
// Mise à jour optimiste
setDailyView(prev => prev ? {
...prev,
today: [...prev.today, result.data!].sort((a, b) => a.order - b.order)
} : null);
resolve(result.data);
} else {
setError(result.error || 'Erreur lors de l\'ajout de la checkbox');
resolve(null);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Erreur lors de l\'ajout de la checkbox');
console.error('Erreur addTodayCheckbox:', err);
resolve(null);
}
});
// Remplacer la checkbox temporaire par la vraie
setDailyView(prev => prev ? {
...prev,
today: prev.today.map(cb =>
cb.id === tempCheckbox.id ? newCheckbox : cb
).sort((a, b) => a.order - b.order)
} : null);
return newCheckbox;
} catch (err) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(err instanceof Error ? err.message : 'Erreur lors de l\'ajout de la checkbox');
console.error('Erreur addTodayCheckbox:', err);
return null;
}
}, [dailyView, currentDate]);
const addYesterdayCheckbox = useCallback(async (text: string, type?: DailyCheckboxType, taskId?: string): Promise<DailyCheckbox | null> => {
if (!dailyView) return null;
// Créer une checkbox temporaire pour l'affichage optimiste
const yesterday = new Date(currentDate);
yesterday.setDate(yesterday.getDate() - 1);
const tempCheckbox: DailyCheckbox = {
id: `temp-${Date.now()}`, // ID temporaire
date: yesterday,
text,
isChecked: false,
type: type || 'task', // Utilise le type fourni ou 'task' par défaut
order: dailyView.yesterday.length, // Ordre temporaire
taskId,
task: undefined,
createdAt: new Date(),
updatedAt: new Date()
};
const previousDailyView = dailyView;
// Mise à jour optimiste IMMÉDIATE
setDailyView(prev => prev ? {
...prev,
yesterday: [...prev.yesterday, tempCheckbox]
} : null);
try {
// Appel API en arrière-plan
const newCheckbox = await dailyClient.addCheckbox({
date: yesterday,
text,
type: type || 'task',
taskId
});
// Remplacer la checkbox temporaire par la vraie
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.map(cb =>
cb.id === tempCheckbox.id ? newCheckbox : cb
).sort((a, b) => a.order - b.order)
} : null);
return newCheckbox;
} catch (err) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(err instanceof Error ? err.message : 'Erreur lors de l\'ajout de la checkbox');
console.error('Erreur addYesterdayCheckbox:', err);
return null;
}
}, [dailyView, currentDate]);
const updateCheckbox = useCallback(async (checkboxId: string, data: UpdateDailyCheckboxData): Promise<DailyCheckbox | null> => {
if (!dailyView) return null;
// Trouver la checkbox existante
let existingCheckbox = dailyView.yesterday.find(cb => cb.id === checkboxId);
if (!existingCheckbox) {
existingCheckbox = dailyView.today.find(cb => cb.id === checkboxId);
}
if (!existingCheckbox) return null;
const previousDailyView = dailyView;
// Créer la checkbox mise à jour pour l'affichage optimiste
const optimisticCheckbox = { ...existingCheckbox, ...data, updatedAt: new Date() };
// Mise à jour optimiste IMMÉDIATE
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.map(cb => cb.id === checkboxId ? optimisticCheckbox : cb),
today: prev.today.map(cb => cb.id === checkboxId ? optimisticCheckbox : cb)
} : null);
try {
// Appel API en arrière-plan
const updatedCheckbox = await dailyClient.updateCheckbox(checkboxId, data);
// Remplacer par la vraie checkbox retournée par l'API
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.map(cb => cb.id === checkboxId ? updatedCheckbox : cb),
today: prev.today.map(cb => cb.id === checkboxId ? updatedCheckbox : cb)
} : null);
return updatedCheckbox;
} catch (err) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour de la checkbox');
console.error('Erreur updateCheckbox:', err);
return null;
}
});
}, [dailyView]);
const deleteCheckbox = useCallback(async (checkboxId: string): Promise<void> => {
if (!dailyView) return;
const addYesterdayCheckbox = useCallback((text: string, type?: DailyCheckboxType, taskId?: string): Promise<DailyCheckbox | null> => {
if (!dailyView) return Promise.resolve(null);
const previousDailyView = dailyView;
// Mise à jour optimiste IMMÉDIATE - supprimer la checkbox
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.filter(cb => cb.id !== checkboxId),
today: prev.today.filter(cb => cb.id !== checkboxId)
} : null);
try {
// Appel API en arrière-plan
await dailyClient.deleteCheckbox(checkboxId);
// Pas besoin de mise à jour supplémentaire, la suppression est déjà faite
} catch (err) {
// Rollback en cas d'erreur - restaurer la checkbox
setDailyView(previousDailyView);
setError(err instanceof Error ? err.message : 'Erreur lors de la suppression de la checkbox');
console.error('Erreur deleteCheckbox:', err);
}
return new Promise((resolve) => {
startTransition(async () => {
try {
setError(null);
const result = await addYesterdayCheckboxAction(text, taskId);
if (result.success && result.data) {
// Mise à jour optimiste
setDailyView(prev => prev ? {
...prev,
yesterday: [...prev.yesterday, result.data!].sort((a, b) => a.order - b.order)
} : null);
resolve(result.data);
} else {
setError(result.error || 'Erreur lors de l\'ajout de la checkbox');
resolve(null);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Erreur lors de l\'ajout de la checkbox');
console.error('Erreur addYesterdayCheckbox:', err);
resolve(null);
}
});
});
}, [dailyView]);
const toggleCheckbox = useCallback(async (checkboxId: string): Promise<void> => {
if (!dailyView) return;
const updateCheckbox = useCallback((checkboxId: string, data: UpdateDailyCheckboxData): Promise<DailyCheckbox | null> => {
if (!dailyView) return Promise.resolve(null);
// Trouver la checkbox dans yesterday ou today
let checkbox = dailyView.yesterday.find(cb => cb.id === checkboxId);
if (!checkbox) {
checkbox = dailyView.today.find(cb => cb.id === checkboxId);
}
if (!checkbox) return;
return new Promise((resolve) => {
startTransition(async () => {
try {
setError(null);
const result = await updateCheckboxAction(checkboxId, data);
if (result.success && result.data) {
// Mise à jour optimiste
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.map(cb => cb.id === checkboxId ? result.data! : cb),
today: prev.today.map(cb => cb.id === checkboxId ? result.data! : cb)
} : null);
resolve(result.data);
} else {
setError(result.error || 'Erreur lors de la mise à jour de la checkbox');
resolve(null);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour de la checkbox');
console.error('Erreur updateCheckbox:', err);
resolve(null);
}
});
});
}, [dailyView]);
// Mise à jour optimiste IMMÉDIATE
const newCheckedState = !checkbox.isChecked;
const previousDailyView = dailyView;
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.map(cb =>
cb.id === checkboxId ? { ...cb, isChecked: newCheckedState } : cb
),
today: prev.today.map(cb =>
cb.id === checkboxId ? { ...cb, isChecked: newCheckedState } : cb
)
} : null);
const deleteCheckbox = useCallback((checkboxId: string): Promise<void> => {
if (!dailyView) return Promise.resolve();
try {
// Appel API en arrière-plan
await dailyClient.updateCheckbox(checkboxId, { isChecked: newCheckedState });
} catch (err) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour de la checkbox');
console.error('Erreur toggleCheckbox:', err);
}
return new Promise((resolve) => {
startTransition(async () => {
const previousDailyView = dailyView;
// Mise à jour optimiste IMMÉDIATE - supprimer la checkbox
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.filter(cb => cb.id !== checkboxId),
today: prev.today.filter(cb => cb.id !== checkboxId)
} : null);
try {
const result = await deleteCheckboxAction(checkboxId);
if (!result.success) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(result.error || 'Erreur lors de la suppression de la checkbox');
}
resolve();
} catch (err) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(err instanceof Error ? err.message : 'Erreur lors de la suppression de la checkbox');
console.error('Erreur deleteCheckbox:', err);
resolve();
}
});
});
}, [dailyView]);
const toggleCheckbox = useCallback((checkboxId: string): Promise<void> => {
if (!dailyView) return Promise.resolve();
return new Promise((resolve) => {
startTransition(async () => {
// Trouver la checkbox dans yesterday ou today
let checkbox = dailyView.yesterday.find(cb => cb.id === checkboxId);
if (!checkbox) {
checkbox = dailyView.today.find(cb => cb.id === checkboxId);
}
if (!checkbox) {
resolve();
return;
}
// Mise à jour optimiste IMMÉDIATE
const newCheckedState = !checkbox.isChecked;
const previousDailyView = dailyView;
setDailyView(prev => prev ? {
...prev,
yesterday: prev.yesterday.map(cb =>
cb.id === checkboxId ? { ...cb, isChecked: newCheckedState } : cb
),
today: prev.today.map(cb =>
cb.id === checkboxId ? { ...cb, isChecked: newCheckedState } : cb
)
} : null);
try {
const result = await toggleCheckboxAction(checkboxId);
if (!result.success) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(result.error || 'Erreur lors de la mise à jour de la checkbox');
}
resolve();
} catch (err) {
// Rollback en cas d'erreur
setDailyView(previousDailyView);
setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour de la checkbox');
console.error('Erreur toggleCheckbox:', err);
resolve();
}
});
});
}, [dailyView]);
const toggleAllToday = useCallback(async (): Promise<void> => {
@@ -408,6 +381,7 @@ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseD
refreshing,
error,
saving,
isPending,
currentDate,
// Actions