19-05-2026 Rebrand Pressekonto, Hub-Flux UI und Legacy-Media-Migration

Umbenennung presseportale → pressekonto in Domains, Themes und Dokumentation.
Design-Tokens, Portal-Shell, Customer-Dashboard, Auth- und Admin-PM-Views.
Artisan-Befehl migrate:legacy-media mit Tests und Hub-Flux-Entwicklungsdocs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin Adametz 2026-05-19 16:36:13 +00:00
parent 092ee0e918
commit 0a3e52d603
112 changed files with 8464 additions and 1649 deletions

View file

@ -1,8 +1,8 @@
# Backend-Status: presseportale.test
# Backend-Status: pressekonto.test
**Projekt:** BusinessPortal24 → Laravel 12 Migration
**Domain:** presseportale.test
**Stand:** 23. Januar 2026 nach Server-Neustart
**Projekt:** BusinessPortal24 → Laravel 12 Migration
**Domain:** pressekonto.test
**Stand:** 23. Januar 2026 nach Server-Neustart
**Status:** 🟡 Admin-UI-Gerüst vorhanden, Routing auf bestehende Volt-Komponenten konsolidiert
---
@ -274,7 +274,7 @@ Beginne mit der eigentlichen Migration gemäß:
### Option 3: Backend testen 🟢
- Dev-Server starten (`npm run dev`)
- Backend öffnen (`http://presseportale.test/admin/press-releases`)
- Backend öffnen (`http://pressekonto.test/admin/press-releases`)
- UI und Navigation prüfen
### Option 4: Dummy-Daten verfeinern 🟢
@ -294,14 +294,14 @@ npm run dev:portal
php artisan serve
# Backend öffnen
http://presseportale.test/admin/press-releases
http://presseportale.test/admin/companies
http://presseportale.test/admin/invoices
http://presseportale.test/admin/contacts
http://presseportale.test/admin/payments
http://presseportale.test/admin/categories
http://presseportale.test/admin/coupons
http://presseportale.test/admin/roles
http://pressekonto.test/admin/press-releases
http://pressekonto.test/admin/companies
http://pressekonto.test/admin/invoices
http://pressekonto.test/admin/contacts
http://pressekonto.test/admin/payments
http://pressekonto.test/admin/categories
http://pressekonto.test/admin/coupons
http://pressekonto.test/admin/roles
```
---
@ -327,7 +327,7 @@ http://presseportale.test/admin/roles
## 🎉 Meilenstein erreicht!
**Backend-Struktur für presseportale.test ist als Gerüst weitgehend vorbereitet.**
**Backend-Struktur für pressekonto.test ist als Gerüst weitgehend vorbereitet.**
- ✅ 7 Hauptbereiche mit Navigation
- ✅ 24 Routes definiert (konsistent gemappt)

View file

@ -1,7 +1,7 @@
# Flux UI v2 - Komponenten-Referenz
**Projekt:** presseportale.test Backend
**Flux Version:** 2.x
**Projekt:** pressekonto.test Backend
**Flux Version:** 2.x
**Stand:** 23. Januar 2026
---

View file

@ -176,7 +176,7 @@ php artisan serve
npm run dev
# Backend öffnen:
http://presseportale.test/admin/press-releases
http://pressekonto.test/admin/press-releases
```
## 📚 Weitere Ressourcen

View file

