'use client'; 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; loading: boolean; 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 { 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 [isPending, startTransition] = useTransition(); 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((text: string, type?: DailyCheckboxType, taskId?: string): Promise => { if (!dailyView) return Promise.resolve(null); 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); } }); }); }, [dailyView]); const addYesterdayCheckbox = useCallback((text: string, type?: DailyCheckboxType, taskId?: string): Promise => { if (!dailyView) return Promise.resolve(null); 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 updateCheckbox = useCallback((checkboxId: string, data: UpdateDailyCheckboxData): Promise => { if (!dailyView) return Promise.resolve(null); 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]); const deleteCheckbox = useCallback((checkboxId: string): Promise => { if (!dailyView) return Promise.resolve(); 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 => { 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 => { 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, isPending, 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 }; }