12-05-2026 Frontend dev
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run

This commit is contained in:
Kevin Adametz 2026-05-12 18:32:33 +02:00
parent 405df0a122
commit 5b8bdf4182
779 changed files with 480564 additions and 6241 deletions

View file

@ -0,0 +1,171 @@
<?php
use App\Models\NewsletterSubscription;
use App\Services\Admin\AdminPerformanceCache;
use App\Services\Newsletter\NewsletterSyncService;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
new #[Layout('components.layouts.app'), Title('Newsletter Sync')] class extends Component
{
public ?string $syncMessage = null;
public ?string $dryRunMessage = null;
public function triggerDryRun(): void
{
$subscription = NewsletterSubscription::query()->latest('id')->first();
if ($subscription === null) {
$this->dryRunMessage = 'Dry-Run: Kein Datensatz fuer Vorschau vorhanden.';
return;
}
$action = ($subscription->is_confirmed && $subscription->unsubscribed_at === null)
? 'subscribe'
: 'unsubscribe';
$this->dryRunMessage = "Dry-Run: Es wuerde {$action} fuer Subscription #{$subscription->id} ({$subscription->email}) ausgefuehrt.";
}
public function triggerTestSync(): void
{
$subscription = NewsletterSubscription::query()->latest('id')->first();
if ($subscription === null) {
$this->syncMessage = 'Kein Datensatz fuer Test-Sync vorhanden.';
return;
}
app(NewsletterSyncService::class)->syncSubscription($subscription);
$action = ($subscription->is_confirmed && $subscription->unsubscribed_at === null)
? 'subscribe'
: 'unsubscribe';
$this->syncMessage = "Test-Sync ausgefuehrt ({$action}) fuer Subscription #{$subscription->id}.";
}
public function with(): array
{
return [
'stats' => $this->stats(),
'syncConfig' => [
'enabled' => (bool) config('newsletter.sync.enabled'),
'provider' => (string) config('newsletter.sync.provider'),
'endpoint' => (string) (config('newsletter.sync.endpoint') ?? '-'),
'timeout' => (int) config('newsletter.sync.timeout', 10),
],
];
}
/**
* @return array{total: int, confirmed: int, pending: int, unsubscribed: int}
*/
private function stats(): array
{
return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::NewsletterStats, AdminPerformanceCache::StatsTtl, function (): array {
$stats = NewsletterSubscription::query()
->toBase()
->selectRaw('COUNT(*) as total')
->selectRaw('SUM(CASE WHEN is_confirmed = ? THEN 1 ELSE 0 END) as confirmed', [true])
->selectRaw('SUM(CASE WHEN is_confirmed = ? THEN 1 ELSE 0 END) as pending', [false])
->selectRaw('SUM(CASE WHEN unsubscribed_at IS NOT NULL THEN 1 ELSE 0 END) as unsubscribed')
->first();
return [
'total' => (int) ($stats->total ?? 0),
'confirmed' => (int) ($stats->confirmed ?? 0),
'pending' => (int) ($stats->pending ?? 0),
'unsubscribed' => (int) ($stats->unsubscribed ?? 0),
];
});
}
}; ?>
<div class="space-y-6">
<flux:card>
<div class="flex items-start justify-between gap-4">
<div class="space-y-2">
<flux:heading size="lg">{{ __('Newsletter Synchronisierung') }}</flux:heading>
<flux:text class="text-zinc-500 dark:text-zinc-400">
{{ __('Vorbereitung fuer die kuenftige externe API-Anbindung. Aktuell ist nur das technische Grundgeruest aktiv.') }}
</flux:text>
</div>
<div class="flex flex-col items-end gap-2">
@if ($syncConfig['enabled'])
<flux:badge color="green" icon="check" size="sm">{{ __('Sync aktiv') }}</flux:badge>
@else
<flux:badge color="zinc" icon="pause" size="sm">{{ __('Sync deaktiviert') }}</flux:badge>
@endif
<div class="flex gap-2">
<flux:button size="sm" variant="ghost" icon="eye" wire:click="triggerDryRun">
{{ __('Dry Run') }}
</flux:button>
<flux:button size="sm" icon="play" wire:click="triggerTestSync">
{{ __('Test-Sync ausfuehren') }}
</flux:button>
</div>
</div>
</div>
</flux:card>
@if ($dryRunMessage)
<flux:card>
<flux:text class="text-sm text-zinc-600 dark:text-zinc-300">{{ $dryRunMessage }}</flux:text>
</flux:card>
@endif
@if ($syncMessage)
<flux:card>
<flux:text class="text-sm">{{ $syncMessage }}</flux:text>
</flux:card>
@endif
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<flux:card>
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Gesamt') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
</flux:card>
<flux:card>
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Bestaetigt') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['confirmed'] }}</flux:text>
</flux:card>
<flux:card>
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Unbestaetigt') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['pending'] }}</flux:text>
</flux:card>
<flux:card>
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Abgemeldet') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['unsubscribed'] }}</flux:text>
</flux:card>
</div>
<flux:card>
<flux:heading size="sm">{{ __('Konfiguration') }}</flux:heading>
<div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2">
<div>
<flux:text class="text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{{ __('Provider') }}</flux:text>
<flux:text class="mt-1">{{ $syncConfig['provider'] }}</flux:text>
</div>
<div>
<flux:text class="text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{{ __('Timeout') }}</flux:text>
<flux:text class="mt-1">{{ $syncConfig['timeout'] }}s</flux:text>
</div>
<div class="sm:col-span-2">
<flux:text class="text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{{ __('Endpoint') }}</flux:text>
<flux:text class="mt-1 break-all">{{ $syncConfig['endpoint'] }}</flux:text>
</div>
</div>
</flux:card>
</div>