- Unterseite /admin/newsletter/abmeldungen listet Newsletter-Opt-outs mit Zeitpunkt, Suche und Link zur User-Detailseite - Kampagnen-Filter: Empfänger einer früheren Kampagne ausschließen (Abgleich per User-ID und E-Mail-Snapshot) für Follow-up-Mailings - Login-Filter kann Monats-Scheiben (von–bis) abgrenzen, um große Listen Monat für Monat abzuarbeiten - Versand läuft in Wellen (Standard 50 Mails / 15 Min, per .env justierbar) gegen SMTP-Limits und Blocklisten; Hinweis im Senden-Modal - web: Firmen-Website im Newsroom & Co. als absolute URL verlinkt (Company::websiteUrl), statt relativ auf /newsroom/www.example.de - kunde: API-Doku-Button öffnet die /de/api-Seite; OpenAPI-Spezifikation als eigener, benannter Download (pressekonto-api-v1.yml) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
6.2 KiB
PHP
121 lines
6.2 KiB
PHP
<?php
|
|
|
|
use App\Models\NewsletterCampaign;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Title;
|
|
use Livewire\Volt\Component;
|
|
use Livewire\WithPagination;
|
|
|
|
new #[Layout('components.layouts.app'), Title('Newsletter')] class extends Component
|
|
{
|
|
use WithPagination;
|
|
|
|
public function with(): array
|
|
{
|
|
return [
|
|
'campaigns' => NewsletterCampaign::query()
|
|
->with('createdBy:id,name')
|
|
->orderByDesc('created_at')
|
|
->paginate(25),
|
|
];
|
|
}
|
|
}; ?>
|
|
|
|
<div class="space-y-8">
|
|
{{-- ============== PAGE HEADER ============== --}}
|
|
<x-portal.page-header
|
|
:badge="__('Admin Backend')"
|
|
:eyebrow="__('Administration · Newsletter')"
|
|
:title="__('Newsletter-Kampagnen')"
|
|
:description="__('Interne Mailings an Bestands-User auf Basis der hinterlegten E-Mail-Adressen — mit Empfänger-Filtern, Versand-Protokoll und Abmelde-Link in jeder Mail.')"
|
|
>
|
|
<x-slot:actions>
|
|
<flux:button variant="filled" icon="bell-slash" href="{{ route('admin.newsletter.unsubscribes') }}" wire:navigate>
|
|
{{ __('Abmeldungen') }}
|
|
</flux:button>
|
|
<flux:button variant="primary" icon="plus" href="{{ route('admin.newsletter.campaigns.create') }}" wire:navigate>
|
|
{{ __('Neue Kampagne') }}
|
|
</flux:button>
|
|
</x-slot:actions>
|
|
</x-portal.page-header>
|
|
|
|
{{-- ============== KAMPAGNEN ============== --}}
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Kampagnen') }}</span>
|
|
<span class="badge hub">{{ number_format($campaigns->total(), 0, ',', '.') }} {{ __('Einträge') }}</span>
|
|
</div>
|
|
<flux:table>
|
|
<flux:table.columns>
|
|
<flux:table.column>{{ __('Erstellt') }}</flux:table.column>
|
|
<flux:table.column>{{ __('Betreff') }}</flux:table.column>
|
|
<flux:table.column>{{ __('Status') }}</flux:table.column>
|
|
<flux:table.column>{{ __('Empfänger') }}</flux:table.column>
|
|
<flux:table.column>{{ __('Angelegt von') }}</flux:table.column>
|
|
<flux:table.column></flux:table.column>
|
|
</flux:table.columns>
|
|
<flux:table.rows>
|
|
@forelse ($campaigns as $campaign)
|
|
<flux:table.row :key="$campaign->id">
|
|
<flux:table.cell class="whitespace-nowrap">
|
|
<span class="text-[12px] text-[color:var(--color-ink-2)] tabular-nums">
|
|
{{ $campaign->created_at->format('d.m.Y H:i') }}
|
|
</span>
|
|
</flux:table.cell>
|
|
<flux:table.cell>
|
|
<a href="{{ route('admin.newsletter.campaigns.show', $campaign->id) }}" wire:navigate
|
|
class="text-[12.5px] font-semibold text-[color:var(--color-ink)] hover:underline">
|
|
{{ $campaign->subject }}
|
|
</a>
|
|
</flux:table.cell>
|
|
<flux:table.cell>
|
|
<flux:badge size="sm" :color="$campaign->status->badgeColor()">{{ $campaign->status->label() }}</flux:badge>
|
|
</flux:table.cell>
|
|
<flux:table.cell class="whitespace-nowrap">
|
|
@if ($campaign->isDraft())
|
|
<span class="text-[12px] text-[color:var(--color-ink-3)]">—</span>
|
|
@else
|
|
<span class="text-[12px] text-[color:var(--color-ink-2)] tabular-nums">
|
|
{{ number_format($campaign->sent_count, 0, ',', '.') }} / {{ number_format($campaign->recipients_total, 0, ',', '.') }}
|
|
</span>
|
|
@if ($campaign->failed_count > 0)
|
|
<flux:badge size="sm" color="red">{{ $campaign->failed_count }} {{ __('Fehler') }}</flux:badge>
|
|
@endif
|
|
@if ($campaign->skipped_count > 0)
|
|
<flux:badge size="sm" color="amber">{{ $campaign->skipped_count }} {{ __('abgemeldet') }}</flux:badge>
|
|
@endif
|
|
@endif
|
|
</flux:table.cell>
|
|
<flux:table.cell class="whitespace-nowrap">
|
|
<span class="text-[12.5px] text-[color:var(--color-ink-3)]">{{ $campaign->createdBy?->name ?? '—' }}</span>
|
|
</flux:table.cell>
|
|
<flux:table.cell class="whitespace-nowrap text-right">
|
|
@if ($campaign->isDraft())
|
|
<flux:button size="xs" variant="filled" icon="pencil" href="{{ route('admin.newsletter.campaigns.edit', $campaign->id) }}" wire:navigate>
|
|
{{ __('Bearbeiten') }}
|
|
</flux:button>
|
|
@else
|
|
<flux:button size="xs" variant="filled" icon="eye" href="{{ route('admin.newsletter.campaigns.show', $campaign->id) }}" wire:navigate>
|
|
{{ __('Protokoll') }}
|
|
</flux:button>
|
|
@endif
|
|
</flux:table.cell>
|
|
</flux:table.row>
|
|
@empty
|
|
<flux:table.row>
|
|
<flux:table.cell colspan="6">
|
|
<div class="py-8 text-center text-[12px] text-[color:var(--color-ink-3)]">
|
|
{{ __('Noch keine Kampagnen. Legen Sie die erste Kampagne an, um Bestands-User zu informieren.') }}
|
|
</div>
|
|
</flux:table.cell>
|
|
</flux:table.row>
|
|
@endforelse
|
|
</flux:table.rows>
|
|
</flux:table>
|
|
@if ($campaigns->hasPages())
|
|
<div class="px-5 py-4 border-t border-[color:var(--color-bg-rule)]">
|
|
{{ $campaigns->links() }}
|
|
</div>
|
|
@endif
|
|
</article>
|
|
</div>
|