Spielerpositionen, Statistiken, Fahrgemeinschaften, Spielfeld-Visualisierung

- PlayerPosition Enum (7 Handball-Positionen) mit Label/ShortLabel
- Spielerstatistik pro Spiel (Tore, Würfe, TW-Paraden, Bemerkung)
- Position-Dropdown in Spieler-Editor und Event-Stats-Formular
- Statistik-Seite: TW zuerst, Trennlinie, Feldspieler, Position-Badges
- Spielfeld-SVG mit Ampel-Performance (grün/gelb/rot)
- Anklickbare Spieler im Spielfeld öffnen Detail-Modal
- Fahrgemeinschaften (Anbieten, Zuordnen, Zurückziehen)
- Übersetzungen in allen 6 Sprachen (de, en, pl, ru, ar, tr)
- .gitignore für Laravel hinzugefügt
- Demo-Daten mit Positionen und Statistiken

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 11:47:34 +01:00
parent 2e24a40d68
commit ad60e7a9f9
46 changed files with 2041 additions and 86 deletions

View File

@@ -390,6 +390,149 @@
</div>
@endif
{{-- Fahrgemeinschaften --}}
@if ($event->type->hasCarpool())
<div id="carpool" class="bg-white rounded-lg shadow p-6 mb-6">
<h2 class="text-lg font-semibold mb-3">{{ __('events.carpool') }}</h2>
@if ($event->status !== \App\Enums\EventStatus::Cancelled && !auth()->user()->isDsgvoRestricted())
{{-- Eigenes Angebot --}}
@if ($myCarpool)
<div class="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div class="flex items-center gap-2 mb-3">
<span class="text-sm font-semibold text-blue-700">{{ __('events.carpool_my_offer') }}</span>
</div>
<form method="POST" action="{{ route('carpool.offer', $event) }}" class="flex flex-col sm:flex-row gap-3 mb-2">
@csrf
<div class="flex items-center gap-2">
<label for="carpool-seats" class="text-sm text-gray-700 whitespace-nowrap">{{ __('events.carpool_seats') }}:</label>
<input type="number" name="seats" id="carpool-seats" min="1" max="9" value="{{ $myCarpool->seats }}"
class="w-16 px-2 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<input type="text" name="note" placeholder="{{ __('events.carpool_note_placeholder') }}" value="{{ $myCarpool->note }}"
class="flex-1 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<button type="submit" class="px-3 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 whitespace-nowrap">
{{ __('events.carpool_update') }}
</button>
</form>
<form method="POST" action="{{ route('carpool.withdraw', $event) }}" class="inline"
onsubmit="return confirm(@js(__('events.carpool_withdraw_confirm')))">
@csrf
<button type="submit" class="text-sm text-red-600 hover:text-red-800 hover:underline">
{{ __('events.carpool_withdraw') }}
</button>
</form>
</div>
@else
<form method="POST" action="{{ route('carpool.offer', $event) }}" class="mb-4">
@csrf
<div class="flex flex-col sm:flex-row gap-3">
<div class="flex items-center gap-2">
<label for="carpool-seats-new" class="text-sm text-gray-700 whitespace-nowrap">{{ __('events.carpool_seats') }}:</label>
<input type="number" name="seats" id="carpool-seats-new" min="1" max="9" value="3"
class="w-16 px-2 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<input type="text" name="note" placeholder="{{ __('events.carpool_note_placeholder') }}"
class="flex-1 px-3 py-1.5 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<button type="submit" class="px-3 py-1.5 text-sm font-medium text-white bg-green-600 rounded-md hover:bg-green-700 whitespace-nowrap">
{{ __('events.carpool_offer') }}
</button>
</div>
</form>
@endif
@error('seats') <p class="text-red-600 text-xs mb-3">{{ $message }}</p> @enderror
@error('carpool') <p class="text-red-600 text-xs mb-3">{{ $message }}</p> @enderror
@elseif (auth()->user()->isDsgvoRestricted())
<p class="text-sm text-orange-600 mb-4">{{ __('ui.dsgvo_restricted_hint') }}</p>
@endif
{{-- Liste aller Fahrten --}}
@if ($event->carpools->isNotEmpty())
<div class="space-y-3">
@foreach ($event->carpools as $carpool)
@php
$isOwn = $carpool->user_id === auth()->id();
$passengerCount = $carpool->passengers->count();
$remaining = $carpool->seats - $passengerCount;
$fillPercent = $carpool->seats > 0 ? ($passengerCount / $carpool->seats) * 100 : 0;
$assignedChildIds = $carpool->passengers->pluck('player_id')->toArray();
$assignableChildren = $userChildIds->filter(fn ($id) => !in_array($id, $assignedChildIds));
@endphp
<div class="p-4 border rounded-lg {{ $isOwn ? 'border-blue-300 bg-blue-50/30' : 'border-gray-200' }}">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<img src="{{ $carpool->driver->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-7 h-7 rounded-full object-cover flex-shrink-0">
<span class="text-sm font-semibold text-gray-900">{{ $carpool->driver->name }}</span>
@if ($isOwn)
<span class="text-xs bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded">{{ __('events.carpool_my_offer') }}</span>
@endif
</div>
<div class="text-sm text-gray-600">
{{ __('events.carpool_seats_count', ['free' => $remaining, 'total' => $carpool->seats]) }}
</div>
</div>
{{-- Fortschrittsbalken --}}
<div class="w-full bg-gray-200 rounded-full h-1.5 mb-2">
<div class="h-1.5 rounded-full transition-all {{ $fillPercent >= 100 ? 'bg-red-500' : ($fillPercent >= 60 ? 'bg-yellow-500' : 'bg-green-500') }}"
style="width: {{ min($fillPercent, 100) }}%"></div>
</div>
@if ($carpool->note)
<p class="text-xs text-gray-500 mb-2">{{ $carpool->note }}</p>
@endif
{{-- Passagiere --}}
@if ($carpool->passengers->isNotEmpty())
<div class="flex flex-wrap gap-1.5 mb-2">
@foreach ($carpool->passengers as $passenger)
<span class="inline-flex items-center gap-1 bg-gray-100 text-gray-700 text-xs px-2 py-1 rounded-full">
{{ $passenger->player->full_name }}
@if ($event->status !== \App\Enums\EventStatus::Cancelled && ($passenger->added_by === auth()->id() || auth()->user()->isAdmin()))
<form method="POST" action="{{ route('carpool.leave', $event) }}" class="inline">
@csrf
<input type="hidden" name="carpool_id" value="{{ $carpool->id }}">
<input type="hidden" name="player_id" value="{{ $passenger->player_id }}">
<button type="submit" class="text-red-400 hover:text-red-600 ml-0.5" title="{{ __('events.carpool_leave') }}">
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</form>
@endif
</span>
@endforeach
</div>
@endif
{{-- Kinder zuordnen --}}
@if ($event->status !== \App\Enums\EventStatus::Cancelled && !auth()->user()->isDsgvoRestricted() && $remaining > 0 && $assignableChildren->isNotEmpty())
<div class="flex flex-wrap gap-1.5">
@foreach ($assignableChildren as $childId)
@php $child = $userChildren->firstWhere('id', $childId); @endphp
@if ($child)
<form method="POST" action="{{ route('carpool.join', $event) }}" class="inline">
@csrf
<input type="hidden" name="carpool_id" value="{{ $carpool->id }}">
<input type="hidden" name="player_id" value="{{ $childId }}">
<button type="submit" class="inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full border border-green-300 text-green-700 bg-green-50 hover:bg-green-100 transition">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
{{ $child->full_name }}
</button>
</form>
@endif
@endforeach
</div>
@elseif ($remaining <= 0 && !$isOwn)
<p class="text-xs text-red-500">{{ __('events.carpool_full') }}</p>
@endif
</div>
@endforeach
</div>
@else
<p class="text-sm text-gray-500">{{ __('events.no_carpool_yet') }}</p>
@endif
</div>
@endif
{{-- Kommentare --}}
<div id="comments" class="bg-white rounded-lg shadow p-6 mb-6">
<h2 class="text-lg font-semibold mb-3">{{ __('events.comments') }}</h2>