559 lines
29 KiB
PHP
559 lines
29 KiB
PHP
<?php
|
||
|
||
use App\Enums\PressReleaseStatus;
|
||
use App\Models\PressRelease;
|
||
use App\Services\Auth\MagicLinkGenerator;
|
||
use App\Services\PressRelease\BlacklistViolationException;
|
||
use App\Services\PressRelease\BookingRequiredException;
|
||
use App\Services\PressRelease\PressReleaseCoverImage;
|
||
use App\Services\PressRelease\PressReleaseService;
|
||
use App\Services\PressRelease\QuotaExceededException;
|
||
use Flux\Flux;
|
||
use Livewire\Attributes\Layout;
|
||
use Livewire\Attributes\Locked;
|
||
use Livewire\Attributes\Title;
|
||
use Livewire\Volt\Component;
|
||
|
||
new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends Component
|
||
{
|
||
#[Locked]
|
||
public int $id;
|
||
|
||
public ?string $shareUrl = null;
|
||
|
||
public ?string $shareExpiresAt = null;
|
||
|
||
public function mount(int $id): void
|
||
{
|
||
$this->id = $id;
|
||
$pr = $this->getMyPR();
|
||
$this->authorize('view', $pr);
|
||
}
|
||
|
||
public function submitForReview(): void
|
||
{
|
||
$pr = $this->getMyPR();
|
||
$this->authorize('submitForReview', $pr);
|
||
|
||
try {
|
||
app(PressReleaseService::class)->submitForReview($pr);
|
||
} catch (BlacklistViolationException $e) {
|
||
Flux::modal('confirm-submit-review')->close();
|
||
|
||
Flux::toast(
|
||
heading: __('Automatisch abgelehnt'),
|
||
text: __('Unzulässiges Wort gefunden: ":word". Bitte überarbeiten.', ['word' => $e->word]),
|
||
variant: 'danger',
|
||
duration: 8000,
|
||
);
|
||
|
||
return;
|
||
} catch (BookingRequiredException|QuotaExceededException $e) {
|
||
Flux::modal('confirm-submit-review')->close();
|
||
|
||
Flux::toast(
|
||
heading: __('Nicht eingereicht'),
|
||
text: $e->getMessage(),
|
||
variant: 'warning',
|
||
duration: 8000,
|
||
);
|
||
|
||
return;
|
||
}
|
||
|
||
Flux::modal('confirm-submit-review')->close();
|
||
|
||
Flux::toast(
|
||
heading: __('Eingereicht'),
|
||
text: __('Die Redaktion prüft die Pressemitteilung typischerweise innerhalb von 24 h.'),
|
||
variant: 'success',
|
||
);
|
||
}
|
||
|
||
public function generateShareLink(MagicLinkGenerator $generator): void
|
||
{
|
||
$pr = $this->getMyPR();
|
||
$this->authorize('view', $pr);
|
||
|
||
$share = $generator->createPressReleaseShareLink($pr, auth()->user());
|
||
|
||
$this->shareUrl = $share['url'];
|
||
$this->shareExpiresAt = $share['expires_at']->format('d.m.Y H:i');
|
||
|
||
Flux::toast(text: __('Vorschau-Link wurde erzeugt.'), variant: 'success');
|
||
}
|
||
|
||
public function with(): array
|
||
{
|
||
$pr = $this->getMyPR();
|
||
$this->authorize('view', $pr);
|
||
|
||
$categoryName = $pr->category?->translations->firstWhere('locale', 'de')?->name ?? '–';
|
||
|
||
$latestRejection = null;
|
||
if ($pr->status->value === 'rejected') {
|
||
$latestRejection = $pr->statusLogs
|
||
->firstWhere(fn ($log) => $log->to_status?->value === 'rejected');
|
||
}
|
||
|
||
$cover = app(PressReleaseCoverImage::class);
|
||
$user = auth()->user();
|
||
|
||
return [
|
||
'pr' => $pr,
|
||
'categoryName' => $categoryName,
|
||
'coverUrl' => $cover->coverUrl($pr, 'cover'),
|
||
'coverIsPlaceholder' => $cover->coverIsPlaceholder($pr),
|
||
'titleImage' => $pr->images()->orderByDesc('is_preview')->orderBy('sort_order')->orderBy('id')->first(),
|
||
'quotaTotal' => $user->pressReleaseQuotaTotal(),
|
||
'quotaRemaining' => $user->pressReleaseQuotaRemaining(),
|
||
'canEdit' => auth()->user()->can('update', $pr)
|
||
&& in_array($pr->status->value, ['draft', 'rejected']),
|
||
'latestRejection' => $latestRejection,
|
||
'contacts' => $pr->contacts,
|
||
'statusLogs' => $pr->statusLogs,
|
||
'statusColor' => match($pr->status->value) {
|
||
'published' => 'green',
|
||
'review' => 'yellow',
|
||
'rejected' => 'red',
|
||
'archived' => 'blue',
|
||
default => 'zinc',
|
||
},
|
||
];
|
||
}
|
||
|
||
private function getMyPR(): PressRelease
|
||
{
|
||
return PressRelease::withoutGlobalScopes()
|
||
->where('user_id', auth()->id())
|
||
->with([
|
||
'company:id,name,email,phone',
|
||
'category.translations',
|
||
'contacts' => fn ($query) => $query
|
||
->withoutGlobalScopes()
|
||
->orderBy('last_name')
|
||
->orderBy('first_name')
|
||
->select(['contacts.id', 'contacts.company_id', 'contacts.first_name', 'contacts.last_name', 'contacts.responsibility', 'contacts.email', 'contacts.phone']),
|
||
'statusLogs.changedBy:id,name,email',
|
||
])
|
||
->findOrFail($this->id);
|
||
}
|
||
}; ?>
|
||
|
||
<div class="space-y-8">
|
||
@php
|
||
$statusClass = match ($pr->status->value) {
|
||
'published' => 'ok',
|
||
'review' => 'warn',
|
||
'rejected' => 'err',
|
||
default => 'hub',
|
||
};
|
||
@endphp
|
||
|
||
{{-- Flash-Banner ersetzt durch <flux:toast /> im Layout. --}}
|
||
|
||
{{-- ============== PAGE HEADER ============== --}}
|
||
<header class="page-header">
|
||
<div class="min-w-0">
|
||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||
<span class="eyebrow muted">{{ __('Mein Bereich · Pressemitteilung') }}</span>
|
||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||
@if ($pr->content_tier?->isPubliclyBadged())
|
||
<span class="badge {{ $pr->content_tier === \App\Enums\PressReleaseContentTier::Hochwertig ? 'ok' : 'hub' }}">
|
||
{{ $pr->content_tier === \App\Enums\PressReleaseContentTier::Hochwertig ? '★ ' : '✓ ' }}{{ $pr->content_tier->label() }}
|
||
</span>
|
||
@endif
|
||
<span class="badge hub">{{ strtoupper($pr->language) }}</span>
|
||
</div>
|
||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||
{{ $pr->title }}
|
||
</h1>
|
||
@if ($pr->subtitle)
|
||
<p class="text-[18px] font-medium tracking-[-0.2px] leading-[1.35] mt-2 m-0 max-w-[720px] text-[color:var(--color-ink-2)]">
|
||
{{ $pr->subtitle }}
|
||
</p>
|
||
@endif
|
||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[720px] text-[color:var(--color-ink-2)]">
|
||
{{ $pr->company?->name ?? '–' }}
|
||
<span class="text-[color:var(--color-bg-rule)] mx-1">·</span>
|
||
{{ $categoryName }}
|
||
<span class="text-[color:var(--color-bg-rule)] mx-1">·</span>
|
||
{{ $pr->created_at->format('d.m.Y') }}
|
||
</p>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-2 flex-shrink-0">
|
||
@if ($canEdit)
|
||
<flux:button variant="filled" icon="pencil" href="{{ route('me.press-releases.edit', $pr->id) }}" wire:navigate>
|
||
{{ __('Bearbeiten') }}
|
||
</flux:button>
|
||
@endif
|
||
<flux:button variant="filled" icon="link" wire:click="generateShareLink">
|
||
{{ __('Vorschau-Link') }}
|
||
</flux:button>
|
||
<flux:button variant="filled" icon="arrow-left" href="{{ route('me.press-releases.index') }}" wire:navigate>
|
||
{{ __('Zurück') }}
|
||
</flux:button>
|
||
</div>
|
||
</header>
|
||
|
||
{{-- ============== SHARE-LINK ERFOLG ============== --}}
|
||
@if ($shareUrl)
|
||
<article class="panel" style="border-color:var(--color-ok);">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Öffentlicher Vorschau-Link erstellt') }}</span>
|
||
<span class="badge ok dot">{{ __('gültig bis :date', ['date' => $shareExpiresAt]) }}</span>
|
||
</div>
|
||
<div class="p-5">
|
||
<flux:input readonly :value="$shareUrl" />
|
||
</div>
|
||
</article>
|
||
@endif
|
||
|
||
{{-- ============== REJECTION-HINWEIS ============== --}}
|
||
@if ($pr->status === PressReleaseStatus::Rejected && $latestRejection)
|
||
<article class="panel" style="border-color:var(--color-err); border-left-width:3px;">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Diese Pressemitteilung wurde abgelehnt') }}</span>
|
||
<span class="badge err dot">{{ __('Handlung erforderlich') }}</span>
|
||
</div>
|
||
<div class="p-5 flex items-start gap-3">
|
||
<div class="w-9 h-9 rounded-[5px] flex items-center justify-center flex-shrink-0
|
||
bg-[color:var(--color-err-soft)] border border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||
<flux:icon.exclamation-triangle class="size-[18px]" />
|
||
</div>
|
||
<div class="flex-1 text-[13px] text-[color:var(--color-ink-2)]">
|
||
@if ($latestRejection->reason)
|
||
<strong class="text-[color:var(--color-ink)] font-semibold">{{ __('Begründung') }}:</strong>
|
||
<span class="block mt-1 whitespace-pre-line">{{ $latestRejection->reason }}</span>
|
||
@else
|
||
{{ __('Bitte überarbeiten Sie den Inhalt und reichen Sie die Pressemitteilung erneut ein.') }}
|
||
@endif
|
||
<span class="mt-2 block text-[11.5px] text-[color:var(--color-ink-3)]">
|
||
{{ __('Abgelehnt am') }} {{ $latestRejection->created_at->format('d.m.Y H:i') }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
@endif
|
||
|
||
{{-- ============== STATUS-WORKFLOW (oben, farblich abgehoben) ============== --}}
|
||
@if ($pr->status === PressReleaseStatus::Draft || $pr->status === PressReleaseStatus::Rejected)
|
||
<article class="panel"
|
||
style="border-color:var(--color-{{ $pr->status === PressReleaseStatus::Rejected ? 'err' : 'hub' }}); border-left-width:3px;
|
||
background:var(--color-{{ $pr->status === PressReleaseStatus::Rejected ? 'err' : 'hub' }}-soft);">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Status-Workflow') }}</span>
|
||
<span @class(['badge', 'dot', $pr->status === PressReleaseStatus::Rejected ? 'err' : 'hub'])>
|
||
{{ $pr->status === PressReleaseStatus::Rejected ? __('Überarbeiten') : __('Entwurf') }}
|
||
</span>
|
||
</div>
|
||
<div class="p-5 flex flex-wrap items-center gap-3">
|
||
<p class="text-[13px] text-[color:var(--color-ink-2)] m-0 flex-1 min-w-[220px]">
|
||
{{ $pr->status === PressReleaseStatus::Rejected
|
||
? __('Sie können den Text bearbeiten und erneut zur Prüfung einreichen.')
|
||
: __('Reichen Sie den Entwurf ein, sobald er vollständig ist.') }}
|
||
</p>
|
||
<div class="flex items-center gap-2 flex-shrink-0 flex-wrap">
|
||
@if ($canEdit)
|
||
<flux:button variant="filled" icon="pencil" href="{{ route('me.press-releases.edit', $pr->id) }}" wire:navigate>
|
||
{{ __('Bearbeiten') }}
|
||
</flux:button>
|
||
@endif
|
||
<flux:modal.trigger name="confirm-submit-review">
|
||
<flux:button type="button" variant="primary">
|
||
{{ $pr->status === PressReleaseStatus::Rejected ? __('Erneut einreichen') : __('Zur Prüfung einreichen') }}
|
||
</flux:button>
|
||
</flux:modal.trigger>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
@endif
|
||
|
||
@if ($pr->status === PressReleaseStatus::Published)
|
||
<article class="panel" style="border-color:var(--color-ok); border-left-width:3px; background:var(--color-ok-soft);">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Status-Workflow') }}</span>
|
||
<span class="badge ok dot">{{ __('Live') }}</span>
|
||
</div>
|
||
<div class="p-5 flex items-start gap-3">
|
||
<div class="w-9 h-9 rounded-[5px] flex items-center justify-center flex-shrink-0
|
||
bg-[color:var(--color-ok-soft)] border border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||
<flux:icon.check-circle class="size-[18px]" />
|
||
</div>
|
||
<p class="flex-1 text-[13px] text-[color:var(--color-ink-2)] m-0">
|
||
{{ __('Diese Pressemitteilung ist veröffentlicht (seit :date).', ['date' => $pr->published_at?->format('d.m.Y H:i') ?? '–']) }}
|
||
</p>
|
||
</div>
|
||
</article>
|
||
@endif
|
||
|
||
@if ($pr->status === PressReleaseStatus::Review)
|
||
<article class="panel" style="border-color:var(--color-warn); border-left-width:3px; background:var(--color-warn-soft);">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Status-Workflow') }}</span>
|
||
<span class="badge warn dot">{{ __('Geduld bitte') }}</span>
|
||
</div>
|
||
<div class="p-5 flex items-start gap-3">
|
||
<div class="w-9 h-9 rounded-[5px] flex items-center justify-center flex-shrink-0
|
||
bg-[color:var(--color-warn-soft)] border border-[color:var(--color-warn)]/30 text-[color:var(--color-accent-deep)]">
|
||
<flux:icon.clock class="size-[18px]" />
|
||
</div>
|
||
<p class="flex-1 text-[13px] text-[color:var(--color-ink-2)] m-0">
|
||
{{ __('Ihre Pressemitteilung wird gerade geprüft. Sie werden benachrichtigt, sobald eine Entscheidung vorliegt.') }}
|
||
</p>
|
||
</div>
|
||
</article>
|
||
@endif
|
||
|
||
{{-- ============== KONTAKTE + STATUS/VERLAUF ============== --}}
|
||
<div class="grid gap-6 xl:grid-cols-2">
|
||
<article class="panel">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Zugeordnete Pressekontakte') }}</span>
|
||
@if ($pr->company)
|
||
<flux:button size="sm" variant="filled" icon="building-office" href="{{ route('me.press-kits.show', $pr->company->id) }}" wire:navigate>
|
||
{{ __('Firma') }}
|
||
</flux:button>
|
||
@endif
|
||
</div>
|
||
<div class="p-5">
|
||
<p class="text-[12px] text-[color:var(--color-ink-3)] mt-0 mb-4">
|
||
{{ __('Kontakte, die dieser Pressemitteilung zugeordnet sind.') }}
|
||
</p>
|
||
<div class="space-y-2">
|
||
@forelse ($contacts as $contact)
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
|
||
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
|
||
</div>
|
||
<div class="text-[12px] text-[color:var(--color-ink-3)]">
|
||
{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}
|
||
</div>
|
||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11.5px] text-[color:var(--color-ink-3)]">
|
||
@if ($contact->email)
|
||
<a href="mailto:{{ $contact->email }}"
|
||
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
|
||
{{ $contact->email }}
|
||
</a>
|
||
@endif
|
||
@if ($contact->phone)
|
||
<span>{{ $contact->phone }}</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@empty
|
||
<div class="rounded-[5px] border border-dashed border-[color:var(--color-bg-rule)] p-4 text-[12.5px] text-[color:var(--color-ink-3)]">
|
||
{{ __('Dieser Pressemitteilung ist noch kein Pressekontakt zugeordnet.') }}
|
||
@if ($pr->company)
|
||
<a href="{{ route('me.press-kits.show', $pr->company->id) }}" wire:navigate
|
||
class="font-medium text-[color:var(--color-hub)] hover:underline">
|
||
{{ __('Kontakte in der Firma prüfen.') }}
|
||
</a>
|
||
@endif
|
||
</div>
|
||
@endforelse
|
||
</div>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="panel">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Status & Verlauf') }}</span>
|
||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||
</div>
|
||
<div class="p-5">
|
||
<div class="grid gap-2 sm:grid-cols-2">
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Aktueller Status') }}</div>
|
||
<div class="mt-1.5">
|
||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Erstellt') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ $pr->created_at?->format('d.m.Y H:i') ?? '–' }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Veröffentlicht') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ $pr->published_at?->format('d.m.Y H:i') ?? '–' }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Aufrufe') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ number_format($pr->hits, 0, ',', '.') }}
|
||
</div>
|
||
</div>
|
||
@if ($pr->scheduled_at)
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Geplante Veröffentlichung') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ $pr->scheduledAtLocal()->format('d.m.Y H:i') }}
|
||
</div>
|
||
</div>
|
||
@endif
|
||
@if ($pr->embargo_at)
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Sperrfrist bis') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ $pr->embargoAtLocal()->format('d.m.Y H:i') }}
|
||
</div>
|
||
</div>
|
||
@endif
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Portal') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ $pr->portal?->label() ?? '–' }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Kategorie') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ $categoryName }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Sprache') }}</div>
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||
{{ strtoupper($pr->language) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@if (filled($pr->keywords))
|
||
<div class="mt-3 rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-2">{{ __('Themen') }}</div>
|
||
<div class="flex flex-wrap gap-1.5">
|
||
@foreach (array_filter(array_map('trim', explode(',', $pr->keywords))) as $keyword)
|
||
<span class="badge hub">{{ $keyword }}</span>
|
||
@endforeach
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($pr->backlink_url)
|
||
<div class="mt-3 rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Backlink') }}</div>
|
||
<a href="{{ $pr->backlink_url }}" target="_blank"
|
||
class="text-[12.5px] break-all text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
|
||
{{ $pr->backlink_url }}
|
||
</a>
|
||
</div>
|
||
@endif
|
||
|
||
@if ($pr->no_export)
|
||
<div class="mt-3 flex items-center gap-2 text-[12px] text-[color:var(--color-ink-3)]">
|
||
<flux:icon.no-symbol variant="micro" class="size-3.5" />
|
||
<span>{{ __('Kein Export aktiv (PM wird nicht über Feeds verteilt).') }}</span>
|
||
</div>
|
||
@endif
|
||
|
||
<div class="my-4 border-t border-[color:var(--color-bg-rule)]"></div>
|
||
|
||
@if ($statusLogs->isNotEmpty())
|
||
<ol class="space-y-3 border-s border-[color:var(--color-bg-rule)] ps-4">
|
||
@foreach ($statusLogs as $log)
|
||
<li class="text-[12.5px]">
|
||
<div class="flex flex-wrap items-center gap-2">
|
||
@php
|
||
$logClass = match ($log->to_status?->value) {
|
||
'published' => 'ok',
|
||
'review' => 'warn',
|
||
'rejected' => 'err',
|
||
default => 'hub',
|
||
};
|
||
@endphp
|
||
<span @class(['badge', $logClass])>{{ $log->to_status?->label() }}</span>
|
||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||
{{ $log->created_at->format('d.m.Y H:i') }}
|
||
</span>
|
||
@if ($log->changedBy)
|
||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||
{{ __('durch :name', ['name' => $log->changedBy->name]) }}
|
||
</span>
|
||
@endif
|
||
</div>
|
||
@if ($log->reason)
|
||
<p class="mt-1.5 text-[color:var(--color-ink-2)] m-0">{{ $log->reason }}</p>
|
||
@endif
|
||
</li>
|
||
@endforeach
|
||
</ol>
|
||
@else
|
||
<p class="text-[12.5px] text-[color:var(--color-ink-3)] m-0">
|
||
{{ __('Noch keine Statusänderungen protokolliert.') }}
|
||
</p>
|
||
@endif
|
||
</div>
|
||
</article>
|
||
</div>
|
||
|
||
{{-- ============== TITELBILD (Hero) ============== --}}
|
||
{{-- Harte Obergrenze 1280x580 px: Container deckelt Breite und Seitenverhältnis,
|
||
damit das Bild auf großen Screens nicht über die Detailgröße hinauswächst. --}}
|
||
<article class="panel overflow-hidden mx-auto w-full max-w-[1280px]">
|
||
<div class="relative aspect-[1280/580] w-full">
|
||
<img src="{{ $coverUrl }}" alt="{{ $pr->title }}"
|
||
class="absolute inset-0 h-full w-full object-cover" loading="lazy" />
|
||
</div>
|
||
@if ($coverIsPlaceholder)
|
||
<div class="flex items-center gap-2 border-t border-[color:var(--color-bg-rule)] px-5 py-2.5 text-[12px] text-[color:var(--color-ink-3)]">
|
||
<flux:icon.photo variant="micro" class="size-3.5" />
|
||
<span>{{ __('Platzhalter-Titelbild — laden Sie im Editor ein eigenes Bild hoch.') }}</span>
|
||
</div>
|
||
@elseif ($titleImage && ($titleImage->copyright || $titleImage->is_ai_generated))
|
||
{{-- Bildnachweis + KI-Kennzeichnung (Art. 50 EU AI Act) --}}
|
||
<div class="flex flex-wrap items-center gap-2 border-t border-[color:var(--color-bg-rule)] px-5 py-2.5 text-[12px] text-[color:var(--color-ink-3)]">
|
||
@if ($titleImage->is_ai_generated)
|
||
<span class="badge hub">{{ __('KI-generiert') }}</span>
|
||
@endif
|
||
<span>{{ $titleImage->copyright ?? __('Bild: KI-generiert') }}</span>
|
||
</div>
|
||
@endif
|
||
</article>
|
||
|
||
{{-- ============== INHALT ============== --}}
|
||
<article class="panel">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Inhalt') }}</span>
|
||
</div>
|
||
<div class="p-5">
|
||
<div class="pr-content max-w-[760px]">
|
||
{!! $pr->renderedText() !!}
|
||
</div>
|
||
</div>
|
||
</article>
|
||
|
||
{{-- ============== VERÖFFENTLICHUNGS-MODAL ============== --}}
|
||
@if ($pr->status === PressReleaseStatus::Draft || $pr->status === PressReleaseStatus::Rejected)
|
||
<x-press-release-submit-modal
|
||
name="confirm-submit-review"
|
||
action="submitForReview"
|
||
:confirm-label="__('Veröffentlichung anfordern')"
|
||
:quota-total="$quotaTotal"
|
||
:quota-remaining="$quotaRemaining" />
|
||
@endif
|
||
|
||
{{-- ============== BOILERPLATE-OVERRIDE ============== --}}
|
||
@if ($pr->boilerplate_override)
|
||
<article class="panel">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Eigener Abbinder (Boilerplate)') }}</span>
|
||
<span class="badge hub">{{ __('Override') }}</span>
|
||
</div>
|
||
<div class="p-5">
|
||
<p class="text-[12px] text-[color:var(--color-ink-3)] mt-0 mb-3">
|
||
{{ __('Dieser Text wird für diese Pressemitteilung anstelle des Standard-Abbinders der Firma verwendet.') }}
|
||
</p>
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-4 text-[13px] leading-[1.6] text-[color:var(--color-ink-2)] whitespace-pre-line">
|
||
{{ $pr->boilerplate_override }}
|
||
</div>
|
||
</div>
|
||
</article>
|
||
@endif
|
||
</div>
|