Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions docs/DEVELOPMENT_SESSION_2025-11-19_Refresh_Button_Spinner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Development Session - 2025-11-19

## Session Overview

**Started**: 18:05
**Branch**: `feature/refresh-button-spinner`
**Focus**: Add loading spinners to refresh buttons and improve Logger admin panel UX

## Major Accomplishments

### Phase 1: Button Order and Spinner Implementation (18:05-18:30)

- **Goal**: Swap button order on cards and add loading spinners to refresh buttons
- **Result**: Successfully implemented across all components
- **Code Changes**:
- `BaseCard.vue`: Swapped button order (Details left, Refresh right), added spinner
- `AutomaticGroupsAdmin.vue`: Added spinner, fixed loading state with `isFetching`
- `TagsAdmin.vue`: Added spinner to refresh button
- `ExpiringAppointmentsAdmin.vue`: Added spinner to refresh button
- `LoggerSummaryAdmin.vue`: Added spinner to refresh button
- `LoggerSummaryAdminBulk.vue`: Added spinner and fixed button styling

### Phase 2: Logger Component Cleanup (18:55-19:00)

- **Goal**: Remove unused legacy LoggerSummaryAdmin component
- **Result**: Cleaned up codebase, renamed Bulk version to standard name
- **Code Changes**:
- Removed `LoggerSummaryAdmin.vue` (legacy, 806 lines)
- Renamed `LoggerSummaryAdminBulk.vue` to `LoggerSummaryAdmin.vue`
- Updated imports in `App.vue`
- Removed "(Bulk Cache)" from component title

### Phase 3: Logger Admin Panel UX Improvements (19:14-19:42)

- **Goal**: Fix table display issues and improve user experience
- **Result**: Comprehensive UX overhaul with better filtering and display
- **Code Changes**:
- Fixed table title to show correct filtered count (not just paginated 50)
- Added dynamic title showing active filters: "Logger System - Letzter Tag - Fehler"
- Removed custom pagination in favor of AdminTable's built-in features
- Fixed search functionality (removed duplicate search fields)
- Changed default from 3 days to 1 day for both Card and Admin
- Increased Card log limit from 1000 to 1500 entries
- Added "1500+" display when Card hits log limit

## Technical Decisions

### Decision: Use isFetching for Loading State (18:15)

**Context**: `isLoading` only true on initial load, not on refetch
**Decision**: Use `computed(() => isLoading.value || isFetching.value)` for better UX
**Impact**: Spinner now shows during manual refresh operations

### Decision: Remove Custom Pagination (19:30)

**Context**: Custom pagination conflicted with AdminTable's search functionality
**Decision**: Remove custom pagination, use AdminTable's built-in features
**Impact**: Simpler code, better search experience, all filtered logs searchable

### Decision: Increase Card Limit to 1500 (19:39)

**Context**: Card showed "1000 Einträge" but Admin showed more
**Decision**: Increase limit to 1500, show "1500+" when limited
**Impact**: Better consistency between Card and Admin, clearer when data is truncated

### Decision: Remove Legacy LoggerSummaryAdmin (18:55)

**Context**: Two versions existed (legacy and bulk), only bulk was used
**Decision**: Remove legacy version, rename bulk to standard name
**Impact**: Cleaner codebase, less confusion, 806 lines removed

## Challenges and Solutions

### Challenge: Duplicate Search Fields (19:25)

**Problem**: AdminTable had its own search field, plus custom search field in actions
**Solution**: Removed custom search, disabled AdminTable search initially, then re-enabled AdminTable search and removed custom implementation
**Lesson**: Use framework features instead of duplicating functionality

### Challenge: goToPage Undefined Error (19:31)

**Problem**: `applyFilters()` called `goToPage(1)` after removing pagination
**Solution**: Made `applyFilters()` empty since filters are reactive
**Lesson**: Clean up all references when removing features

### Challenge: Table Showing Wrong Count (19:14)

**Problem**: Table title showed "50 Einträge" (paginated count) instead of total filtered
**Solution**: Pass `filteredLogs` to AdminTable instead of `paginatedLogs`, remove custom pagination
**Lesson**: Let table components handle their own pagination

## Code Quality

- All changes passed `npm run lint` (type-check + format-check)
- Consistent spinner styling across all components
- Proper use of TanStack Query's `isFetching` for loading states
- Clean separation of concerns (filtering vs pagination)

## Commits

