Ajout d'un dialogue pour créer un nouveau calendrier et mise à jour de la logique de gestion des calendriers dans le composant client.

This commit is contained in:
Kevin 2025-03-03 18:31:33 +01:00
parent bc6a9fe10c
commit a94e3eabc5
4 changed files with 267 additions and 63 deletions

View File

@ -56,8 +56,8 @@
with_indexed_items: "{{ trusted_domains }}" with_indexed_items: "{{ trusted_domains }}"
register: trusted_domains_config register: trusted_domains_config
until: trusted_domains_config is success until: trusted_domains_config is success
retries: 3 retries: 6
delay: 5 delay: 10
- name: Installer l'application SSO & SAML - name: Installer l'application SSO & SAML
shell: > shell: >

View File

@ -12,14 +12,16 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Loader2, Plus } from "lucide-react"; import { Loader2, Plus } from "lucide-react";
import { useCalendarEvents } from "@/hooks/use-calendar-events"; import { useCalendarEvents } from "@/hooks/use-calendar-events";
import { EventDialog } from "@/components/calendar/event-dialog"; import { EventDialog } from "@/components/calendar/event-dialog";
import { Calendar as CalendarType } from "@prisma/client"; import { Calendar, Calendar as CalendarType } from "@prisma/client";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { CalendarDialog } from "./calendar-dialog";
interface CalendarClientProps { interface CalendarClientProps {
initialCalendars: CalendarType[]; initialCalendars: CalendarType[];
} }
export function CalendarClient({ initialCalendars }: CalendarClientProps) { export function CalendarClient({ initialCalendars }: CalendarClientProps) {
const [isCalendarDialogOpen, setIsCalendarDialogOpen] = useState(false);
const [calendars, setCalendars] = useState<CalendarType[]>(initialCalendars); const [calendars, setCalendars] = useState<CalendarType[]>(initialCalendars);
const [selectedCalendarId, setSelectedCalendarId] = useState<string>( const [selectedCalendarId, setSelectedCalendarId] = useState<string>(
initialCalendars[0]?.id || "" initialCalendars[0]?.id || ""
@ -146,6 +148,43 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
setSelectedCalendarId(calendarId); setSelectedCalendarId(calendarId);
}; };
// Fonction pour créer un nouveau calendrier
const handleCreateCalendar = async (calendarData: Partial<Calendar>) => {
try {
const response = await fetch("/api/calendars", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(calendarData),
});
if (!response.ok) {
throw new Error(`Erreur ${response.status}: ${await response.text()}`);
}
const newCalendar = await response.json();
// Mettre à jour la liste des calendriers
setCalendars([...calendars, newCalendar]);
// Sélectionner automatiquement le nouveau calendrier
setSelectedCalendarId(newCalendar.id);
toast({
title: "Calendrier créé",
description: `Le calendrier "${newCalendar.name}" a été créé avec succès.`,
});
} catch (error) {
console.error("Erreur lors de la création du calendrier:", error);
toast({
title: "Erreur",
description: "Impossible de créer le calendrier.",
variant: "destructive",
});
}
};
return ( return (
<div className='space-y-4'> <div className='space-y-4'>
{/* Options et filtres du calendrier */} {/* Options et filtres du calendrier */}
@ -169,6 +208,14 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
{calendar.name} {calendar.name}
</Button> </Button>
))} ))}
<Button
variant='ghost'
size='icon'
onClick={() => setIsCalendarDialogOpen(true)}
title='Créer un nouveau calendrier'
>
<Plus className='h-4 w-4' />
</Button>
</div> </div>
<Button <Button
onClick={() => { onClick={() => {
@ -253,6 +300,14 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
</Card> </Card>
</Tabs> </Tabs>
{isCalendarDialogOpen && (
<CalendarDialog
open={isCalendarDialogOpen}
onClose={() => setIsCalendarDialogOpen(false)}
onSave={handleCreateCalendar}
/>
)}
{/* Dialogue pour créer/modifier un événement */} {/* Dialogue pour créer/modifier un événement */}
{isDialogOpen && ( {isDialogOpen && (
<EventDialog <EventDialog
@ -261,6 +316,7 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
onClose={() => setIsDialogOpen(false)} onClose={() => setIsDialogOpen(false)}
onSave={handleEventSave} onSave={handleEventSave}
onDelete={selectedEvent?.id ? handleEventDelete : undefined} onDelete={selectedEvent?.id ? handleEventDelete : undefined}
calendars={calendars}
/> />
)} )}
</div> </div>

View File

@ -0,0 +1,114 @@
"use client";
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Calendar } from "@prisma/client";
interface CalendarDialogProps {
open: boolean;
onClose: () => void;
onSave: (calendarData: Partial<Calendar>) => Promise<void>;
}
export function CalendarDialog({ open, onClose, onSave }: CalendarDialogProps) {
const [name, setName] = useState("");
const [color, setColor] = useState("#0082c9");
const [description, setDescription] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
try {
await onSave({ name, color, description });
resetForm();
} catch (error) {
console.error("Erreur lors de la création du calendrier:", error);
} finally {
setIsSubmitting(false);
}
};
const resetForm = () => {
setName("");
setColor("#0082c9");
setDescription("");
onClose();
};
return (
<Dialog open={open} onOpenChange={(open) => !open && onClose()}>
<DialogContent className='sm:max-w-md'>
<DialogHeader>
<DialogTitle>Créer un nouveau calendrier</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className='space-y-4 py-2'>
<div className='space-y-2'>
<Label htmlFor='calendar-name'>Nom</Label>
<Input
id='calendar-name'
value={name}
onChange={(e) => setName(e.target.value)}
placeholder='Nom du calendrier'
required
/>
</div>
<div className='space-y-2'>
<Label htmlFor='calendar-color'>Couleur</Label>
<div className='flex items-center gap-4'>
<Input
id='calendar-color'
type='color'
value={color}
onChange={(e) => setColor(e.target.value)}
className='w-12 h-12 p-1 cursor-pointer'
/>
<span className='text-sm font-medium'>{color}</span>
</div>
</div>
<div className='space-y-2'>
<Label htmlFor='calendar-description'>
Description (optionnelle)
</Label>
<Textarea
id='calendar-description'
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder='Description du calendrier'
rows={3}
/>
</div>
</div>
<DialogFooter className='mt-4'>
<Button
type='button'
variant='outline'
onClick={onClose}
disabled={isSubmitting}
>
Annuler
</Button>
<Button type='submit' disabled={!name || isSubmitting}>
{isSubmitting ? "Création..." : "Créer"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}

View File

@ -1,6 +1,4 @@
"use client"; import { useState, useEffect } from "react";
import { useState } from "react";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -10,11 +8,9 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
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 { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { parseISO, format } from "date-fns";
import { fr } from "date-fns/locale";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@ -24,14 +20,25 @@ import {
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from "../ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { format, parseISO } from "date-fns";
import { fr } from "date-fns/locale";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Calendar as CalendarType } from "@prisma/client";
interface EventDialogProps { interface EventDialogProps {
open: boolean; open: boolean;
event?: any; event?: any;
onClose: () => void; onClose: () => void;
onSave: (event: any) => void; onSave: (eventData: any) => Promise<void>;
onDelete?: (eventId: string) => void; onDelete?: (eventId: string) => Promise<void>;
calendars: CalendarType[];
} }
export function EventDialog({ export function EventDialog({
@ -40,6 +47,7 @@ export function EventDialog({
onClose, onClose,
onSave, onSave,
onDelete, onDelete,
calendars,
}: EventDialogProps) { }: EventDialogProps) {
const [title, setTitle] = useState(event?.title || ""); const [title, setTitle] = useState(event?.title || "");
const [description, setDescription] = useState(event?.description || ""); const [description, setDescription] = useState(event?.description || "");
@ -47,6 +55,7 @@ export function EventDialog({
const [start, setStart] = useState(event?.start || ""); const [start, setStart] = useState(event?.start || "");
const [end, setEnd] = useState(event?.end || ""); const [end, setEnd] = useState(event?.end || "");
const [allDay, setAllDay] = useState(event?.allDay || false); const [allDay, setAllDay] = useState(event?.allDay || false);
const [calendarId, setCalendarId] = useState(event?.calendarId || "");
const [confirmDelete, setConfirmDelete] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false);
// Formater les dates pour l'affichage // Formater les dates pour l'affichage
@ -94,6 +103,7 @@ export function EventDialog({
location, location,
start, start,
end, end,
calendarId,
isAllDay: allDay, isAllDay: allDay,
}); });
}; };
@ -127,88 +137,112 @@ export function EventDialog({
/> />
</div> </div>
{/* Sélection du calendrier */}
<div className='grid gap-2'> <div className='grid gap-2'>
<Label htmlFor='description'>Description</Label> <Label htmlFor='calendar'>Calendrier *</Label>
<Textarea <Select value={calendarId} onValueChange={setCalendarId} required>
id='description' <SelectTrigger>
value={description} <SelectValue placeholder='Sélectionner un calendrier' />
onChange={(e) => setDescription(e.target.value)} </SelectTrigger>
placeholder="Description de l'événement" <SelectContent>
className='min-h-[100px]' {calendars.map((calendar) => (
/> <SelectItem key={calendar.id} value={calendar.id}>
</div> <div className='flex items-center gap-2'>
<div
<div className='grid gap-2'> className='w-3 h-3 rounded-full'
<Label htmlFor='location'>Lieu</Label> style={{ backgroundColor: calendar.color }}
<Input />
id='location' <span>{calendar.name}</span>
value={location} </div>
onChange={(e) => setLocation(e.target.value)} </SelectItem>
placeholder='Emplacement' ))}
/> </SelectContent>
</div> </Select>
<div className='flex items-center space-x-2'>
<Checkbox
id='allDay'
checked={allDay}
onCheckedChange={handleAllDayChange}
/>
<Label htmlFor='allDay'>Toute la journée</Label>
</div> </div>
<div className='grid grid-cols-2 gap-4'> <div className='grid grid-cols-2 gap-4'>
<div className='grid gap-2'> <div className='grid gap-2'>
<Label htmlFor='start'>Début *</Label> <Label htmlFor='start-date'>Début *</Label>
<Input <Input
id='start'
type={allDay ? "date" : "datetime-local"} type={allDay ? "date" : "datetime-local"}
id='start-date'
value={formatDate(start)} value={formatDate(start)}
onChange={(e) => setStart(e.target.value)} onChange={(e) => setStart(e.target.value)}
required required
/> />
</div> </div>
<div className='grid gap-2'> <div className='grid gap-2'>
<Label htmlFor='end'>Fin *</Label> <Label htmlFor='end-date'>Fin *</Label>
<Input <Input
id='end'
type={allDay ? "date" : "datetime-local"} type={allDay ? "date" : "datetime-local"}
id='end-date'
value={formatDate(end)} value={formatDate(end)}
onChange={(e) => setEnd(e.target.value)} onChange={(e) => setEnd(e.target.value)}
required required
/> />
</div> </div>
</div> </div>
<div className='flex items-center gap-2'>
<Checkbox
id='all-day'
checked={allDay}
onCheckedChange={handleAllDayChange}
/>
<Label htmlFor='all-day'>Toute la journée</Label>
</div>
<div className='grid gap-2'>
<Label htmlFor='location'>Lieu (optionnel)</Label>
<Input
id='location'
value={location}
onChange={(e) => setLocation(e.target.value)}
placeholder='Ajouter un lieu'
/>
</div>
<div className='grid gap-2'>
<Label htmlFor='description'>Description (optionnel)</Label>
<Textarea
id='description'
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder='Ajouter une description'
rows={3}
/>
</div>
</div> </div>
<DialogFooter className='flex justify-between'> <DialogFooter>
<div> {event?.id && onDelete && (
{event?.id && onDelete && ( <Button
<Button variant='destructive'
variant='destructive' onClick={() => setConfirmDelete(true)}
onClick={() => setConfirmDelete(true)} type='button'
> >
Supprimer Supprimer
</Button>
)}
</div>
<div className='flex space-x-2'>
<Button variant='outline' onClick={onClose}>
Annuler
</Button> </Button>
<Button onClick={handleSave} disabled={!title || !start || !end}> )}
Enregistrer <Button variant='outline' onClick={onClose} type='button'>
</Button> Annuler
</div> </Button>
<Button
onClick={handleSave}
disabled={!title || !start || !end || !calendarId}
type='button'
>
Enregistrer
</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Dialogue de confirmation de suppression */}
<AlertDialog open={confirmDelete} onOpenChange={setConfirmDelete}> <AlertDialog open={confirmDelete} onOpenChange={setConfirmDelete}>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Confirmer la suppression</AlertDialogTitle> <AlertDialogTitle>Supprimer l'événement</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
Êtes-vous sûr de vouloir supprimer cet événement ? Cette action Êtes-vous sûr de vouloir supprimer cet événement ? Cette action
est irréversible. est irréversible.