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:
269
app/Http/Controllers/Admin/PlayerController.php
Executable file
269
app/Http/Controllers/Admin/PlayerController.php
Executable file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Event;
|
||||
use App\Models\Player;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PlayerController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Player::with(['team', 'parents']);
|
||||
|
||||
// Team-Scoping: Coach sieht nur Spieler eigener Teams (V04)
|
||||
$user = auth()->user();
|
||||
if (!$user->isAdmin()) {
|
||||
$teamIds = $user->isCoach()
|
||||
? $user->coachTeams()->pluck('teams.id')
|
||||
: $user->accessibleTeamIds();
|
||||
$query->whereIn('team_id', $teamIds);
|
||||
}
|
||||
|
||||
if ($request->filled('team_id')) {
|
||||
$query->where('team_id', $request->team_id);
|
||||
}
|
||||
|
||||
// Sortierung
|
||||
$sortable = ['name', 'team', 'jersey_number', 'is_active', 'created_at'];
|
||||
$sort = in_array($request->input('sort'), $sortable) ? $request->input('sort') : 'created_at';
|
||||
$direction = $request->input('direction') === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
match ($sort) {
|
||||
'name' => $query->orderBy('last_name', $direction)->orderBy('first_name', $direction),
|
||||
'team' => $query->orderBy(
|
||||
Team::select('name')->whereColumn('teams.id', 'players.team_id'), $direction
|
||||
),
|
||||
'jersey_number' => $query->orderBy(DB::raw('CASE WHEN jersey_number IS NULL THEN 1 ELSE 0 END'))->orderBy('jersey_number', $direction),
|
||||
'is_active' => $query->orderBy('is_active', $direction),
|
||||
default => $query->orderBy('created_at', $direction),
|
||||
};
|
||||
|
||||
$players = $query->paginate(20)->withQueryString();
|
||||
$teams = Team::active()->orderBy('name')->get();
|
||||
$trashedPlayers = Player::onlyTrashed()
|
||||
->with('team')
|
||||
->where('deleted_at', '>=', now()->subDays(7))
|
||||
->latest('deleted_at')
|
||||
->get();
|
||||
|
||||
return view('admin.players.index', compact('players', 'teams', 'trashedPlayers', 'sort', 'direction'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$teams = Team::active()->orderBy('name')->get();
|
||||
return view('admin.players.create', compact('teams'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'first_name' => ['required', 'string', 'max:100'],
|
||||
'last_name' => ['required', 'string', 'max:100'],
|
||||
'team_id' => ['required', 'integer', 'exists:teams,id', function ($attribute, $value, $fail) {
|
||||
$team = Team::find($value);
|
||||
if (!$team || !$team->is_active) {
|
||||
$fail(__('validation.exists', ['attribute' => $attribute]));
|
||||
}
|
||||
}],
|
||||
'birth_year' => ['nullable', 'integer', 'min:2000', 'max:2030'],
|
||||
'jersey_number' => ['nullable', 'integer', 'min:1', 'max:99'],
|
||||
'is_active' => ['boolean'],
|
||||
'photo_permission' => ['boolean'],
|
||||
'notes' => ['nullable', 'string', 'max:2000'],
|
||||
]);
|
||||
|
||||
$validated['is_active'] = $request->boolean('is_active', true);
|
||||
$validated['photo_permission'] = $request->boolean('photo_permission');
|
||||
|
||||
$player = Player::create($validated);
|
||||
|
||||
if ($player->is_active) {
|
||||
Event::syncParticipantsForTeam($player->team_id, auth()->id());
|
||||
}
|
||||
|
||||
ActivityLog::logWithChanges('created', __('admin.log_player_created', ['name' => $player->full_name]), 'Player', $player->id, null, ['name' => $player->full_name, 'team' => $player->team->name ?? $player->team_id]);
|
||||
|
||||
return redirect()->route('admin.players.index')
|
||||
->with('success', __('admin.player_created'));
|
||||
}
|
||||
|
||||
public function edit(Player $player)
|
||||
{
|
||||
$player->load('parents');
|
||||
$teams = Team::active()->orderBy('name')->get();
|
||||
$users = User::active()->orderBy('name')->get();
|
||||
|
||||
return view('admin.players.edit', compact('player', 'teams', 'users'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Player $player)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'first_name' => ['required', 'string', 'max:100'],
|
||||
'last_name' => ['required', 'string', 'max:100'],
|
||||
'team_id' => ['required', 'integer', 'exists:teams,id', function ($attribute, $value, $fail) {
|
||||
$team = Team::find($value);
|
||||
if (!$team || !$team->is_active) {
|
||||
$fail(__('validation.exists', ['attribute' => $attribute]));
|
||||
}
|
||||
}],
|
||||
'birth_year' => ['nullable', 'integer', 'min:2000', 'max:2030'],
|
||||
'jersey_number' => ['nullable', 'integer', 'min:1', 'max:99'],
|
||||
'photo_permission' => ['boolean'],
|
||||
'notes' => ['nullable', 'string', 'max:2000'],
|
||||
'profile_picture' => ['nullable', 'image', 'max:2048', 'mimes:jpg,jpeg,png,gif,webp'],
|
||||
]);
|
||||
|
||||
$validated['photo_permission'] = $request->boolean('photo_permission');
|
||||
|
||||
// Handle profile picture upload
|
||||
if ($request->hasFile('profile_picture')) {
|
||||
if ($player->profile_picture) {
|
||||
Storage::disk('public')->delete($player->profile_picture);
|
||||
}
|
||||
$file = $request->file('profile_picture');
|
||||
$storedName = 'avatars/' . Str::uuid() . '.' . $file->guessExtension();
|
||||
Storage::disk('public')->putFileAs('', $file, $storedName);
|
||||
$validated['profile_picture'] = $storedName;
|
||||
} else {
|
||||
unset($validated['profile_picture']);
|
||||
}
|
||||
|
||||
$oldData = ['first_name' => $player->first_name, 'last_name' => $player->last_name, 'team_id' => $player->team_id, 'birth_year' => $player->birth_year, 'jersey_number' => $player->jersey_number, 'photo_permission' => $player->photo_permission];
|
||||
|
||||
$oldTeamId = $player->team_id;
|
||||
$player->update($validated);
|
||||
|
||||
// Sync: neues Team bekommt den Spieler, bei Team-Wechsel auch altes Team
|
||||
if ($player->is_active) {
|
||||
Event::syncParticipantsForTeam($player->team_id, auth()->id());
|
||||
}
|
||||
if ($oldTeamId !== (int) $validated['team_id']) {
|
||||
Event::syncParticipantsForTeam($oldTeamId, auth()->id());
|
||||
}
|
||||
|
||||
$newData = ['first_name' => $player->first_name, 'last_name' => $player->last_name, 'team_id' => $player->team_id, 'birth_year' => $player->birth_year, 'jersey_number' => $player->jersey_number, 'photo_permission' => $player->photo_permission];
|
||||
ActivityLog::logWithChanges('updated', __('admin.log_player_updated', ['name' => $player->full_name]), 'Player', $player->id, $oldData, $newData);
|
||||
|
||||
return redirect()->route('admin.players.index')
|
||||
->with('success', __('admin.player_updated'));
|
||||
}
|
||||
|
||||
public function quickUpdate(Request $request, Player $player)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'team_id' => ['sometimes', 'integer', 'exists:teams,id', function ($attribute, $value, $fail) {
|
||||
$team = Team::find($value);
|
||||
if (!$team || !$team->is_active) {
|
||||
$fail(__('validation.exists', ['attribute' => $attribute]));
|
||||
}
|
||||
}],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
'photo_permission' => ['sometimes', 'boolean'],
|
||||
]);
|
||||
|
||||
$oldTeamId = $player->team_id;
|
||||
$player->update($validated);
|
||||
|
||||
// Sync future events when team or active status changes
|
||||
if (isset($validated['team_id']) || isset($validated['is_active'])) {
|
||||
if ($player->is_active) {
|
||||
Event::syncParticipantsForTeam($player->team_id, auth()->id());
|
||||
}
|
||||
if (isset($validated['team_id']) && $oldTeamId !== (int) $validated['team_id']) {
|
||||
Event::syncParticipantsForTeam($oldTeamId, auth()->id());
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function assignParent(Request $request, Player $player)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'parent_id' => ['required', 'exists:users,id'],
|
||||
'relationship_label' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
|
||||
$player->parents()->syncWithoutDetaching([
|
||||
$validated['parent_id'] => [
|
||||
'relationship_label' => $validated['relationship_label'] ?? null,
|
||||
'created_at' => now(),
|
||||
],
|
||||
]);
|
||||
|
||||
$parent = User::find($validated['parent_id']);
|
||||
ActivityLog::logWithChanges('parent_assigned', __('admin.log_parent_assigned', ['parent' => $parent?->name, 'player' => $player->full_name]), 'Player', $player->id, null, ['parent' => $parent?->name, 'player' => $player->full_name]);
|
||||
|
||||
return back()->with('success', __('admin.parent_assigned'));
|
||||
}
|
||||
|
||||
public function removeParent(Player $player, User $user)
|
||||
{
|
||||
$player->parents()->detach($user->id);
|
||||
|
||||
ActivityLog::logWithChanges('parent_removed', __('admin.log_parent_removed', ['parent' => $user->name, 'player' => $player->full_name]), 'Player', $player->id, ['parent' => $user->name, 'player' => $player->full_name], null);
|
||||
|
||||
return back()->with('success', __('admin.parent_removed'));
|
||||
}
|
||||
|
||||
public function removePicture(Player $player): RedirectResponse
|
||||
{
|
||||
if ($player->profile_picture) {
|
||||
Storage::disk('public')->delete($player->profile_picture);
|
||||
$player->update(['profile_picture' => null]);
|
||||
}
|
||||
|
||||
return back()->with('success', __('admin.picture_removed'));
|
||||
}
|
||||
|
||||
public function toggleActive(Player $player): RedirectResponse
|
||||
{
|
||||
$oldActive = $player->is_active;
|
||||
|
||||
$player->update(['is_active' => !$player->is_active]);
|
||||
|
||||
// Sync future events
|
||||
Event::syncParticipantsForTeam($player->team_id, auth()->id());
|
||||
|
||||
$status = $player->is_active ? __('admin.activated') : __('admin.deactivated');
|
||||
ActivityLog::logWithChanges('toggled_active', __('admin.log_player_toggled', ['name' => $player->full_name, 'status' => $status]), 'Player', $player->id, ['is_active' => $oldActive], ['is_active' => $player->is_active]);
|
||||
|
||||
return back()->with('success', __('admin.player_toggled', ['status' => $status]));
|
||||
}
|
||||
|
||||
public function destroy(Player $player): RedirectResponse
|
||||
{
|
||||
$player->delete();
|
||||
|
||||
ActivityLog::logWithChanges('deleted', __('admin.log_player_deleted', ['name' => $player->full_name]), 'Player', $player->id, ['name' => $player->full_name, 'team' => $player->team->name ?? ''], null);
|
||||
|
||||
return redirect()->route('admin.players.index')->with('success', __('admin.player_deleted'));
|
||||
}
|
||||
|
||||
public function restore(int $id): RedirectResponse
|
||||
{
|
||||
$player = Player::onlyTrashed()->findOrFail($id);
|
||||
|
||||
if (! $player->isRestorable()) {
|
||||
return back()->with('error', __('admin.restore_expired'));
|
||||
}
|
||||
|
||||
$player->restore();
|
||||
|
||||
ActivityLog::logWithChanges('restored', __('admin.log_player_restored', ['name' => $player->full_name]), 'Player', $player->id, null, ['name' => $player->full_name]);
|
||||
|
||||
return back()->with('success', __('admin.player_restored'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user