sortBy === $column) { $this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc'; } else { $this->sortBy = $column; $this->sortDir = 'asc'; } $this->resetPage(); } public function updatedSearch(): void { $this->resetPage(); } public function updatedStatusFilter(): void { $this->resetPage(); } public function updatedPortalFilter(): void { $this->resetPage(); } public function updatedLanguageFilter(): void { $this->resetPage(); } public function updatedCategoryFilter(): void { $this->resetPage(); } public function updatedUserFilter(): void { $this->resetPage(); } public function updatedCompanyFilter(): void { $this->resetPage(); } public function updatedContactFilter(): void { $this->resetPage(); } public function clearUserFilter(): void { $this->userFilter = 'all'; $this->userLookup = ''; $this->resetPage(); } public function clearCompanyFilter(): void { $this->companyFilter = 'all'; $this->companyLookup = ''; $this->resetPage(); } public function clearContactFilter(): void { $this->contactFilter = 'all'; $this->contactLookup = ''; $this->resetPage(); } public function resetEntityFilters(): void { $this->clearUserFilter(); $this->clearCompanyFilter(); $this->clearContactFilter(); } public function setView(string $view): void { $this->statusFilter = $view; $this->resetPage(); } public function resetFilters(): void { $this->search = ''; $this->statusFilter = 'all'; $this->portalFilter = 'all'; $this->languageFilter = 'all'; $this->categoryFilter = 'all'; $this->clearUserFilter(); $this->clearCompanyFilter(); $this->clearContactFilter(); $this->resetPage(); } public function publish(int $id): void { $pr = PressRelease::withoutGlobalScopes()->findOrFail($id); try { app(PressReleaseService::class)->publish($pr); } catch (BlacklistViolationException $e) { Flux::toast( heading: __('Automatisch abgelehnt'), text: __('Unzulässiges Wort gefunden: ":word".', ['word' => $e->word]), variant: 'danger', duration: 8000, ); return; } catch (\LogicException $e) { Flux::toast(text: $e->getMessage(), variant: 'danger'); return; } Flux::toast(text: __('Pressemitteilung veröffentlicht.'), variant: 'success'); } public function reject(int $id): void { $pr = PressRelease::withoutGlobalScopes()->findOrFail($id); try { app(PressReleaseService::class)->reject($pr, __('Bitte überarbeiten Sie die Pressemitteilung.')); } catch (\LogicException $e) { Flux::toast(text: $e->getMessage(), variant: 'danger'); return; } Flux::toast(text: __('Pressemitteilung abgelehnt.'), variant: 'warning'); } public function archive(int $id): void { $pr = PressRelease::withoutGlobalScopes()->findOrFail($id); try { app(PressReleaseService::class)->archive($pr); } catch (\LogicException $e) { Flux::toast(text: $e->getMessage(), variant: 'danger'); return; } Flux::toast(text: __('Pressemitteilung archiviert.'), variant: 'success'); } public function with(): array { $query = PressRelease::withoutGlobalScopes() ->with(['company:id,name', 'category.translations', 'user:id,name']) ->when(filled($this->search), function ($q): void { $term = trim($this->search); $q->where(function ($q) use ($term): void { if ($this->supportsFullTextSearch($term)) { $q->whereFullText(['title', 'keywords'], $term) ->orWhereHas('company', fn ($q) => $q->whereFullText(['name', 'email', 'slug'], $term)); return; } $q->where('title', 'like', '%'.$term.'%') ->orWhere('keywords', 'like', '%'.$term.'%') ->orWhereHas('company', fn ($q) => $q->where('name', 'like', '%'.$term.'%')); }); }) ->when($this->statusFilter !== 'all', fn ($q) => $q->where('status', $this->statusFilter)) ->when($this->portalFilter !== 'all', fn ($q) => $q->where('portal', $this->portalFilter)) ->when($this->languageFilter !== 'all', fn ($q) => $q->where('language', $this->languageFilter)) ->when($this->categoryFilter !== 'all', fn ($q) => $q->where('category_id', (int) $this->categoryFilter)) ->when($this->userFilter !== 'all', fn ($q) => $q->where('user_id', (int) $this->userFilter)) ->when($this->companyFilter !== 'all', fn ($q) => $q->where('company_id', (int) $this->companyFilter)) ->when($this->contactFilter !== 'all', fn ($q) => $q->whereHas('contacts', fn ($contactQuery) => $contactQuery->where('contacts.id', (int) $this->contactFilter))) ->orderBy(in_array($this->sortBy, ['title', 'status', 'portal', 'hits', 'created_at']) ? $this->sortBy : 'created_at', $this->sortDir) ->paginate(50); return [ 'pressReleases' => $query, 'stats' => $this->pressReleaseStats(), 'statusOptions' => PressReleaseStatus::cases(), 'portalOptions' => Portal::cases(), 'categoryOptions' => $this->categoryOptions(), 'userLookupResults' => $this->userLookupResults(), 'companyLookupResults' => $this->companyLookupResults(), 'contactLookupResults' => $this->contactLookupResults(), ]; } /** * @return array{total: int, published: int, review: int, draft: int, rejected: int, archived: int} */ private function pressReleaseStats(): array { return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::PressReleaseStats, AdminPerformanceCache::StatsTtl, function (): array { $stats = PressRelease::withoutGlobalScopes() ->toBase() ->selectRaw('COUNT(*) as total') ->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as published', [PressReleaseStatus::Published->value]) ->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as review', [PressReleaseStatus::Review->value]) ->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as draft', [PressReleaseStatus::Draft->value]) ->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as rejected', [PressReleaseStatus::Rejected->value]) ->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as archived', [PressReleaseStatus::Archived->value]) ->first(); return [ 'total' => (int) ($stats->total ?? 0), 'published' => (int) ($stats->published ?? 0), 'review' => (int) ($stats->review ?? 0), 'draft' => (int) ($stats->draft ?? 0), 'rejected' => (int) ($stats->rejected ?? 0), 'archived' => (int) ($stats->archived ?? 0), ]; }); } private function categoryOptions() { return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::PressReleaseCategoryOptions, AdminPerformanceCache::OptionsTtl, fn () => Category::query() ->select(['id', 'is_active']) ->with(['translations:id,category_id,locale,name,slug']) ->where('is_active', true) ->orderBy('id') ->get()); } private function userLookupResults() { $term = trim($this->userLookup); if ($term === '' && $this->userFilter === 'all') { return collect(); } return User::query() ->select(['id', 'name', 'email']) ->where(function ($query) use ($term): void { if ($this->userFilter !== 'all') { $query->where('id', (int) $this->userFilter); } if ($term !== '') { $query->orWhere(function ($searchQuery) use ($term): void { $searchQuery ->where('name', 'like', '%'.$term.'%') ->orWhere('email', 'like', '%'.$term.'%'); }); } }) ->orderBy('name') ->limit(20) ->get(); } private function companyLookupResults() { $term = trim($this->companyLookup); if ($term === '' && $this->companyFilter === 'all') { return collect(); } return Company::withoutGlobalScopes() ->select(['id', 'name', 'slug', 'email']) ->where(function ($query) use ($term): void { if ($this->companyFilter !== 'all') { $query->where('id', (int) $this->companyFilter); } if ($term !== '') { $query->orWhere(function ($searchQuery) use ($term): void { $searchQuery ->where('name', 'like', '%'.$term.'%') ->orWhere('slug', 'like', '%'.$term.'%') ->orWhere('email', 'like', '%'.$term.'%'); }); } }) ->orderBy('name') ->limit(20) ->get(); } private function contactLookupResults() { $term = trim($this->contactLookup); if ($term === '' && $this->contactFilter === 'all') { return collect(); } return Contact::withoutGlobalScopes() ->select(['id', 'company_id', 'first_name', 'last_name', 'email']) ->with('company:id,name') ->where(function ($query) use ($term): void { if ($this->contactFilter !== 'all') { $query->where('id', (int) $this->contactFilter); } if ($term !== '') { $query->orWhere(function ($searchQuery) use ($term): void { $searchQuery ->where('first_name', 'like', '%'.$term.'%') ->orWhere('last_name', 'like', '%'.$term.'%') ->orWhere('email', 'like', '%'.$term.'%'); }); } }) ->orderBy('last_name') ->orderBy('first_name') ->limit(20) ->get(); } private function supportsFullTextSearch(string $term): bool { return mb_strlen($term) >= 3 && in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true); } }; ?>
{{-- Flash-Banner ersetzt durch im Layout. --}} {{-- ============== PAGE HEADER ============== --}}
{{ __('Admin Backend') }} {{ __('Content · Pressemitteilungen') }}

