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:
parent
bc6a9fe10c
commit
a94e3eabc5
@ -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: >
|
||||
|
||||
@ -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>
|
||||
|
||||
114
front/components/calendar/calendar-dialog.tsx
Normal file
114
front/components/calendar/calendar-dialog.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user