Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill

- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 07:30:37 +01:00
commit 2e24a40d68
9633 changed files with 1300799 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
<?php
namespace App\Http\Controllers;
use App\Enums\EventStatus;
use App\Enums\EventType;
use App\Models\ActivityLog;
use App\Models\Event;
use App\Models\Setting;
use App\Models\Team;
use Illuminate\Http\Request;
use Illuminate\View\View;
class EventController extends Controller
{
public function index(Request $request): View
{
$user = auth()->user();
$query = Event::with(['team', 'participants'])
->withCount([
'caterings as caterings_yes_count' => fn ($q) => $q->where('status', 'yes'),
'timekeepers as timekeepers_yes_count' => fn ($q) => $q->where('status', 'yes'),
]);
// Admins sehen auch Entwürfe, Eltern nur published
if ($user->canAccessAdminPanel()) {
$teams = Team::where('is_active', true)->orderBy('name')->get();
} else {
$teamIds = $user->accessibleTeamIds();
$query->published()->whereIn('team_id', $teamIds);
$teams = Team::whereIn('id', $teamIds)->orderBy('name')->get();
}
// Filter: Team (nur Integer-IDs akzeptieren)
if ($request->filled('team_id')) {
$query->where('team_id', (int) $request->team_id);
}
// Filter: Typ (nur gueltige EventType-Werte)
if ($request->filled('type')) {
$validTypes = array_column(EventType::cases(), 'value');
if (in_array($request->type, $validTypes)) {
$query->where('type', $request->type);
}
}
// Filter: Zeitraum
if ($request->input('period') === 'past') {
$query->where('start_at', '<', now())->orderByDesc('start_at');
} else {
$query->where('start_at', '>=', now())->orderBy('start_at');
}
$events = $query->paginate(15)->withQueryString();
return view('events.index', compact('events', 'teams'));
}
public function show(Event $event): View
{
$user = auth()->user();
// Entwürfe nur für Admins
if ($event->status === EventStatus::Draft && !$user->canAccessAdminPanel()) {
abort(403);
}
// Kinder einmal laden, für Zugriffsprüfung + Teilnahme-Buttons
$userChildren = $user->children()->select('players.id', 'players.team_id')->get();
// Zugriffsbeschraenkung: User muss Zugang zum Team haben (ueber accessibleTeamIds)
if (!$user->canAccessAdminPanel()) {
$accessibleTeamIds = $user->accessibleTeamIds();
if (!$accessibleTeamIds->contains($event->team_id)) {
abort(403);
}
}
$event->syncParticipants($user->id);
$isMeeting = $event->type === EventType::Meeting;
$relations = ['team', 'comments.user', 'files.category'];
$relations[] = $isMeeting ? 'participants.user' : 'participants.player';
$relations[] = 'participants.setByUser';
if ($event->type->hasCatering()) {
$relations[] = 'caterings.user';
}
if ($event->type->hasTimekeepers()) {
$relations[] = 'timekeepers.user';
}
$event->load($relations);
$userChildIds = $userChildren->pluck('id');
// Eigener Catering-Status
$myCatering = $event->type->hasCatering()
? $event->caterings->where('user_id', $user->id)->first()
: null;
// Eigener Zeitnehmer-Status
$myTimekeeper = $event->type->hasTimekeepers()
? $event->timekeepers->where('user_id', $user->id)->first()
: null;
// Catering/Zeitnehmer-Verlauf für Staff (chronologische Statusänderungen)
$cateringHistory = collect();
$timekeeperHistory = collect();
if ($user->canAccessAdminPanel() && Setting::isFeatureVisibleFor('catering_history', $user)) {
$statusLogs = ActivityLog::where('model_type', 'Event')
->where('model_id', $event->id)
->where('action', 'status_changed')
->orderBy('created_at')
->get();
$cateringHistory = $statusLogs->filter(
fn ($log) => ($log->properties['new']['source'] ?? null) === 'catering'
);
$timekeeperHistory = $statusLogs->filter(
fn ($log) => ($log->properties['new']['source'] ?? null) === 'timekeeper'
);
}
return view('events.show', compact('event', 'userChildIds', 'myCatering', 'myTimekeeper', 'cateringHistory', 'timekeeperHistory'));
}
}