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:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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 />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user