'use client'; import { useState, useEffect, useCallback } from 'react'; import { dailyClient, DailyHistoryFilters, DailySearchFilters, ReorderCheckboxesData } from '@/clients/daily-client'; import { DailyView, DailyCheckbox, UpdateDailyCheckboxData, DailyCheckboxType } from '@/lib/types'; interface UseDailyState { dailyView: DailyView | null; loading: boolean; refreshing: boolean; // Pour les refresh silencieux error: string | null; saving: boolean; // Pour indiquer les opérations en cours } interface UseDailyActions { refreshDaily: () => Promise; refreshDailySilent: () => Promise; addTodayCheckbox: (text: string, type?: DailyCheckboxType, taskId?: string) => Promise; addYesterdayCheckbox: (text: string, type?: DailyCheckboxType, taskId?: string) => Promise; updateCheckbox: (checkboxId: string, data: UpdateDailyCheckboxData) => Promise; deleteCheckbox: (checkboxId: string) => Promise; toggleCheckbox: (checkboxId: string) => Promise; toggleAllToday: () => Promise; toggleAllYesterday: () => Promise; reorderCheckboxes: (data: ReorderCheckboxesData) => Promise; goToPreviousDay: () => Promise; goToNextDay: () => Promise; goToToday: () => Promise; setDate: (date: Date) => Promise; } /** * Hook pour la gestion d'une vue daily spécifique */ export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseDailyState & UseDailyActions & { currentDate: Date } { const [currentDate, setCurrentDate] = useState(initialDate || new Date()); const [dailyView, setDailyView] = useState(initialDailyView || null); const [loading, setLoading] = useState(!initialDailyView); // Pas de loading si on a des données SSR const [refreshing, setRefreshing] = useState(false); // Pour les refresh silencieux const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const refreshDaily = useCallback(async () => { try { setLoading(true); setError(null); const view = await dailyClient.getDailyView(currentDate); setDailyView(view); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur lors du chargement du daily'); console.error('Erreur refreshDaily:', err); } finally { setLoading(false); } }, [currentDate]); const refreshDailySilent = useCallback(async () => { try { setRefreshing(true); // Refresh silencieux sans setLoading(true) pour éviter le clignotement const view = await dailyClient.getDailyView(currentDate); setDailyView(view); } catch (err) { console.error('Erreur refreshDailySilent:', err); // On n'affiche pas l'erreur pour ne pas perturber l'UX } finally { setRefreshing(false); } }, [currentDate]); const addTodayCheckbox = useCallback(async (text: string, type?: DailyCheckboxType, taskId?: string): Promise => { if (!dailyView) return 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 }); // 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 => { 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 => { 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 => { if (!dailyView) return; 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); } }, [dailyView]); const toggleCheckbox = useCallback(async (checkboxId: string): Promise => { if (!dailyView) return; // 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; // 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 { // 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); } }, [dailyView]); const toggleAllToday = useCallback(async (): Promise => { if (!dailyView) return; const todayCheckboxes = dailyView.today; if (todayCheckboxes.length === 0) return; // Déterminer si on coche tout ou on décoche tout const allChecked = todayCheckboxes.every(cb => cb.isChecked); const newCheckedState = !allChecked; const previousDailyView = dailyView; // Mise à jour optimiste IMMÉDIATE setDailyView(prev => prev ? { ...prev, today: prev.today.map(cb => ({ ...cb, isChecked: newCheckedState })) } : null); try { // Appeler l'API pour chaque checkbox en parallèle await Promise.all( todayCheckboxes.map(checkbox => dailyClient.updateCheckbox(checkbox.id, { isChecked: newCheckedState }) ) ); } catch (err) { // Rollback en cas d'erreur setDailyView(previousDailyView); setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour des checkboxes'); console.error('Erreur toggleAllToday:', err); } }, [dailyView]); const toggleAllYesterday = useCallback(async (): Promise => { if (!dailyView) return; const yesterdayCheckboxes = dailyView.yesterday; if (yesterdayCheckboxes.length === 0) return; // Déterminer si on coche tout ou on décoche tout const allChecked = yesterdayCheckboxes.every(cb => cb.isChecked); const newCheckedState = !allChecked; const previousDailyView = dailyView; // Mise à jour optimiste IMMÉDIATE setDailyView(prev => prev ? { ...prev, yesterday: prev.yesterday.map(cb => ({ ...cb, isChecked: newCheckedState })) } : null); try { // Appeler l'API pour chaque checkbox en parallèle await Promise.all( yesterdayCheckboxes.map(checkbox => dailyClient.updateCheckbox(checkbox.id, { isChecked: newCheckedState }) ) ); } catch (err) { // Rollback en cas d'erreur setDailyView(previousDailyView); setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour des checkboxes'); console.error('Erreur toggleAllYesterday:', err); } }, [dailyView]); const reorderCheckboxes = useCallback(async (data: ReorderCheckboxesData): Promise => { try { setSaving(true); setError(null); await dailyClient.reorderCheckboxes(data); // Rafraîchir pour obtenir l'ordre correct await refreshDaily(); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur lors du réordonnancement'); console.error('Erreur reorderCheckboxes:', err); } finally { setSaving(false); } }, [refreshDaily]); const goToPreviousDay = useCallback(async (): Promise => { const previousDay = new Date(currentDate); previousDay.setDate(previousDay.getDate() - 1); setCurrentDate(previousDay); }, [currentDate]); const goToNextDay = useCallback(async (): Promise => { const nextDay = new Date(currentDate); nextDay.setDate(nextDay.getDate() + 1); setCurrentDate(nextDay); }, [currentDate]); const goToToday = useCallback(async (): Promise => { setCurrentDate(new Date()); }, []); const setDate = useCallback(async (date: Date): Promise => { setCurrentDate(date); }, []); // État pour savoir si c'est le premier chargement const [isInitialLoad, setIsInitialLoad] = useState(!initialDailyView); // Charger le daily quand la date change useEffect(() => { if (isInitialLoad) { // Premier chargement : utiliser refreshDaily normal seulement si pas de données SSR if (!initialDailyView) { refreshDaily().finally(() => setIsInitialLoad(false)); } else { setIsInitialLoad(false); } } else { // Changements suivants : utiliser refreshDailySilent refreshDailySilent(); } }, [refreshDaily, refreshDailySilent, isInitialLoad, initialDailyView]); return { // State dailyView, loading, refreshing, error, saving, currentDate, // Actions refreshDaily, refreshDailySilent, addTodayCheckbox, addYesterdayCheckbox, updateCheckbox, deleteCheckbox, toggleCheckbox, toggleAllToday, toggleAllYesterday, reorderCheckboxes, goToPreviousDay, goToNextDay, goToToday, setDate }; } /** * Hook pour l'historique des checkboxes */ export function useDailyHistory() { const [history, setHistory] = useState<{ date: Date; checkboxes: DailyCheckbox[] }[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const loadHistory = useCallback(async (filters?: DailyHistoryFilters) => { try { setLoading(true); setError(null); const historyData = await dailyClient.getCheckboxHistory(filters); setHistory(historyData); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur lors du chargement de l\'historique'); console.error('Erreur loadHistory:', err); } finally { setLoading(false); } }, []); const searchCheckboxes = useCallback(async (filters: DailySearchFilters) => { try { setLoading(true); setError(null); const checkboxes = await dailyClient.searchCheckboxes(filters); // Grouper par date pour l'affichage const groupedHistory = checkboxes.reduce((acc, checkbox) => { const dateKey = checkbox.date.toDateString(); const existing = acc.find(item => item.date.toDateString() === dateKey); if (existing) { existing.checkboxes.push(checkbox); } else { acc.push({ date: checkbox.date, checkboxes: [checkbox] }); } return acc; }, [] as { date: Date; checkboxes: DailyCheckbox[] }[]); setHistory(groupedHistory); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur lors de la recherche'); console.error('Erreur searchCheckboxes:', err); } finally { setLoading(false); } }, []); return { history, loading, error, loadHistory, searchCheckboxes }; }