1. `feat: add loading spinner to refresh buttons` - Initial spinner implementation
2. `feat: add refresh button with spinner to logger admin panels` - Logger components
3. `refactor: remove unused LoggerSummaryAdmin component` - Cleanup legacy code
4. `refactor: rename LoggerSummaryAdminBulk to LoggerSummaryAdmin` - Simplify naming
5. `feat: improve logger admin panel UX and fix table display` - UX improvements

## Next Steps

- [ ] Monitor user feedback on new button order
- [ ] Consider adding spinner to other loading operations
- [ ] Evaluate if 1500 log limit is sufficient for Card
- [ ] Consider adding pagination info to AdminTable footer

## Lessons Learned

### 1. Loading State Management with TanStack Query

**Problem**: `isLoading` only true on initial load, not on refetch
**Solution**: Combine `isLoading` and `isFetching` in computed property
**Application**: Use for any manual refresh operations with TanStack Query

### 2. Avoid Duplicating Framework Features

**Problem**: Created custom search field when AdminTable already had one
**Solution**: Use AdminTable's built-in search functionality
**Application**: Always check if framework/component already provides needed feature

### 3. Clean Up All References When Removing Features

**Problem**: Removed pagination but left `goToPage()` call
**Solution**: Search for all references before removing features
**Application**: Use grep/search to find all usages before deletion

### 4. Consistent Spinner Styling

**Problem**: Different components might have different spinner implementations
**Solution**: Use consistent CSS classes and animations across all components
**Application**: Define common UI patterns in shared styles or components

## Files Modified

- `src/components/common/BaseCard.vue`
- `src/components/automatic-groups/AutomaticGroupsAdmin.vue`
- `src/components/tags/TagsAdmin.vue`
- `src/components/expiring-appointments/ExpiringAppointmentsAdmin.vue`
- `src/components/loggerSummary/LoggerSummaryAdmin.vue` (renamed from Bulk)
- `src/components/loggerSummary/LoggerSummaryCard.vue`
- `src/composables/useLoggerSummaryQuery.ts`
- `src/App.vue`

## Files Deleted

- `src/components/loggerSummary/LoggerSummaryAdmin.vue` (legacy version)
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "churchtools-dashboard",
"private": true,
"version": "1.0.7",
"version": "1.0.8",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
3 changes: 1 addition & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ 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'

import { useToast } from './composables/useToast'
Expand Down Expand Up @@ -93,7 +92,7 @@ const modules: DashboardModule[] = [
icon: '📋',
description: 'Überwachung und Verwaltung von Log-Einträgen',
cardComponent: LoggerSummaryCard,
adminComponent: LoggerSummaryAdminBulk,
adminComponent: LoggerSummaryAdmin,
},
]

Expand Down
28 changes: 25 additions & 3 deletions src/components/automatic-groups/AutomaticGroupsAdmin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Suche zurücksetzen
</button>
<button @click="refreshGroups" class="ct-btn ct-btn-primary refresh-btn" :disabled="loading">
<span v-if="loading" class="btn-spinner"></span>
{{ loading ? 'Lädt...' : 'Aktualisieren' }}
</button>
</template>
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -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);
}
}
</style>
28 changes: 20 additions & 8 deletions src/components/common/BaseCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,6 @@
<template v-else>&nbsp;</template>
</span>
<div class="footer-actions">
<button
type="button"
@click="$emit('refresh')"
class="ct-btn ct-btn-primary ct-btn-sm"
:disabled="isLoading"
>
{{ isLoading ? refreshingText : refreshText }}
</button>
<slot name="actions">
<button
type="button"
Expand All @@ -98,6 +90,15 @@
{{ detailsText }}
</button>
</slot>
<button
type="button"
@click="$emit('refresh')"
class="ct-btn ct-btn-primary ct-btn-sm"
:disabled="isLoading"
>
<span v-if="isLoading" class="btn-spinner"></span>
{{ isLoading ? refreshingText : refreshText }}
</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions src/components/expiring-appointments/ExpiringAppointmentsAdmin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
Filter löschen
</button>
<button @click="refreshData" class="ct-btn ct-btn-primary refresh-btn" :disabled="isLoading">
<span v-if="isLoading" class="btn-spinner"></span>
{{ isLoading ? 'Lädt...' : 'Aktualisieren' }}
</button>
</template>
Expand Down Expand Up @@ -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);
}
}
</style>
Loading