From 7a29a218224f7069812541ec5adfafc89ab9ac68 Mon Sep 17 00:00:00 2001 From: Thorsten Bus Date: Sun, 21 Jun 2026 11:53:55 +0200 Subject: [PATCH] feat: confirm-state for service songs + service-edit UX fixes - add explicit confirmed_at assignment state (red/amber/green) with confirm/unconfirm endpoints - clone leader arrangement before opening dialog to avoid flicker - agenda slide strip: show all previews, drag-reorder, number badges, auto-hide uploader - fix info-slide expire-date saving via axios (was rendering raw JSON modal) - point top-left logo to / instead of /dashboard --- app/Http/Controllers/ServiceController.php | 1 + .../Controllers/ServiceSongController.php | 41 +++++ app/Models/ServiceSong.php | 2 + app/Services/SongMatchingService.php | 6 + ...dd_confirmed_at_to_service_songs_table.php | 22 +++ resources/js/Components/AgendaItemRow.vue | 73 +++++++-- resources/js/Components/ArrangementDialog.vue | 59 +------- resources/js/Components/SlideGrid.vue | 15 +- resources/js/Components/SongAgendaItem.vue | 113 ++++++++++++-- resources/js/Layouts/AuthenticatedLayout.vue | 2 +- resources/js/Pages/Services/Edit.vue | 105 ++++++++++++- routes/api.php | 6 + tests/Feature/ServiceSongControllerTest.php | 140 ++++++++++++++++++ 13 files changed, 487 insertions(+), 98 deletions(-) create mode 100644 database/migrations/2026_06_21_100000_add_confirmed_at_to_service_songs_table.php create mode 100644 tests/Feature/ServiceSongControllerTest.php diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index b2755b8..1c444f7 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -308,6 +308,7 @@ public function edit(Service $service, \App\Services\ServiceImageResolver $image 'song_id' => $ss->song_id, 'song_arrangement_id' => $ss->song_arrangement_id, 'matched_at' => $ss->matched_at?->toJSON(), + 'confirmed_at' => $ss->confirmed_at?->toJSON(), 'request_sent_at' => $ss->request_sent_at?->toJSON(), 'has_content_slides' => $ss->song ? $this->defaultArrangementHasContentSlides($ss->song) : null, 'song' => $ss->song ? [ diff --git a/app/Http/Controllers/ServiceSongController.php b/app/Http/Controllers/ServiceSongController.php index db09f83..7a6dfe1 100644 --- a/app/Http/Controllers/ServiceSongController.php +++ b/app/Http/Controllers/ServiceSongController.php @@ -7,6 +7,7 @@ use App\Services\SongMatchingService; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Carbon; class ServiceSongController extends Controller { @@ -92,6 +93,12 @@ public function update(int $serviceSongId, Request $request): JsonResponse } } + // Bei tatsaechlicher Aenderung des Arrangements muss neu bestaetigt werden. + if (array_key_exists('song_arrangement_id', $validated) + && $validated['song_arrangement_id'] !== $serviceSong->song_arrangement_id) { + $validated['confirmed_at'] = null; + } + $serviceSong->update($validated); return response()->json([ @@ -99,4 +106,38 @@ public function update(int $serviceSongId, Request $request): JsonResponse 'service_song' => $serviceSong->fresh(), ]); } + + /** + * Zuordnung eines Service-Songs explizit bestaetigen. + */ + public function confirm(int $serviceSongId): JsonResponse + { + $serviceSong = ServiceSong::findOrFail($serviceSongId); + + $serviceSong->update([ + 'confirmed_at' => Carbon::now(), + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Zuordnung wurde bestaetigt.', + ]); + } + + /** + * Bestaetigung eines Service-Songs zuruecknehmen. + */ + public function unconfirm(int $serviceSongId): JsonResponse + { + $serviceSong = ServiceSong::findOrFail($serviceSongId); + + $serviceSong->update([ + 'confirmed_at' => null, + ]); + + return response()->json([ + 'success' => true, + 'message' => 'Bestaetigung wurde zurueckgenommen.', + ]); + } } diff --git a/app/Models/ServiceSong.php b/app/Models/ServiceSong.php index a60b441..76f80fc 100644 --- a/app/Models/ServiceSong.php +++ b/app/Models/ServiceSong.php @@ -20,6 +20,7 @@ class ServiceSong extends Model 'cts_ccli_id', 'cts_song_id', 'matched_at', + 'confirmed_at', 'request_sent_at', ]; @@ -28,6 +29,7 @@ protected function casts(): array return [ 'use_translation' => 'boolean', 'matched_at' => 'datetime', + 'confirmed_at' => 'datetime', 'request_sent_at' => 'datetime', ]; } diff --git a/app/Services/SongMatchingService.php b/app/Services/SongMatchingService.php index 62f2de6..d245b20 100644 --- a/app/Services/SongMatchingService.php +++ b/app/Services/SongMatchingService.php @@ -66,6 +66,11 @@ public function manualAssign(ServiceSong $serviceSong, Song $song): void 'use_translation' => $song->has_translation, ]; + // Bei Aenderung des Songs muss die Zuordnung neu bestaetigt werden. + if ($serviceSong->song_id !== $song->id) { + $updateData['confirmed_at'] = null; + } + // Only set arrangement if currently null if ($serviceSong->song_arrangement_id === null) { // Find default arrangement: is_default → name='normal' → first @@ -112,6 +117,7 @@ public function unassign(ServiceSong $serviceSong): void $serviceSong->update([ 'song_id' => null, 'matched_at' => null, + 'confirmed_at' => null, ]); } } diff --git a/database/migrations/2026_06_21_100000_add_confirmed_at_to_service_songs_table.php b/database/migrations/2026_06_21_100000_add_confirmed_at_to_service_songs_table.php new file mode 100644 index 0000000..a96220d --- /dev/null +++ b/database/migrations/2026_06_21_100000_add_confirmed_at_to_service_songs_table.php @@ -0,0 +1,22 @@ +timestamp('confirmed_at')->nullable()->after('matched_at'); + }); + } + + public function down(): void + { + Schema::table('service_songs', function (Blueprint $table) { + $table->dropColumn('confirmed_at'); + }); + } +}; diff --git a/resources/js/Components/AgendaItemRow.vue b/resources/js/Components/AgendaItemRow.vue index de4f499..518bb49 100644 --- a/resources/js/Components/AgendaItemRow.vue +++ b/resources/js/Components/AgendaItemRow.vue @@ -1,7 +1,8 @@