{{ __('Pressemitteilungen') }}

{{ __('Übersicht aller PMs beider Portale, mit Filter, Status-Workflow und Schnellaktionen.') }}

{{ __('Neue PM') }}
{{-- ============== KPI-Reihe ============== --}}
{{ now()->format('Y') }} {{ __('über beide Portale') }} {{ __('live') }} {{ __('öffentlich sichtbar') }} {{ __('queue') }} {{ __('redaktionelle Prüfung') }} {{ __('privat') }} {{ __('nicht eingereicht') }}
{{-- ============== SAVED-VIEWS-TABS ============== --}} {{-- ============== FILTER-PANEL ============== --}}
{{ __('Filter & Suche') }}
@foreach ($statusOptions as $s) @endforeach @foreach ($portalOptions as $p) @if ($p !== \App\Enums\Portal::Both) @endif @endforeach @foreach ($categoryOptions as $categoryOption) @php $categoryName = $categoryOption->translations->firstWhere('locale', 'de')?->name ?? '#'.$categoryOption->id; @endphp @endforeach
{{ __('Alle User') }} @foreach($userLookupResults as $userOption) {{ $userOption->name }} · {{ $userOption->email }} @endforeach {{ blank(trim($userLookup)) ? __('Zum Laden Usernamen oder E-Mail eingeben.') : __('Kein User gefunden.') }}
{{ __('Alle Firmen') }} @foreach($companyLookupResults as $companyOption) {{ $companyOption->name }} @if($companyOption->email)· {{ $companyOption->email }}@endif @endforeach {{ blank(trim($companyLookup)) ? __('Zum Laden Firmennamen, Slug oder E-Mail eingeben.') : __('Keine Firma gefunden.') }}
{{ __('Alle Kontakte') }} @foreach($contactLookupResults as $contactOption) @php $contactName = trim(($contactOption->first_name ?? '').' '.($contactOption->last_name ?? '')) ?: __('Kontakt ohne Name'); @endphp {{ $contactName }} @if($contactOption->email)· {{ $contactOption->email }} @endif · {{ $contactOption->company?->name ?? __('Unbekannte Firma') }} @endforeach {{ blank(trim($contactLookup)) ? __('Zum Laden Kontaktname oder E-Mail eingeben.') : __('Kein Kontakt gefunden.') }}
{{-- Active-Chips --}} @php $hasAnyFilter = $search !== '' || $statusFilter !== 'all' || $portalFilter !== 'all' || $languageFilter !== 'all' || $categoryFilter !== 'all' || $userFilter !== 'all' || $companyFilter !== 'all' || $contactFilter !== 'all'; @endphp @if ($hasAnyFilter)
{{ __('Aktiv') }} @if ($search !== '') {{ __('Suche') }}: „{{ \Illuminate\Support\Str::limit($search, 40) }}" @endif @if ($statusFilter !== 'all') @php $statusEnum = \App\Enums\PressReleaseStatus::tryFrom($statusFilter); @endphp {{ __('Status') }}: {{ $statusEnum?->label() ?? $statusFilter }} @endif @if ($portalFilter !== 'all') @php $portalEnum = \App\Enums\Portal::tryFrom($portalFilter); @endphp {{ __('Portal') }}: {{ $portalEnum?->label() ?? $portalFilter }} @endif @if ($languageFilter !== 'all') {{ __('Sprache') }}: {{ strtoupper($languageFilter) }} @endif @if ($categoryFilter !== 'all') @php $activeCat = $categoryOptions->firstWhere('id', (int) $categoryFilter); $activeCatName = $activeCat?->translations->firstWhere('locale', 'de')?->name ?? '#'.$categoryFilter; @endphp {{ __('Kategorie') }}: {{ $activeCatName }} @endif @if ($userFilter !== 'all') @php $activeUser = $userLookupResults->firstWhere('id', (int) $userFilter); @endphp {{ __('User') }}: {{ $activeUser?->name ?? '#'.$userFilter }} @endif @if ($companyFilter !== 'all') @php $activeCompany = $companyLookupResults->firstWhere('id', (int) $companyFilter); @endphp {{ __('Firma') }}: {{ $activeCompany?->name ?? '#'.$companyFilter }} @endif @if ($contactFilter !== 'all') @php $activeContact = $contactLookupResults->firstWhere('id', (int) $contactFilter); $contactName = $activeContact ? trim(($activeContact->first_name ?? '').' '.($activeContact->last_name ?? '')) : ''; $contactName = $contactName !== '' ? $contactName : '#'.$contactFilter; @endphp {{ __('Kontakt') }}: {{ $contactName }} @endif
@endif
{{-- ============== TABELLE-PANEL ============== --}}
{{ __('Alle Pressemitteilungen') }} {{ __(':count Einträge', ['count' => $pressReleases->count()]) }}
{{ __('Status') }} {{ __('Titel') }} {{ __('Portal') }} {{ __('Kategorie') }} {{ __('Datum') }} {{ __('Hits') }} {{ __('Aktionen') }} @forelse($pressReleases as $pr) @php $status = $pr->status->value; $rowClass = match ($status) { 'review' => 'is-row-warn', 'rejected' => 'is-row-err', default => '', }; $badgeClass = match ($status) { 'published' => 'ok', 'review' => 'warn', 'rejected' => 'err', 'archived', 'draft' => 'muted', default => 'hub', }; $portal = $pr->portal?->value ?? 'both'; $showPe = in_array($portal, ['presseecho', 'both'], true); $showBp = in_array($portal, ['businessportal24', 'both'], true); $dateSubLabel = match ($status) { 'published' => __('veröffentlicht'), 'review' => __('eingereicht'), 'rejected' => __('abgelehnt'), 'draft' => __('erstellt'), 'archived' => __('archiviert'), default => __('erstellt'), }; $primaryDate = $status === 'published' && $pr->published_at ? $pr->published_at : $pr->created_at; @endphp
{{ $pr->status->label() }} @if ($pr->status === \App\Enums\PressReleaseStatus::Review) @elseif ($pr->status === \App\Enums\PressReleaseStatus::Published) @endif
{{ $pr->title ?? '—' }}
PM-{{ $pr->id }} @if ($pr->company) · {{ $pr->company->name }} @endif · {{ strtoupper($pr->language) }}
@if ($showPe) presseecho @endif @if ($showBp) businessportal24 @endif
@php $categoryName = $pr->category?->translations->firstWhere('locale', 'de')?->name ?? '–'; @endphp {{ $categoryName }}
{{ $primaryDate?->format('d.m.Y') }}
{{ $dateSubLabel }} · {{ $primaryDate?->format('H:i') }}
@if ($status === 'review' && $pr->scheduled_at && $pr->scheduled_at->isFuture())
{{ __('geplant') }} · {{ $pr->scheduled_at->format('d.m. H:i') }}
@endif @if ($pr->embargo_at && $pr->embargo_at->isFuture())
{{ __('Embargo bis') }} {{ $pr->embargo_at->format('d.m.') }}
@endif
{{ number_format($pr->hits) }}
@if ($pr->status === \App\Enums\PressReleaseStatus::Review)
{{ __('Pressemitteilung veröffentlichen?') }} {{ __('Diese Aktion veröffentlicht die Pressemitteilung ":title".', ['title' => \Illuminate\Support\Str::limit($pr->title, 80)]) }}
{{ __('Abbrechen') }} {{ __('Veröffentlichen') }}
{{ __('Pressemitteilung ablehnen?') }} {{ __('Diese Aktion lehnt die Pressemitteilung ":title" ab.', ['title' => \Illuminate\Support\Str::limit($pr->title, 80)]) }}
{{ __('Abbrechen') }} {{ __('Ablehnen') }}
@endif @if ($pr->status === \App\Enums\PressReleaseStatus::Published)
{{ __('Pressemitteilung archivieren?') }} {{ __('Diese Aktion archiviert die Pressemitteilung ":title".', ['title' => \Illuminate\Support\Str::limit($pr->title, 80)]) }}
{{ __('Abbrechen') }} {{ __('Archivieren') }}
@endif
@empty @if ($hasAnyFilter)

{{ __('Keine Pressemitteilungen mit diesen Filtern') }}

{{ __('Passen Sie die Filter an oder setzen Sie alle Filter zurück, um wieder alle PMs zu sehen.') }}

{{ __('Filter zurücksetzen') }}
@else

{{ __('Noch keine Pressemitteilungen') }}

{{ __('Sobald Kunden PMs anlegen oder einreichen, erscheinen sie hier zur Bearbeitung.') }}

{{ __('Neue PM anlegen') }}
@endif
@endforelse
{{ $pressReleases->links('components.portal.pagination') }}