Feature-Toggles, Administration, wiederkehrende Events und Event-Serien

- Administration & Rollenmanagement: Neuer Admin-Bereich mit Feature-Toggles
  und Sichtbarkeitseinstellungen pro Rolle (11 Toggles, 24 Visibility-Settings)
- AdministrationController mit eigenem Settings-Tab, aus SettingsController extrahiert
- Feature-Toggle-Guards in Controllers (Invitation, File, ListGenerator, Comment)
  und Views (events/show, events/edit, events/create)
- Setting::isFeatureEnabled() und isFeatureVisibleFor() Hilfsmethoden
- Wiederkehrende Trainings: Täglich/Wöchentlich/2-Wöchentlich mit Ende per
  Datum oder Anzahl (max. 52), Vorschau im Formular
- Event-Serien: Verknüpfung über event_series_id (UUID), Modal-Dialog beim
  Speichern und Löschen mit Optionen "nur dieses" / "alle folgenden"
- Löschen-Button direkt in der Event-Bearbeitung mit Serien-Dialog
- DemoDataSeeder: 4 Trainings als Serie mit gemeinsamer event_series_id
- Übersetzungen in allen 6 Sprachen (de, en, pl, ru, ar, tr)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-03 08:38:45 +01:00
parent 0990e4249c
commit 8ccadbe89f
27 changed files with 1968 additions and 698 deletions

View File

@@ -0,0 +1,81 @@
<?php
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
public function up(): void
{
// Globale Feature-Toggles (Master-Schalter)
$featureToggles = [
['key' => 'feature_statistics', 'label' => 'Feature: Statistiken', 'type' => 'number', 'value' => '1'],
['key' => 'feature_finances', 'label' => 'Feature: Finanzen', 'type' => 'number', 'value' => '1'],
['key' => 'feature_catering', 'label' => 'Feature: Catering', 'type' => 'number', 'value' => '1'],
['key' => 'feature_timekeepers', 'label' => 'Feature: Zeitnehmer', 'type' => 'number', 'value' => '1'],
['key' => 'feature_carpools', 'label' => 'Feature: Fahrgemeinschaften', 'type' => 'number', 'value' => '1'],
['key' => 'feature_comments', 'label' => 'Feature: Kommentare', 'type' => 'number', 'value' => '1'],
['key' => 'feature_files', 'label' => 'Feature: Dateien', 'type' => 'number', 'value' => '1'],
['key' => 'feature_faqs', 'label' => 'Feature: FAQs', 'type' => 'number', 'value' => '1'],
['key' => 'feature_list_generator', 'label' => 'Feature: Listenerstellung', 'type' => 'number', 'value' => '1'],
['key' => 'feature_invitations', 'label' => 'Feature: Einladungen', 'type' => 'number', 'value' => '1'],
['key' => 'feature_player_stats', 'label' => 'Feature: Spielerstatistiken', 'type' => 'number', 'value' => '1'],
];
foreach ($featureToggles as $toggle) {
if (!Setting::where('key', $toggle['key'])->exists()) {
$setting = new Setting([
'label' => $toggle['label'],
'type' => $toggle['type'],
'value' => $toggle['value'],
]);
$setting->key = $toggle['key'];
$setting->save();
}
}
// Neue pro-Rolle Visibility-Settings (zusätzlich zu bestehenden)
$newVisibility = [
'catering', 'timekeepers', 'carpools', 'comments',
'files', 'faqs', 'list_generator', 'invitations', 'player_stats',
];
foreach ($newVisibility as $feature) {
foreach (['coach', 'parent_rep'] as $role) {
$key = "visibility_{$feature}_{$role}";
if (!Setting::where('key', $key)->exists()) {
$setting = new Setting([
'label' => ucfirst(str_replace('_', ' ', $feature)) . ': ' . ucfirst(str_replace('_', ' ', $role)),
'type' => 'number',
'value' => '1',
]);
$setting->key = $key;
$setting->save();
}
}
}
}
public function down(): void
{
$featureKeys = [
'feature_statistics', 'feature_finances', 'feature_catering',
'feature_timekeepers', 'feature_carpools', 'feature_comments',
'feature_files', 'feature_faqs', 'feature_list_generator',
'feature_invitations', 'feature_player_stats',
];
Setting::whereIn('key', $featureKeys)->delete();
$newVisibility = [
'catering', 'timekeepers', 'carpools', 'comments',
'files', 'faqs', 'list_generator', 'invitations', 'player_stats',
];
foreach ($newVisibility as $feature) {
foreach (['coach', 'parent_rep'] as $role) {
Setting::where('key', "visibility_{$feature}_{$role}")->delete();
}
}
}
};

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('events', function (Blueprint $table) {
$table->string('event_series_id', 36)->nullable()->after('status')->index();
});
}
public function down(): void
{
Schema::table('events', function (Blueprint $table) {
$table->dropIndex(['event_series_id']);
$table->dropColumn('event_series_id');
});
}
};