feat: add calendar drag-and-drop rescheduling and iCalendar import

- Drag events in week/day views to reschedule (15-min snap intervals)
- Drag events in month view to change date (preserves time)
- Visual snap indicators with time labels during drag
- iCalendar (.ics) file import via JMAP CalendarEvent/parse
- Import modal with file upload, event preview, calendar selector
- File validation (5MB max), bulk import with progress tracking
This commit is contained in:
Matthieu MALVACHE
2026-02-16 21:09:13 +01:00
committed by Matthieu MALVACHE
parent e6f67c24cb
commit ff65693e26
19 changed files with 842 additions and 29 deletions
+11
View File
@@ -21,6 +21,7 @@ import { CalendarAgendaView } from "@/components/calendar/calendar-agenda-view";
import { MiniCalendar } from "@/components/calendar/mini-calendar";
import { CalendarSidebarPanel } from "@/components/calendar/calendar-sidebar-panel";
import { EventModal } from "@/components/calendar/event-modal";
import { ICalImportModal } from "@/components/calendar/ical-import-modal";
import type { CalendarEvent } from "@/lib/jmap/types";
export default function CalendarPage() {
@@ -37,6 +38,7 @@ export default function CalendarPage() {
const { firstDayOfWeek, timeFormat } = useSettingsStore();
const [showEventModal, setShowEventModal] = useState(false);
const [showImportModal, setShowImportModal] = useState(false);
const [editEvent, setEditEvent] = useState<CalendarEvent | null>(null);
const [defaultModalDate, setDefaultModalDate] = useState<Date | undefined>();
const [miniMonth, setMiniMonth] = useState(new Date());
@@ -295,6 +297,7 @@ export default function CalendarPage() {
onToday={goToToday}
onViewModeChange={setViewMode}
onCreateEvent={() => openCreateModal()}
onImport={() => setShowImportModal(true)}
isMobile={isMobile}
/>
@@ -330,6 +333,14 @@ export default function CalendarPage() {
onClose={() => { setShowEventModal(false); setEditEvent(null); }}
/>
)}
{showImportModal && client && (
<ICalImportModal
calendars={calendars}
client={client}
onClose={() => setShowImportModal(false)}
/>
)}
</div>
);
}