- 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>
477 lines
30 KiB
PHP
Executable File
477 lines
30 KiB
PHP
Executable File
<x-layouts.app :title="$event->title">
|
||
{{-- Cancelled Banner --}}
|
||
@if ($event->status === \App\Enums\EventStatus::Cancelled)
|
||
<div class="bg-red-600 text-white text-center py-3 px-4 rounded-lg mb-6 font-semibold">
|
||
{{ __('events.cancelled_banner') }}
|
||
</div>
|
||
@endif
|
||
|
||
@if ($event->status === \App\Enums\EventStatus::Draft)
|
||
<div class="bg-yellow-500 text-white text-center py-3 px-4 rounded-lg mb-6 font-semibold">
|
||
{{ __('events.draft_banner') }}
|
||
</div>
|
||
@endif
|
||
|
||
{{-- Header --}}
|
||
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
||
<div class="flex items-start justify-between flex-wrap gap-2">
|
||
<div>
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<x-event-type-badge :type="$event->type" />
|
||
<span class="text-xs text-gray-500">{{ $event->team->name }}</span>
|
||
</div>
|
||
<h1 class="text-2xl font-bold {{ $event->status === \App\Enums\EventStatus::Cancelled ? 'line-through text-gray-400' : 'text-gray-900' }}">
|
||
{{ $event->title }}
|
||
</h1>
|
||
@if ($event->type->isGameType() && $event->opponent)
|
||
<p class="text-sm text-gray-600 mt-1">
|
||
{{ __('events.vs') }} {{ $event->opponent }}
|
||
@if ($event->hasScore())
|
||
<span class="font-semibold ml-2">{{ $event->scoreDisplay() }}</span>
|
||
@endif
|
||
</p>
|
||
@endif
|
||
</div>
|
||
@if (auth()->user()->canAccessAdminPanel())
|
||
<a href="{{ route('admin.events.edit', $event) }}" class="text-sm text-blue-600 hover:underline">{{ __('ui.edit') }}</a>
|
||
@endif
|
||
</div>
|
||
|
||
<div class="mt-4 space-y-2 text-sm text-gray-700">
|
||
<div class="flex items-center gap-2">
|
||
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||
<span>
|
||
{{ $event->start_at->translatedFormat(__('ui.date_format_long')) }} {{ __('ui.clock') }}
|
||
@if ($event->end_at)
|
||
– {{ $event->end_at->format('H:i') }} {{ __('ui.clock') }}
|
||
@endif
|
||
</span>
|
||
</div>
|
||
|
||
@if ($event->location_name || $event->address_text)
|
||
<div class="flex items-start gap-2">
|
||
<svg class="w-4 h-4 text-gray-400 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||
<div>
|
||
@if ($event->location_name)
|
||
<span class="font-medium">{{ $event->location_name }}</span><br>
|
||
@endif
|
||
@if ($event->address_text)
|
||
<span class="text-gray-500">{{ $event->address_text }}</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Navigation Button --}}
|
||
@if ($event->hasCoordinates())
|
||
<div class="mt-4">
|
||
<a href="https://www.openstreetmap.org/directions?engine=graphhopper_car&route=&to={{ $event->location_lat }}%2C{{ $event->location_lng }}"
|
||
class="hidden sm:inline-flex items-center gap-1 bg-blue-600 text-white text-sm px-4 py-2 rounded-md hover:bg-blue-700"
|
||
target="_blank" rel="noopener">
|
||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"/></svg>
|
||
{{ __('events.plan_route') }}
|
||
</a>
|
||
<a href="geo:{{ $event->location_lat }},{{ $event->location_lng }}"
|
||
class="sm:hidden inline-flex items-center gap-1 bg-blue-600 text-white text-sm px-4 py-2 rounded-md hover:bg-blue-700">
|
||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"/></svg>
|
||
{{ __('events.start_navigation') }}
|
||
</a>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Karte --}}
|
||
@if ($event->hasCoordinates())
|
||
<div class="bg-white rounded-lg shadow mb-6 overflow-hidden relative z-0">
|
||
<div id="map" class="h-64 w-full"></div>
|
||
</div>
|
||
@endif
|
||
|
||
{{-- Beschreibung --}}
|
||
@if ($event->description_html)
|
||
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
||
<h2 class="text-lg font-semibold mb-3">{{ __('events.description') }}</h2>
|
||
<div class="prose prose-sm max-w-none text-gray-700">
|
||
{!! app(\App\Services\HtmlSanitizerService::class)->sanitize($event->description_html) !!}
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
{{-- Dateien --}}
|
||
@if ($event->files->isNotEmpty())
|
||
<div id="files" class="bg-white rounded-lg shadow p-6 mb-6 overflow-hidden">
|
||
<h2 class="text-lg font-semibold mb-3">{{ __('events.files') }}</h2>
|
||
<div class="grid gap-2" x-data>
|
||
@foreach ($event->files->sortBy('category.name') as $file)
|
||
<div @click="$dispatch('open-file-preview', @js($file->previewData()))"
|
||
class="flex items-center gap-3 border border-gray-100 rounded-md px-3 sm:px-4 py-3 hover:bg-gray-50 cursor-pointer transition-colors min-w-0">
|
||
@if ($file->isImage())
|
||
<img src="{{ route('files.preview', $file) }}" alt="" class="flex-shrink-0 w-9 h-9 rounded-lg object-cover bg-gray-100" loading="lazy">
|
||
@elseif ($file->isPdf())
|
||
<div class="flex-shrink-0 w-9 h-9 rounded-lg flex items-center justify-center bg-red-100 text-red-600 font-bold text-xs">
|
||
PDF
|
||
</div>
|
||
@else
|
||
<div class="flex-shrink-0 w-9 h-9 rounded-lg flex items-center justify-center
|
||
{{ match($file->iconType()) {
|
||
'word' => 'bg-blue-100 text-blue-600',
|
||
'excel' => 'bg-green-100 text-green-600',
|
||
default => 'bg-gray-100 text-gray-600',
|
||
} }}">
|
||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zm-1 2l5 5h-5V4z"/></svg>
|
||
</div>
|
||
@endif
|
||
<div class="min-w-0 flex-1">
|
||
<p class="text-sm font-medium text-gray-900 truncate">{{ $file->original_name }}</p>
|
||
<div class="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-gray-500 mt-0.5">
|
||
<span class="inline-flex items-center px-1.5 py-0.5 rounded bg-gray-100 text-gray-600 font-medium">{{ $file->category->name }}</span>
|
||
<span>{{ $file->humanSize() }}</span>
|
||
</div>
|
||
</div>
|
||
<svg class="w-4 h-4 text-gray-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
|
||
<x-file-preview-modal />
|
||
@endif
|
||
|
||
{{-- Teilnehmer --}}
|
||
<div id="participants" class="bg-white rounded-lg shadow p-6 mb-6">
|
||
<h2 class="text-lg font-semibold mb-3">{{ __('events.participants') }}</h2>
|
||
|
||
@php
|
||
$yesCount = $event->participants->where('status', \App\Enums\ParticipantStatus::Yes)->count();
|
||
$noCount = $event->participants->where('status', \App\Enums\ParticipantStatus::No)->count();
|
||
$openCount = $event->participants->where('status', \App\Enums\ParticipantStatus::Unknown)->count();
|
||
@endphp
|
||
|
||
<div class="flex gap-4 text-sm mb-4">
|
||
<span class="text-green-700 font-medium">{{ $yesCount }} {{ __('events.confirmations') }}</span>
|
||
<span class="text-red-700 font-medium">{{ $noCount }} {{ __('events.rejections') }}</span>
|
||
<span class="text-gray-500">{{ $openCount }} {{ __('events.open_responses') }}</span>
|
||
</div>
|
||
|
||
<div class="divide-y divide-gray-100">
|
||
@if ($event->type === \App\Enums\EventType::Meeting)
|
||
{{-- Besprechung: User-basierte Teilnehmer --}}
|
||
@foreach ($event->participants->sortBy(fn($p) => $p->user->name ?? '') as $participant)
|
||
<div class="py-2 flex items-center justify-between gap-2 flex-wrap">
|
||
<div class="flex items-center gap-2">
|
||
<img src="{{ $participant->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="text-sm font-medium text-gray-900">{{ $participant->user->name ?? '–' }}</span>
|
||
</div>
|
||
|
||
@if (!auth()->user()->isDsgvoRestricted())
|
||
@if ($participant->user_id === auth()->id() || auth()->user()->canAccessAdminPanel())
|
||
@if ($event->status !== \App\Enums\EventStatus::Cancelled)
|
||
<form method="POST" action="{{ route('participants.update', $event) }}" class="flex gap-1">
|
||
@csrf
|
||
<input type="hidden" name="user_id" value="{{ $participant->user_id }}">
|
||
<button type="submit" name="status" value="yes"
|
||
class="px-2 py-1 text-xs rounded-md {{ $participant->status === \App\Enums\ParticipantStatus::Yes ? 'bg-green-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-green-100' }}">
|
||
{{ __('ui.yes') }}
|
||
</button>
|
||
<button type="submit" name="status" value="no"
|
||
class="px-2 py-1 text-xs rounded-md {{ $participant->status === \App\Enums\ParticipantStatus::No ? 'bg-red-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-red-100' }}">
|
||
{{ __('ui.no') }}
|
||
</button>
|
||
<button type="submit" name="status" value="unknown"
|
||
class="px-2 py-1 text-xs rounded-md {{ $participant->status === \App\Enums\ParticipantStatus::Unknown ? 'bg-gray-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200' }}">
|
||
{{ __('ui.open') }}
|
||
</button>
|
||
</form>
|
||
@endif
|
||
@endif
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
@else
|
||
{{-- Reguläre Events: Spieler-basierte Teilnehmer --}}
|
||
@foreach ($event->participants->sortBy('player.last_name') as $participant)
|
||
<div class="py-2 flex items-center justify-between gap-2 flex-wrap">
|
||
<div class="flex items-center gap-2">
|
||
<img src="{{ $participant->player->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="text-sm font-medium text-gray-900">{{ $participant->player->full_name }}</span>
|
||
</div>
|
||
|
||
@if (!auth()->user()->isDsgvoRestricted())
|
||
@if ($userChildIds->contains($participant->player_id) || auth()->user()->canAccessAdminPanel())
|
||
@if ($event->status !== \App\Enums\EventStatus::Cancelled)
|
||
<form method="POST" action="{{ route('participants.update', $event) }}" class="flex gap-1">
|
||
@csrf
|
||
<input type="hidden" name="player_id" value="{{ $participant->player_id }}">
|
||
<button type="submit" name="status" value="yes"
|
||
class="px-2 py-1 text-xs rounded-md {{ $participant->status === \App\Enums\ParticipantStatus::Yes ? 'bg-green-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-green-100' }}">
|
||
{{ __('ui.yes') }}
|
||
</button>
|
||
<button type="submit" name="status" value="no"
|
||
class="px-2 py-1 text-xs rounded-md {{ $participant->status === \App\Enums\ParticipantStatus::No ? 'bg-red-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-red-100' }}">
|
||
{{ __('ui.no') }}
|
||
</button>
|
||
<button type="submit" name="status" value="unknown"
|
||
class="px-2 py-1 text-xs rounded-md {{ $participant->status === \App\Enums\ParticipantStatus::Unknown ? 'bg-gray-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200' }}">
|
||
{{ __('ui.open') }}
|
||
</button>
|
||
</form>
|
||
@endif
|
||
@endif
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Catering --}}
|
||
@if ($event->type->hasCatering())
|
||
<div id="catering" class="bg-white rounded-lg shadow p-6 mb-6">
|
||
<h2 class="text-lg font-semibold mb-3">{{ __('events.catering') }}</h2>
|
||
|
||
@if ($event->status !== \App\Enums\EventStatus::Cancelled && !auth()->user()->isDsgvoRestricted())
|
||
<form method="POST" action="{{ route('catering.update', $event) }}" class="mb-4">
|
||
@csrf
|
||
<div class="flex flex-col sm:flex-row gap-3">
|
||
<div class="flex gap-1">
|
||
<button type="submit" name="status" value="yes"
|
||
class="px-3 py-1.5 text-sm rounded-md {{ $myCatering && $myCatering->status === \App\Enums\CateringStatus::Yes ? 'bg-green-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-green-100' }}">
|
||
{{ __('events.bring_something') }}
|
||
</button>
|
||
<button type="submit" name="status" value="no"
|
||
class="px-3 py-1.5 text-sm rounded-md {{ $myCatering && $myCatering->status === \App\Enums\CateringStatus::No ? 'bg-red-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-red-100' }}">
|
||
{{ __('events.bring_nothing') }}
|
||
</button>
|
||
</div>
|
||
<input type="text" name="note" placeholder="{{ __('events.catering_note_placeholder') }}" value="{{ $myCatering?->note }}"
|
||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||
</div>
|
||
</form>
|
||
@elseif (auth()->user()->isDsgvoRestricted())
|
||
<p class="text-sm text-orange-600 mb-4">{{ __('ui.dsgvo_restricted_hint') }}</p>
|
||
@endif
|
||
|
||
@php
|
||
$cateringsWithContribution = $event->caterings->where('status', \App\Enums\CateringStatus::Yes);
|
||
$cateringsWithdrawn = (auth()->user()->canAccessAdminPanel() && \App\Models\Setting::isFeatureVisibleFor('catering_history', auth()->user()))
|
||
? $event->caterings->where('status', \App\Enums\CateringStatus::No)
|
||
: collect();
|
||
@endphp
|
||
|
||
@if ($cateringsWithContribution->isNotEmpty() || $cateringsWithdrawn->isNotEmpty())
|
||
<div class="divide-y divide-gray-100">
|
||
@foreach ($cateringsWithContribution as $catering)
|
||
<div class="py-2 text-sm flex items-center gap-2">
|
||
<img src="{{ $catering->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="font-medium text-gray-900">{{ $catering->user->name }}</span>
|
||
@if ($catering->note)
|
||
<span class="text-gray-600">— {{ $catering->note }}</span>
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
@foreach ($cateringsWithdrawn as $catering)
|
||
@php
|
||
$userHistory = $cateringHistory->filter(
|
||
fn ($log) => ($log->properties['new']['user_id'] ?? null) == $catering->user_id
|
||
);
|
||
@endphp
|
||
@if ($userHistory->isNotEmpty())
|
||
{{-- Chronologische Verlaufseinträge --}}
|
||
@foreach ($userHistory as $log)
|
||
@php $newStatus = $log->properties['new']['status'] ?? 'unknown'; @endphp
|
||
<div class="py-2 text-sm flex items-center justify-between gap-2 opacity-40">
|
||
<div class="flex items-center gap-2">
|
||
<img src="{{ $catering->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="font-medium text-gray-400 {{ $newStatus === 'no' ? 'line-through' : '' }}">{{ $catering->user->name }}</span>
|
||
<span class="text-xs text-gray-400">({{ $newStatus === 'yes' ? __('events.signed_up') : __('events.withdrawn') }})</span>
|
||
</div>
|
||
@if (auth()->user()->isStaff())
|
||
<span class="text-xs text-gray-400 whitespace-nowrap">{{ $log->created_at->format('d.m.Y, H:i') }}</span>
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
@else
|
||
{{-- Fallback ohne Verlaufsdaten --}}
|
||
<div class="py-2 text-sm flex items-center justify-between gap-2 opacity-40">
|
||
<div class="flex items-center gap-2">
|
||
<img src="{{ $catering->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="font-medium text-gray-400 line-through">{{ $catering->user->name }}</span>
|
||
<span class="text-xs text-gray-400">({{ __('events.withdrawn') }})</span>
|
||
</div>
|
||
@if (auth()->user()->isStaff())
|
||
<span class="text-xs text-gray-400 whitespace-nowrap">{{ $catering->updated_at->format('d.m.Y, H:i') }}</span>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
@endforeach
|
||
</div>
|
||
@else
|
||
<p class="text-sm text-gray-500">{{ __('events.no_catering_yet') }}</p>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
|
||
{{-- Zeitnehmer --}}
|
||
@if ($event->type->hasTimekeepers())
|
||
<div id="timekeeper" class="bg-white rounded-lg shadow p-6 mb-6">
|
||
<h2 class="text-lg font-semibold mb-3">{{ __('events.timekeeper') }}</h2>
|
||
|
||
@if ($event->status !== \App\Enums\EventStatus::Cancelled && !auth()->user()->isDsgvoRestricted())
|
||
<form method="POST" action="{{ route('timekeeper.update', $event) }}" class="mb-4">
|
||
@csrf
|
||
<div class="flex gap-1">
|
||
<button type="submit" name="status" value="yes"
|
||
class="px-3 py-1.5 text-sm rounded-md {{ $myTimekeeper && $myTimekeeper->status === \App\Enums\CateringStatus::Yes ? 'bg-green-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-green-100' }}">
|
||
{{ __('events.timekeeper_yes') }}
|
||
</button>
|
||
<button type="submit" name="status" value="no"
|
||
class="px-3 py-1.5 text-sm rounded-md {{ $myTimekeeper && $myTimekeeper->status === \App\Enums\CateringStatus::No ? 'bg-red-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-red-100' }}">
|
||
{{ __('events.timekeeper_no') }}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
@elseif (auth()->user()->isDsgvoRestricted())
|
||
<p class="text-sm text-orange-600 mb-4">{{ __('ui.dsgvo_restricted_hint') }}</p>
|
||
@endif
|
||
|
||
@php
|
||
$timekeepersYes = $event->timekeepers->where('status', \App\Enums\CateringStatus::Yes);
|
||
$timekeepersWithdrawn = (auth()->user()->canAccessAdminPanel() && \App\Models\Setting::isFeatureVisibleFor('catering_history', auth()->user()))
|
||
? $event->timekeepers->where('status', \App\Enums\CateringStatus::No)
|
||
: collect();
|
||
@endphp
|
||
|
||
@if ($timekeepersYes->isNotEmpty() || $timekeepersWithdrawn->isNotEmpty())
|
||
<div class="divide-y divide-gray-100">
|
||
@foreach ($timekeepersYes as $tk)
|
||
<div class="py-2 text-sm flex items-center gap-2">
|
||
<img src="{{ $tk->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="font-medium text-gray-900">{{ $tk->user->name }}</span>
|
||
</div>
|
||
@endforeach
|
||
@foreach ($timekeepersWithdrawn as $tk)
|
||
@php
|
||
$tkHistory = $timekeeperHistory->filter(
|
||
fn ($log) => ($log->properties['new']['user_id'] ?? null) == $tk->user_id
|
||
);
|
||
@endphp
|
||
@if ($tkHistory->isNotEmpty())
|
||
@foreach ($tkHistory as $log)
|
||
@php $newStatus = $log->properties['new']['status'] ?? 'unknown'; @endphp
|
||
<div class="py-2 text-sm flex items-center justify-between gap-2 opacity-40">
|
||
<div class="flex items-center gap-2">
|
||
<img src="{{ $tk->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="font-medium text-gray-400 {{ $newStatus === 'no' ? 'line-through' : '' }}">{{ $tk->user->name }}</span>
|
||
<span class="text-xs text-gray-400">({{ $newStatus === 'yes' ? __('events.signed_up') : __('events.withdrawn') }})</span>
|
||
</div>
|
||
@if (auth()->user()->isStaff())
|
||
<span class="text-xs text-gray-400 whitespace-nowrap">{{ $log->created_at->format('d.m.Y, H:i') }}</span>
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
@else
|
||
<div class="py-2 text-sm flex items-center justify-between gap-2 opacity-40">
|
||
<div class="flex items-center gap-2">
|
||
<img src="{{ $tk->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0">
|
||
<span class="font-medium text-gray-400 line-through">{{ $tk->user->name }}</span>
|
||
<span class="text-xs text-gray-400">({{ __('events.withdrawn') }})</span>
|
||
</div>
|
||
@if (auth()->user()->isStaff())
|
||
<span class="text-xs text-gray-400 whitespace-nowrap">{{ $tk->updated_at->format('d.m.Y, H:i') }}</span>
|
||
@endif
|
||
</div>
|
||
@endif
|
||
@endforeach
|
||
</div>
|
||
@else
|
||
<p class="text-sm text-gray-500">{{ __('events.no_timekeeper_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>
|
||
|
||
@if ($event->status !== \App\Enums\EventStatus::Cancelled && !auth()->user()->isDsgvoRestricted())
|
||
<form method="POST" action="{{ route('comments.store', $event) }}" class="mb-4">
|
||
@csrf
|
||
<div class="flex gap-2">
|
||
<input type="text" name="body" placeholder="{{ __('events.comment_placeholder') }}" required
|
||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500" maxlength="1000">
|
||
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-md text-sm hover:bg-blue-700">{{ __('ui.send') }}</button>
|
||
</div>
|
||
@error('body')
|
||
<p class="text-red-600 text-xs mt-1">{{ $message }}</p>
|
||
@enderror
|
||
</form>
|
||
@elseif (auth()->user()->isDsgvoRestricted())
|
||
<p class="text-sm text-orange-600 mb-4">{{ __('ui.dsgvo_restricted_hint') }}</p>
|
||
@endif
|
||
|
||
@if ($event->comments->isEmpty())
|
||
<p class="text-sm text-gray-500">{{ __('events.no_comments') }}</p>
|
||
@else
|
||
<div class="space-y-3">
|
||
@foreach ($event->comments->sortByDesc('created_at') as $comment)
|
||
@if ($comment->isDeleted() && !auth()->user()->isStaff())
|
||
@continue
|
||
@endif
|
||
<div class="flex justify-between items-start gap-2 {{ $comment->isDeleted() ? 'opacity-50' : '' }}">
|
||
<div class="flex gap-2">
|
||
<img src="{{ $comment->user->getAvatarUrl() ?? asset('images/profil_empty.png') }}" alt="" class="w-6 h-6 rounded-full object-cover flex-shrink-0 mt-0.5">
|
||
<div>
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-sm font-medium text-gray-900">{{ $comment->user->name }}</span>
|
||
<span class="text-xs text-gray-400">{{ $comment->created_at->diffForHumans() }}</span>
|
||
</div>
|
||
@if ($comment->isDeleted())
|
||
<p class="text-sm text-gray-400 italic">{{ $comment->body }} <span class="text-xs">({{ __('events.deleted_label') }})</span></p>
|
||
@else
|
||
<p class="text-sm text-gray-700 mt-0.5">{{ $comment->body }}</p>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@if (!$comment->isDeleted() && auth()->user()->isStaff())
|
||
<form method="POST" action="{{ route('admin.comments.destroy', $comment) }}" onsubmit="return confirm(@js(__('events.confirm_delete_comment')))">
|
||
@csrf
|
||
@method('DELETE')
|
||
<button type="submit" class="text-xs text-red-500 hover:text-red-700">{{ __('ui.delete') }}</button>
|
||
</form>
|
||
@endif
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<div class="mb-6">
|
||
<a href="{{ route('events.index') }}" class="text-sm text-blue-600 hover:underline">← {{ __('events.back_to_list') }}</a>
|
||
</div>
|
||
|
||
{{-- Leaflet.js Karte --}}
|
||
@if ($event->hasCoordinates())
|
||
@push('styles')
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||
@endpush
|
||
@push('scripts')
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const map = L.map('map').setView([@js($event->location_lat), @js($event->location_lng)], 15);
|
||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||
}).addTo(map);
|
||
const popupText = document.createElement('span');
|
||
popupText.textContent = @json($event->location_name ?? $event->title);
|
||
L.marker([@js($event->location_lat), @js($event->location_lng)])
|
||
.addTo(map)
|
||
.bindPopup(popupText)
|
||
.openPopup();
|
||
});
|
||
</script>
|
||
@endpush
|
||
@endif
|
||
</x-layouts.app>
|