- 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>
202 lines
15 KiB
PHP
Executable File
202 lines
15 KiB
PHP
Executable File
<x-layouts.admin :title="__('admin.users_title')">
|
||
<h1 class="text-2xl font-bold mb-6">{{ __('admin.users_title') }}</h1>
|
||
|
||
<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' => 'email', 'direction' => ($sort === 'email' && $direction === 'asc') ? 'desc' : 'asc']) }}"
|
||
class="inline-flex items-center gap-1 hover:text-blue-600 {{ $sort === 'email' ? 'text-blue-600' : '' }}">
|
||
{{ __('ui.email') }}
|
||
@if ($sort === 'email')
|
||
<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.phone') }}</th>
|
||
<th class="text-center px-4 py-3 font-medium text-gray-700">
|
||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'role', 'direction' => ($sort === 'role' && $direction === 'asc') ? 'desc' : 'asc']) }}"
|
||
class="inline-flex items-center gap-1 hover:text-blue-600 {{ $sort === 'role' ? 'text-blue-600' : '' }}">
|
||
{{ __('ui.role') }}
|
||
@if ($sort === 'role')
|
||
<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.children') }}</th>
|
||
<th class="text-center px-4 py-3 font-medium text-gray-700">{{ __('admin.dsgvo_short') }}</th>
|
||
<th class="text-center px-4 py-3 font-medium text-gray-700">
|
||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'last_login_at', 'direction' => ($sort === 'last_login_at' && $direction === 'asc') ? 'desc' : 'asc']) }}"
|
||
class="inline-flex items-center gap-1 hover:text-blue-600 {{ $sort === 'last_login_at' ? 'text-blue-600' : '' }}">
|
||
{{ __('admin.last_login') }}
|
||
@if ($sort === 'last_login_at')
|
||
<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' => '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.actions') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100">
|
||
@foreach ($users as $user)
|
||
<tr class="hover:bg-gray-50">
|
||
<td class="px-4 py-3 font-medium text-gray-900">
|
||
<div class="flex items-center gap-2">
|
||
<img src="{{ $user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-7 h-7 rounded-full object-cover flex-shrink-0">
|
||
{{ $user->name }}
|
||
</div>
|
||
</td>
|
||
<td class="px-4 py-3 text-gray-600">{{ $user->email }}</td>
|
||
<td class="px-4 py-3 text-gray-600 text-sm whitespace-nowrap">
|
||
@if ($user->phone)
|
||
<a href="tel:{{ $user->phone }}" class="hover:text-blue-600">{{ $user->phone }}</a>
|
||
@else
|
||
<span class="text-gray-400">–</span>
|
||
@endif
|
||
</td>
|
||
<td class="px-4 py-3 text-center">
|
||
@if ($user->id !== auth()->id())
|
||
<form method="POST" action="{{ route('admin.users.role', $user) }}" class="inline">
|
||
@csrf
|
||
@method('PUT')
|
||
<select name="role" onchange="this.form.submit()" class="text-xs border border-gray-300 rounded-md px-2 py-1">
|
||
<option value="user" {{ $user->role === \App\Enums\UserRole::User ? 'selected' : '' }}>{{ __('ui.enums.user_role.user') }}</option>
|
||
<option value="parent_rep" {{ $user->role === \App\Enums\UserRole::ParentRep ? 'selected' : '' }}>{{ __('ui.enums.user_role.parent_rep') }}</option>
|
||
<option value="coach" {{ $user->role === \App\Enums\UserRole::Coach ? 'selected' : '' }}>{{ __('ui.enums.user_role.coach') }}</option>
|
||
@if (auth()->user()->isAdmin())
|
||
<option value="admin" {{ $user->role === \App\Enums\UserRole::Admin ? 'selected' : '' }}>{{ __('ui.enums.user_role.admin') }}</option>
|
||
@endif
|
||
</select>
|
||
</form>
|
||
@else
|
||
<span class="text-xs font-medium text-gray-500">{{ __('ui.enums.user_role.' . $user->role->value) }} {{ __('admin.you_suffix') }}</span>
|
||
@endif
|
||
</td>
|
||
<td class="px-4 py-3 text-xs text-gray-600">
|
||
@foreach ($user->children as $child)
|
||
{{ $child->first_name }}@if (!$loop->last), @endif
|
||
@endforeach
|
||
@if ($user->children->isEmpty())
|
||
<span class="text-gray-400">–</span>
|
||
@endif
|
||
</td>
|
||
<td class="px-4 py-3 text-center">
|
||
@if ($user->isDsgvoConfirmed())
|
||
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800" title="{{ __('admin.dsgvo_confirmed_tooltip') }}">✓</span>
|
||
@elseif ($user->hasDsgvoConsent())
|
||
<span class="inline-block px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800" title="{{ __('admin.dsgvo_pending_tooltip') }}">!</span>
|
||
@else
|
||
<span class="text-gray-400" title="{{ __('admin.dsgvo_missing_tooltip') }}">—</span>
|
||
@endif
|
||
</td>
|
||
<td class="px-4 py-3 text-center text-xs text-gray-500">
|
||
{{ $user->last_login_at ? $user->last_login_at->diffForHumans() : __('admin.never') }}
|
||
</td>
|
||
<td class="px-4 py-3 text-center">
|
||
@if ($user->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.deactivated_label') }}</span>
|
||
@endif
|
||
</td>
|
||
<td class="px-4 py-3 text-right">
|
||
<a href="{{ route('admin.users.edit', $user) }}" class="text-xs text-blue-600 hover:text-blue-800">{{ __('admin.edit') }}</a>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="mt-4">{{ $users->links() }}</div>
|
||
|
||
{{-- Papierkorb --}}
|
||
@if ($trashedUsers->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">{{ __('ui.email') }}</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 ($trashedUsers as $user)
|
||
<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="{{ $user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-7 h-7 rounded-full object-cover flex-shrink-0 opacity-50">
|
||
{{ $user->name }}
|
||
</div>
|
||
</td>
|
||
<td class="px-4 py-3 text-gray-400">{{ $user->email }}</td>
|
||
<td class="px-4 py-3 text-center text-xs text-gray-500">{{ $user->deleted_at->diffForHumans() }}</td>
|
||
<td class="px-4 py-3 text-right">
|
||
<form method="POST" action="{{ route('admin.users.restore', $user->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
|
||
</x-layouts.admin>
|