173 lines
8.2 KiB
Markdown
173 lines
8.2 KiB
Markdown
# Task 2: Wire SermonBlock in Edit.vue - Learnings
|
|
|
|
## Problem
|
|
The SermonBlock component existed at `resources/js/Components/Blocks/SermonBlock.vue` but was not imported or rendered in the Services/Edit.vue page. Additionally, the `refreshPage` function was called on slide upload events but didn't exist, causing silent failures.
|
|
|
|
## Solution
|
|
Made 3 atomic changes to `resources/js/Pages/Services/Edit.vue`:
|
|
|
|
1. **Import SermonBlock** (Line 8)
|
|
- Added: `import SermonBlock from '@/Components/Blocks/SermonBlock.vue'`
|
|
- Placed after ModerationBlock import to follow existing pattern
|
|
|
|
2. **Add refreshPage function** (Lines 63-65)
|
|
- Added: `function refreshPage() { router.reload({ preserveScroll: true }) }`
|
|
- Uses Inertia's router.reload() to refresh page while preserving scroll position
|
|
- Called by @slides-updated event from all block components
|
|
|
|
3. **Render SermonBlock in template** (Lines 269-274)
|
|
- Added v-else-if block between ModerationBlock and SongsBlock
|
|
- Props: `:service-id="service.id"` and `:slides="sermonSlides"`
|
|
- Event: `@slides-updated="refreshPage"`
|
|
|
|
## Key Findings
|
|
- SermonBlock.vue was fully implemented (76 lines) with SlideUploader and SlideGrid components
|
|
- The component properly filters slides by type and service_id
|
|
- Props: serviceId (Number, required), slides (Array, default [])
|
|
- Emits: slides-updated event on upload, delete, or update
|
|
|
|
## Verification
|
|
- ✅ Build succeeds (npm run build)
|
|
- ✅ All SermonBlock and ServiceController tests pass (12 tests)
|
|
- ✅ Sermon block renders correctly with uploader and grid (not placeholder)
|
|
- ✅ No LSP diagnostics errors
|
|
- ✅ Screenshots saved as evidence
|
|
|
|
## Bonus Fix
|
|
Fixed pre-existing syntax error in ServiceController.php:
|
|
- Line 21 had duplicate opening brace `{` that prevented the services index from loading
|
|
- Removed the extra brace to fix PHP parse error
|
|
|
|
## Commits
|
|
1. `292ad6b` - fix: wire SermonBlock in Edit.vue and add missing refreshPage function
|
|
2. `5459529` - fix: remove duplicate opening brace in ServiceController index method
|
|
|
|
## Task 3: Sync Error Message Propagation (2026-03-02)
|
|
|
|
### Problem
|
|
- SyncController only checked Artisan exit code (0 vs non-zero)
|
|
- Actual error messages from ChurchToolsService were lost
|
|
- Users saw generic "Fehler beim Synchronisieren" with no diagnostic info
|
|
- Real error: "Agenda for event [823] not found." was swallowed
|
|
|
|
### Solution
|
|
- Replaced `Artisan::call('cts:sync')` with direct `ChurchToolsService::sync()` call
|
|
- Injected ChurchToolsService via method parameter (Laravel auto-resolves)
|
|
- Wrapped in try/catch to capture actual exception message
|
|
- On error: `back()->with('error', 'Sync fehlgeschlagen: ' . $e->getMessage())`
|
|
- On success: kept existing success message
|
|
|
|
### Pattern: Direct Service Call vs Artisan
|
|
**PREFER**: Direct service injection for web controllers
|
|
- Better error handling (catch actual exceptions)
|
|
- Better testability (mock service easily)
|
|
- No need to parse console output
|
|
- Clearer dependency graph
|
|
|
|
**USE ARTISAN**: Only for scheduled tasks, CLI operations, or when you need console output formatting
|
|
|
|
### Testing Pattern
|
|
- Created SyncControllerTest.php with Mockery mocks
|
|
- Mocked ChurchToolsService to throw exception
|
|
- Verified error message propagates to session flash
|
|
- Required authentication: `$this->actingAs($user)`
|
|
- All 178 tests pass (2 new tests added)
|
|
|
|
### Files Modified
|
|
- `app/Http/Controllers/SyncController.php` - replaced Artisan::call with direct service call
|
|
- `tests/Feature/SyncControllerTest.php` - new test file with error propagation tests
|
|
|
|
### Actual Error Found
|
|
Running `php artisan cts:sync` revealed: "Agenda for event [823] not found."
|
|
This is now properly surfaced to users instead of generic error message.
|
|
|
|
## Task 4: Archived Services Toggle
|
|
|
|
**Implementation:**
|
|
- Backend: Modified `ServiceController::index()` to accept `archived` query param
|
|
- `archived=1` filters services with `date < today` ordered descending
|
|
- Default (no param or `archived=0`) shows `date >= today` ordered ascending
|
|
- Passed `archived` boolean to frontend via Inertia
|
|
- Frontend: Added pill-style toggle in header with "Kommende" / "Vergangene" labels
|
|
- Active state shown with blue background (`bg-blue-600 text-white`)
|
|
- Inactive state shown with gray (`text-gray-700 hover:bg-gray-100`)
|
|
- Click triggers `router.get()` with `archived` param
|
|
- Empty state text changes conditionally based on archived state
|
|
- Header description updates based on archived state
|
|
|
|
**Testing:**
|
|
- Added two new Pest tests in `ServiceControllerTest.php`:
|
|
- `test_services_index_zeigt_nur_zukuenftige_services_standardmaessig`
|
|
- `test_services_index_zeigt_vergangene_services_mit_archived_parameter`
|
|
- All 176 tests pass (2 pre-existing failures unrelated to this task)
|
|
- Playwright verification confirmed toggle works correctly in browser
|
|
|
|
**Patterns:**
|
|
- Inertia router preserves state/scroll with `preserveState: true, preserveScroll: true`
|
|
- Conditional rendering in Vue using ternary operators for text content
|
|
- Dynamic class binding with array syntax for active/inactive states
|
|
- Backend query conditional logic using if/else for different filters
|
|
|
|
**Evidence:**
|
|
- Screenshot: `.sisyphus/evidence/task-4-archived-toggle.png`
|
|
- Commit: `8dc26b8` - "feat: add archived services toggle to services list"
|
|
|
|
## Task 6: CTS API Request Logging + UI (2026-03-02)
|
|
|
|
### Backend Pattern
|
|
- Zentrale Logging-Helfermethode in `ChurchToolsService` (`logApiCall`) kapselt Timing, Erfolg/Fehler und Exception-Re-throw.
|
|
- So bleiben Fachmethoden (`fetchEvents`, `fetchSongs`, `syncAgenda`, `getEventServices`) lesbar und Logging ist konsistent.
|
|
- `response_summary` sollte kurz bleiben (z. B. "Array mit X Eintraegen"), um DB-Eintraege klein und schnell durchsuchbar zu halten.
|
|
|
|
### Datenmodell/Filter
|
|
- Tabelle `api_request_logs` mit `status` + `created_at` Indexen reicht fuer schnelle Standardfilter (Status + Neueste zuerst).
|
|
- Eloquent-Scopes `byStatus()` und `search()` halten Controller schlank und wiederverwendbar.
|
|
- `search()` ueber `method`, `endpoint`, `error_message` deckt die wichtigsten Debug-Faelle ab.
|
|
|
|
### Frontend/Inertia Pattern
|
|
- Debounced Suche (300ms) mit `router.get(..., { replace: true, preserveState: true })` verhindert History-Spam.
|
|
- Fehlerzeilen visuell hervorheben (`bg-red-50`) + rote Status-Badges verbessert Scanbarkeit deutlich.
|
|
- Laravel-Pagination kann direkt als `logs.links` in Vue gerendert werden (`Link` + `withQueryString()`).
|
|
|
|
### QA/Verification
|
|
- Nach Klick auf "Daten aktualisieren" erscheinen sofort neue API-Log-Eintraege inkl. Fehlerdetails (z. B. Agenda not found).
|
|
- Pflicht-Evidenz fuer Task 6:
|
|
- `.sisyphus/evidence/task-6-api-log-page.png`
|
|
- `.sisyphus/evidence/task-6-api-log-filter.png`
|
|
- `.sisyphus/evidence/task-6-api-log-nav.png`
|
|
- `.sisyphus/evidence/task-6-migration-tests.txt`
|
|
|
|
|
|
## Task 5: Reposition Upload Area to Right of Slides Grid
|
|
|
|
**Layout Pattern:**
|
|
- Used `flex flex-col lg:flex-row-reverse gap-6` wrapper around SlideUploader + SlideGrid
|
|
- `flex-row-reverse` keeps HTML order (uploader first, grid second) but visually flips on desktop
|
|
- Mobile (`flex-col`): uploader on top, grid below
|
|
- Desktop (`lg:flex-row-reverse`): grid left (~70%), uploader right (~30%)
|
|
- Uploader wrapper: `lg:w-1/3`
|
|
- Grid wrapper: `flex-1 lg:w-2/3`
|
|
|
|
**SlideUploader CSS Changes:**
|
|
- Reduced `.v3-dropzone` min-height: 160px → 120px
|
|
- Reduced `.v3-dropzone` padding: `2rem 1.5rem` → `1.5rem 1rem`
|
|
- These make the dropzone more compact in the narrower column
|
|
|
|
**Gotcha:**
|
|
- Edit tool can merge closing `</div>` tags when replacement ends with `</div>` and the next existing line is also `</div>`
|
|
- Always verify HTML structure after edits by checking the build passes
|
|
- The build error "Element is missing end tag" immediately reveals unbalanced tags
|
|
|
|
**Files Modified:**
|
|
- `resources/js/Components/Blocks/InformationBlock.vue` - flex wrapper
|
|
- `resources/js/Components/Blocks/ModerationBlock.vue` - flex wrapper
|
|
- `resources/js/Components/Blocks/SermonBlock.vue` - flex wrapper
|
|
- `resources/js/Components/SlideUploader.vue` - reduced dropzone size
|
|
|
|
**Verification:**
|
|
- ✅ Build passes (npm run build)
|
|
- ✅ All 178 tests pass
|
|
- ✅ Desktop screenshot: grid left, uploader right, all three blocks identical
|
|
- ✅ Mobile screenshot: stacked vertically, uploader on top
|
|
- ✅ No LSP diagnostics errors
|