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:
Kevin 2025-03-01 17:38:58 +01:00
parent 2332d31c0a
commit bc6a9fe10c
3 changed files with 194 additions and 9 deletions

View File

@ -38,6 +38,7 @@ export default async function Page() {
</div> </div>
<div className='col-span-3 space-y-4'> <div className='col-span-3 space-y-4'>
<Podcast /> <Podcast />
<CalendarWidget />
<Todo /> <Todo />
</div> </div>
</div> </div>

View File

@ -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() { 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 ( return (
<Card className='transition-transform duration-500 ease-in-out transform hover:scale-105'> <Card className='transition-transform duration-500 ease-in-out transform hover:scale-105'>
<CardHeader> <CardHeader className='flex flex-row items-center justify-between pb-2'>
<div className='text-sm font-medium'>JEUDI</div> <CardTitle className='text-lg font-medium'>
<div className='text-4xl font-bold'>24</div> É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> </CardHeader>
<CardContent> <CardContent className='pb-3'>
<div className='space-y-2'> {loading ? (
<h3 className='font-medium'>Classe UX Design en visio conférence</h3> <div className='flex items-center justify-center py-4'>
<p className='text-sm text-gray-500'>Rappel : Examen dans 2 jours</p> <div className='h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent' />
</div> <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> </CardContent>
</Card> </Card>
); );

View File

@ -53,8 +53,11 @@ export function EventDialog({
const formatDate = (dateStr: string) => { const formatDate = (dateStr: string) => {
if (!dateStr) return ""; if (!dateStr) return "";
try { try {
// @ts-ignore
const date = parseISO(dateStr); const date = parseISO(dateStr);
// @ts-ignore
return format(date, allDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm", { return format(date, allDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm", {
// @ts-ignore
locale: fr, locale: fr,
}); });
} catch (e) { } catch (e) {
@ -68,11 +71,15 @@ export function EventDialog({
// Ajuster les dates si nécessaire // Ajuster les dates si nécessaire
if (checked && start) { if (checked && start) {
// @ts-ignore
const startDate = parseISO(start); const startDate = parseISO(start);
// @ts-ignore
setStart(format(startDate, "yyyy-MM-dd")); setStart(format(startDate, "yyyy-MM-dd"));
if (end) { if (end) {
// @ts-ignore
const endDate = parseISO(end); const endDate = parseISO(end);
// @ts-ignore
setEnd(format(endDate, "yyyy-MM-dd")); setEnd(format(endDate, "yyyy-MM-dd"));
} }
} }