calendar 36

This commit is contained in:
Alma 2025-04-13 17:22:02 +02:00
parent f53b6556a1
commit 51ab2c2150

View File

@ -1003,9 +1003,9 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
<span className="font-medium">Nouveau calendrier</span> <span className="font-medium">Nouveau calendrier</span>
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
setSelectedEvent(null); setSelectedEvent(null);
setEventForm({ setEventForm({
title: "", title: "",
description: null, description: null,
@ -1021,50 +1021,91 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
> >
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
<span className="font-medium">Nouvel événement</span> <span className="font-medium">Nouvel événement</span>
</Button> </Button>
</div> </div>
<Tabs value={view} className="w-auto"> <Tabs value={view} className="w-auto">
<TabsList> <TabsList>
<TabsTrigger <TabsTrigger
value="dayGridMonth" value="dayGridMonth"
onClick={() => handleViewChange("dayGridMonth")} onClick={() => handleViewChange("dayGridMonth")}
> >
Mois Mois
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="timeGridWeek" value="timeGridWeek"
onClick={() => handleViewChange("timeGridWeek")} onClick={() => handleViewChange("timeGridWeek")}
> >
Semaine Semaine
</TabsTrigger> </TabsTrigger>
<TabsTrigger <TabsTrigger
value="timeGridDay" value="timeGridDay"
onClick={() => handleViewChange("timeGridDay")} onClick={() => handleViewChange("timeGridDay")}
> >
Jour Jour
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
</Tabs> </Tabs>
</div> </div>
<CalendarSelector /> <CalendarSelector />
<style jsx global>{`
/* Fixed height and scrolling for day cells only */
.fc .fc-daygrid-day-frame {
min-height: 100px !important;
max-height: 100px !important;
overflow-y: auto !important;
}
/* Keep events container relative positioning */
.fc .fc-daygrid-day-events {
margin-bottom: 0 !important;
}
/* Custom scrollbar styles */
.fc .fc-daygrid-day-frame::-webkit-scrollbar {
width: 4px;
}
.fc .fc-daygrid-day-frame::-webkit-scrollbar-track {
background: transparent;
}
.fc .fc-daygrid-day-frame::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.fc .fc-daygrid-day-frame::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Hide scrollbar when not needed */
.fc .fc-daygrid-day-frame::-webkit-scrollbar {
display: none;
}
.fc .fc-daygrid-day-frame:hover::-webkit-scrollbar {
display: block;
}
`}</style>
{loading ? ( {loading ? (
<div className="h-96 flex items-center justify-center"> <div className="h-96 flex items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-primary" /> <Loader2 className="h-8 w-8 animate-spin text-primary" />
<span className="ml-2">Chargement des événements...</span> <span className="ml-2">Chargement des événements...</span>
</div> </div>
) : ( ) : (
<FullCalendar <FullCalendar
ref={calendarRef} ref={calendarRef}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]} plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView={view} initialView={view}
headerToolbar={{ headerToolbar={{
left: "prev,next today", left: "prev,next today",
center: "title", center: "title",
right: "", right: "",
}} }}
events={calendars events={calendars
.filter(cal => visibleCalendarIds.includes(cal.id)) .filter(cal => visibleCalendarIds.includes(cal.id))
.flatMap(cal => .flatMap(cal =>
@ -1117,7 +1158,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
locale={frLocale} locale={frLocale}
selectable={true} selectable={true}
selectMirror={true} selectMirror={true}
dayMaxEvents={true} dayMaxEventRows={false}
dayMaxEvents={false}
weekends={true} weekends={true}
select={handleDateSelect} select={handleDateSelect}
eventClick={handleEventClick} eventClick={handleEventClick}
@ -1132,219 +1174,219 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
minute: '2-digit', minute: '2-digit',
hour12: false hour12: false
}} }}
/> />
)} )}
</Card> </Card>
{/* Calendar dialog */} {/* Calendar dialog */}
<CalendarDialog <CalendarDialog
open={isCalendarModalOpen} open={isCalendarModalOpen}
onClose={() => setIsCalendarModalOpen(false)} onClose={() => setIsCalendarModalOpen(false)}
onSave={handleCalendarSave} onSave={handleCalendarSave}
onDelete={handleCalendarDelete} onDelete={handleCalendarDelete}
initialData={selectedCalendar || undefined} initialData={selectedCalendar || undefined}
/> />
{/* Event dialog */} {/* Event dialog */}
<Dialog open={isEventModalOpen} onOpenChange={(open) => { <Dialog open={isEventModalOpen} onOpenChange={(open) => {
if (!open) { if (!open) {
setIsEventModalOpen(false); setIsEventModalOpen(false);
setEventForm({ setEventForm({
title: "", title: "",
description: null, description: null,
start: "", start: "",
end: "", end: "",
allDay: false, allDay: false,
location: null, location: null,
calendarId: selectedCalendarId calendarId: selectedCalendarId
}); });
setSelectedEvent(null); setSelectedEvent(null);
setError(null); setError(null);
} }
}}> }}>
<DialogContent className="sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{selectedEvent ? "Modifier l'événement" : "Nouvel événement"} {selectedEvent ? "Modifier l'événement" : "Nouvel événement"}
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
{error && ( {error && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-2 rounded-md text-sm"> <div className="bg-red-50 border border-red-200 text-red-600 px-4 py-2 rounded-md text-sm">
{error} {error}
</div> </div>
)} )}
<div className="space-y-4 py-4"> <div className="space-y-4 py-4">
<div className="space-y-2"> <div className="space-y-2">
<Label>Titre</Label> <Label>Titre</Label>
<Input <Input
value={eventForm.title} value={eventForm.title}
onChange={(e) => onChange={(e) =>
setEventForm({ ...eventForm, title: e.target.value }) setEventForm({ ...eventForm, title: e.target.value })
} }
placeholder="Titre de l'événement" placeholder="Titre de l'événement"
/> />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Calendrier</Label> <Label>Calendrier</Label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{calendars.map((cal) => ( {calendars.map((cal) => (
<button <button
key={cal.id} key={cal.id}
type="button" type="button"
onClick={() => setSelectedCalendarId(cal.id)} onClick={() => setSelectedCalendarId(cal.id)}
className={`flex items-center gap-2 px-3 py-2 rounded-md border transition-all ${ className={`flex items-center gap-2 px-3 py-2 rounded-md border transition-all ${
cal.id === selectedCalendarId cal.id === selectedCalendarId
? 'border-primary bg-primary/5' ? 'border-primary bg-primary/5'
: 'border-gray-200 hover:border-gray-300' : 'border-gray-200 hover:border-gray-300'
}`} }`}
> >
<div <div
className="w-3 h-3 rounded-full" className="w-3 h-3 rounded-full"
style={{ backgroundColor: cal.color }} style={{ backgroundColor: cal.color }}
/>
<span className="text-sm font-medium">{cal.name}</span>
</button>
))}
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Début</Label>
<div className="flex gap-2">
<div className="flex-1">
<DatePicker
selected={getDateFromString(eventForm.start)}
onChange={handleStartDateChange}
dateFormat="dd/MM/yyyy"
locale="fr"
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
placeholderText="Date"
customInput={<Input />}
/>
</div>
<Input
type="time"
value={eventForm.start ? new Date(eventForm.start).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) : ''}
onChange={(e) => {
const [hours, minutes] = e.target.value.split(':');
const date = getDateFromString(eventForm.start);
date.setHours(parseInt(hours), parseInt(minutes));
handleStartDateChange(date);
}}
className="w-32"
/> />
</div> <span className="text-sm font-medium">{cal.name}</span>
</div> </button>
))}
<div className="space-y-2">
<Label>Fin</Label>
<div className="flex gap-2">
<div className="flex-1">
<DatePicker
selected={getDateFromString(eventForm.end)}
onChange={handleEndDateChange}
dateFormat="dd/MM/yyyy"
locale="fr"
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
placeholderText="Date"
customInput={<Input />}
minDate={getDateFromString(eventForm.start)}
/>
</div>
<Input
type="time"
value={eventForm.end ? new Date(eventForm.end).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) : ''}
onChange={(e) => {
const [hours, minutes] = e.target.value.split(':');
const date = getDateFromString(eventForm.end);
date.setHours(parseInt(hours), parseInt(minutes));
handleEndDateChange(date);
}}
className="w-32"
/>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="allDay"
checked={eventForm.allDay}
onCheckedChange={(checked) =>
setEventForm({ ...eventForm, allDay: checked as boolean })
}
/>
<Label htmlFor="allDay">Toute la journée</Label>
</div>
<div className="space-y-2">
<Label>Lieu</Label>
<Input
value={eventForm.location || ""}
onChange={(e) =>
setEventForm({ ...eventForm, location: e.target.value })
}
placeholder="Ajouter un lieu"
/>
</div>
<div className="space-y-2">
<Label>Description</Label>
<Textarea
value={eventForm.description || ""}
onChange={(e) =>
setEventForm({ ...eventForm, description: e.target.value })
}
placeholder="Ajouter une description"
/>
</div> </div>
</div> </div>
<DialogFooter> <div className="grid grid-cols-2 gap-4">
{selectedEvent && ( <div className="space-y-2">
<Button <Label>Début</Label>
variant="destructive" <div className="flex gap-2">
onClick={handleEventDelete} <div className="flex-1">
disabled={loading} <DatePicker
> selected={getDateFromString(eventForm.start)}
{loading ? ( onChange={handleStartDateChange}
<> dateFormat="dd/MM/yyyy"
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> locale="fr"
Suppression... className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
</> placeholderText="Date"
) : ( customInput={<Input />}
"Supprimer" />
)} </div>
</Button> <Input
)} type="time"
<div className="flex space-x-2"> value={eventForm.start ? new Date(eventForm.start).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) : ''}
<Button onChange={(e) => {
variant="outline" const [hours, minutes] = e.target.value.split(':');
onClick={() => setIsEventModalOpen(false)} const date = getDateFromString(eventForm.start);
disabled={loading} date.setHours(parseInt(hours), parseInt(minutes));
> handleStartDateChange(date);
Annuler }}
</Button> className="w-32"
<Button onClick={handleEventSubmit} disabled={loading}> />
{loading ? ( </div>
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Enregistrement...
</>
) : selectedEvent ? (
"Mettre à jour"
) : (
"Créer"
)}
</Button>
</div> </div>
</DialogFooter>
</DialogContent> <div className="space-y-2">
</Dialog> <Label>Fin</Label>
<div className="flex gap-2">
<div className="flex-1">
<DatePicker
selected={getDateFromString(eventForm.end)}
onChange={handleEndDateChange}
dateFormat="dd/MM/yyyy"
locale="fr"
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
placeholderText="Date"
customInput={<Input />}
minDate={getDateFromString(eventForm.start)}
/>
</div>
<Input
type="time"
value={eventForm.end ? new Date(eventForm.end).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) : ''}
onChange={(e) => {
const [hours, minutes] = e.target.value.split(':');
const date = getDateFromString(eventForm.end);
date.setHours(parseInt(hours), parseInt(minutes));
handleEndDateChange(date);
}}
className="w-32"
/>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="allDay"
checked={eventForm.allDay}
onCheckedChange={(checked) =>
setEventForm({ ...eventForm, allDay: checked as boolean })
}
/>
<Label htmlFor="allDay">Toute la journée</Label>
</div>
<div className="space-y-2">
<Label>Lieu</Label>
<Input
value={eventForm.location || ""}
onChange={(e) =>
setEventForm({ ...eventForm, location: e.target.value })
}
placeholder="Ajouter un lieu"
/>
</div>
<div className="space-y-2">
<Label>Description</Label>
<Textarea
value={eventForm.description || ""}
onChange={(e) =>
setEventForm({ ...eventForm, description: e.target.value })
}
placeholder="Ajouter une description"
/>
</div>
</div>
<DialogFooter>
{selectedEvent && (
<Button
variant="destructive"
onClick={handleEventDelete}
disabled={loading}
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Suppression...
</>
) : (
"Supprimer"
)}
</Button>
)}
<div className="flex space-x-2">
<Button
variant="outline"
onClick={() => setIsEventModalOpen(false)}
disabled={loading}
>
Annuler
</Button>
<Button onClick={handleEventSubmit} disabled={loading}>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Enregistrement...
</>
) : selectedEvent ? (
"Mettre à jour"
) : (
"Créer"
)}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</div> </div>
); );
} }