From f6626da114975284ef8b9489a21d35512f2e1a7c Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Mon, 13 Apr 2026 11:11:12 +0200 Subject: [PATCH] feat(calendar): Mehrfachauswahl + Bulk-Loeschen in der Listen-Ansicht Checkbox-Spalte plus Header-Checkbox "Alle". Bulk-Aktion mit Bestaetigung loescht ausgewaehlte Termine; Read-Only-Eintraege werden uebersprungen. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/views/CalendarView.vue | 81 +++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/CalendarView.vue b/frontend/src/views/CalendarView.vue index 47a5a7b..32971f1 100644 --- a/frontend/src/views/CalendarView.vue +++ b/frontend/src/views/CalendarView.vue @@ -45,10 +45,22 @@ optionLabel="label" optionValue="value" placeholder="Alle Kalender" showClear style="min-width: 180px" /> -
{{ filteredListEvents.length }} Termin(e)
+
+
{{ filteredListEvents.length }} Termin(e)
+
+ {{ selectedListIds.length }} ausgewaehlt +
+
+ @@ -61,7 +73,14 @@ - + + - +
+ + Datum
+ +
{{ formatListDate(ev) }}
{{ formatListTime(ev) }}
@@ -80,7 +99,7 @@
Keine Termine gefunden.Keine Termine gefunden.
@@ -337,6 +356,7 @@ import InputText from 'primevue/inputtext' import Textarea from 'primevue/textarea' import Select from 'primevue/select' import SelectButton from 'primevue/selectbutton' +import Checkbox from 'primevue/checkbox' import FullCalendar from '@fullcalendar/vue3' import dayGridPlugin from '@fullcalendar/daygrid' import timeGridPlugin from '@fullcalendar/timegrid' @@ -368,6 +388,57 @@ const listTo = ref('') const listCalFilter = ref(null) const listSort = ref('dtstart') const listSortDir = ref('asc') +const selectedListIds = ref([]) + +const allListSelected = computed({ + get: () => { + const writable = filteredListEvents.value.filter(e => e._cal?.permission !== 'read') + return writable.length > 0 && writable.every(e => selectedListIds.value.includes(e.id)) + }, + set: () => {}, +}) + +function toggleAllListSelected() { + const writableIds = filteredListEvents.value + .filter(e => e._cal?.permission !== 'read').map(e => e.id) + const allSel = writableIds.length > 0 && writableIds.every(id => selectedListIds.value.includes(id)) + if (allSel) { + selectedListIds.value = selectedListIds.value.filter(id => !writableIds.includes(id)) + } else { + const set = new Set(selectedListIds.value) + writableIds.forEach(id => set.add(id)) + selectedListIds.value = [...set] + } +} + +function toggleListSelect(id, checked) { + if (checked) { + if (!selectedListIds.value.includes(id)) selectedListIds.value = [...selectedListIds.value, id] + } else { + selectedListIds.value = selectedListIds.value.filter(x => x !== id) + } +} + +async function deleteSelectedListEvents() { + const ids = [...selectedListIds.value] + if (!ids.length) return + if (!confirm(`${ids.length} Termin(e) wirklich loeschen?`)) return + let ok = 0, fail = 0 + for (const id of ids) { + try { + await apiClient.delete(`/events/${id}`) + ok++ + } catch { fail++ } + } + selectedListIds.value = [] + toast.add({ + severity: fail ? 'warn' : 'success', + summary: `${ok} geloescht${fail ? `, ${fail} fehlgeschlagen` : ''}`, + life: 3000, + }) + await loadListEvents() + refreshEvents() +} const listCalOptions = computed(() => calendars.value.map(c => ({ label: c.name, value: c.id }))) @@ -1117,6 +1188,10 @@ onUnmounted(() => { .list-view { display: flex; flex-direction: column; gap: 0.75rem; } .list-filters { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; } .list-meta { font-size: 0.8rem; color: var(--p-text-muted-color); } +.list-meta-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; } +.list-bulk { display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; } +.col-check { width: 36px; } +.list-row.selected { background: var(--p-primary-50); } .list-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; } .list-table th { text-align: left; padding: 0.5rem; border-bottom: 2px solid var(--p-surface-200); font-weight: 600; user-select: none; } .list-table th.sortable { cursor: pointer; }