calendar 36
This commit is contained in:
parent
f53b6556a1
commit
51ab2c2150
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user