Files
WebAPP/resources/views/admin/teams/edit.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

287 lines
17 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.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">&#10003;</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 ?? '' }} &middot; {{ $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') }} &darr;
</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>