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'], 'position' => ['nullable', 'string', \Illuminate\Validation\Rule::in(PlayerPosition::values())], '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'], 'position' => ['nullable', 'string', \Illuminate\Validation\Rule::in(PlayerPosition::values())], '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')); } }