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 }}"
|
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: >
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
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, 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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user