Compare commits
No commits in common. "c5adc316ef7aea96ead2e190dafa103b4e069135" and "901c766cedbd960cb8ef84dfd0474ffbf24b8f5b" have entirely different histories.
c5adc316ef
...
901c766ced
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from 'react';
|
import { useState } from 'react';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import FullCalendar from '@fullcalendar/react';
|
import FullCalendar from '@fullcalendar/react';
|
||||||
@ -12,12 +12,9 @@ import EventForm from './EventForm';
|
|||||||
|
|
||||||
export default function CalendarPage() {
|
export default function CalendarPage() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const calendarRef = useRef<FullCalendar>(null);
|
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
const [editingEvent, setEditingEvent] = useState<CalendarEvent | null>(null);
|
const [editingEvent, setEditingEvent] = useState<CalendarEvent | null>(null);
|
||||||
const [selectedStart, setSelectedStart] = useState<string | null>(null);
|
const [selectedDate, setSelectedDate] = useState<string | null>(null);
|
||||||
const [selectedEnd, setSelectedEnd] = useState<string | null>(null);
|
|
||||||
const [selectedAllDay, setSelectedAllDay] = useState(false);
|
|
||||||
|
|
||||||
const { data: events = [] } = useQuery({
|
const { data: events = [] } = useQuery({
|
||||||
queryKey: ['calendar-events'],
|
queryKey: ['calendar-events'],
|
||||||
@ -27,43 +24,8 @@ export default function CalendarPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const toLocalDatetime = (d: Date): string => {
|
const eventDropMutation = useMutation({
|
||||||
const pad = (n: number) => n.toString().padStart(2, '0');
|
mutationFn: async ({ id, start, end, allDay }: { id: number; start: string; end: string; allDay: boolean }) => {
|
||||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateEventTimes = (
|
|
||||||
id: number,
|
|
||||||
start: string,
|
|
||||||
end: string,
|
|
||||||
allDay: boolean,
|
|
||||||
revert: () => void,
|
|
||||||
) => {
|
|
||||||
// Optimistically update cache so re-renders don't snap back
|
|
||||||
queryClient.setQueryData<CalendarEvent[]>(['calendar-events'], (old) =>
|
|
||||||
old?.map((e) =>
|
|
||||||
e.id === id
|
|
||||||
? { ...e, start_datetime: start, end_datetime: end, all_day: allDay }
|
|
||||||
: e,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
eventMutation.mutate({ id, start, end, allDay, revert });
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventMutation = useMutation({
|
|
||||||
mutationFn: async ({
|
|
||||||
id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
allDay,
|
|
||||||
}: {
|
|
||||||
id: number;
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
allDay: boolean;
|
|
||||||
revert: () => void;
|
|
||||||
}) => {
|
|
||||||
const response = await api.put(`/events/${id}`, {
|
const response = await api.put(`/events/${id}`, {
|
||||||
start_datetime: start,
|
start_datetime: start,
|
||||||
end_datetime: end,
|
end_datetime: end,
|
||||||
@ -73,12 +35,11 @@ export default function CalendarPage() {
|
|||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['calendar-events'] });
|
queryClient.invalidateQueries({ queryKey: ['calendar-events'] });
|
||||||
toast.success('Event updated');
|
toast.success('Event moved');
|
||||||
},
|
},
|
||||||
onError: (error, variables) => {
|
onError: (error) => {
|
||||||
variables.revert();
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['calendar-events'] });
|
queryClient.invalidateQueries({ queryKey: ['calendar-events'] });
|
||||||
toast.error(getErrorMessage(error, 'Failed to update event'));
|
toast.error(getErrorMessage(error, 'Failed to move event'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,50 +61,31 @@ export default function CalendarPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toLocalDatetime = (d: Date): string => {
|
||||||
|
const pad = (n: number) => n.toString().padStart(2, '0');
|
||||||
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||||
|
};
|
||||||
|
|
||||||
const handleEventDrop = (info: EventDropArg) => {
|
const handleEventDrop = (info: EventDropArg) => {
|
||||||
const id = parseInt(info.event.id);
|
const id = parseInt(info.event.id);
|
||||||
const start = info.event.allDay
|
const start = info.event.allDay
|
||||||
? info.event.startStr
|
? info.event.startStr
|
||||||
: info.event.start
|
: info.event.start ? toLocalDatetime(info.event.start) : info.event.startStr;
|
||||||
? toLocalDatetime(info.event.start)
|
|
||||||
: info.event.startStr;
|
|
||||||
const end = info.event.allDay
|
const end = info.event.allDay
|
||||||
? info.event.endStr || info.event.startStr
|
? info.event.endStr || info.event.startStr
|
||||||
: info.event.end
|
: info.event.end ? toLocalDatetime(info.event.end) : start;
|
||||||
? toLocalDatetime(info.event.end)
|
eventDropMutation.mutate({ id, start, end, allDay: info.event.allDay });
|
||||||
: start;
|
|
||||||
updateEventTimes(id, start, end, info.event.allDay, info.revert);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEventResize = (info: { event: EventDropArg['event']; revert: () => void }) => {
|
|
||||||
const id = parseInt(info.event.id);
|
|
||||||
const start = info.event.allDay
|
|
||||||
? info.event.startStr
|
|
||||||
: info.event.start
|
|
||||||
? toLocalDatetime(info.event.start)
|
|
||||||
: info.event.startStr;
|
|
||||||
const end = info.event.allDay
|
|
||||||
? info.event.endStr || info.event.startStr
|
|
||||||
: info.event.end
|
|
||||||
? toLocalDatetime(info.event.end)
|
|
||||||
: start;
|
|
||||||
updateEventTimes(id, start, end, info.event.allDay, info.revert);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateSelect = (selectInfo: DateSelectArg) => {
|
const handleDateSelect = (selectInfo: DateSelectArg) => {
|
||||||
setSelectedStart(selectInfo.startStr);
|
setSelectedDate(selectInfo.startStr);
|
||||||
setSelectedEnd(selectInfo.endStr);
|
|
||||||
setSelectedAllDay(selectInfo.allDay);
|
|
||||||
setShowForm(true);
|
setShowForm(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseForm = () => {
|
const handleCloseForm = () => {
|
||||||
calendarRef.current?.getApi().unselect();
|
|
||||||
setShowForm(false);
|
setShowForm(false);
|
||||||
setEditingEvent(null);
|
setEditingEvent(null);
|
||||||
setSelectedStart(null);
|
setSelectedDate(null);
|
||||||
setSelectedEnd(null);
|
|
||||||
setSelectedAllDay(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -155,7 +97,6 @@ export default function CalendarPage() {
|
|||||||
<div className="flex-1 overflow-y-auto p-6">
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
<div className="bg-card rounded-lg border p-4">
|
<div className="bg-card rounded-lg border p-4">
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
ref={calendarRef}
|
|
||||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||||
initialView="dayGridMonth"
|
initialView="dayGridMonth"
|
||||||
headerToolbar={{
|
headerToolbar={{
|
||||||
@ -167,12 +108,10 @@ export default function CalendarPage() {
|
|||||||
editable={true}
|
editable={true}
|
||||||
selectable={true}
|
selectable={true}
|
||||||
selectMirror={true}
|
selectMirror={true}
|
||||||
unselectAuto={false}
|
|
||||||
dayMaxEvents={true}
|
dayMaxEvents={true}
|
||||||
weekends={true}
|
weekends={true}
|
||||||
eventClick={handleEventClick}
|
eventClick={handleEventClick}
|
||||||
eventDrop={handleEventDrop}
|
eventDrop={handleEventDrop}
|
||||||
eventResize={handleEventResize}
|
|
||||||
select={handleDateSelect}
|
select={handleDateSelect}
|
||||||
height="auto"
|
height="auto"
|
||||||
/>
|
/>
|
||||||
@ -180,13 +119,7 @@ export default function CalendarPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showForm && (
|
{showForm && (
|
||||||
<EventForm
|
<EventForm event={editingEvent} initialDate={selectedDate} onClose={handleCloseForm} />
|
||||||
event={editingEvent}
|
|
||||||
initialStart={selectedStart}
|
|
||||||
initialEnd={selectedEnd}
|
|
||||||
initialAllDay={selectedAllDay}
|
|
||||||
onClose={handleCloseForm}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,9 +20,7 @@ import { Checkbox } from '@/components/ui/checkbox';
|
|||||||
|
|
||||||
interface EventFormProps {
|
interface EventFormProps {
|
||||||
event: CalendarEvent | null;
|
event: CalendarEvent | null;
|
||||||
initialStart?: string | null;
|
initialDate?: string | null;
|
||||||
initialEnd?: string | null;
|
|
||||||
initialAllDay?: boolean;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +54,11 @@ function formatForInput(dt: string, allDay: boolean, fallbackTime: string = '09:
|
|||||||
return allDay ? toDateOnly(dt) : toDatetimeLocal(dt, fallbackTime);
|
return allDay ? toDateOnly(dt) : toDatetimeLocal(dt, fallbackTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EventForm({ event, initialStart, initialEnd, initialAllDay, onClose }: EventFormProps) {
|
export default function EventForm({ event, initialDate, onClose }: EventFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const isAllDay = event?.all_day ?? initialAllDay ?? false;
|
const isAllDay = event?.all_day ?? false;
|
||||||
const rawStart = event?.start_datetime || initialStart || '';
|
const rawStart = event?.start_datetime || initialDate || '';
|
||||||
const rawEnd = event?.end_datetime || initialEnd || '';
|
const rawEnd = event?.end_datetime || initialDate || '';
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
title: event?.title || '',
|
title: event?.title || '',
|
||||||
description: event?.description || '',
|
description: event?.description || '',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user