feat: enhance useDaily hook and DailyPageClient for improved data handling

- Added `refreshDailySilent` method to `useDaily` for silent data refresh without loading state.
- Updated `useDaily` to accept an optional `initialDailyView` parameter, improving initial state management.
- Modified `DailyPageClient` to utilize `refreshDailySilent` for smoother user experience during checkbox updates.
- Implemented server-side data fetching in `DailyPage` for better initial load performance.
- Enhanced UI to indicate refreshing state in `DailySectionComponent`.
This commit is contained in:
Julien Froidefond
2025-09-15 21:23:03 +02:00
parent 936e0306fc
commit cb2e8e9c9f
3 changed files with 111 additions and 27 deletions

View File

@@ -7,12 +7,14 @@ import { DailyView, DailyCheckbox, UpdateDailyCheckboxData } from '@/lib/types';
interface UseDailyState { interface UseDailyState {
dailyView: DailyView | null; dailyView: DailyView | null;
loading: boolean; loading: boolean;
refreshing: boolean; // Pour les refresh silencieux
error: string | null; error: string | null;
saving: boolean; // Pour indiquer les opérations en cours saving: boolean; // Pour indiquer les opérations en cours
} }
interface UseDailyActions { interface UseDailyActions {
refreshDaily: () => Promise<void>; refreshDaily: () => Promise<void>;
refreshDailySilent: () => Promise<void>;
addTodayCheckbox: (text: string, taskId?: string) => Promise<DailyCheckbox | null>; addTodayCheckbox: (text: string, taskId?: string) => Promise<DailyCheckbox | null>;
addYesterdayCheckbox: (text: string, taskId?: string) => Promise<DailyCheckbox | null>; addYesterdayCheckbox: (text: string, taskId?: string) => Promise<DailyCheckbox | null>;
updateCheckbox: (checkboxId: string, data: UpdateDailyCheckboxData) => Promise<DailyCheckbox | null>; updateCheckbox: (checkboxId: string, data: UpdateDailyCheckboxData) => Promise<DailyCheckbox | null>;
@@ -28,10 +30,11 @@ interface UseDailyActions {
/** /**
* Hook pour la gestion d'une vue daily spécifique * Hook pour la gestion d'une vue daily spécifique
*/ */
export function useDaily(initialDate?: Date): UseDailyState & UseDailyActions & { currentDate: Date } { export function useDaily(initialDate?: Date, initialDailyView?: DailyView): UseDailyState & UseDailyActions & { currentDate: Date } {
const [currentDate, setCurrentDate] = useState<Date>(initialDate || new Date()); const [currentDate, setCurrentDate] = useState<Date>(initialDate || new Date());
const [dailyView, setDailyView] = useState<DailyView | null>(null); const [dailyView, setDailyView] = useState<DailyView | null>(initialDailyView || null);
const [loading, setLoading] = useState(true); 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<string | null>(null); const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
@@ -50,6 +53,20 @@ export function useDaily(initialDate?: Date): UseDailyState & UseDailyActions &
} }
}, [currentDate]); }, [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, taskId?: string): Promise<DailyCheckbox | null> => { const addTodayCheckbox = useCallback(async (text: string, taskId?: string): Promise<DailyCheckbox | null> => {
if (!dailyView) return null; if (!dailyView) return null;
@@ -200,21 +217,36 @@ export function useDaily(initialDate?: Date): UseDailyState & UseDailyActions &
setCurrentDate(date); setCurrentDate(date);
}, []); }, []);
// État pour savoir si c'est le premier chargement
const [isInitialLoad, setIsInitialLoad] = useState(!initialDailyView);
// Charger le daily quand la date change // Charger le daily quand la date change
useEffect(() => { useEffect(() => {
refreshDaily(); if (isInitialLoad) {
}, [refreshDaily]); // 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 { return {
// State // State
dailyView, dailyView,
loading, loading,
refreshing,
error, error,
saving, saving,
currentDate, currentDate,
// Actions // Actions
refreshDaily, refreshDaily,
refreshDailySilent,
addTodayCheckbox, addTodayCheckbox,
addYesterdayCheckbox, addYesterdayCheckbox,
updateCheckbox, updateCheckbox,

View File

@@ -3,12 +3,13 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import React from 'react'; import React from 'react';
import { useDaily } from '@/hooks/useDaily'; import { useDaily } from '@/hooks/useDaily';
import { DailyCheckbox } from '@/lib/types'; import { DailyCheckbox, DailyView } from '@/lib/types';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
import Link from 'next/link'; import Link from 'next/link';
import { DailyCalendar } from '@/components/daily/DailyCalendar'; import { DailyCalendar } from '@/components/daily/DailyCalendar';
import { dailyClient } from '@/clients/daily-client';
interface DailySectionProps { interface DailySectionProps {
title: string; title: string;
@@ -19,6 +20,7 @@ interface DailySectionProps {
onUpdateCheckbox: (checkboxId: string, text: string) => Promise<void>; onUpdateCheckbox: (checkboxId: string, text: string) => Promise<void>;
onDeleteCheckbox: (checkboxId: string) => Promise<void>; onDeleteCheckbox: (checkboxId: string) => Promise<void>;
saving: boolean; saving: boolean;
refreshing?: boolean;
} }
function DailySectionComponent({ function DailySectionComponent({
@@ -29,7 +31,8 @@ function DailySectionComponent({
onToggleCheckbox, onToggleCheckbox,
onUpdateCheckbox, onUpdateCheckbox,
onDeleteCheckbox, onDeleteCheckbox,
saving saving,
refreshing = false
}: DailySectionProps) { }: DailySectionProps) {
const [newCheckboxText, setNewCheckboxText] = useState(''); const [newCheckboxText, setNewCheckboxText] = useState('');
const [addingCheckbox, setAddingCheckbox] = useState(false); const [addingCheckbox, setAddingCheckbox] = useState(false);
@@ -103,8 +106,11 @@ function DailySectionComponent({
return ( return (
<Card className="p-4"> <Card className="p-4">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-[var(--foreground)] font-mono"> <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> {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> </h2>
<span className="text-xs text-[var(--muted-foreground)] font-mono"> <span className="text-xs text-[var(--muted-foreground)] font-mono">
{checkboxes.filter(cb => cb.isChecked).length}/{checkboxes.length} {checkboxes.filter(cb => cb.isChecked).length}/{checkboxes.length}
@@ -204,14 +210,25 @@ function DailySectionComponent({
); );
} }
export function DailyPageClient() { interface DailyPageClientProps {
initialDailyView?: DailyView;
initialDailyDates?: string[];
initialDate?: Date;
}
export function DailyPageClient({
initialDailyView,
initialDailyDates = [],
initialDate
}: DailyPageClientProps = {}) {
const { const {
dailyView, dailyView,
loading, loading,
refreshing,
error, error,
saving, saving,
currentDate, currentDate,
refreshDaily, refreshDailySilent,
toggleCheckbox, toggleCheckbox,
updateCheckbox, updateCheckbox,
deleteCheckbox, deleteCheckbox,
@@ -219,16 +236,28 @@ export function DailyPageClient() {
goToNextDay, goToNextDay,
goToToday, goToToday,
setDate setDate
} = useDaily(); } = useDaily(initialDate, initialDailyView);
const [dailyDates, setDailyDates] = useState<string[]>([]); const [dailyDates, setDailyDates] = useState<string[]>(initialDailyDates);
// Charger les dates avec des dailies pour le calendrier // Fonction pour rafraîchir la liste des dates avec des dailies
const refreshDailyDates = async () => {
try {
const dates = await dailyClient.getDailyDates();
setDailyDates(dates);
} catch (error) {
console.error('Erreur lors du refresh des dates:', error);
}
};
// Charger les dates avec des dailies pour le calendrier (seulement si pas de données SSR)
useEffect(() => { useEffect(() => {
import('@/clients/daily-client').then(({ dailyClient }) => { if (initialDailyDates.length === 0) {
return dailyClient.getDailyDates(); import('@/clients/daily-client').then(({ dailyClient }) => {
}).then(setDailyDates).catch(console.error); return dailyClient.getDailyDates();
}, []); }).then(setDailyDates).catch(console.error);
}
}, [initialDailyDates.length]);
const handleAddTodayCheckbox = async (text: string) => { const handleAddTodayCheckbox = async (text: string) => {
try { try {
@@ -238,11 +267,10 @@ export function DailyPageClient() {
text, text,
isChecked: false isChecked: false
}); });
// Recharger la vue daily après ajout // Recharger silencieusement la vue daily (sans clignotement)
await refreshDaily(); refreshDailySilent().catch(console.error);
// Recharger aussi les dates pour le calendrier // Recharger aussi les dates pour le calendrier
const updatedDates = await dailyClient.getDailyDates(); await refreshDailyDates();
setDailyDates(updatedDates);
} catch (error) { } catch (error) {
console.error('Erreur lors de l\'ajout de la tâche:', error); console.error('Erreur lors de l\'ajout de la tâche:', error);
} }
@@ -259,11 +287,10 @@ export function DailyPageClient() {
text, text,
isChecked: false isChecked: false
}); });
// Recharger la vue daily après ajout // Recharger silencieusement la vue daily (sans clignotement)
await refreshDaily(); refreshDailySilent().catch(console.error);
// Recharger aussi les dates pour le calendrier // Recharger aussi les dates pour le calendrier
const updatedDates = await dailyClient.getDailyDates(); await refreshDailyDates();
setDailyDates(updatedDates);
} catch (error) { } catch (error) {
console.error('Erreur lors de l\'ajout de la tâche:', error); console.error('Erreur lors de l\'ajout de la tâche:', error);
} }
@@ -275,6 +302,8 @@ export function DailyPageClient() {
const handleDeleteCheckbox = async (checkboxId: string) => { const handleDeleteCheckbox = async (checkboxId: string) => {
await deleteCheckbox(checkboxId); await deleteCheckbox(checkboxId);
// Refresh dates après suppression pour mettre à jour le calendrier
await refreshDailyDates();
}; };
const handleUpdateCheckbox = async (checkboxId: string, text: string) => { const handleUpdateCheckbox = async (checkboxId: string, text: string) => {
@@ -416,6 +445,7 @@ export function DailyPageClient() {
onUpdateCheckbox={handleUpdateCheckbox} onUpdateCheckbox={handleUpdateCheckbox}
onDeleteCheckbox={handleDeleteCheckbox} onDeleteCheckbox={handleDeleteCheckbox}
saving={saving} saving={saving}
refreshing={refreshing}
/> />
{/* Section Aujourd'hui */} {/* Section Aujourd'hui */}
@@ -428,6 +458,7 @@ export function DailyPageClient() {
onUpdateCheckbox={handleUpdateCheckbox} onUpdateCheckbox={handleUpdateCheckbox}
onDeleteCheckbox={handleDeleteCheckbox} onDeleteCheckbox={handleDeleteCheckbox}
saving={saving} saving={saving}
refreshing={refreshing}
/> />
</div> </div>
)} )}

View File

@@ -1,11 +1,32 @@
import { Metadata } from 'next'; import { Metadata } from 'next';
import { DailyPageClient } from './DailyPageClient'; import { DailyPageClient } from './DailyPageClient';
import { dailyService } from '@/services/daily';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Daily - Tower Control', title: 'Daily - Tower Control',
description: 'Gestion quotidienne des tâches et objectifs', description: 'Gestion quotidienne des tâches et objectifs',
}; };
export default function DailyPage() { export default async function DailyPage() {
return <DailyPageClient />; // Récupérer les données côté serveur
const today = new Date();
try {
const [dailyView, dailyDates] = await Promise.all([
dailyService.getDailyView(today),
dailyService.getDailyDates()
]);
return (
<DailyPageClient
initialDailyView={dailyView}
initialDailyDates={dailyDates}
initialDate={today}
/>
);
} catch (error) {
console.error('Erreur SSR Daily:', error);
// Fallback vers client-side rendering
return <DailyPageClient />;
}
} }