Files
WebAPP/resources/views/admin/players/index.blade.php
Rhino 2e24a40d68 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>
2026-03-02 07:30:37 +01:00

232 lines
15 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<x-layouts.admin :title="__('admin.players_title')">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold">{{ __('admin.players_title') }}</h1>
<a href="{{ route('admin.players.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 text-sm font-medium">
{{ __('admin.new_player') }}
</a>
</div>
{{-- Filter --}}
<form method="GET" class="mb-4">
@if (request('sort'))
<input type="hidden" name="sort" value="{{ request('sort') }}">
<input type="hidden" name="direction" value="{{ request('direction') }}">
@endif
<select name="team_id" onchange="this.form.submit()" class="px-3 py-2 border border-gray-300 rounded-md text-sm">
<option value="">{{ __('ui.all_teams') }}</option>
@foreach ($teams as $team)
<option value="{{ $team->id }}" {{ request('team_id') == $team->id ? 'selected' : '' }}>{{ $team->name }}</option>
@endforeach
</select>
</form>
<div class="bg-white rounded-lg shadow overflow-hidden overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b">
<tr>
<th class="text-left px-4 py-3 font-medium text-gray-700">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'name', 'direction' => ($sort === 'name' && $direction === 'asc') ? 'desc' : 'asc']) }}"
class="inline-flex items-center gap-1 hover:text-blue-600 {{ $sort === 'name' ? 'text-blue-600' : '' }}">
{{ __('ui.name') }}
@if ($sort === 'name')
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
@if ($direction === 'asc')
<path fill-rule="evenodd" d="M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L10 6.414l-3.293 3.293a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
@else
<path fill-rule="evenodd" d="M14.707 10.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L10 13.586l3.293-3.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
@endif
</svg>
@endif
</a>
</th>
<th class="text-left px-4 py-3 font-medium text-gray-700">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'team', 'direction' => ($sort === 'team' && $direction === 'asc') ? 'desc' : 'asc']) }}"
class="inline-flex items-center gap-1 hover:text-blue-600 {{ $sort === 'team' ? 'text-blue-600' : '' }}">
{{ __('admin.nav_teams') }}
@if ($sort === 'team')
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
@if ($direction === 'asc')
<path fill-rule="evenodd" d="M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L10 6.414l-3.293 3.293a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
@else
<path fill-rule="evenodd" d="M14.707 10.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L10 13.586l3.293-3.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
@endif
</svg>
@endif
</a>
</th>
<th class="text-center px-4 py-3 font-medium text-gray-700">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'jersey_number', 'direction' => ($sort === 'jersey_number' && $direction === 'asc') ? 'desc' : 'asc']) }}"
class="inline-flex items-center gap-1 hover:text-blue-600 {{ $sort === 'jersey_number' ? 'text-blue-600' : '' }}">
{{ __('admin.nr') }}
@if ($sort === 'jersey_number')
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
@if ($direction === 'asc')
<path fill-rule="evenodd" d="M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L10 6.414l-3.293 3.293a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
@else
<path fill-rule="evenodd" d="M14.707 10.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L10 13.586l3.293-3.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
@endif
</svg>
@endif
</a>
</th>
<th class="text-left px-4 py-3 font-medium text-gray-700">{{ __('admin.parents') }}</th>
<th class="text-center px-4 py-3 font-medium text-gray-700">{{ __('admin.photo') }}</th>
<th class="text-center px-4 py-3 font-medium text-gray-700">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'is_active', 'direction' => ($sort === 'is_active' && $direction === 'asc') ? 'desc' : 'asc']) }}"
class="inline-flex items-center gap-1 hover:text-blue-600 {{ $sort === 'is_active' ? 'text-blue-600' : '' }}">
{{ __('admin.status') }}
@if ($sort === 'is_active')
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
@if ($direction === 'asc')
<path fill-rule="evenodd" d="M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L10 6.414l-3.293 3.293a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
@else
<path fill-rule="evenodd" d="M14.707 10.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L10 13.586l3.293-3.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
@endif
</svg>
@endif
</a>
</th>
<th class="text-right px-4 py-3 font-medium text-gray-700">{{ __('admin.action') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse ($players as $player)
<tr class="hover:bg-gray-50" x-data="playerRow({{ $player->id }}, '{{ $player->team_id }}', '{{ $player->photo_permission ? 1 : 0 }}')">
<td class="px-4 py-3 font-medium text-gray-900">
<a href="{{ route('admin.players.edit', $player) }}" class="flex items-center gap-2 hover:underline">
<img src="{{ $player->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-7 h-7 rounded-full object-cover flex-shrink-0">
{{ $player->full_name }}
</a>
</td>
<td class="px-4 py-3">
<select
x-model="teamId"
@change="save({ team_id: teamId })"
class="px-2 py-1 border border-gray-200 rounded text-xs bg-white focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
>
@foreach ($teams as $team)
<option value="{{ $team->id }}" {{ $player->team_id == $team->id ? 'selected' : '' }}>{{ $team->name }}</option>
@endforeach
</select>
</td>
<td class="px-4 py-3 text-center text-gray-600">{{ $player->jersey_number ?? '' }}</td>
<td class="px-4 py-3 text-gray-600 text-xs">
@foreach ($player->parents as $parent)
{{ $parent->name }}{{ $parent->pivot->relationship_label ? " ({$parent->pivot->relationship_label})" : '' }}@if (!$loop->last), @endif
@endforeach
@if ($player->parents->isEmpty())
<span class="text-gray-400"></span>
@endif
</td>
<td class="px-4 py-3 text-center">
<select
x-model="photo"
@change="save({ photo_permission: photo })"
class="px-2 py-1 border border-gray-200 rounded text-xs bg-white focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
:class="photo == '1' ? 'text-green-700' : 'text-red-600'"
>
<option value="1" {{ $player->photo_permission ? 'selected' : '' }}>{{ __('ui.yes') }}</option>
<option value="0" {{ !$player->photo_permission ? 'selected' : '' }}>{{ __('ui.no') }}</option>
</select>
</td>
<td class="px-4 py-3 text-center">
@if ($player->is_active)
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">{{ __('admin.active') }}</span>
@else
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">{{ __('admin.inactive') }}</span>
@endif
</td>
<td class="px-4 py-3 text-right space-x-2">
<span x-show="saving" class="text-xs text-gray-400">...</span>
<span x-show="saved" x-cloak class="text-xs text-green-600">&#10003;</span>
<a x-show="!saving && !saved" href="{{ route('admin.players.edit', $player) }}" class="text-blue-600 hover:underline text-sm">{{ __('ui.edit') }}</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-gray-500">{{ __('admin.no_players_yet') }}</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">{{ $players->links() }}</div>
{{-- Papierkorb --}}
@if ($trashedPlayers->isNotEmpty())
<div class="mt-8">
<h2 class="text-lg font-semibold text-gray-700 mb-3">{{ __('admin.trash') }}</h2>
<div class="bg-white rounded-lg shadow overflow-hidden overflow-x-auto border border-red-200">
<table class="w-full text-sm">
<thead class="bg-red-50 border-b border-red-200">
<tr>
<th class="text-left px-4 py-3 font-medium text-gray-700">{{ __('ui.name') }}</th>
<th class="text-left px-4 py-3 font-medium text-gray-700">{{ __('admin.nav_teams') }}</th>
<th class="text-center px-4 py-3 font-medium text-gray-700">{{ __('admin.deleted_at') }}</th>
<th class="text-right px-4 py-3 font-medium text-gray-700">{{ __('admin.actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@foreach ($trashedPlayers as $player)
<tr class="hover:bg-red-50/50">
<td class="px-4 py-3 font-medium text-gray-500">
<div class="flex items-center gap-2">
<img src="{{ $player->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-7 h-7 rounded-full object-cover flex-shrink-0 opacity-50">
{{ $player->full_name }}
</div>
</td>
<td class="px-4 py-3 text-gray-400">{{ $player->team?->name ?? '' }}</td>
<td class="px-4 py-3 text-center text-xs text-gray-500">{{ $player->deleted_at->diffForHumans() }}</td>
<td class="px-4 py-3 text-right">
<form method="POST" action="{{ route('admin.players.restore', $player->id) }}" class="inline">
@csrf
@method('PUT')
<button type="submit" class="text-xs text-green-600 hover:text-green-800 font-medium">{{ __('admin.restore') }}</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
@push('scripts')
<script>
function playerRow(playerId, initialTeam, initialPhoto) {
return {
teamId: initialTeam,
photo: initialPhoto,
saving: false,
saved: false,
async save(data) {
this.saving = true;
this.saved = false;
try {
const res = await fetch(`/admin/players/${playerId}/quick-update`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Accept': 'application/json',
},
body: JSON.stringify(data),
});
if (res.ok) {
this.saved = true;
setTimeout(() => this.saved = false, 1500);
}
} catch (e) {
console.error(e);
} finally {
this.saving = false;
}
}
};
}
</script>
@endpush
</x-layouts.admin>