From 405a81569636d1d66c37934952e0bc1fbbc7306f Mon Sep 17 00:00:00 2001 From: Bernhard Weichel Date: Wed, 19 Nov 2025 18:32:12 +0000 Subject: [PATCH 1/6] feat: add loading spinner to refresh buttons - Add spinner animation to refresh buttons in BaseCard and all admin views - Swap button order in BaseCard footer (Details left, Refresh right) - Use isFetching in AutomaticGroupsAdmin for better loading state detection - Consistent spinner styling across all components Co-authored-by: Ona --- .../automatic-groups/AutomaticGroupsAdmin.vue | 28 +++++++++++++++++-- src/components/common/BaseCard.vue | 28 +++++++++++++------ .../ExpiringAppointmentsAdmin.vue | 21 ++++++++++++++ src/components/tags/TagsAdmin.vue | 21 ++++++++++++++ 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/components/automatic-groups/AutomaticGroupsAdmin.vue b/src/components/automatic-groups/AutomaticGroupsAdmin.vue index 43a384e..5c7dc1a 100644 --- a/src/components/automatic-groups/AutomaticGroupsAdmin.vue +++ b/src/components/automatic-groups/AutomaticGroupsAdmin.vue @@ -23,6 +23,7 @@ Suche zurücksetzen @@ -75,7 +76,8 @@ defineProps<{ }>() // Use TanStack Query composable for data management -const { data: groups, isLoading: loading, error, refetch } = useAutomaticGroups() +const { data: groups, isLoading, isFetching, error, refetch } = useAutomaticGroups() +const loading = computed(() => isLoading.value || isFetching.value) // AdminTable reference const adminTableRef = ref() @@ -193,8 +195,8 @@ const formatDate = (dateString: string | null) => { // URL generation is now handled by the churchtools service // Data loading -const refreshGroups = () => { - refetch() +const refreshGroups = async () => { + await refetch() } const clearSearch = () => { @@ -304,4 +306,24 @@ const clearSearch = () => { padding: 0.5rem 1rem; font-size: 0.85rem; } + +.btn-spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: spin 0.6s linear infinite; + margin-right: 0.5rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/components/common/BaseCard.vue b/src/components/common/BaseCard.vue index 0d2158f..d4a0528 100644 --- a/src/components/common/BaseCard.vue +++ b/src/components/common/BaseCard.vue @@ -81,14 +81,6 @@ @@ -415,6 +416,17 @@ const getStatusClass = (type?: string) => { cursor: not-allowed; } +.btn-spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: spin 0.6s linear infinite; + margin-right: 0.5rem; +} + .ct-btn-icon { background: none; border: none; diff --git a/src/components/expiring-appointments/ExpiringAppointmentsAdmin.vue b/src/components/expiring-appointments/ExpiringAppointmentsAdmin.vue index 360321d..ef2702c 100644 --- a/src/components/expiring-appointments/ExpiringAppointmentsAdmin.vue +++ b/src/components/expiring-appointments/ExpiringAppointmentsAdmin.vue @@ -70,6 +70,7 @@ Filter löschen @@ -548,4 +549,24 @@ onMounted(() => { border-color: var(--ct-primary, #3498db); box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1); } + +.btn-spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: spin 0.6s linear infinite; + margin-right: 0.5rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/components/tags/TagsAdmin.vue b/src/components/tags/TagsAdmin.vue index 4a9435c..8ab8323 100644 --- a/src/components/tags/TagsAdmin.vue +++ b/src/components/tags/TagsAdmin.vue @@ -124,6 +124,7 @@ class="ct-btn ct-btn-primary" :disabled="isLoading" > + {{ isLoading ? 'Laden...' : 'Aktualisieren' }} @@ -781,4 +782,24 @@ onMounted(() => { border-color: #3498db !important; color: white !important; } + +.btn-spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: spin 0.6s linear infinite; + margin-right: 0.5rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/components/loggerSummary/LoggerSummaryAdminBulk.vue b/src/components/loggerSummary/LoggerSummaryAdminBulk.vue index 36a6874..3653bc7 100644 --- a/src/components/loggerSummary/LoggerSummaryAdminBulk.vue +++ b/src/components/loggerSummary/LoggerSummaryAdminBulk.vue @@ -44,6 +44,11 @@ + + @@ -192,7 +197,7 @@ defineProps<{ const { showInfo, showWarning } = useToast() // State -const selectedDays = ref(3) +const selectedDays = ref(1) const selectedCategory = ref('') const searchTerm = ref('') @@ -200,7 +205,7 @@ const searchTerm = ref('') const { processedLogs: allProcessedLogs, statistics, - isLoading, + isLoading: isInitialLoading, isFetching, error, refetch, @@ -209,6 +214,9 @@ const { limitReason, } = useLoggerBulkCache(selectedDays) +// Combine loading states for better UX +const isLoading = computed(() => isInitialLoading.value || isFetching.value) + // Watch for limitation and show toast watch( [wasLimited, limitReason], @@ -586,18 +594,47 @@ watch( color: var(--color-text-secondary); } +/* Button styles */ +.ct-btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 500; + transition: all 0.2s; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + text-decoration: none; +} + +.ct-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + .ct-btn-primary { - background: transparent; - border: 1px solid #3498db; - color: #3498db; + background-color: var(--ct-primary, #3498db); + color: white; } -.ct-btn-primary:hover { - background: #3498db; - border-color: #3498db; +.ct-btn-primary:hover:not(:disabled) { + background-color: var(--ct-primary-dark, #2980b9); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.ct-btn-secondary { + background-color: var(--ct-secondary, #6c757d); color: white; +} + +.ct-btn-secondary:hover:not(:disabled) { + background-color: var(--ct-secondary-dark, #5a6268); transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(52, 152, 219, 0.3); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .ct-btn-primary-outline { @@ -622,4 +659,24 @@ watch( margin-top: 8px; } } + +.btn-spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: white; + border-radius: 50%; + animation: spin 0.6s linear infinite; + margin-right: 0.5rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} From ec9c33a93dce06c46207e3385eb894b7748784cc Mon Sep 17 00:00:00 2001 From: Bernhard Weichel Date: Wed, 19 Nov 2025 18:56:27 +0000 Subject: [PATCH 3/6] refactor: remove unused LoggerSummaryAdmin component - Remove LoggerSummaryAdmin.vue (legacy version, not used in production) - Remove unused import from App.vue - LoggerSummaryAdminBulk is the active version with better caching - Keep useLoggerSummary.ts as it exports types used by useLoggerBulkCache Co-authored-by: Ona --- src/App.vue | 1 - .../loggerSummary/LoggerSummaryAdmin.vue | 805 ------------------ 2 files changed, 806 deletions(-) delete mode 100644 src/components/loggerSummary/LoggerSummaryAdmin.vue diff --git a/src/App.vue b/src/App.vue index e796336..be92c3c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -55,7 +55,6 @@ import TagsAdmin from './components/tags/TagsAdmin.vue' import BeispielCard from './components/beispiel/BeispielCard.vue' import ColorPickerExample from './components/common/ColorPickerExample.vue' import LoggerSummaryCard from './components/loggerSummary/LoggerSummaryCard.vue' -import LoggerSummaryAdmin from './components/loggerSummary/LoggerSummaryAdmin.vue' import LoggerSummaryAdminBulk from './components/loggerSummary/LoggerSummaryAdminBulk.vue' import Toast from './components/common/Toast.vue' diff --git a/src/components/loggerSummary/LoggerSummaryAdmin.vue b/src/components/loggerSummary/LoggerSummaryAdmin.vue deleted file mode 100644 index ce24d71..0000000 --- a/src/components/loggerSummary/LoggerSummaryAdmin.vue +++ /dev/null @@ -1,805 +0,0 @@ - - - - - From f1055a5b434be390adb466864ad6e806df9f3eaf Mon Sep 17 00:00:00 2001 From: Bernhard Weichel Date: Wed, 19 Nov 2025 18:58:56 +0000 Subject: [PATCH 4/6] refactor: rename LoggerSummaryAdminBulk to LoggerSummaryAdmin - Rename LoggerSummaryAdminBulk.vue to LoggerSummaryAdmin.vue - Update imports in App.vue - Remove '(Bulk Cache)' from component title - Now that legacy version is removed, bulk version becomes the standard Co-authored-by: Ona --- src/App.vue | 4 ++-- .../{LoggerSummaryAdminBulk.vue => LoggerSummaryAdmin.vue} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/components/loggerSummary/{LoggerSummaryAdminBulk.vue => LoggerSummaryAdmin.vue} (99%) diff --git a/src/App.vue b/src/App.vue index be92c3c..039c15e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -55,7 +55,7 @@ import TagsAdmin from './components/tags/TagsAdmin.vue' import BeispielCard from './components/beispiel/BeispielCard.vue' import ColorPickerExample from './components/common/ColorPickerExample.vue' import LoggerSummaryCard from './components/loggerSummary/LoggerSummaryCard.vue' -import LoggerSummaryAdminBulk from './components/loggerSummary/LoggerSummaryAdminBulk.vue' +import LoggerSummaryAdmin from './components/loggerSummary/LoggerSummaryAdmin.vue' import Toast from './components/common/Toast.vue' import { useToast } from './composables/useToast' @@ -92,7 +92,7 @@ const modules: DashboardModule[] = [ icon: '📋', description: 'Überwachung und Verwaltung von Log-Einträgen', cardComponent: LoggerSummaryCard, - adminComponent: LoggerSummaryAdminBulk, + adminComponent: LoggerSummaryAdmin, }, ] diff --git a/src/components/loggerSummary/LoggerSummaryAdminBulk.vue b/src/components/loggerSummary/LoggerSummaryAdmin.vue similarity index 99% rename from src/components/loggerSummary/LoggerSummaryAdminBulk.vue rename to src/components/loggerSummary/LoggerSummaryAdmin.vue index 3653bc7..ff2dddb 100644 --- a/src/components/loggerSummary/LoggerSummaryAdminBulk.vue +++ b/src/components/loggerSummary/LoggerSummaryAdmin.vue @@ -6,7 +6,7 @@ :error="error?.message || null" :columns="tableColumns" row-key="id" - title="Logger System - Admin Panel (Bulk Cache)" + title="Logger System - Admin Panel" description="Überwachung und Verwaltung aller Log-Einträge" searchable search-placeholder="Log-Einträge durchsuchen..." From 5c2f3999ba30fb63744a52f85dd8554518fa6188 Mon Sep 17 00:00:00 2001 From: Bernhard Weichel Date: Wed, 19 Nov 2025 19:42:48 +0000 Subject: [PATCH 5/6] feat: improve logger admin panel UX and fix table display - Fix table title to show correct filtered count instead of paginated count - Add dynamic title showing active filters (days, category) - Remove custom pagination in favor of AdminTable's built-in features - Enable AdminTable search to work across all filtered logs - Change both Card and Admin to use 1 day as default - Increase Card log limit from 1000 to 1500 entries - Show '1500+' in Card when log limit is reached - Remove duplicate search field issue - Fix goToPage undefined error Co-authored-by: Ona --- .../loggerSummary/LoggerSummaryAdmin.vue | 89 ++++++++----------- .../loggerSummary/LoggerSummaryCard.vue | 14 +-- src/composables/useLoggerSummaryQuery.ts | 12 ++- 3 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/components/loggerSummary/LoggerSummaryAdmin.vue b/src/components/loggerSummary/LoggerSummaryAdmin.vue index ff2dddb..d0e9e88 100644 --- a/src/components/loggerSummary/LoggerSummaryAdmin.vue +++ b/src/components/loggerSummary/LoggerSummaryAdmin.vue @@ -1,12 +1,12 @@