From 3c9b2840e7f534cf953445d8088988ea866e85f2 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Mon, 4 May 2026 08:41:02 +0200 Subject: [PATCH] feat(caldav): add Settings UI and Event Modal CalDAV target selection - Add CalDAV accounts card to Settings page with: * List of configured accounts showing URL and last sync * Expandable calendar list with enable/disable checkboxes * Sync Now, Refresh Calendars, and Delete actions per account * Add Account modal with name, URL, username, password fields - Add CalDAV target selector to event modal: * Dropdown showing local and all enabled CalDAV calendars * Grouped by account using optgroups * Pre-selects current target when editing events * Includes target_caldav_account_id and target_caldav_calendar_url in save - Add CalDAV component styles to settings.css: * Account cards with header, meta, and action sections * Expandable calendar details with checkboxes and color dots * Empty state for no accounts - Add missing i18n keys for calendar enable/disable, refresh, delete confirm - Load CalDAV targets async when modal opens - Admin-only access to account management Co-Authored-By: Claude Opus 4.7 --- public/locales/de.json | 7 +- public/pages/calendar.js | 82 +++++++++++++ public/pages/settings.js | 231 +++++++++++++++++++++++++++++++++++++ public/styles/settings.css | 119 +++++++++++++++++++ 4 files changed, 438 insertions(+), 1 deletion(-) diff --git a/public/locales/de.json b/public/locales/de.json index dd9025c..29466b8 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -1010,7 +1010,12 @@ "caldavRefreshCalendars": "Kalender aktualisieren", "caldavSyncSuccess": "CalDAV-Synchronisation erfolgreich", "caldavSyncFailed": "CalDAV-Synchronisation fehlgeschlagen", - "caldavConnectionFailed": "Verbindung zum CalDAV-Server fehlgeschlagen" + "caldavConnectionFailed": "Verbindung zum CalDAV-Server fehlgeschlagen", + "calendarEnabled": "Kalender aktiviert", + "calendarDisabled": "Kalender deaktiviert", + "calendarsRefreshed": "Kalender aktualisiert", + "deleteAccountConfirm": "CalDAV-Konto wirklich löschen? Alle synchronisierten Kalender werden entfernt.", + "lastSync": "Zuletzt synchronisiert" }, "login": { "tagline": "Familienplanung. Sicher. Datenschutzfreundlich. Open Source.", diff --git a/public/pages/calendar.js b/public/pages/calendar.js index 5611262..190ce48 100644 --- a/public/pages/calendar.js +++ b/public/pages/calendar.js @@ -1253,6 +1253,59 @@ function bindTimeInputs(root) { }); } +// -------------------------------------------------------- +// CalDAV Target Helpers +// -------------------------------------------------------- + +async function loadCalDAVTargets(selectElement, currentEvent = null) { + if (!selectElement) return; + + try { + const accountsRes = await api.get('/calendar/caldav/accounts'); + const accounts = accountsRes.data || []; + + // Keep only the "local" option + selectElement.replaceChildren(); + const localOption = document.createElement('option'); + localOption.value = ''; + localOption.textContent = t('calendar.caldavTargetLocal'); + selectElement.appendChild(localOption); + + // Load calendars for each account and build options + for (const account of accounts) { + try { + const calendarsRes = await api.get(`/calendar/caldav/accounts/${account.id}/calendars`); + const calendars = calendarsRes.data || []; + const enabledCalendars = calendars.filter((cal) => cal.enabled); + + if (enabledCalendars.length === 0) continue; + + const optgroup = document.createElement('optgroup'); + optgroup.label = account.name; + + for (const calendar of enabledCalendars) { + const option = document.createElement('option'); + option.value = `${account.id}|${calendar.url}`; + option.textContent = calendar.display_name || calendar.url; + optgroup.appendChild(option); + } + + selectElement.appendChild(optgroup); + } catch (err) { + console.warn(`Failed to load calendars for account ${account.id}:`, err); + } + } + + // Pre-select current event's target if editing + if (currentEvent?.target_caldav_account_id && currentEvent?.target_caldav_calendar_url) { + const targetValue = `${currentEvent.target_caldav_account_id}|${currentEvent.target_caldav_calendar_url}`; + selectElement.value = targetValue; + } + } catch (err) { + console.warn('Failed to load CalDAV targets:', err); + } +} + // -------------------------------------------------------- // Event-Modal (Erstellen / Bearbeiten) // -------------------------------------------------------- @@ -1465,6 +1518,12 @@ function openEventModal({ mode, event = null, date = null, reminder = null }) { if (reminderCustom) reminderCustom.hidden = reminderOffset.value !== 'custom'; }); + // Load CalDAV targets + const caldavTargetSelect = panel.querySelector('#event-caldav-target'); + if (caldavTargetSelect) { + loadCalDAVTargets(caldavTargetSelect, event); + } + panel.querySelector('#modal-cancel').addEventListener('click', closeModal); panel.querySelector('#modal-delete')?.addEventListener('click', async () => { @@ -1611,6 +1670,14 @@ function buildEventModalContent({ mode, event, date, reminder = null }) { +
+ + + ${t('calendar.caldavTargetHint')} +
+