Neah version calendar fix 3 debuger ????
This commit is contained in:
parent
1198883064
commit
0b19056473
@ -28,7 +28,7 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp
|
ChevronUp
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Calendar, Event } from "@prisma/client";
|
import { Calendar as CalendarType, Event as EventType } from "@prisma/client";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
@ -62,8 +62,16 @@ const colorPalette = [
|
|||||||
"#2563eb", // Blue
|
"#2563eb", // Blue
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface Calendar extends CalendarType {
|
||||||
|
events: Event[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Event extends EventType {
|
||||||
|
// No additional fields needed as we're extending from Prisma's Event type
|
||||||
|
}
|
||||||
|
|
||||||
interface CalendarClientProps {
|
interface CalendarClientProps {
|
||||||
initialCalendars: (Calendar & { events: Event[] })[];
|
initialCalendars: Calendar[];
|
||||||
userId: string;
|
userId: string;
|
||||||
userProfile: {
|
userProfile: {
|
||||||
name: string;
|
name: string;
|
||||||
@ -431,164 +439,67 @@ function EventPreview({ event, calendar }: { event: Event; calendar: Calendar })
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CalendarClient({ initialCalendars, userId, userProfile }: CalendarClientProps) {
|
export function CalendarClient({ initialCalendars, userId, userProfile }: CalendarClientProps) {
|
||||||
const [calendars, setCalendars] = useState(initialCalendars.map(cal => ({
|
const [calendars, setCalendars] = useState<Calendar[]>(initialCalendars);
|
||||||
...cal,
|
const [selectedCalendarId, setSelectedCalendarId] = useState<string | null>(null);
|
||||||
events: cal.events || []
|
|
||||||
})));
|
|
||||||
const [selectedCalendarId, setSelectedCalendarId] = useState<string>(
|
|
||||||
initialCalendars[0]?.id || ""
|
|
||||||
);
|
|
||||||
const [view, setView] = useState<"dayGridMonth" | "timeGridWeek" | "timeGridDay">("dayGridMonth");
|
const [view, setView] = useState<"dayGridMonth" | "timeGridWeek" | "timeGridDay">("dayGridMonth");
|
||||||
const [isEventModalOpen, setIsEventModalOpen] = useState(false);
|
|
||||||
const [isCalendarModalOpen, setIsCalendarModalOpen] = useState(false);
|
|
||||||
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
|
||||||
const [selectedCalendar, setSelectedCalendar] = useState<Calendar | null>(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [eventForm, setEventForm] = useState<EventFormData>({
|
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
|
||||||
title: "",
|
const [isEventDialogOpen, setIsEventDialogOpen] = useState(false);
|
||||||
description: null,
|
const [isCalendarDialogOpen, setIsCalendarDialogOpen] = useState(false);
|
||||||
start: "",
|
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
|
||||||
end: "",
|
|
||||||
allDay: false,
|
|
||||||
location: null,
|
|
||||||
calendarId: selectedCalendarId
|
|
||||||
});
|
|
||||||
|
|
||||||
const [selectedEventPreview, setSelectedEventPreview] = useState<Event | null>(null);
|
|
||||||
const [upcomingEvents, setUpcomingEvents] = useState<Event[]>([]);
|
|
||||||
const [statistics, setStatistics] = useState({
|
|
||||||
totalEvents: 0,
|
|
||||||
upcomingEvents: 0,
|
|
||||||
completedEvents: 0,
|
|
||||||
meetingHours: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
const [visibleCalendarIds, setVisibleCalendarIds] = useState<string[]>([]);
|
|
||||||
|
|
||||||
// Update useEffect to initialize visible calendars and fetch events
|
|
||||||
useEffect(() => {
|
|
||||||
if (calendars.length > 0) {
|
|
||||||
setVisibleCalendarIds(calendars.map(cal => cal.id));
|
|
||||||
updateStatistics();
|
|
||||||
updateUpcomingEvents();
|
|
||||||
}
|
|
||||||
}, [calendars]);
|
|
||||||
|
|
||||||
const updateStatistics = () => {
|
|
||||||
const now = new Date();
|
|
||||||
const stats = {
|
|
||||||
totalEvents: 0,
|
|
||||||
upcomingEvents: 0,
|
|
||||||
completedEvents: 0,
|
|
||||||
meetingHours: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
calendars.forEach(cal => {
|
|
||||||
cal.events.forEach(event => {
|
|
||||||
stats.totalEvents++;
|
|
||||||
const eventStart = new Date(event.start);
|
|
||||||
const eventEnd = new Date(event.end);
|
|
||||||
|
|
||||||
if (eventStart > now) {
|
|
||||||
stats.upcomingEvents++;
|
|
||||||
} else if (eventEnd < now) {
|
|
||||||
stats.completedEvents++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = (eventEnd.getTime() - eventStart.getTime()) / (1000 * 60 * 60);
|
|
||||||
stats.meetingHours += duration;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
setStatistics(stats);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateUpcomingEvents = () => {
|
|
||||||
const now = new Date();
|
|
||||||
const upcoming = calendars
|
|
||||||
.flatMap(cal => cal.events)
|
|
||||||
.filter(event => new Date(event.start) > now)
|
|
||||||
.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime())
|
|
||||||
.slice(0, 5);
|
|
||||||
setUpcomingEvents(upcoming);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchCalendars = async () => {
|
const fetchCalendars = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch("/api/calendars", {
|
const response = await fetch('/api/calendars', {
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
if (!response.ok) throw new Error("Failed to fetch calendars");
|
|
||||||
const data = await response.json();
|
|
||||||
console.log("Raw calendars data:", data);
|
|
||||||
|
|
||||||
// Process calendars and events
|
if (!response.ok) {
|
||||||
const processedCalendars = data.map((cal: Calendar & { events: Event[] }) => ({
|
throw new Error('Failed to fetch calendars');
|
||||||
...cal,
|
|
||||||
events: Array.isArray(cal.events) ? cal.events.map(event => ({
|
|
||||||
...event,
|
|
||||||
start: new Date(event.start),
|
|
||||||
end: new Date(event.end)
|
|
||||||
})) : []
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("Setting calendars with processed events:", processedCalendars);
|
|
||||||
setCalendars(processedCalendars);
|
|
||||||
|
|
||||||
// Update statistics and upcoming events
|
|
||||||
updateStatistics();
|
|
||||||
updateUpcomingEvents();
|
|
||||||
|
|
||||||
// Force calendar refresh
|
|
||||||
if (calendarRef.current) {
|
|
||||||
const calendarApi = calendarRef.current.getApi();
|
|
||||||
calendarApi.refetchEvents();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching calendars:", error);
|
const data = await response.json();
|
||||||
setError(error instanceof Error ? error.message : "Failed to fetch calendars");
|
setCalendars(data);
|
||||||
|
setError(null);
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to load calendars');
|
||||||
|
console.error('Error fetching calendars:', err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const calendarRef = useRef<any>(null);
|
|
||||||
|
|
||||||
const handleCalendarSelect = (calendarId: string) => {
|
const handleCalendarSelect = (calendarId: string) => {
|
||||||
console.log("Calendar selected:", calendarId);
|
|
||||||
setSelectedCalendarId(calendarId);
|
setSelectedCalendarId(calendarId);
|
||||||
setEventForm(prev => ({
|
|
||||||
...prev,
|
|
||||||
calendarId: calendarId
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCalendarSave = async (calendarData: Partial<Calendar>) => {
|
const handleCalendarSave = async (calendarData: Partial<Calendar>) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch("/api/calendars", {
|
const response = await fetch('/api/calendars', {
|
||||||
method: calendarData.id ? "PUT" : "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
...calendarData,
|
...calendarData,
|
||||||
userId,
|
userId,
|
||||||
}),
|
}),
|
||||||
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to save calendar");
|
throw new Error('Failed to save calendar');
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetchCalendars();
|
const newCalendar = await response.json();
|
||||||
setIsCalendarModalOpen(false);
|
setCalendars(prev => [...prev, newCalendar]);
|
||||||
} catch (error) {
|
setError(null);
|
||||||
console.error("Error saving calendar:", error);
|
} catch (err) {
|
||||||
setError(error instanceof Error ? error.message : "Failed to save calendar");
|
setError('Failed to save calendar');
|
||||||
|
console.error('Error saving calendar:', err);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -598,212 +509,100 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`/api/calendars/${calendarId}`, {
|
const response = await fetch(`/api/calendars/${calendarId}`, {
|
||||||
method: "DELETE",
|
method: 'DELETE',
|
||||||
credentials: 'include'
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to delete calendar");
|
throw new Error('Failed to delete calendar');
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetchCalendars();
|
setCalendars(prev => prev.filter(cal => cal.id !== calendarId));
|
||||||
setIsCalendarModalOpen(false);
|
setError(null);
|
||||||
|
} catch (err) {
|
||||||
// If the deleted calendar was selected, select another one
|
setError('Failed to delete calendar');
|
||||||
if (selectedCalendarId === calendarId) {
|
console.error('Error deleting calendar:', err);
|
||||||
const remainingCalendars = calendars.filter(cal => cal.id !== calendarId);
|
|
||||||
setSelectedCalendarId(remainingCalendars[0]?.id || "");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error deleting calendar:", error);
|
|
||||||
setError(error instanceof Error ? error.message : "Failed to delete calendar");
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateSelect = (selectInfo: any) => {
|
const handleDateSelect = (selectInfo: any) => {
|
||||||
const startDate = new Date(selectInfo.start);
|
setSelectedDate(selectInfo.start);
|
||||||
const endDate = new Date(selectInfo.end);
|
setIsEventDialogOpen(true);
|
||||||
|
|
||||||
console.log("Date select handler - Current state:", {
|
|
||||||
calendars: calendars.map(c => ({ id: c.id, name: c.name })),
|
|
||||||
selectedCalendarId,
|
|
||||||
availableCalendars: calendars.length
|
|
||||||
});
|
|
||||||
|
|
||||||
// If no calendar is selected, use the first available calendar
|
|
||||||
if (!selectedCalendarId && calendars.length > 0) {
|
|
||||||
const firstCalendar = calendars[0];
|
|
||||||
console.log("No calendar selected, selecting first calendar:", firstCalendar);
|
|
||||||
setSelectedCalendarId(firstCalendar.id);
|
|
||||||
|
|
||||||
setEventForm({
|
|
||||||
title: "",
|
|
||||||
description: null,
|
|
||||||
start: startDate.toISOString(),
|
|
||||||
end: endDate.toISOString(),
|
|
||||||
allDay: selectInfo.allDay,
|
|
||||||
location: null,
|
|
||||||
calendarId: firstCalendar.id
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setEventForm({
|
|
||||||
title: "",
|
|
||||||
description: null,
|
|
||||||
start: startDate.toISOString(),
|
|
||||||
end: endDate.toISOString(),
|
|
||||||
allDay: selectInfo.allDay,
|
|
||||||
location: null,
|
|
||||||
calendarId: selectedCalendarId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsEventModalOpen(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEventClick = (clickInfo: any) => {
|
const handleEventClick = (clickInfo: any) => {
|
||||||
const event = clickInfo.event;
|
setSelectedEvent(clickInfo.event);
|
||||||
const startDate = new Date(event.start);
|
setIsEventDialogOpen(true);
|
||||||
const endDate = new Date(event.end || event.start);
|
|
||||||
|
|
||||||
setSelectedEvent(event.extendedProps.originalEvent);
|
|
||||||
setEventForm({
|
|
||||||
title: event.title,
|
|
||||||
description: event.extendedProps.description,
|
|
||||||
start: startDate.toISOString().slice(0, 16),
|
|
||||||
end: endDate.toISOString().slice(0, 16),
|
|
||||||
allDay: event.isAllDay,
|
|
||||||
location: event.extendedProps.location,
|
|
||||||
calendarId: event.extendedProps.calendarId,
|
|
||||||
});
|
|
||||||
setIsEventModalOpen(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEventSubmit = async () => {
|
const handleEventSubmit = async (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
try {
|
try {
|
||||||
// Validate required fields including calendar
|
setLoading(true);
|
||||||
if (!eventForm.title || !eventForm.start || !eventForm.end || !eventForm.calendarId) {
|
if (!selectedEvent) {
|
||||||
console.log("Form validation failed:", {
|
throw new Error('No event selected');
|
||||||
title: eventForm.title,
|
|
||||||
start: eventForm.start,
|
|
||||||
end: eventForm.end,
|
|
||||||
calendarId: eventForm.calendarId
|
|
||||||
});
|
|
||||||
setError("Veuillez remplir tous les champs obligatoires et sélectionner un calendrier");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
const eventData = {
|
const eventData = {
|
||||||
...eventForm,
|
...selectedEvent,
|
||||||
start: new Date(eventForm.start).toISOString(),
|
start: selectedEvent.start ? new Date(selectedEvent.start).toISOString() : null,
|
||||||
end: new Date(eventForm.end).toISOString(),
|
end: selectedEvent.end ? new Date(selectedEvent.end).toISOString() : null,
|
||||||
userId,
|
userId,
|
||||||
...(selectedEvent ? { id: selectedEvent.id } : {}), // Include ID for updates
|
|
||||||
allDay: eventForm.allDay // Use allDay instead of isAllDay
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Submitting event with data:", eventData);
|
const response = await fetch('/api/events', {
|
||||||
|
method: 'POST',
|
||||||
const response = await fetch("/api/events", {
|
|
||||||
method: selectedEvent ? "PUT" : "POST",
|
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(eventData),
|
body: JSON.stringify(eventData),
|
||||||
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
const responseData = await response.json();
|
|
||||||
console.log("Response from server:", responseData);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Error response:", responseData);
|
throw new Error('Failed to save event');
|
||||||
throw new Error(responseData.error || "Failed to save event");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset form and close modal first
|
const newEvent = await response.json();
|
||||||
setIsEventModalOpen(false);
|
setCalendars(prev => prev.map(cal => {
|
||||||
setEventForm({
|
if (cal.id === newEvent.calendarId) {
|
||||||
title: "",
|
|
||||||
description: null,
|
|
||||||
start: "",
|
|
||||||
end: "",
|
|
||||||
allDay: false,
|
|
||||||
location: null,
|
|
||||||
calendarId: selectedCalendarId
|
|
||||||
});
|
|
||||||
setSelectedEvent(null);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
// Update calendars state with the new event
|
|
||||||
const updatedCalendars = calendars.map(cal => {
|
|
||||||
if (cal.id === eventData.calendarId) {
|
|
||||||
return {
|
return {
|
||||||
...cal,
|
...cal,
|
||||||
events: [...cal.events, responseData]
|
events: [...cal.events, newEvent],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return cal;
|
return cal;
|
||||||
});
|
}));
|
||||||
setCalendars(updatedCalendars);
|
setError(null);
|
||||||
|
} catch (err) {
|
||||||
// Fetch fresh data to ensure all calendars are up to date
|
setError('Failed to save event');
|
||||||
await fetchCalendars();
|
console.error('Error saving event:', err);
|
||||||
} catch (error) {
|
|
||||||
console.error("Error saving event:", error);
|
|
||||||
setError(error instanceof Error ? error.message : "Failed to save event");
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEventDelete = async () => {
|
const handleEventDelete = async (eventId: string) => {
|
||||||
if (!selectedEvent?.id) return;
|
|
||||||
|
|
||||||
if (!confirm("Êtes-vous sûr de vouloir supprimer cet événement ?")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`/api/events/${selectedEvent.id}`, {
|
const response = await fetch(`/api/events/${eventId}`, {
|
||||||
method: "DELETE",
|
method: 'DELETE',
|
||||||
|
credentials: 'include'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
throw new Error('Failed to delete event');
|
||||||
throw new Error(error.message || "Failed to delete event");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the event from local state
|
setCalendars(prev => prev.map(cal => ({
|
||||||
const updatedCalendars = calendars.map(cal => ({
|
|
||||||
...cal,
|
...cal,
|
||||||
events: cal.events.filter(e => e.id !== selectedEvent.id)
|
events: cal.events.filter(event => event.id !== eventId),
|
||||||
}));
|
})));
|
||||||
setCalendars(updatedCalendars);
|
setError(null);
|
||||||
|
} catch (err) {
|
||||||
// Close modal and reset form
|
setError('Failed to delete event');
|
||||||
setIsEventModalOpen(false);
|
console.error('Error deleting event:', err);
|
||||||
setSelectedEvent(null);
|
|
||||||
setEventForm({
|
|
||||||
title: "",
|
|
||||||
description: null,
|
|
||||||
start: "",
|
|
||||||
end: "",
|
|
||||||
allDay: false,
|
|
||||||
location: null,
|
|
||||||
calendarId: selectedCalendarId
|
|
||||||
});
|
|
||||||
|
|
||||||
// Force calendar refresh
|
|
||||||
if (calendarRef.current) {
|
|
||||||
const calendarApi = calendarRef.current.getApi();
|
|
||||||
calendarApi.refetchEvents();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error deleting event:", error);
|
|
||||||
setError(error instanceof Error ? error.message : "Failed to delete event");
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -811,112 +610,6 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
|
|
||||||
const handleViewChange = (newView: "dayGridMonth" | "timeGridWeek" | "timeGridDay") => {
|
const handleViewChange = (newView: "dayGridMonth" | "timeGridWeek" | "timeGridDay") => {
|
||||||
setView(newView);
|
setView(newView);
|
||||||
if (calendarRef.current) {
|
|
||||||
const calendarApi = calendarRef.current.getApi();
|
|
||||||
calendarApi.changeView(newView);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update CalendarSelector to handle visibility
|
|
||||||
const CalendarSelector = () => (
|
|
||||||
<div className="flex flex-wrap items-center gap-2 mb-4">
|
|
||||||
{calendars.map((calendar) => (
|
|
||||||
<div key={calendar.id} className="relative group">
|
|
||||||
<Button
|
|
||||||
variant={visibleCalendarIds.includes(calendar.id) ? "secondary" : "ghost"}
|
|
||||||
className="flex items-center gap-2 pr-8"
|
|
||||||
onClick={() => {
|
|
||||||
if (visibleCalendarIds.includes(calendar.id)) {
|
|
||||||
setVisibleCalendarIds(visibleCalendarIds.filter(id => id !== calendar.id));
|
|
||||||
} else {
|
|
||||||
setVisibleCalendarIds([...visibleCalendarIds, calendar.id]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 rounded-full"
|
|
||||||
style={{ backgroundColor: calendar.color }}
|
|
||||||
/>
|
|
||||||
<span>{calendar.name}</span>
|
|
||||||
<div className="ml-2">
|
|
||||||
{visibleCalendarIds.includes(calendar.id) ? (
|
|
||||||
<Check className="h-4 w-4" />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
{calendar.name !== "Calendrier principal" && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="absolute right-0 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setSelectedCalendar(calendar);
|
|
||||||
setIsCalendarModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Settings className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add these helper functions for date handling
|
|
||||||
const getDateFromString = (dateString: string) => {
|
|
||||||
return dateString ? new Date(dateString) : new Date();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleStartDateChange = (date: Date | null) => {
|
|
||||||
if (!date) return;
|
|
||||||
|
|
||||||
const endDate = getDateFromString(eventForm.end);
|
|
||||||
if (date > endDate) {
|
|
||||||
// If start date is after end date, set end date to start date + 1 hour
|
|
||||||
const newEndDate = new Date(date);
|
|
||||||
newEndDate.setHours(date.getHours() + 1);
|
|
||||||
setEventForm({
|
|
||||||
...eventForm,
|
|
||||||
start: date.toISOString(),
|
|
||||||
end: newEndDate.toISOString(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setEventForm({
|
|
||||||
...eventForm,
|
|
||||||
start: date.toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEndDateChange = (date: Date | null) => {
|
|
||||||
if (!date) return;
|
|
||||||
|
|
||||||
const startDate = getDateFromString(eventForm.start);
|
|
||||||
if (date < startDate) {
|
|
||||||
// If end date is before start date, set start date to end date - 1 hour
|
|
||||||
const newStartDate = new Date(date);
|
|
||||||
newStartDate.setHours(date.getHours() - 1);
|
|
||||||
setEventForm({
|
|
||||||
...eventForm,
|
|
||||||
start: newStartDate.toISOString(),
|
|
||||||
end: date.toISOString(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setEventForm({
|
|
||||||
...eventForm,
|
|
||||||
end: date.toISOString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update the date handlers to maintain consistent time format
|
|
||||||
const formatTimeForInput = (date: Date) => {
|
|
||||||
return date.toLocaleTimeString('fr-FR', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
hour12: false
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -927,8 +620,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedCalendar(null);
|
setSelectedCalendarId(null);
|
||||||
setIsCalendarModalOpen(true);
|
setIsCalendarDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
className="bg-white hover:bg-gray-50 text-gray-900 border-gray-200"
|
className="bg-white hover:bg-gray-50 text-gray-900 border-gray-200"
|
||||||
>
|
>
|
||||||
@ -938,16 +631,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedEvent(null);
|
setSelectedEvent(null);
|
||||||
setEventForm({
|
setSelectedDate(new Date());
|
||||||
title: "",
|
setIsEventDialogOpen(true);
|
||||||
description: null,
|
|
||||||
start: new Date().toISOString(),
|
|
||||||
end: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(),
|
|
||||||
allDay: false,
|
|
||||||
location: null,
|
|
||||||
calendarId: selectedCalendarId
|
|
||||||
});
|
|
||||||
setIsEventModalOpen(true);
|
|
||||||
}}
|
}}
|
||||||
className="bg-primary hover:bg-primary/90 text-white"
|
className="bg-primary hover:bg-primary/90 text-white"
|
||||||
>
|
>
|
||||||
@ -980,8 +665,6 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CalendarSelector />
|
|
||||||
|
|
||||||
<style jsx global>{`
|
<style jsx global>{`
|
||||||
/* Fixed height and scrolling for day cells only */
|
/* Fixed height and scrolling for day cells only */
|
||||||
.fc .fc-daygrid-day-frame {
|
.fc .fc-daygrid-day-frame {
|
||||||
@ -1030,7 +713,6 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
ref={calendarRef}
|
|
||||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||||
initialView={view}
|
initialView={view}
|
||||||
headerToolbar={{
|
headerToolbar={{
|
||||||
@ -1039,7 +721,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
right: "",
|
right: "",
|
||||||
}}
|
}}
|
||||||
events={calendars
|
events={calendars
|
||||||
.filter(cal => visibleCalendarIds.includes(cal.id))
|
.filter(cal => selectedCalendarId === cal.id)
|
||||||
.flatMap(cal =>
|
.flatMap(cal =>
|
||||||
(cal.events || []).map(event => ({
|
(cal.events || []).map(event => ({
|
||||||
id: event.id,
|
id: event.id,
|
||||||
@ -1150,26 +832,18 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
|
|
||||||
{/* Calendar dialog */}
|
{/* Calendar dialog */}
|
||||||
<CalendarDialog
|
<CalendarDialog
|
||||||
open={isCalendarModalOpen}
|
open={isCalendarDialogOpen}
|
||||||
onClose={() => setIsCalendarModalOpen(false)}
|
onClose={() => setIsCalendarDialogOpen(false)}
|
||||||
onSave={handleCalendarSave}
|
onSave={handleCalendarSave}
|
||||||
onDelete={handleCalendarDelete}
|
onDelete={handleCalendarDelete}
|
||||||
initialData={selectedCalendar || undefined}
|
initialData={calendars.find(cal => cal.id === selectedCalendarId) || undefined}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Event dialog */}
|
{/* Event dialog */}
|
||||||
<Dialog open={isEventModalOpen} onOpenChange={(open) => {
|
<Dialog open={isEventDialogOpen} onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setIsEventModalOpen(false);
|
setIsEventDialogOpen(false);
|
||||||
setEventForm({
|
setSelectedDate(null);
|
||||||
title: "",
|
|
||||||
description: null,
|
|
||||||
start: "",
|
|
||||||
end: "",
|
|
||||||
allDay: false,
|
|
||||||
location: null,
|
|
||||||
calendarId: selectedCalendarId || calendars[0]?.id
|
|
||||||
});
|
|
||||||
setSelectedEvent(null);
|
setSelectedEvent(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
}
|
}
|
||||||
@ -1194,8 +868,12 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
placeholder="Titre de l'événement"
|
placeholder="Titre de l'événement"
|
||||||
value={eventForm.title}
|
value={selectedEvent?.title || ""}
|
||||||
onChange={(e) => setEventForm({ ...eventForm, title: e.target.value })}
|
onChange={(e) => {
|
||||||
|
if (selectedEvent) {
|
||||||
|
setSelectedEvent({ ...selectedEvent, title: e.target.value });
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1207,13 +885,10 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
key={cal.id}
|
key={cal.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setEventForm(prev => ({
|
setSelectedCalendarId(cal.id);
|
||||||
...prev,
|
|
||||||
calendarId: cal.id
|
|
||||||
}));
|
|
||||||
}}
|
}}
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-all ${
|
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-all ${
|
||||||
eventForm.calendarId === cal.id
|
selectedCalendarId === cal.id
|
||||||
? 'bg-white ring-2 ring-primary'
|
? 'bg-white ring-2 ring-primary'
|
||||||
: 'bg-gray-900 hover:bg-gray-800'
|
: 'bg-gray-900 hover:bg-gray-800'
|
||||||
}`}
|
}`}
|
||||||
@ -1223,7 +898,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
style={{ backgroundColor: cal.color }}
|
style={{ backgroundColor: cal.color }}
|
||||||
/>
|
/>
|
||||||
<span className={`text-sm ${
|
<span className={`text-sm ${
|
||||||
eventForm.calendarId === cal.id
|
selectedCalendarId === cal.id
|
||||||
? 'font-medium text-gray-900'
|
? 'font-medium text-gray-900'
|
||||||
: 'text-gray-100'
|
: 'text-gray-100'
|
||||||
}`}>
|
}`}>
|
||||||
@ -1240,8 +915,13 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={getDateFromString(eventForm.start)}
|
selected={selectedDate ? new Date(selectedDate) : undefined}
|
||||||
onChange={handleStartDateChange}
|
onChange={(date) => {
|
||||||
|
if (date && selectedEvent) {
|
||||||
|
setSelectedDate(date);
|
||||||
|
setSelectedEvent({ ...selectedEvent, start: date });
|
||||||
|
}
|
||||||
|
}}
|
||||||
dateFormat="dd/MM/yyyy"
|
dateFormat="dd/MM/yyyy"
|
||||||
locale="fr"
|
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"
|
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||||
@ -1250,8 +930,13 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={getDateFromString(eventForm.start)}
|
selected={selectedDate ? new Date(selectedDate) : undefined}
|
||||||
onChange={handleStartDateChange}
|
onChange={(date) => {
|
||||||
|
if (date && selectedEvent) {
|
||||||
|
setSelectedDate(date);
|
||||||
|
setSelectedEvent({ ...selectedEvent, start: date });
|
||||||
|
}
|
||||||
|
}}
|
||||||
showTimeSelect
|
showTimeSelect
|
||||||
showTimeSelectOnly
|
showTimeSelectOnly
|
||||||
timeIntervals={15}
|
timeIntervals={15}
|
||||||
@ -1268,19 +953,29 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={getDateFromString(eventForm.end)}
|
selected={selectedDate ? new Date(selectedDate) : undefined}
|
||||||
onChange={handleEndDateChange}
|
onChange={(date) => {
|
||||||
|
if (date && selectedEvent) {
|
||||||
|
setSelectedDate(date);
|
||||||
|
setSelectedEvent({ ...selectedEvent, end: date });
|
||||||
|
}
|
||||||
|
}}
|
||||||
dateFormat="dd/MM/yyyy"
|
dateFormat="dd/MM/yyyy"
|
||||||
locale="fr"
|
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"
|
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"
|
placeholderText="Date"
|
||||||
customInput={<Input />}
|
customInput={<Input />}
|
||||||
minDate={getDateFromString(eventForm.start)}
|
minDate={selectedDate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={getDateFromString(eventForm.end)}
|
selected={selectedDate ? new Date(selectedDate) : undefined}
|
||||||
onChange={handleEndDateChange}
|
onChange={(date) => {
|
||||||
|
if (date && selectedEvent) {
|
||||||
|
setSelectedDate(date);
|
||||||
|
setSelectedEvent({ ...selectedEvent, end: date });
|
||||||
|
}
|
||||||
|
}}
|
||||||
showTimeSelect
|
showTimeSelect
|
||||||
showTimeSelectOnly
|
showTimeSelectOnly
|
||||||
timeIntervals={15}
|
timeIntervals={15}
|
||||||
@ -1297,10 +992,12 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="allDay"
|
id="allDay"
|
||||||
checked={eventForm.allDay}
|
checked={selectedEvent?.isAllDay || false}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) => {
|
||||||
setEventForm({ ...eventForm, allDay: checked as boolean })
|
if (selectedEvent) {
|
||||||
}
|
setSelectedEvent({ ...selectedEvent, isAllDay: checked as boolean });
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="allDay">Toute la journée</Label>
|
<Label htmlFor="allDay">Toute la journée</Label>
|
||||||
</div>
|
</div>
|
||||||
@ -1308,10 +1005,12 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Lieu</Label>
|
<Label>Lieu</Label>
|
||||||
<Input
|
<Input
|
||||||
value={eventForm.location || ""}
|
value={selectedEvent?.location || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
setEventForm({ ...eventForm, location: e.target.value })
|
if (selectedEvent) {
|
||||||
}
|
setSelectedEvent({ ...selectedEvent, location: e.target.value });
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder="Ajouter un lieu"
|
placeholder="Ajouter un lieu"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -1319,10 +1018,12 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Description</Label>
|
<Label>Description</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={eventForm.description || ""}
|
value={selectedEvent?.description || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
setEventForm({ ...eventForm, description: e.target.value })
|
if (selectedEvent) {
|
||||||
}
|
setSelectedEvent({ ...selectedEvent, description: e.target.value });
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder="Ajouter une description"
|
placeholder="Ajouter une description"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -1332,7 +1033,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
{selectedEvent && (
|
{selectedEvent && (
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={handleEventDelete}
|
onClick={() => handleEventDelete(selectedEvent.id)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@ -1348,22 +1049,16 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setIsEventModalOpen(false)}
|
onClick={() => setIsEventDialogOpen(false)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
Annuler
|
Annuler
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleEventSubmit} disabled={loading}>
|
<Button
|
||||||
{loading ? (
|
onClick={handleEventSubmit}
|
||||||
<>
|
disabled={loading}
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
>
|
||||||
Enregistrement...
|
{loading ? 'Enregistrement...' : 'Enregistrer'}
|
||||||
</>
|
|
||||||
) : selectedEvent ? (
|
|
||||||
"Mettre à jour"
|
|
||||||
) : (
|
|
||||||
"Créer"
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user