resetPage();
}
public function updatedStatusFilter(): void
{
$this->resetPage();
}
public function updatedClassificationFilter(): 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->classificationFilter = '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 = $this->findAdminPR($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 = $this->findAdminPR($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 = $this->findAdminPR($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->classificationFilter !== 'all', fn ($q) => $q->where('classification', $this->classificationFilter))
->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();
}
}; ?>
{{-- Flash-Banner ersetzt durch
im Layout. --}}
{{-- ============== PAGE HEADER ============== --}}
{{-- ============== 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 (\App\Enums\PressReleaseClassification::cases() as $c)
@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'
|| $classificationFilter !== '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 ($classificationFilter !== 'all')
@php $classificationEnum = \App\Enums\PressReleaseClassification::tryFrom($classificationFilter); @endphp
{{ __('KI-Bewertung') }}:
{{ $classificationEnum?->label() ?? $classificationFilter }}
@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->classification)
@php
$kiBadge = match ($pr->classification) {
\App\Enums\PressReleaseClassification::Green => 'ok',
\App\Enums\PressReleaseClassification::Yellow => 'warn',
\App\Enums\PressReleaseClassification::Red => 'err',
};
@endphp
{{ __('KI: :label', ['label' => $pr->classification->label()]) }}
@endif
@if (! is_null($pr->content_score) && $pr->content_tier)
@php
$tierBadge = match ($pr->content_tier) {
\App\Enums\PressReleaseContentTier::Hochwertig => 'ok',
\App\Enums\PressReleaseContentTier::Geprueft => 'hub',
\App\Enums\PressReleaseContentTier::Standard => 'muted',
};
@endphp
{{ $pr->content_score }} · {{ $pr->content_tier->label() }}
@endif
@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->scheduledAtLocal()->format('d.m. H:i') }}
@endif
@if ($pr->embargo_at && $pr->embargo_at->isFuture())
{{ __('Embargo bis') }} {{ $pr->embargoAtLocal()->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') }}