Ajout d'un widget de calendrier pour afficher les événements à venir et mise à jour du dialogue d'événements pour le formatage des dates.
This commit is contained in:
parent
2332d31c0a
commit
bc6a9fe10c
@ -38,6 +38,7 @@ export default async function Page() {
|
||||
</div>
|
||||
<div className='col-span-3 space-y-4'>
|
||||
<Podcast />
|
||||
<CalendarWidget />
|
||||
<Todo />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,17 +1,194 @@
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { format, isToday, isTomorrow, addDays } from "date-fns";
|
||||
import { fr } from "date-fns/locale";
|
||||
import { CalendarIcon, ClockIcon, ChevronRight } from "lucide-react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
type Event = {
|
||||
id: string;
|
||||
title: string;
|
||||
start: string;
|
||||
end: string;
|
||||
isAllDay: boolean;
|
||||
calendarId: string;
|
||||
calendarName?: string;
|
||||
calendarColor?: string;
|
||||
};
|
||||
|
||||
export function CalendarWidget() {
|
||||
const { data: session } = useSession();
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Ne charger les événements que si l'utilisateur est connecté
|
||||
if (!session) return;
|
||||
|
||||
const fetchUpcomingEvents = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Récupérer d'abord les calendriers de l'utilisateur
|
||||
const calendarsRes = await fetch("/api/calendars");
|
||||
|
||||
if (!calendarsRes.ok) {
|
||||
throw new Error("Impossible de charger les calendriers");
|
||||
}
|
||||
|
||||
const calendars = await calendarsRes.json();
|
||||
|
||||
if (calendars.length === 0) {
|
||||
setEvents([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Date actuelle et date dans 7 jours
|
||||
const now = new Date();
|
||||
// @ts-ignore
|
||||
const nextWeek = addDays(now, 7);
|
||||
|
||||
// Récupérer les événements pour chaque calendrier
|
||||
const allEventsPromises = calendars.map(async (calendar: any) => {
|
||||
const eventsRes = await fetch(
|
||||
`/api/calendars/${
|
||||
calendar.id
|
||||
}/events?start=${now.toISOString()}&end=${nextWeek.toISOString()}`
|
||||
);
|
||||
|
||||
if (!eventsRes.ok) {
|
||||
console.warn(
|
||||
`Impossible de charger les événements du calendrier ${calendar.id}`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
const events = await eventsRes.json();
|
||||
|
||||
// Ajouter les informations du calendrier à chaque événement
|
||||
return events.map((event: any) => ({
|
||||
...event,
|
||||
calendarName: calendar.name,
|
||||
calendarColor: calendar.color,
|
||||
}));
|
||||
});
|
||||
|
||||
// Attendre toutes les requêtes d'événements
|
||||
const allEventsArrays = await Promise.all(allEventsPromises);
|
||||
|
||||
// Fusionner tous les événements en un seul tableau
|
||||
const allEvents = allEventsArrays.flat();
|
||||
|
||||
// Trier par date de début
|
||||
const sortedEvents = allEvents.sort(
|
||||
(a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()
|
||||
);
|
||||
|
||||
// Limiter à 5 événements
|
||||
setEvents(sortedEvents.slice(0, 5));
|
||||
} catch (err) {
|
||||
console.error("Erreur lors du chargement des événements:", err);
|
||||
setError("Impossible de charger les événements à venir");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUpcomingEvents();
|
||||
}, [session]);
|
||||
|
||||
// Formater la date d'un événement pour l'affichage
|
||||
const formatEventDate = (date: string, isAllDay: boolean) => {
|
||||
const eventDate = new Date(date);
|
||||
|
||||
let dateString = "";
|
||||
// @ts-ignore
|
||||
if (isToday(eventDate)) {
|
||||
dateString = "Aujourd'hui";
|
||||
// @ts-ignore
|
||||
} else if (isTomorrow(eventDate)) {
|
||||
dateString = "Demain";
|
||||
} else {
|
||||
// @ts-ignore
|
||||
dateString = format(eventDate, "EEEE d MMMM", { locale: fr });
|
||||
}
|
||||
|
||||
if (!isAllDay) {
|
||||
// @ts-ignore
|
||||
dateString += ` · ${format(eventDate, "HH:mm", { locale: fr })}`;
|
||||
}
|
||||
|
||||
return dateString;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className='transition-transform duration-500 ease-in-out transform hover:scale-105'>
|
||||
<CardHeader>
|
||||
<div className='text-sm font-medium'>JEUDI</div>
|
||||
<div className='text-4xl font-bold'>24</div>
|
||||
<CardHeader className='flex flex-row items-center justify-between pb-2'>
|
||||
<CardTitle className='text-lg font-medium'>
|
||||
Événements à venir
|
||||
</CardTitle>
|
||||
<Link href='/calendar' passHref>
|
||||
<Button variant='ghost' size='sm' className='h-8 w-8 p-0'>
|
||||
<ChevronRight className='h-4 w-4' />
|
||||
<span className='sr-only'>Voir le calendrier</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='space-y-2'>
|
||||
<h3 className='font-medium'>Classe UX Design en visio conférence</h3>
|
||||
<p className='text-sm text-gray-500'>Rappel : Examen dans 2 jours</p>
|
||||
</div>
|
||||
<CardContent className='pb-3'>
|
||||
{loading ? (
|
||||
<div className='flex items-center justify-center py-4'>
|
||||
<div className='h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent' />
|
||||
<span className='ml-2 text-sm text-muted-foreground'>
|
||||
Chargement...
|
||||
</span>
|
||||
</div>
|
||||
) : error ? (
|
||||
<p className='text-sm text-red-500'>{error}</p>
|
||||
) : events.length === 0 ? (
|
||||
<p className='text-sm text-muted-foreground py-2'>
|
||||
Aucun événement à venir cette semaine
|
||||
</p>
|
||||
) : (
|
||||
<div className='space-y-3'>
|
||||
{events.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className='flex items-start space-x-3 rounded-md border border-muted p-2'
|
||||
>
|
||||
<div
|
||||
className='h-3 w-3 flex-shrink-0 rounded-full mt-1'
|
||||
style={{ backgroundColor: event.calendarColor || "#0082c9" }}
|
||||
/>
|
||||
<div className='flex-1 min-w-0'>
|
||||
<h5
|
||||
className='text-sm font-medium truncate'
|
||||
title={event.title}
|
||||
>
|
||||
{event.title}
|
||||
</h5>
|
||||
<div className='flex items-center text-xs text-muted-foreground mt-1'>
|
||||
<CalendarIcon className='h-3 w-3 mr-1' />
|
||||
<span>{formatEventDate(event.start, event.isAllDay)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Link href='/calendar' passHref>
|
||||
<Button
|
||||
size='sm'
|
||||
className='w-full transition-all ease-in-out duration-500 bg-muted text-black hover:text-white hover:bg-primary'
|
||||
>
|
||||
Voir tous les événements
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@ -53,8 +53,11 @@ export function EventDialog({
|
||||
const formatDate = (dateStr: string) => {
|
||||
if (!dateStr) return "";
|
||||
try {
|
||||
// @ts-ignore
|
||||
const date = parseISO(dateStr);
|
||||
// @ts-ignore
|
||||
return format(date, allDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm", {
|
||||
// @ts-ignore
|
||||
locale: fr,
|
||||
});
|
||||
} catch (e) {
|
||||
@ -68,11 +71,15 @@ export function EventDialog({
|
||||
|
||||
// Ajuster les dates si nécessaire
|
||||
if (checked && start) {
|
||||
// @ts-ignore
|
||||
const startDate = parseISO(start);
|
||||
// @ts-ignore
|
||||
setStart(format(startDate, "yyyy-MM-dd"));
|
||||
|
||||
if (end) {
|
||||
// @ts-ignore
|
||||
const endDate = parseISO(end);
|
||||
// @ts-ignore
|
||||
setEnd(format(endDate, "yyyy-MM-dd"));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user