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 }}"
register: trusted_domains_config
until: trusted_domains_config is success
retries: 3
delay: 5
retries: 6
delay: 10
- name: Installer l'application SSO & SAML
shell: >

View File

@ -12,14 +12,16 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Loader2, Plus } from "lucide-react";
import { useCalendarEvents } from "@/hooks/use-calendar-events";
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 { CalendarDialog } from "./calendar-dialog";
interface CalendarClientProps {
initialCalendars: CalendarType[];
}
export function CalendarClient({ initialCalendars }: CalendarClientProps) {
const [isCalendarDialogOpen, setIsCalendarDialogOpen] = useState(false);
const [calendars, setCalendars] = useState<CalendarType[]>(initialCalendars);
const [selectedCalendarId, setSelectedCalendarId] = useState<string>(
initialCalendars[0]?.id || ""
@ -146,6 +148,43 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
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 (
<div className='space-y-4'>
{/* Options et filtres du calendrier */}
@ -169,6 +208,14 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
{calendar.name}
</Button>
))}
<Button
variant='ghost'
size='icon'
onClick={() => setIsCalendarDialogOpen(true)}
title='Créer un nouveau calendrier'
>
<Plus className='h-4 w-4' />
</Button>
</div>
<Button
onClick={() => {
@ -253,6 +300,14 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
</Card>
</Tabs>
{isCalendarDialogOpen && (
<CalendarDialog
open={isCalendarDialogOpen}
onClose={() => setIsCalendarDialogOpen(false)}
onSave={handleCreateCalendar}
/>
)}
{/* Dialogue pour créer/modifier un événement */}
{isDialogOpen && (
<EventDialog
@ -261,6 +316,7 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) {
onClose={() => setIsDialogOpen(false)}
onSave={handleEventSave}
onDelete={selectedEvent?.id ? handleEventDelete : undefined}
calendars={calendars}
/>
)}
</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 } from "react";
import { useState, useEffect } from "react";
import {
Dialog,
DialogContent,
@ -10,11 +8,9 @@ import {
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { parseISO, format } from "date-fns";
import { fr } from "date-fns/locale";
import {
AlertDialog,
AlertDialogAction,
@ -24,14 +20,25 @@ import {
AlertDialogFooter,
AlertDialogHeader,
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 {
open: boolean;
event?: any;
onClose: () => void;
onSave: (event: any) => void;
onDelete?: (eventId: string) => void;
onSave: (eventData: any) => Promise<void>;
onDelete?: (eventId: string) => Promise<void>;
calendars: CalendarType[];
}
export function EventDialog({
@ -40,6 +47,7 @@ export function EventDialog({
onClose,
onSave,
onDelete,
calendars,
}: EventDialogProps) {
const [title, setTitle] = useState(event?.title || "");
const [description, setDescription] = useState(event?.description || "");
@ -47,6 +55,7 @@ export function EventDialog({
const [start, setStart] = useState(event?.start || "");
const [end, setEnd] = useState(event?.end || "");
const [allDay, setAllDay] = useState(event?.allDay || false);
const [calendarId, setCalendarId] = useState(event?.calendarId || "");
const [confirmDelete, setConfirmDelete] = useState(false);
// Formater les dates pour l'affichage
@ -94,6 +103,7 @@ export function EventDialog({
location,
start,
end,
calendarId,
isAllDay: allDay,
});
};
@ -127,88 +137,112 @@ export function EventDialog({
/>
</div>
{/* Sélection du calendrier */}
<div className='grid gap-2'>
<Label htmlFor='description'>Description</Label>
<Textarea
id='description'
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Description de l'événement"
className='min-h-[100px]'
/>
</div>
<div className='grid gap-2'>
<Label htmlFor='location'>Lieu</Label>
<Input
id='location'
value={location}
onChange={(e) => setLocation(e.target.value)}
placeholder='Emplacement'
/>
</div>
<div className='flex items-center space-x-2'>
<Checkbox
id='allDay'
checked={allDay}
onCheckedChange={handleAllDayChange}
/>
<Label htmlFor='allDay'>Toute la journée</Label>
<Label htmlFor='calendar'>Calendrier *</Label>
<Select value={calendarId} onValueChange={setCalendarId} required>
<SelectTrigger>
<SelectValue placeholder='Sélectionner un calendrier' />
</SelectTrigger>
<SelectContent>
{calendars.map((calendar) => (
<SelectItem key={calendar.id} value={calendar.id}>
<div className='flex items-center gap-2'>
<div
className='w-3 h-3 rounded-full'
style={{ backgroundColor: calendar.color }}
/>
<span>{calendar.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className='grid grid-cols-2 gap-4'>
<div className='grid gap-2'>
<Label htmlFor='start'>Début *</Label>
<Label htmlFor='start-date'>Début *</Label>
<Input
id='start'
type={allDay ? "date" : "datetime-local"}
id='start-date'
value={formatDate(start)}
onChange={(e) => setStart(e.target.value)}
required
/>
</div>
<div className='grid gap-2'>
<Label htmlFor='end'>Fin *</Label>
<Label htmlFor='end-date'>Fin *</Label>
<Input
id='end'
type={allDay ? "date" : "datetime-local"}
id='end-date'
value={formatDate(end)}
onChange={(e) => setEnd(e.target.value)}
required
/>
</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>
<DialogFooter className='flex justify-between'>
<div>
{event?.id && onDelete && (
<Button
variant='destructive'
onClick={() => setConfirmDelete(true)}
>
Supprimer
</Button>
)}
</div>
<div className='flex space-x-2'>
<Button variant='outline' onClick={onClose}>
Annuler
<DialogFooter>
{event?.id && onDelete && (
<Button
variant='destructive'
onClick={() => setConfirmDelete(true)}
type='button'
>
Supprimer
</Button>
<Button onClick={handleSave} disabled={!title || !start || !end}>
Enregistrer
</Button>
</div>
)}
<Button variant='outline' onClick={onClose} type='button'>
Annuler
</Button>
<Button
onClick={handleSave}
disabled={!title || !start || !end || !calendarId}
type='button'
>
Enregistrer
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Dialogue de confirmation de suppression */}
<AlertDialog open={confirmDelete} onOpenChange={setConfirmDelete}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirmer la suppression</AlertDialogTitle>
<AlertDialogTitle>Supprimer l'événement</AlertDialogTitle>
<AlertDialogDescription>
Êtes-vous sûr de vouloir supprimer cet événement ? Cette action
est irréversible.