-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Issue: Auslaufende Termine erfordern manuelle Aktualisierung bei Filter-Änderungen
🐛 Problem Description
Im Expiring Appointments Admin Panel werden Daten nicht automatisch aktualisiert, wenn Filter geändert werden. Benutzer müssen manuell den "Aktualisieren"-Button klicken, um die gefilterten Ergebnisse zu sehen. Dies ist inkonsistent mit dem erwarteten Verhalten und führt zu Verwirrung.
🔍 Current Behavior
Beobachtetes Verhalten:
- Filter ändern: Benutzer wählt anderen Zeitraum (z.B. "7 Tage" → "30 Tage")
- Keine Aktualisierung: Tabelle zeigt weiterhin alte Daten
- Manueller Refresh nötig: Benutzer muss "Aktualisieren"-Button klicken
- Dann Update: Erst jetzt werden neue Daten geladen
Betroffene Filter:
- Tage-Filter: "1, 7, 14, 30, 60, 90, 180, 365 Tage" oder "alle"
- Kalender-Filter: Dropdown mit verfügbaren Kalendern
- Status-Filter: "Läuft bald ab", "Abgelaufen", "Aktiv"
Aktuelle Implementierung (Problematisch):
<!-- ExpiringAppointmentsAdmin.vue -->
<template>
<!-- Tage-Filter -->
<select v-model="daysInAdvance" @change="refreshData" class="ct-select">
<option value="alle">alle</option>
<option value="1">1</option>
<option value="7">7</option>
<!-- ... -->
</select>
<!-- Kalender-Filter -->
<select v-model="calendarFilter" class="ct-select filter-select">
<!-- KEIN @change Event! -->
<option value="">Alle Kalender</option>
<!-- ... -->
</select>
<!-- Status-Filter -->
<select v-model="statusFilter" class="ct-select filter-select">
<!-- KEIN @change Event! -->
<option value="">Alle Status</option>
<!-- ... -->
</select>
</template>
<script setup lang="ts">
// Nur daysInAdvance triggert refreshData()
// calendarFilter und statusFilter sind nur reactive, aber triggern keine API-Calls
</script>⚠️ Inconsistent Behavior
Problem-Analyse:
- Tage-Filter: ✅ Triggert
refreshData()bei Änderung - Kalender-Filter: ❌ Nur client-seitige Filterung
- Status-Filter: ❌ Nur client-seitige Filterung
Warum ist das problematisch?
Tage-Filter Verhalten:
// Lädt neue Daten vom Server
const changeDaysFilter = () => {
refreshData() // API-Call mit neuem daysInAdvance
}Andere Filter Verhalten:
// Nur client-seitige Filterung der bereits geladenen Daten
const filteredAppointments = computed(() => {
let filtered = appointments.value
// Kalender-Filter
if (calendarFilter.value) {
filtered = filtered.filter(apt =>
apt.base?.calendar?.id?.toString() === calendarFilter.value
)
}
// Status-Filter
if (statusFilter.value) {
filtered = filtered.filter(apt =>
getAppointmentStatus(apt) === statusFilter.value
)
}
return filtered
})🎯 Expected Behavior
Konsistentes Verhalten für alle Filter:
- Sofortige Aktualisierung: Alle Filter sollten sofort wirken
- Keine manuellen Refreshs: Benutzer sollte nie "Aktualisieren" klicken müssen
- Einheitliche UX: Alle Filter verhalten sich gleich
Zwei mögliche Ansätze:
Ansatz 1: Alle Filter client-seitig (Empfohlen)
// Lade einmal ALLE Daten, filtere dann client-seitig
const fetchData = async () => {
// Lade alle Termine (großer Zeitraum)
const allAppointments = await findExpiringSeries(365 * 24 * 60 * 60 * 1000) // 1 Jahr
appointments.value = allAppointments
}
const filteredAppointments = computed(() => {
let filtered = appointments.value
// Tage-Filter (client-seitig)
if (daysInAdvance.value !== 'alle') {
const days = parseInt(daysInAdvance.value)
const maxDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000)
filtered = filtered.filter(apt => {
const endDate = getEffectiveEndDate(apt)
return endDate && endDate <= maxDate
})
}
// Kalender-Filter
if (calendarFilter.value) {
filtered = filtered.filter(apt =>
apt.base?.calendar?.id?.toString() === calendarFilter.value
)
}
// Status-Filter
if (statusFilter.value) {
filtered = filtered.filter(apt =>
getAppointmentStatus(apt) === statusFilter.value
)
}
return filtered
})Ansatz 2: Alle Filter server-seitig
// Jeder Filter triggert neuen API-Call
watch([daysInAdvance, calendarFilter, statusFilter], () => {
refreshData()
}, { deep: true })
const fetchData = async () => {
const params = {
daysInAdvance: daysInAdvance.value,
calendarId: calendarFilter.value,
status: statusFilter.value
}
const filteredAppointments = await findExpiringSeriesFiltered(params)
appointments.value = filteredAppointments
}🔧 Recommended Solution: Client-Side Filtering
Warum client-seitig besser ist:
- Performance: Keine API-Calls bei Filter-Änderungen
- Instant Response: Sofortige UI-Updates
- Reduced Server Load: Weniger ChurchTools API-Aufrufe
- Better UX: Keine Loading-Spinner bei Filter-Änderungen
- Caching-Friendly: Funktioniert gut mit TanStack Query
Implementation Plan:
1. Einmaliger Daten-Load:
// Lade alle relevanten Termine einmal
const fetchAllAppointments = async () => {
isLoading.value = true
try {
// Lade großen Zeitraum (z.B. 1 Jahr)
const allAppointments = await findExpiringSeries(365 * 24 * 60 * 60 * 1000)
allAppointmentsData.value = allAppointments
} finally {
isLoading.value = false
}
}2. Reactive Filtering:
const filteredAppointments = computed(() => {
if (!allAppointmentsData.value) return []
let filtered = [...allAppointmentsData.value]
// Tage-Filter
if (daysInAdvance.value !== 'alle') {
const days = parseInt(daysInAdvance.value)
const cutoffDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000)
filtered = filtered.filter(appointment => {
const endDate = getEffectiveEndDate(appointment)
return endDate && endDate <= cutoffDate
})
}
// Kalender-Filter
if (calendarFilter.value) {
filtered = filtered.filter(appointment =>
appointment.base?.calendar?.id?.toString() === calendarFilter.value
)
}
// Status-Filter
if (statusFilter.value) {
filtered = filtered.filter(appointment =>
getAppointmentStatus(appointment) === statusFilter.value
)
}
return filtered
})3. Remove Manual Refresh Dependency:
<template>
<!-- Alle Filter wirken sofort -->
<select v-model="daysInAdvance" class="ct-select">
<!-- Kein @change nötig -->
</select>
<select v-model="calendarFilter" class="ct-select">
<!-- Kein @change nötig -->
</select>
<select v-model="statusFilter" class="ct-select">
<!-- Kein @change nötig -->
</select>
<!-- Refresh-Button nur für neue Daten vom Server -->
<button @click="fetchAllAppointments" class="ct-btn ct-btn-primary">
{{ isLoading ? 'Lädt...' : 'Daten aktualisieren' }}
</button>
</template>4. Smart Data Management:
// Intelligente Refresh-Strategie
const shouldRefreshData = computed(() => {
if (!lastFetchTime.value) return true
const now = new Date()
const lastFetch = new Date(lastFetchTime.value)
const hoursSinceLastFetch = (now.getTime() - lastFetch.getTime()) / (1000 * 60 * 60)
// Refresh wenn Daten älter als 1 Stunde
return hoursSinceLastFetch > 1
})
// Auto-refresh bei Mount wenn nötig
onMounted(() => {
if (shouldRefreshData.value) {
fetchAllAppointments()
}
})🧪 Test Cases
Zu testende Szenarien:
-
Filter-Responsiveness:
- Tage-Filter ändert sofort die Anzeige
- Kalender-Filter ändert sofort die Anzeige
- Status-Filter ändert sofort die Anzeige
- Kombinierte Filter funktionieren korrekt
-
Performance:
- Filter-Änderungen < 100ms Response-Zeit
- Keine API-Calls bei Filter-Änderungen
- Smooth UI-Updates ohne Flackern
-
Data Consistency:
- Alle Filter-Kombinationen zeigen korrekte Ergebnisse
- Reset-Funktionalität funktioniert
- Refresh lädt aktuelle Server-Daten
-
Edge Cases:
- Leere Filter-Ergebnisse
- Sehr große Datenmengen
- Schnelle Filter-Änderungen
📊 Performance Impact
Vorher (Problematisch):
- Tage-Filter: API-Call (2-5s)
- Kalender-Filter: Client-seitig (instant)
- Status-Filter: Client-seitig (instant)
- Inkonsistente UX: Verwirrend für Benutzer
Nachher (Optimiert):
- Alle Filter: Client-seitig (instant)
- Initial Load: Einmalig (2-5s)
- Filter-Changes: <100ms
- Konsistente UX: Alle Filter verhalten sich gleich
Memory Considerations:
// Speicher-effiziente Implementierung
const MAX_APPOINTMENTS_IN_MEMORY = 10000
const CACHE_DURATION = 60 * 60 * 1000 // 1 Stunde
const manageMemoryUsage = () => {
if (allAppointmentsData.value.length > MAX_APPOINTMENTS_IN_MEMORY) {
// Älteste Termine entfernen oder Paging implementieren
console.warn('Large dataset detected, consider implementing pagination')
}
}🔄 Alternative Solutions
Alternative 1: Hybrid Approach
// Kritische Filter server-seitig, andere client-seitig
const criticalFilters = ['daysInAdvance'] // Große Datenmengen-Reduktion
const clientFilters = ['calendarFilter', 'statusFilter'] // Kleine Filterung
watch(criticalFilters, () => refreshData())
// clientFilters werden nur computedAlternative 2: Debounced Server Calls
// Verzögerte API-Calls um Spam zu vermeiden
const debouncedRefresh = debounce(() => {
refreshData()
}, 500)
watch([daysInAdvance, calendarFilter, statusFilter], () => {
debouncedRefresh()
})Alternative 3: Progressive Loading
// Lade Daten schrittweise basierend auf Filtern
const loadDataProgressively = async () => {
// 1. Lade Basis-Daten
const baseData = await findExpiringSeries(30 * 24 * 60 * 60 * 1000)
// 2. Erweitere bei Bedarf
if (daysInAdvance.value > 30) {
const extendedData = await findExpiringSeries(daysInAdvance.value * 24 * 60 * 60 * 1000)
allAppointmentsData.value = extendedData
}
}🎯 Acceptance Criteria
✅ Definition of Done:
-
Consistent Filter Behavior:
- Alle Filter wirken sofort ohne manuellen Refresh
- Keine Unterschiede zwischen Filter-Typen
- Kombinierte Filter funktionieren korrekt
-
Performance:
- Filter-Änderungen < 100ms Response-Zeit
- Keine unnötigen API-Calls bei Filter-Änderungen
- Initial Load-Zeit bleibt akzeptabel
-
User Experience:
- Intuitive Bedienung ohne Verwirrung
- Klare Indikation wenn Daten geladen werden
- Smooth Transitions zwischen Filter-States
-
Technical Quality:
- Clean, maintainable Code
- Proper error handling
- Memory-efficient implementation
📁 Files to Modify
Primary:
src/components/expiring-appointments/ExpiringAppointmentsAdmin.vue- Haupt-Filter-Logik
Secondary:
src/services/churchtools.ts- Möglicherweise API-Optimierungensrc/composables/useExpiringAppointments.ts- Falls TanStack Query implementiert
🏷️ Labels
bug- Inkonsistentes Verhaltenuser-experience- UX-Problemexpiring-appointments- Betrifft Expiring Appointments Modulfilter-behavior- Filter-Funktionalitätpriority-medium- Beeinträchtigt Benutzerfreundlichkeit
🔗 Related Issues
- Performance & Caching Issue (TanStack Query)
- Filter System Improvements
- User Experience Optimization
Reporter: Development Team
Priority: Medium
Complexity: Low-Medium
Estimated Effort: 2-4 hours
📝 User Story
Als Administrator
möchte ich dass alle Filter sofort wirken
damit ich nicht manuell aktualisieren muss und eine konsistente Benutzererfahrung habe.
Acceptance Criteria:
- Wenn ich einen Filter ändere, sehe ich sofort die Ergebnisse
- Alle Filter verhalten sich gleich (keine Inkonsistenzen)
- Ich muss nie manuell "Aktualisieren" klicken für Filter-Änderungen
Metadata
Metadata
Assignees
Labels
No labels