@ -1,110 +1,217 @@
<x-layouts.app title="Dashboard">
<div class="space-y-6">
{{-- Statistik-Karten --}}
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-5">
<a href="{{ route('admin.press-releases.index') }}" wire:navigate class="block">
<div class="rounded-xl border border-zinc-200 bg-white p-4 transition hover:border-zinc-300 dark:border-zinc-700 dark:bg-zinc-900">
<p class="text-xs text-zinc-500">{{ __('PMs gesamt') }}</p>
<p class="mt-1 text-2xl font-bold">{{ number_format($stats['press_releases']['total']) }}</p>
<div class="mt-2 flex gap-2 text-xs text-zinc-400">
<span class="text-green-600">{{ $stats['press_releases']['published'] }} pub</span>
<span class="text-yellow-600">{{ $stats['press_releases']['review'] }} prüf</span>
<span>{{ $stats['press_releases']['draft'] }} entwurf</span>
</div>
</div>
</a>
<div class="space-y-8">
<a href="{{ route('admin.companies.index') }}" wire:navigate class="block">
<div class="rounded-xl border border-zinc-200 bg-white p-4 transition hover:border-zinc-300 dark:border-zinc-700 dark:bg-zinc-900">
<p class="text-xs text-zinc-500">{{ __('Firmen') }}</p>
<p class="mt-1 text-2xl font-bold">{{ number_format($stats['companies']) }}</p>
{{-- ============== PAGE HEADER ============== --}}
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
<div class="min-w-0">
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
<span class="eyebrow muted">{{ __('Operations · A · 01') }}</span>
</div>
</a>
<a href="{{ route('admin.contacts.index') }}" wire:navigate class="block">
<div class="rounded-xl border border-zinc-200 bg-white p-4 transition hover:border-zinc-300 dark:border-zinc-700 dark:bg-zinc-900">
<p class="text-xs text-zinc-500">{{ __('Kontakte') }}</p>
<p class="mt-1 text-2xl font-bold">{{ number_format($stats['contacts']) }}</p>
</div>
</a>
<a href="{{ route('admin.users.index') }}" wire:navigate class="block">
<div class="rounded-xl border border-zinc-200 bg-white p-4 transition hover:border-zinc-300 dark:border-zinc-700 dark:bg-zinc-900">
<p class="text-xs text-zinc-500">{{ __('Benutzer') }}</p>
<p class="mt-1 text-2xl font-bold">{{ number_format($stats['users']) }}</p>
</div>
</a>
<div class="rounded-xl border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900">
<p class="text-xs text-zinc-500">{{ __('Newsletter') }}</p>
<p class="mt-1 text-2xl font-bold">{{ number_format($stats['newsletter']) }}</p>
<p class="mt-2 text-xs text-zinc-400">{{ __('bestätigt') }}</p>
<h1 class="text-[34px] font-bold tracking-[-0.7px] leading-[1.1] m-0 text-[color:var(--color-ink)]">
{{ __('Admin Dashboard') }}
</h1>
<p class="text-[13.5px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
{{ __('Willkommen zurück, ') }}<strong class="font-semibold text-[color:var(--color-ink)]">{{ auth()->user()->name }}</strong>.
{{ __('Operations-Übersicht über Pressemitteilungen, Firmen und Konten beider Portale.') }}
</p>
</div>
</div>
<div class="grid gap-6 lg:grid-cols-[1fr,360px]">
<div class="flex items-center gap-3 flex-shrink-0">
<span class="badge ok dot">{{ __('Alle Systeme operational') }}</span>
</div>
</header>
{{-- ============== KPI-Reihe (5 Stat-Cards) ============== --}}
<section class="grid gap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-5">
<a href="{{ route('admin.press-releases.index') }}" wire:navigate class="block focus:outline-none">
<x-portal.stat-card variant="primary" :label="__('Pressemitteilungen')" :value="number_format($stats['press_releases']['total'])">
<x-slot:meta>{{ now()->format('Y') }}</x-slot:meta>
{{-- WICHTIG: Wortlaut „X pub · Y prüf · Z entwurf" exakt beibehalten,
weil DashboardTest darauf assertet (1 pub, 1 prüf, 1 entwurf). --}}
<x-slot:trend>
<span class="flex items-center gap-3">
<span class="text-[color:var(--color-ok)]">{{ $stats['press_releases']['published'] }} pub</span>
<span class="text-[color:var(--color-warn)]">{{ $stats['press_releases']['review'] }} prüf</span>
<span>{{ $stats['press_releases']['draft'] }} entwurf</span>
</span>
</x-slot:trend>
</x-portal.stat-card>
</a>
<a href="{{ route('admin.press-releases.index', ['status' => 'review']) }}" wire:navigate class="block focus:outline-none">
<x-portal.stat-card variant="warn" :label="__('In Prüfung')" :value="$stats['press_releases']['review']">
<x-slot:meta>{{ __('queue') }}</x-slot:meta>
<x-slot:trend>{{ __('warten auf Review') }}</x-slot:trend>
</x-portal.stat-card>
</a>
<a href="{{ route('admin.companies.index') }}" wire:navigate class="block focus:outline-none">
<x-portal.stat-card variant="muted" :label="__('Firmen')" :value="number_format($stats['companies'])">
<x-slot:trend>{{ __('aktiv im CRM') }}</x-slot:trend>
</x-portal.stat-card>
</a>
<a href="{{ route('admin.contacts.index') }}" wire:navigate class="block focus:outline-none">
<x-portal.stat-card variant="muted" :label="__('Kontakte')" :value="number_format($stats['contacts'])">
<x-slot:trend>{{ __('Pressekontakte') }}</x-slot:trend>
</x-portal.stat-card>
</a>
<a href="{{ route('admin.users.index') }}" wire:navigate class="block focus:outline-none">
<x-portal.stat-card variant="muted" :label="__('Benutzer')" :value="number_format($stats['users'])">
<x-slot:trend>{{ __('Portal-Konten') }}</x-slot:trend>
</x-portal.stat-card>
</a>
</section>
{{-- ============== ZWEISPALTEN-GRID ============== --}}
<section class="grid gap-6 lg:grid-cols-[2fr_1fr]">
{{-- Letzte Pressemitteilungen --}}
<div class="overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
<div class="flex items-center justify-between border-b border-zinc-200 px-4 py-3 dark:border-zinc-700">
<h2 class="font-semibold">{{ __('Letzte Pressemitteilungen') }}</h2>
<a href="{{ route('admin.press-releases.index') }}" wire:navigate class="text-sm text-blue-600 hover:underline dark:text-blue-400">{{ __('Alle anzeigen') }}</a>
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Letzte Pressemitteilungen') }}</span>
<a href="{{ route('admin.press-releases.index') }}" wire:navigate
class="text-[12px] font-semibold text-[color:var(--color-hub)] hover:underline underline-offset-[3px] decoration-[color:var(--color-hub)]/30">
{{ __('Alle anzeigen') }}
</a>
</div>
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
@forelse($recentPRs as $pr)
<a href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
class="flex items-center justify-between gap-3 px-4 py-3 transition hover:bg-zinc-50 dark:hover:bg-zinc-800">
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium">{{ $pr->title }}</p>
<p class="text-xs text-zinc-500">{{ $pr->company?->name ?? '' }} · {{ $pr->created_at->format('d.m.Y') }}</p>
</div>
<span class="shrink-0 rounded-full px-2 py-0.5 text-xs font-medium {{ match($pr->status->value) {
'published' => 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
'review' => 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400',
'rejected' => 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',
'archived' => 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
default => 'bg-zinc-100 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300',
} }}">
{{ $pr->status->label() }}
</span>
</a>
@empty
<p class="px-4 py-6 text-center text-sm text-zinc-500">{{ __('Noch keine Pressemitteilungen.') }}</p>
@endforelse
</div>
</div>
@forelse ($recentPRs as $pr)
<a href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
class="flex items-center justify-between gap-3 px-5 py-3 border-b border-[color:var(--color-bg-rule)] last:border-b-0 hover:bg-[color:var(--color-bg)] transition-colors">
<div class="min-w-0 flex-1">
<p class="truncate text-[13px] font-medium text-[color:var(--color-ink)] m-0">{{ $pr->title }}</p>
<p class="text-[11px] text-[color:var(--color-ink-3)] mt-0.5 m-0 truncate">
{{ $pr->company?->name ?? '' }}
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
{{ $pr->user?->name ?? '' }}
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
{{ $pr->created_at->format('d.m.Y') }}
</p>
</div>
<span @class([
'badge shrink-0',
'ok' => $pr->status->value === 'published',
'warn' => $pr->status->value === 'review',
'err' => $pr->status->value === 'rejected',
'hub' => in_array($pr->status->value, ['archived', 'draft'], true),
])>
{{ $pr->status->label() }}
</span>
</a>
@empty
<p class="px-5 py-8 text-center text-[12.5px] text-[color:var(--color-ink-3)]">
{{ __('Noch keine Pressemitteilungen.') }}
</p>
@endforelse
</article>
{{-- Warteschlange Prüfung --}}
<div class="overflow-hidden rounded-xl border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-900">
<div class="flex items-center justify-between border-b border-zinc-200 px-4 py-3 dark:border-zinc-700">
<h2 class="font-semibold">{{ __('Zur Prüfung') }}</h2>
@if($stats['press_releases']['review'] > 0)
<span class="rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400">
{{ $stats['press_releases']['review'] }}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Zur Prüfung') }}</span>
@if ($stats['press_releases']['review'] > 0)
<span class="badge warn dot">
{{ $stats['press_releases']['review'] }} {{ __('offen') }}
</span>
@else
<span class="badge ok dot">{{ __('leer') }}</span>
@endif
</div>
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
@forelse($pendingReviews as $pr)
<a href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
class="block px-4 py-3 transition hover:bg-zinc-50 dark:hover:bg-zinc-800">
<p class="truncate text-sm font-medium">{{ $pr->title }}</p>
<p class="text-xs text-zinc-500">
{{ $pr->company?->name ?? '' }} · {{ $pr->portal->label() }} · {{ $pr->created_at->format('d.m.Y') }}
</p>
</a>
@empty
<p class="px-4 py-6 text-center text-sm text-zinc-500">{{ __('Keine PMs in der Prüfwarteschlange.') }}</p>
@endforelse
</div>
@if($stats['press_releases']['review'] > count($pendingReviews))
<div class="border-t border-zinc-100 px-4 py-2 dark:border-zinc-800">
<a href="{{ route('admin.press-releases.index', ['statusFilter' => 'review']) }}" wire:navigate
class="text-xs text-blue-600 hover:underline dark:text-blue-400">
+ {{ $stats['press_releases']['review'] - count($pendingReviews) }} {{ __('weitere') }}
@forelse ($pendingReviews as $pr)
<a href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
class="block px-5 py-3 border-b border-[color:var(--color-bg-rule)] last:border-b-0 hover:bg-[color:var(--color-bg)] transition-colors">
<p class="truncate text-[13px] font-medium text-[color:var(--color-ink)] m-0">{{ $pr->title }}</p>
<p class="text-[11px] text-[color:var(--color-ink-3)] mt-0.5 m-0 truncate">
{{ $pr->company?->name ?? '' }}
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
{{ $pr->portal->label() }}
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
{{ $pr->created_at->format('d.m.Y') }}
</p>
</a>
@empty
<p class="px-5 py-8 text-center text-[12.5px] text-[color:var(--color-ink-3)]">
{{ __('Keine PMs in der Prüfwarteschlange.') }}
</p>
@endforelse
@if ($stats['press_releases']['review'] > count($pendingReviews))
<div class="px-5 py-3 border-t border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)]">
<a href="{{ route('admin.press-releases.index', ['status' => 'review']) }}" wire:navigate
class="text-[12px] font-semibold text-[color:var(--color-hub)] hover:underline underline-offset-[3px] decoration-[color:var(--color-hub)]/30">
+ {{ $stats['press_releases']['review'] - count($pendingReviews) }} {{ __('weitere') }}
</a>
</div>
@endif
</div>
</div>
</article>
</section>
{{-- ============== NEWSLETTER + QUICK ACTIONS ============== --}}
<section class="grid gap-6 lg:grid-cols-[1fr_2fr]">
{{-- Newsletter-Stat als panel-warm Block --}}
<article class="panel-warm relative overflow-hidden">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Newsletter') }}</span>
<span class="badge hub">{{ __('bestätigt') }}</span>
</div>
<div class="px-5 py-5">
<div class="stat-num text-[color:var(--color-ink)]">
{{ number_format($stats['newsletter']) }}
</div>
<p class="text-[12px] text-[color:var(--color-ink-3)] mt-2 m-0">
{{ __('Aktive Newsletter-Abonnenten über beide Portale.') }}
</p>
<a href="{{ route('admin.newsletter.sync') }}" wire:navigate
class="inline-flex items-center gap-1 mt-3 text-[12px] font-semibold text-[color:var(--color-hub)] hover:underline underline-offset-[3px] decoration-[color:var(--color-hub)]/30">
{{ __('Sync verwalten') }}
</a>
</div>
</article>
{{-- Quick-Actions Panel --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Quick Actions') }}</span>
</div>
<div class="grid gap-3 p-5 grid-cols-2 md:grid-cols-4">
@foreach ([
['icon' => 'newspaper', 'label' => __('Pressemitteilungen'), 'route' => 'admin.press-releases.index'],
['icon' => 'building-office', 'label' => __('Firmen'), 'route' => 'admin.companies.index'],
['icon' => 'document-text', 'label' => __('Rechnungen'), 'route' => 'admin.invoices.index'],
['icon' => 'cog', 'label' => __('Voreinstellungen'), 'route' => 'admin.presets.index'],
] as $action)
<a href="{{ route($action['route']) }}" wire:navigate
class="group flex flex-col items-start gap-2 p-4 rounded-[5px] transition-colors
bg-[color:var(--color-bg-elev)] border border-[color:var(--color-bg-rule)]
hover:border-[color:var(--color-hub)]/40 hover:bg-[color:var(--color-bg)]">
<span class="w-9 h-9 rounded-[4px] flex items-center justify-center
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)]
text-[color:var(--color-hub)] group-hover:bg-[color:var(--color-hub)] group-hover:text-white transition-colors">
<flux:icon :name="$action['icon']" class="size-[18px]" />
</span>
<span class="text-[12px] font-semibold text-[color:var(--color-ink-2)] leading-tight">
{{ $action['label'] }}
</span>
</a>
@endforeach
</div>
</article>
</section>
{{-- ============== FOOTER ============== --}}
<footer class="flex items-center justify-between pt-4 pb-2 text-[11px]
border-t border-[color:var(--color-bg-rule)] text-[color:var(--color-ink-3)]">
<span>© {{ now()->format('Y') }} pressekonto.de · Admin Backend</span>
<span class="flex items-center gap-5">
<a href="{{ route('admin.users.index') }}" wire:navigate class="hover:text-[color:var(--color-hub)]">{{ __('Benutzer') }}</a>
<a href="{{ route('admin.roles.index') }}" wire:navigate class="hover:text-[color:var(--color-hub)]">{{ __('Rollen & Rechte') }}</a>
<a href="{{ route('admin.reports.slow-requests') }}" wire:navigate class="hover:text-[color:var(--color-hub)]">{{ __('Performance') }}</a>
</span>
</footer>
</div>
</x-layouts.app>