sortBy === $column) {
$this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $column;
$this->sortDir = 'asc';
}
$this->resetPage();
}
public function setView(string $view): void
{
$this->statusFilter = $view;
$this->resetPage();
}
public function resetFilters(): void
{
$this->search = '';
$this->statusFilter = 'all';
$this->portalFilter = 'all';
if ($this->companyFilter !== 'all') {
$this->companyFilter = 'all';
}
$this->resetPage();
}
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedStatusFilter(): void
{
$this->resetPage();
}
public function updatedCompanyFilter(): void
{
$this->resetPage();
}
public function updatedPortalFilter(): void
{
$this->resetPage();
}
public function submitForReview(int $id): void
{
$pr = $this->findMyPR($id);
if (!$pr) {
return;
}
// KI-Kennzeichnung (Art. 50 KI-VO): Pflicht-Deklaration aus dem
// Submit-Modal validieren und vor dem Einreichen persistieren.
$this->validateAiDeclaration();
$pr->update($this->aiDeclarationAttributes());
try {
app(PressReleaseService::class)->submitForReview($pr);
Flux::toast(heading: __('Eingereicht'), text: __('Pressemitteilung zur Prüfung eingereicht.'), variant: 'success');
} catch (BlacklistViolationException $e) {
Flux::toast(heading: __('Automatisch abgelehnt'), text: __('Unzulässiges Wort gefunden: ":word".', ['word' => $e->word]), variant: 'danger', duration: 8000);
} catch (AiDeclarationRequiredException | BookingRequiredException | QuotaExceededException $e) {
Flux::toast(text: $e->getMessage(), variant: 'warning', duration: 8000);
} catch (\LogicException $e) {
Flux::toast(text: $e->getMessage(), variant: 'danger');
}
// Deklaration nicht zwischen PMs „verschleppen": Die Listen-Modals
// teilen sich die Properties, daher nach jedem Versuch zurücksetzen.
$this->reset('aiTextDeclaration', 'aiDeclarationConfirmed');
}
public function with(): array
{
$context = app(CustomerCompanyContext::class);
$user = auth()->user();
$selectedCompanyId = $context->selectedCompanyId($user);
// Firmen-Scope der Übersicht: auf eine gewählte Firma einschränken bzw.
// (im globalen Kontext) auf zugeordnete/unzugeordnete PMs. Wird sowohl
// auf die Status-Counts als auch die Liste angewandt, damit beide
// deckungsgleich filtern.
$applyCompanyFilter = function ($query) use ($selectedCompanyId): void {
$query
->when($selectedCompanyId !== null, fn($q) => $q->where('company_id', $selectedCompanyId))
->when($selectedCompanyId === null && $this->companyFilter === 'assigned', fn($q) => $q->whereNotNull('company_id'))
->when($selectedCompanyId === null && $this->companyFilter === 'unassigned', fn($q) => $q->whereNull('company_id'));
};
$statusCountsRaw = PressRelease::accessibleStatusCountsFor($user, $applyCompanyFilter);
$statusCounts = [
'all' => array_sum($statusCountsRaw),
PressReleaseStatus::Published->value => $statusCountsRaw[PressReleaseStatus::Published->value] ?? 0,
PressReleaseStatus::Draft->value => $statusCountsRaw[PressReleaseStatus::Draft->value] ?? 0,
PressReleaseStatus::Review->value => $statusCountsRaw[PressReleaseStatus::Review->value] ?? 0,
PressReleaseStatus::Rejected->value => $statusCountsRaw[PressReleaseStatus::Rejected->value] ?? 0,
PressReleaseStatus::Archived->value => $statusCountsRaw[PressReleaseStatus::Archived->value] ?? 0,
];
// Der Firmen-Scope plus die Suchleiste/Status-/Portal-Filter werden in
// beide UNION-Zweige gezogen (siehe PressRelease::accessiblePageFor()).
$applyListFilters = function ($query) use ($applyCompanyFilter): void {
$applyCompanyFilter($query);
$query
->when(filled($this->search), fn($q) => $q->where('title', 'like', '%' . $this->search . '%'))
->when($this->statusFilter !== 'all', fn($q) => $q->where('status', $this->statusFilter))
->when($this->portalFilter !== 'all', fn($q) => $q->where('portal', $this->portalFilter));
};
$prs = PressRelease::accessiblePageFor($user, $applyListFilters, $this->sortBy, $this->sortDir);
// Laufende Boosts der Seite in einer Query (statt isBoosted() je
// Zeile): [press_release_id => spätestes ends_at].
$boostEndsById = Boost::query()
->active()
->whereIn('press_release_id', collect($prs->items())->pluck('id'))
->selectRaw('press_release_id, max(ends_at) as ends_at')
->groupBy('press_release_id')
->pluck('ends_at', 'press_release_id')
->map(fn ($endsAt) => \Illuminate\Support\Carbon::parse($endsAt));
return [
'pressReleases' => $prs,
'boostEndsById' => $boostEndsById,
'statusOptions' => PressReleaseStatus::cases(),
'portalOptions' => Portal::cases(),
'selectedCompany' => $context->selectedCompany(auth()->user()),
'hasGlobalCompanyContext' => $selectedCompanyId === null,
'statusCounts' => $statusCounts,
];
}
private function findMyPR(int $id): ?PressRelease
{
return PressRelease::withoutGlobalScopes()
->where('id', $id)
->where('user_id', auth()->id())
->first();
}
}; ?>
@php
$hasAnyFilter =
$search !== '' ||
$statusFilter !== 'all' ||
$portalFilter !== 'all' ||
($hasGlobalCompanyContext && $companyFilter !== 'all');
$hasAnyPR = $statusCounts['all'] > 0;
@endphp
{{-- Flash-Banner ersetzt durch
im Layout. --}}
{{-- ============== PAGE HEADER ============== --}}
{{-- ============== SAVED-VIEWS-TABS ============== --}}
@if ($hasAnyPR)
$statusFilter === 'all'])>
{{ __('Alle') }} {{ $statusCounts['all'] }}
$statusFilter === \App\Enums\PressReleaseStatus::Published->value,
])>
{{ __('Veröffentlicht') }} {{ $statusCounts[\App\Enums\PressReleaseStatus::Published->value] }}
$statusFilter === \App\Enums\PressReleaseStatus::Draft->value,
])>
{{ __('Entwürfe') }} {{ $statusCounts[\App\Enums\PressReleaseStatus::Draft->value] }}
$statusFilter === \App\Enums\PressReleaseStatus::Review->value,
])>
{{ __('In Prüfung') }} {{ $statusCounts[\App\Enums\PressReleaseStatus::Review->value] }}
$statusFilter === \App\Enums\PressReleaseStatus::Rejected->value,
])>
{{ __('Abgelehnt') }} {{ $statusCounts[\App\Enums\PressReleaseStatus::Rejected->value] }}
$statusFilter === \App\Enums\PressReleaseStatus::Archived->value,
])>
{{ __('Archiv') }} {{ $statusCounts[\App\Enums\PressReleaseStatus::Archived->value] }}
@endif
{{-- ============== FILTER + SUCHE ============== --}}
@if ($hasAnyPR)
{{ __('Portal: Alle') }}
@foreach ($portalOptions as $p)
{{ __('Portal:') }} {{ $p->label() }}
@endforeach
@if ($hasGlobalCompanyContext)
{{ __('Firma: Alle') }}
{{ __('Firma: Mit Firma') }}
{{ __('Firma: Ohne Firma') }}
@endif
{{-- Active-Chips --}}
@if ($hasAnyFilter)
{{ __('Aktiv') }}
@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 ($hasGlobalCompanyContext && $companyFilter !== 'all')
{{ __('Firma') }}:
{{ $companyFilter === 'assigned' ? __('Mit Firma') : __('Ohne Firma') }}
@endif
@if ($search !== '')
{{ __('Suche') }}:
„{{ \Illuminate\Support\Str::limit($search, 40) }}"
@endif
{{ __('Alle zurücksetzen') }}
@endif
@endif
{{-- ============== TABELLE / EMPTY ============== --}}
@if ($pressReleases->isNotEmpty())
@if ($statusFilter !== 'all')
@php $sEnum = \App\Enums\PressReleaseStatus::tryFrom($statusFilter); @endphp
{{ $sEnum?->label() ?? __('Pressemitteilungen') }}
@else
{{ __('Alle Pressemitteilungen') }}
@endif
{{ __(':count Einträge', ['count' => $pressReleases->total()]) }}
{{ __('Status') }}
{{ __('Bild') }}
{{ __('Titel') }}
{{ __('Portal') }}
{{ __('Firma') }}
{{ __('Datum') }}
{{ __('Aktionen') }}
@foreach ($pressReleases as $pr)
@php
$status = $pr->status->value;
// Grün geprüft = boostbar (Gate im BoostService); der Haken
// zeigt das Prüfergebnis auch abseits des Boost-Hinweises.
$isGreen = $pr->classification === \App\Enums\PressReleaseClassification::Green;
$rowClass = match ($status) {
'review' => 'is-row-warn',
'rejected' => 'is-row-err',
default => '',
};
$badgeClass = $pr->status->badgeClass();
$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 = match (true) {
$status === 'published' && $pr->published_at => $pr->published_at,
default => $pr->created_at,
};
@endphp
{{ $pr->status->label() }}
@if ($status === 'draft')
{{-- Öffnet das gemeinsame Einreichungs-Modal (rechtliche
Bestätigungen inkl. Erlöschensklausel) statt eines
Schnell-Submits, der die Bestätigungen umgehen würde. --}}
{{ __('Zur Prüfung →') }}
@endif
@if ($boostEndsAt = $boostEndsById->get($pr->id))
{{-- Laufende Boost-Platzierung (Add-on) direkt unter dem Status. --}}
{{-- Deckt beide Stufen ab (Startseiten-/Kategorie-Boost), daher neutral formuliert. --}}
{{ __('Boost bis :date', ['date' => $boostEndsAt->format('d.m.Y')]) }}
@elseif ($status === 'published' && $isGreen)
{{-- Boostbar (veröffentlicht + grün): Hinweis auf das Add-on (13.07.). --}}
@endif
{{-- Vorschaubild: Titelbild der PM, sonst der gewählte
Platzhalter, sonst generischer Bild-Fallback. --}}
@php $thumbUrl = $pr->thumbImageUrl() ?? $pr->chosenPlaceholderUrl(); @endphp
@if ($thumbUrl)
@else
@endif
{{ $pr->title }}
@if ($isGreen)
{{-- Grüner Haken = Inhaltsprüfung mit Ergebnis Grün (13.07.). --}}
@endif
PM-{{ $pr->id }}
@if ($showPe)
presseecho
@endif
@if ($showBp)
businessportal24
@endif
@if ($categoryName = $pr->localizedCategoryName())
{{-- Rubrik direkt unter den Portal-Pills (13.07.). --}}
{{ $categoryName }}
@endif
@if ($pr->company)
{{ $pr->company->name }}
@else
{{ __('— keine —') }}
@endif
{{ $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
@if (in_array($status, ['draft', 'rejected']))
@endif
@endforeach
@elseif ($hasAnyPR && $search !== '')
{{-- Empty: Suche ohne Treffer --}}
{{ __('Suche zurücksetzen') }}
@elseif ($hasAnyPR && $hasAnyFilter)
{{-- Empty: Filter ohne Treffer --}}
{{ __('Alle Filter zurücksetzen') }}
@else
{{-- Empty: gar keine PMs überhaupt --}}
{{ __('Erste Pressemitteilung erstellen') }}
01
{{ __('Firma zuordnen') }}
02
{{ __('Mitteilung verfassen') }}
03
{{ __('Zur Prüfung senden') }}
@endif
{{-- ============== STATUS-AKTIONEN-LEGENDE ============== --}}
@if ($hasAnyPR)
{{ __('Aktionen je Status') }}
{{ __('Welche Aktionen pro Status verfügbar sind.') }}
{{ __('Entwurf') }}
{{ __('Bearbeiten') }}
{{ __('Vorschau') }}
{{ __('→ Zur Prüfung senden') }}
{{ __('In Prüfung') }}
{{ __('Vorschau') }}
{{ __('Warten auf Prüfung') }}
{{ __('Veröffentlicht') }}
{{ __('Vorschau') }}
{{ __('Statistik (bald)') }}
{{ __('Abgelehnt') }}
{{ __('Bearbeiten') }}
{{ __('→ Erneut einreichen') }}
@endif