- 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>
287 lines
17 KiB
PHP
Executable File
287 lines
17 KiB
PHP
Executable File
<x-layouts.admin :title="__('admin.edit_team') . ': ' . $team->name">
|
||
<h1 class="text-2xl font-bold mb-6">{{ __('admin.edit_team') }}: {{ $team->name }}</h1>
|
||
|
||
<form method="POST" action="{{ route('admin.teams.update', $team) }}" enctype="multipart/form-data">
|
||
@csrf
|
||
@method('PUT')
|
||
|
||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
||
|
||
{{-- ══ LINKS ══════════════════════════════════════════ --}}
|
||
<div class="space-y-6">
|
||
|
||
{{-- Card: Stammdaten --}}
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<div class="mb-4">
|
||
<label for="name" class="block text-sm font-semibold text-gray-700 mb-1">{{ __('admin.team_name') }} *</label>
|
||
<input type="text" name="name" id="name" value="{{ old('name', $team->name) }}" required
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||
@error('name')
|
||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||
@enderror
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<label for="year_group" class="block text-sm font-semibold text-gray-700 mb-1">{{ __('admin.year_group') }}</label>
|
||
<input type="text" name="year_group" id="year_group" value="{{ old('year_group', $team->year_group) }}"
|
||
placeholder="{{ __('admin.year_group_placeholder') }}"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||
</div>
|
||
|
||
<div class="mb-6">
|
||
<label class="flex items-center gap-2">
|
||
<input type="hidden" name="is_active" value="0">
|
||
<input type="checkbox" name="is_active" value="1" {{ old('is_active', $team->is_active) ? 'checked' : '' }}
|
||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||
<span class="text-sm text-gray-700">{{ __('admin.team_is_active') }}</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="flex gap-3">
|
||
<button type="submit" class="bg-blue-600 text-white px-5 py-2 rounded-md hover:bg-blue-700 font-medium text-sm">{{ __('ui.save') }}</button>
|
||
<a href="{{ route('admin.teams.index') }}" class="bg-gray-200 text-gray-700 px-5 py-2 rounded-md hover:bg-gray-300 text-sm">{{ __('ui.cancel') }}</a>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Card: Notizen --}}
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<label for="notes" class="block text-sm font-semibold text-gray-700 mb-2">{{ __('admin.team_notes') }}</label>
|
||
<textarea name="notes" id="notes" rows="6"
|
||
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
placeholder="{{ __('admin.team_notes_placeholder') }}">{{ old('notes', $team->notes) }}</textarea>
|
||
@error('notes')
|
||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||
@enderror
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{{-- ══ RECHTS ═════════════════════════════════════════ --}}
|
||
<div class="space-y-6">
|
||
|
||
{{-- Card: Trainer --}}
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<h2 class="text-sm font-semibold text-gray-700 mb-3">{{ __('admin.team_coaches') }}</h2>
|
||
|
||
@php
|
||
$selectedCoachIds = collect(old('coach_ids', $team->coaches->pluck('id')->toArray()))->map(fn ($v) => (int) $v)->toArray();
|
||
@endphp
|
||
|
||
@if ($allCoaches->isEmpty())
|
||
<p class="text-sm text-gray-500">{{ __('admin.no_coaches_available') }}</p>
|
||
@else
|
||
<div class="space-y-2">
|
||
@foreach ($allCoaches as $coach)
|
||
<label class="flex items-center gap-3 py-1 cursor-pointer hover:bg-gray-50 -mx-2 px-2 rounded">
|
||
<input type="checkbox" name="coach_ids[]" value="{{ $coach->id }}"
|
||
{{ in_array($coach->id, $selectedCoachIds) ? 'checked' : '' }}
|
||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||
<div class="flex items-center gap-2">
|
||
@if ($coach->getAvatarUrl())
|
||
<img src="{{ $coach->getAvatarUrl() }}" alt="" class="w-7 h-7 rounded-full object-cover">
|
||
@else
|
||
<div class="w-7 h-7 rounded-full bg-blue-100 text-blue-700 text-xs font-semibold flex items-center justify-center">
|
||
{{ $coach->getInitials() }}
|
||
</div>
|
||
@endif
|
||
<span class="text-sm text-gray-900">{{ $coach->name }}</span>
|
||
</div>
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Card: Spieler --}}
|
||
<div class="bg-white rounded-lg shadow p-6" x-data="playerTeamSwitcher()">
|
||
<h2 class="text-sm font-semibold text-gray-700 mb-3">
|
||
{{ __('admin.team_players') }}
|
||
<span class="font-normal text-gray-400">({{ $team->players->count() }})</span>
|
||
</h2>
|
||
|
||
@if ($team->players->isEmpty())
|
||
<p class="text-sm text-gray-500">{{ __('admin.no_players_yet') }}</p>
|
||
@else
|
||
<div class="divide-y divide-gray-100">
|
||
@foreach ($team->players as $player)
|
||
<div class="py-2 flex items-center justify-between gap-2" data-player-row="{{ $player->id }}">
|
||
<div class="flex items-center gap-2 min-w-0">
|
||
@if ($player->getAvatarUrl())
|
||
<img src="{{ $player->getAvatarUrl() }}" alt="" class="w-8 h-8 rounded-full object-cover flex-shrink-0">
|
||
@else
|
||
<div class="w-8 h-8 rounded-full bg-gray-100 text-gray-600 text-xs font-semibold flex items-center justify-center flex-shrink-0">
|
||
{{ $player->getInitials() }}
|
||
</div>
|
||
@endif
|
||
<div class="min-w-0">
|
||
<a href="{{ route('admin.players.edit', $player) }}" class="text-sm font-medium text-gray-900 hover:text-blue-600 truncate block">
|
||
{{ $player->full_name }}
|
||
</a>
|
||
<span class="text-xs text-gray-400">#{{ $player->jersey_number ?? '–' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-1.5 flex-shrink-0">
|
||
<select @change="switchTeam({{ $player->id }}, $event.target.value, $event.target)"
|
||
class="text-xs px-2 py-1 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500">
|
||
@foreach ($allTeams as $t)
|
||
<option value="{{ $t->id }}" {{ $t->id === $team->id ? 'selected' : '' }}>{{ $t->name }}</option>
|
||
@endforeach
|
||
</select>
|
||
<span x-show="savingId === {{ $player->id }}" class="text-xs text-gray-400">...</span>
|
||
<span x-show="savedId === {{ $player->id }}" x-cloak class="text-xs text-green-600">✓</span>
|
||
<span x-show="errorId === {{ $player->id }}" x-cloak class="text-xs text-red-600">!</span>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Card: Elternvertretung --}}
|
||
<div class="bg-white rounded-lg shadow p-6">
|
||
<h2 class="text-sm font-semibold text-gray-700 mb-1">{{ __('admin.team_parent_reps') }}</h2>
|
||
<p class="text-xs text-gray-400 mb-3">{{ __('admin.team_parent_reps_hint') }}</p>
|
||
|
||
@if ($parentReps->isEmpty())
|
||
<p class="text-sm text-gray-500">{{ __('admin.no_parent_reps') }}</p>
|
||
@else
|
||
<div class="space-y-2">
|
||
@foreach ($parentReps as $rep)
|
||
<div class="flex items-center gap-2">
|
||
@if ($rep->getAvatarUrl())
|
||
<img src="{{ $rep->getAvatarUrl() }}" alt="" class="w-7 h-7 rounded-full object-cover">
|
||
@else
|
||
<div class="w-7 h-7 rounded-full bg-green-100 text-green-700 text-xs font-semibold flex items-center justify-center">
|
||
{{ $rep->getInitials() }}
|
||
</div>
|
||
@endif
|
||
<div>
|
||
<span class="text-sm font-medium text-gray-900">{{ $rep->name }}</span>
|
||
<span class="text-xs text-gray-400 block">{{ $rep->email }}</span>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
{{-- ══ FULL-WIDTH: Dateien ════════════════════════════════ --}}
|
||
<div class="mt-6 bg-white rounded-lg shadow p-6" x-data="{ showPicker: false, newFileCount: 0 }">
|
||
<h2 class="text-sm font-semibold text-gray-700 mb-3">{{ __('admin.event_files') }}</h2>
|
||
|
||
{{-- Angehängte Dateien --}}
|
||
@if ($team->files->isNotEmpty())
|
||
<div class="mb-3 space-y-1">
|
||
<p class="text-xs font-semibold text-gray-500 mb-1">{{ __('admin.attached_files') }}</p>
|
||
@foreach ($team->files as $file)
|
||
<label class="flex items-center gap-2 py-1 text-sm text-gray-700 bg-blue-50 px-2 rounded">
|
||
<input type="checkbox" name="existing_files[]" value="{{ $file->id }}" checked class="rounded border-gray-300">
|
||
<span class="truncate">{{ $file->original_name }}</span>
|
||
<span class="text-xs text-gray-400 whitespace-nowrap">({{ $file->category->name ?? '' }} · {{ $file->humanSize() }})</span>
|
||
<button type="button" class="ml-auto text-xs text-blue-600 hover:underline flex-shrink-0"
|
||
@click="$dispatch('open-file-preview', @js($file->previewData()))">
|
||
{{ __('ui.preview') }}
|
||
</button>
|
||
</label>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
|
||
{{-- Aus Bibliothek anhängen --}}
|
||
<button type="button" @click="showPicker = !showPicker" class="text-sm text-blue-600 hover:text-blue-800 mb-2">
|
||
{{ __('admin.attach_from_library') }} ↓
|
||
</button>
|
||
<div x-show="showPicker" x-cloak class="border border-gray-200 rounded-md p-3 mb-3 max-h-48 overflow-y-auto">
|
||
@php $attachedIds = $team->files->pluck('id')->toArray(); @endphp
|
||
@foreach ($fileCategories as $cat)
|
||
@if ($cat->files->isNotEmpty())
|
||
<p class="text-xs font-semibold text-gray-500 mt-2 first:mt-0 mb-1">{{ $cat->name }}</p>
|
||
@foreach ($cat->files as $libFile)
|
||
@if (!in_array($libFile->id, $attachedIds))
|
||
<label class="flex items-center gap-2 py-0.5 text-sm text-gray-700 hover:bg-gray-50 px-1 rounded">
|
||
<input type="checkbox" name="existing_files[]" value="{{ $libFile->id }}" class="rounded border-gray-300">
|
||
{{ $libFile->original_name }}
|
||
<span class="text-xs text-gray-400">({{ $libFile->humanSize() }})</span>
|
||
</label>
|
||
@endif
|
||
@endforeach
|
||
@endif
|
||
@endforeach
|
||
</div>
|
||
|
||
{{-- Neue Dateien hochladen --}}
|
||
<div class="space-y-2">
|
||
<template x-for="i in newFileCount" :key="i">
|
||
<div class="flex flex-wrap items-center gap-2">
|
||
<input type="file" :name="'new_files[' + (i-1) + ']'" accept=".pdf,.docx,.xlsx,.jpg,.jpeg,.png,.gif,.webp"
|
||
class="flex-1 min-w-[200px] text-sm px-3 py-1.5 border border-gray-300 rounded-md file:mr-3 file:py-1 file:px-3 file:rounded-md file:border-0 file:text-sm file:bg-gray-100 file:text-gray-700">
|
||
<select :name="'new_file_categories[' + (i-1) + ']'" required
|
||
class="px-2 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500">
|
||
<option value="">{{ __('admin.select_category') }}</option>
|
||
@foreach ($fileCategories as $cat)
|
||
<option value="{{ $cat->id }}">{{ $cat->name }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<button type="button" @click="newFileCount++" class="mt-2 text-sm text-blue-600 hover:text-blue-800">
|
||
+ {{ __('admin.upload_new_file') }}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
{{-- File preview modal --}}
|
||
<x-file-preview-modal />
|
||
|
||
@push('scripts')
|
||
<script>
|
||
function playerTeamSwitcher() {
|
||
return {
|
||
savingId: null,
|
||
savedId: null,
|
||
errorId: null,
|
||
async switchTeam(playerId, newTeamId, selectEl) {
|
||
if (parseInt(newTeamId) === @js($team->id)) return;
|
||
|
||
this.savingId = playerId;
|
||
this.savedId = null;
|
||
this.errorId = null;
|
||
|
||
try {
|
||
const res = await fetch(@js(route('admin.teams.update-player-team', $team)), {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({ player_id: playerId, new_team_id: newTeamId }),
|
||
});
|
||
|
||
if (res.ok) {
|
||
this.savedId = playerId;
|
||
this.savingId = null;
|
||
setTimeout(() => window.location.reload(), 500);
|
||
} else {
|
||
this.errorId = playerId;
|
||
this.savingId = null;
|
||
selectEl.value = @js($team->id);
|
||
setTimeout(() => { if (this.errorId === playerId) this.errorId = null; }, 3000);
|
||
}
|
||
} catch (e) {
|
||
this.errorId = playerId;
|
||
this.savingId = null;
|
||
selectEl.value = @js($team->id);
|
||
console.error(e);
|
||
}
|
||
}
|
||
};
|
||
}
|
||
</script>
|
||
@endpush
|
||
</x-layouts.admin>
|