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:
231
resources/views/admin/players/index.blade.php
Executable file
231
resources/views/admin/players/index.blade.php
Executable file
@@ -0,0 +1,231 @@
|
||||
<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">✓</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>
|
||||
Reference in New Issue
Block a user