From c0287367c0054d5ee4f87e7976debbc7833444ab Mon Sep 17 00:00:00 2001 From: Rhino Date: Tue, 3 Mar 2026 10:45:35 +0100 Subject: [PATCH] Event-Thumbnails: Vorschaubilder mit Auto-Resize und Typ-Logos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migration: thumbnail-Spalte in events-Tabelle - Event-Model: imageUrl() liefert Custom-Thumbnail oder Standard-Logo je Event-Typ (Logo_Training.png, Logo_Heimspiel.png, etc.) - Thumbnail-Upload neben Typ-Auswahl bei Erstellen/Bearbeiten mit Live-Vorschau und Entfernen-Button - Automatische Skalierung auf max. FullHD (1920x1080) via GD und Speicherung als JPEG (Qualität 85) - Event-Listen (App + Admin): Logo/Thumbnail links im Terminblock - Übersetzungen in allen 6 Sprachen Co-Authored-By: Claude Opus 4.6 --- .../Controllers/Admin/EventController.php | 67 +++++++++++++++++++ app/Models/Event.php | 22 ++++++ ...3_01_01_000000_add_thumbnail_to_events.php | 22 ++++++ lang/ar/admin.php | 2 + lang/de/admin.php | 2 + lang/en/admin.php | 2 + lang/pl/admin.php | 2 + lang/ru/admin.php | 2 + lang/tr/admin.php | 2 + resources/views/admin/events/create.blade.php | 63 +++++++++++------ resources/views/admin/events/edit.blade.php | 64 ++++++++++++------ resources/views/admin/events/index.blade.php | 2 + resources/views/events/index.blade.php | 1 + 13 files changed, 215 insertions(+), 38 deletions(-) create mode 100644 database/migrations/0043_01_01_000000_add_thumbnail_to_events.php diff --git a/app/Http/Controllers/Admin/EventController.php b/app/Http/Controllers/Admin/EventController.php index 0d38d77..328e376 100755 --- a/app/Http/Controllers/Admin/EventController.php +++ b/app/Http/Controllers/Admin/EventController.php @@ -99,6 +99,7 @@ class EventController extends Controller $event->updated_by = $request->user()->id; $event->save(); + $this->handleThumbnail($request, $event); $this->createParticipantsForTeam($event); $this->syncAssignments($event, $request); $this->saveKnownLocation($validated, $request->input('location_name')); @@ -172,6 +173,7 @@ class EventController extends Controller $event->syncParticipants($request->user()->id); } + $this->handleThumbnail($request, $event); $this->syncAssignments($event, $request); $this->saveKnownLocation($validated, $request->input('location_name')); $this->syncEventFiles($event, $request); @@ -349,6 +351,7 @@ class EventController extends Controller 'new_files.*' => ['file', 'max:10240', 'mimes:pdf,docx,xlsx,jpg,jpeg,png,gif,webp'], 'new_file_categories' => ['nullable', 'array'], 'new_file_categories.*' => ['integer', 'exists:file_categories,id'], + 'thumbnail' => ['nullable', 'image', 'max:10240', 'mimes:jpg,jpeg,png,gif,webp'], ]); $validated = $request->validate([ @@ -666,4 +669,68 @@ class EventController extends Controller return count($dates); } + + /** + * Thumbnail hochladen, auf max. 1920x1080 skalieren und als JPEG speichern. + */ + private function handleThumbnail(Request $request, Event $event): void + { + // Thumbnail entfernen + if ($request->boolean('remove_thumbnail') && $event->thumbnail) { + Storage::disk('public')->delete('thumbnails/' . $event->thumbnail); + $event->thumbnail = null; + $event->save(); + return; + } + + if (!$request->hasFile('thumbnail')) { + return; + } + + // Altes Thumbnail löschen + if ($event->thumbnail) { + Storage::disk('public')->delete('thumbnails/' . $event->thumbnail); + } + + $uploadedFile = $request->file('thumbnail'); + $storedName = Str::uuid() . '.jpg'; + + // Bild laden und auf max. FullHD skalieren + $imageData = file_get_contents($uploadedFile->getRealPath()); + $source = @imagecreatefromstring($imageData); + + if (!$source) { + return; + } + + $origWidth = imagesx($source); + $origHeight = imagesy($source); + $maxWidth = 1920; + $maxHeight = 1080; + + // Nur verkleinern, nicht vergrößern + if ($origWidth > $maxWidth || $origHeight > $maxHeight) { + $ratio = min($maxWidth / $origWidth, $maxHeight / $origHeight); + $newWidth = (int) round($origWidth * $ratio); + $newHeight = (int) round($origHeight * $ratio); + + $resized = imagecreatetruecolor($newWidth, $newHeight); + imagecopyresampled($resized, $source, 0, 0, 0, 0, $newWidth, $newHeight, $origWidth, $origHeight); + imagedestroy($source); + $source = $resized; + } + + // Verzeichnis sicherstellen + $dir = storage_path('app/public/thumbnails'); + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + + // Als JPEG speichern (Qualität 85) + imagejpeg($source, $dir . '/' . $storedName, 85); + imagedestroy($source); + + $event->thumbnail = $storedName; + $event->save(); + } } diff --git a/app/Models/Event.php b/app/Models/Event.php index 548c775..ac9ef9a 100755 --- a/app/Models/Event.php +++ b/app/Models/Event.php @@ -36,6 +36,7 @@ class Event extends Model 'opponent', 'score_home', 'score_away', + 'thumbnail', ]; protected function casts(): array @@ -300,6 +301,27 @@ class Event extends Model return $query->where('team_id', $teamId); } + /** + * URL zum Event-Bild: Custom-Thumbnail oder Standard-Logo nach Event-Typ. + */ + public function imageUrl(): string + { + if ($this->thumbnail) { + return asset('storage/thumbnails/' . $this->thumbnail); + } + + $map = [ + 'home_game' => 'Logo_Heimspiel.png', + 'away_game' => 'Logo_Auswärtsspiel.png', + 'training' => 'Logo_Training.png', + 'tournament' => 'Logo_Turnier.png', + 'meeting' => 'Logo_Besprechung.png', + 'other' => 'Logo_Sonstiges.png', + ]; + + return asset('images/' . ($map[$this->type->value] ?? 'Logo_Sonstiges.png')); + } + public function isPartOfSeries(): bool { return $this->event_series_id !== null; diff --git a/database/migrations/0043_01_01_000000_add_thumbnail_to_events.php b/database/migrations/0043_01_01_000000_add_thumbnail_to_events.php new file mode 100644 index 0000000..16b9db4 --- /dev/null +++ b/database/migrations/0043_01_01_000000_add_thumbnail_to_events.php @@ -0,0 +1,22 @@ +string('thumbnail', 255)->nullable()->after('event_series_id'); + }); + } + + public function down(): void + { + Schema::table('events', function (Blueprint $table) { + $table->dropColumn('thumbnail'); + }); + } +}; diff --git a/lang/ar/admin.php b/lang/ar/admin.php index b5953b7..4dad4c6 100755 --- a/lang/ar/admin.php +++ b/lang/ar/admin.php @@ -221,6 +221,8 @@ return [ 'category_not_empty' => 'لا يمكن حذف الفئة لأنها تحتوي على ملفات.', 'confirm_delete_category' => 'هل تريد حذف هذه الفئة حقاً؟', 'event_files' => 'الملفات', + 'event_thumbnail' => 'صورة مصغرة', + 'upload_thumbnail' => 'اختر صورة', 'attach_from_library' => 'إرفاق من المكتبة', 'upload_new_file' => 'رفع ملف جديد', 'attached_files' => 'الملفات المرفقة', diff --git a/lang/de/admin.php b/lang/de/admin.php index 01f4d0f..613cf3a 100755 --- a/lang/de/admin.php +++ b/lang/de/admin.php @@ -252,6 +252,8 @@ return [ 'category_not_empty' => 'Kategorie kann nicht gelöscht werden, da sie noch Dateien enthält.', 'confirm_delete_category' => 'Kategorie wirklich löschen?', 'event_files' => 'Dateien', + 'event_thumbnail' => 'Vorschaubild', + 'upload_thumbnail' => 'Bild wählen', 'attach_from_library' => 'Aus Bibliothek anhängen', 'upload_new_file' => 'Neue Datei hochladen', 'attached_files' => 'Angehängte Dateien', diff --git a/lang/en/admin.php b/lang/en/admin.php index 131b215..cc2388f 100755 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -220,6 +220,8 @@ return [ 'category_not_empty' => 'Category cannot be deleted because it still contains files.', 'confirm_delete_category' => 'Really delete this category?', 'event_files' => 'Files', + 'event_thumbnail' => 'Thumbnail', + 'upload_thumbnail' => 'Choose image', 'attach_from_library' => 'Attach from library', 'upload_new_file' => 'Upload new file', 'attached_files' => 'Attached Files', diff --git a/lang/pl/admin.php b/lang/pl/admin.php index 6b20062..0012f8f 100755 --- a/lang/pl/admin.php +++ b/lang/pl/admin.php @@ -221,6 +221,8 @@ return [ 'category_not_empty' => 'Nie można usunąć kategorii, ponieważ zawiera pliki.', 'confirm_delete_category' => 'Naprawdę usunąć tę kategorię?', 'event_files' => 'Pliki', + 'event_thumbnail' => 'Miniatura', + 'upload_thumbnail' => 'Wybierz obraz', 'attach_from_library' => 'Dołącz z biblioteki', 'upload_new_file' => 'Prześlij nowy plik', 'attached_files' => 'Dołączone pliki', diff --git a/lang/ru/admin.php b/lang/ru/admin.php index dd4bcd4..87ae392 100755 --- a/lang/ru/admin.php +++ b/lang/ru/admin.php @@ -239,6 +239,8 @@ return [ 'category_not_empty' => 'Категория не может быть удалена, так как содержит файлы.', 'confirm_delete_category' => 'Действительно удалить эту категорию?', 'event_files' => 'Файлы', + 'event_thumbnail' => 'Миниатюра', + 'upload_thumbnail' => 'Выбрать фото', 'attach_from_library' => 'Прикрепить из библиотеки', 'upload_new_file' => 'Загрузить новый файл', 'attached_files' => 'Прикреплённые файлы', diff --git a/lang/tr/admin.php b/lang/tr/admin.php index 54d8055..2649ff9 100755 --- a/lang/tr/admin.php +++ b/lang/tr/admin.php @@ -239,6 +239,8 @@ return [ 'category_not_empty' => 'Kategori dosya içerdiği için silinemez.', 'confirm_delete_category' => 'Bu kategoriyi gerçekten silmek istiyor musunuz?', 'event_files' => 'Dosyalar', + 'event_thumbnail' => 'Önizleme resmi', + 'upload_thumbnail' => 'Resim seç', 'attach_from_library' => 'Kütüphaneden ekle', 'upload_new_file' => 'Yeni dosya yükle', 'attached_files' => 'Ekli Dosyalar', diff --git a/resources/views/admin/events/create.blade.php b/resources/views/admin/events/create.blade.php index e4000fb..a4d1121 100755 --- a/resources/views/admin/events/create.blade.php +++ b/resources/views/admin/events/create.blade.php @@ -12,28 +12,53 @@
+ x-data="{ currentType: @js(old('type', '')), thumbnailPreview: null }" x-init="$watch('currentType', () => {}); document.getElementById('type').addEventListener('change', (e) => currentType = e.target.value); currentType = document.getElementById('type').value;"> @csrf -
-
- - - @error('team_id')

{{ $message }}

@enderror +
+
+
+ + + @error('team_id')

{{ $message }}

@enderror +
+
+ + + @error('type')

{{ $message }}

@enderror +
-
- - - @error('type')

{{ $message }}

@enderror + {{-- Thumbnail --}} +
+ +
+ + + +
+ + @error('thumbnail')

{{ $message }}

@enderror
diff --git a/resources/views/admin/events/edit.blade.php b/resources/views/admin/events/edit.blade.php index cb4ace4..ff67d21 100755 --- a/resources/views/admin/events/edit.blade.php +++ b/resources/views/admin/events/edit.blade.php @@ -12,7 +12,7 @@
+ x-data="{ currentType: @js(old('type', $event->type->value)), thumbnailPreview: @js($event->thumbnail ? $event->imageUrl() : null) }" x-init="document.getElementById('type').addEventListener('change', (e) => currentType = e.target.value);"> @csrf @method('PUT') @@ -22,25 +22,51 @@
@endif -
-
- - - @error('team_id')

{{ $message }}

@enderror +
+
+
+ + + @error('team_id')

{{ $message }}

@enderror +
+
+ + + @error('type')

{{ $message }}

@enderror +
-
- - - @error('type')

{{ $message }}

@enderror + {{-- Thumbnail --}} +
+ +
+ + + +
+ + + @error('thumbnail')

{{ $message }}

@enderror
diff --git a/resources/views/admin/events/index.blade.php b/resources/views/admin/events/index.blade.php index c46574b..91e33e0 100755 --- a/resources/views/admin/events/index.blade.php +++ b/resources/views/admin/events/index.blade.php @@ -38,6 +38,8 @@ @endphp
+ {{-- Bild --}} + {{-- Links: Typ, Team, Titel, Datum, Ort, Status --}}
diff --git a/resources/views/events/index.blade.php b/resources/views/events/index.blade.php index 9d2fd0d..0ba6477 100755 --- a/resources/views/events/index.blade.php +++ b/resources/views/events/index.blade.php @@ -61,6 +61,7 @@
+