presseportale/resources/views/livewire/customer/dashboard.blade.php
Kevin Adametz 31da9ea8b6
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
livewire: Volt entfernt — Komponenten als Livewire-4-Single-File-Components, Route::livewire(), Livewire::test(); livewire/volt deinstalliert
- 83 Komponenten: Livewire\Volt\Component → Livewire\Component
- 73 Routen (admin, auth, customer): Volt::route() → Route::livewire()
- 88 Testdateien: Volt::test/actingAs → Livewire::test/actingAs
- VoltServiceProvider gelöscht; composer remove livewire/volt
- config/livewire.php auf v4-Schlüssel (component_layout, component_placeholder,
  component_locations, make_command: SFC ohne ⚡). Der alte Schlüssel layout war
  wirkungslos; alle gerouteten Komponenten setzen ihr Layout selbst.
- CLAUDE.md, AGENTS.md, boost.json, .cursorrules, Skill volt-development
- Roadmap: erledigt; Larastan erfasst SFCs weiterhin nicht (Blade-Dateien)

Suite 2.325 grün, einer übersprungen; Larastan und Pint ohne Meldung;
route:cache und view:cache laufen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:59:36 +00:00

669 lines
35 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Enums\PressReleaseStatus;
use App\Models\BillingAddress;
use App\Models\Company;
use App\Models\Contact;
use App\Models\PressRelease;
use App\Models\Profile;
use App\Models\User;
use App\Services\Customer\CustomerCompanyContext;
use App\Scopes\PortalScope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends Component {
public function with(): array
{
$user = auth()->user();
$context = app(CustomerCompanyContext::class);
$selectedCompanyId = $context->selectedCompanyId($user);
$selectedCompany = $selectedCompanyId === null ? null : $context->findFor($user, $selectedCompanyId);
$pressReleaseQuery = PressRelease::withoutGlobalScope(PortalScope::class)
->where('user_id', $user->id)
->when($selectedCompanyId !== null, fn($query) => $query->where('company_id', $selectedCompanyId));
$now = Carbon::now();
$currentStart = $now->copy()->startOfMonth();
$previousStart = $now->copy()->subMonthNoOverflow()->startOfMonth();
$previousEnd = $now->copy()->subMonthNoOverflow()->endOfMonth();
$stats = (clone $pressReleaseQuery)
->toBase()
->selectRaw('COUNT(*) as total')
->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as published', [PressReleaseStatus::Published->value])
->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as review', [PressReleaseStatus::Review->value])
->selectRaw('SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as draft', [PressReleaseStatus::Draft->value])
->selectRaw('SUM(CASE WHEN created_at >= ? THEN 1 ELSE 0 END) as current_month', [$currentStart])
->selectRaw('SUM(CASE WHEN created_at BETWEEN ? AND ? THEN 1 ELSE 0 END) as previous_month', [$previousStart, $previousEnd])
->first();
$recent = (clone $pressReleaseQuery)
->with('company:id,name')
->latest()
->limit(5)
->get(['id', 'title', 'status', 'portal', 'company_id', 'created_at', 'published_at']);
$profile = $user->profile;
$billingAddress = $user->billingAddress;
$profilePercent = $this->profileCompleteness($profile);
$billingPercent = $this->billingCompleteness($billingAddress);
$pressReleaseTotal = (int) ($stats->total ?? 0);
$companiesTotal = $context->companyCountFor($user);
$onboardingSteps = $this->onboardingSteps($profilePercent, $billingPercent, $selectedCompany, $companiesTotal, $pressReleaseTotal);
$onboardingCompleted = collect($onboardingSteps)->where('done', true)->count();
return [
'user' => $user,
'selectedCompany' => $selectedCompany,
'onboardingSteps' => $onboardingSteps,
'onboardingCompleted' => $onboardingCompleted,
'onboardingTotal' => count($onboardingSteps),
// Launch-Gate (WS-5): Solange billing.enforce_booking aus ist, ist
// dies immer false und der Hinweis bleibt unsichtbar. Bei scharfem
// Gate sehen Bestandskunden (grandfathered) den Banner nicht.
'bookingRequired' => config('billing.enforce_booking') && !$user->hasActiveBooking(),
'stats' => [
'total' => (int) ($stats->total ?? 0),
'published' => (int) ($stats->published ?? 0),
'review' => (int) ($stats->review ?? 0),
'draft' => (int) ($stats->draft ?? 0),
'deltaMonth' => (int) ($stats->current_month ?? 0) - (int) ($stats->previous_month ?? 0),
],
'profileCompleteness' => $profilePercent,
'billingCompleteness' => $billingPercent,
'qualityHints' => $this->qualityHints($user, $profile, $billingAddress, $selectedCompany, $pressReleaseQuery),
'recent' => $recent,
'companies' => $context->latestCompaniesFor($user),
'companiesTotal' => $companiesTotal,
'bridgeStatus' => [
/* Heute hardcoded — perspektivisch aus echtem Sync-Service. */
'presseecho' => ['state' => 'connected', 'subline' => __('Archiv · Branchen-Tiefe')],
'businessportal24' => ['state' => 'connected', 'subline' => __('Wirtschaft · Live')],
],
];
}
/**
* Heuristische Profil-Vollständigkeit in %.
* 6 Kernfelder, jedes ~17 %.
*/
private function profileCompleteness(?Profile $profile): int
{
if (!$profile) {
return 0;
}
$fields = ['salutation_key', 'first_name', 'last_name', 'phone', 'address', 'country_code'];
$filled = collect($fields)->filter(fn(string $field) => filled($profile->{$field}))->count();
return (int) round(($filled / count($fields)) * 100);
}
/**
* Rechnungsadressen-Vollständigkeit in %.
* Ohne hinterlegte Adresse: 0; sonst je nach gefüllten Pflichtfeldern.
*/
private function billingCompleteness(?BillingAddress $address): int
{
return $address?->completenessPercent() ?? 0;
}
/**
* Geführte „Erste Schritte" für neue Kunden. Jeder Schritt gilt als
* erledigt, sobald die zugehörigen Stammdaten vorhanden sind.
*
* @return list<array{num: string, icon: string, title: string, description: string, href: string, action: string, done: bool}>
*/
private function onboardingSteps(int $profilePercent, int $billingPercent, ?Company $selectedCompany, int $companiesTotal, int $pressReleaseTotal): array
{
return [
[
'num' => '01',
'icon' => 'user',
'title' => __('Profil vervollständigen'),
'description' => __('Hinterlegen Sie Anrede, Telefon und Adresse für eine saubere Kundenakte.'),
'href' => route('me.profile') . '#profil',
'action' => __('Profil öffnen'),
'done' => $profilePercent >= 100,
],
[
'num' => '02',
'icon' => 'archive-box',
'title' => __('Rechnungsadresse hinterlegen'),
'description' => __('Damit spätere Buchungen sauber abgerechnet werden können.'),
'href' => route('me.profile') . '#rechnungsadresse',
'action' => __('Adresse ergänzen'),
'done' => $billingPercent >= 100,
],
[
'num' => '03',
'icon' => 'building-office',
'title' => __('Firma zuordnen'),
'description' => __('Ordnen Sie Ihrem Konto eine Firma bzw. Pressestelle zu, für die Sie veröffentlichen.'),
'href' => route('me.press-kits.index'),
'action' => __('Firmen öffnen'),
'done' => $selectedCompany !== null || $companiesTotal > 0,
],
[
'num' => '04',
'icon' => 'newspaper',
'title' => __('Erste Pressemitteilung erstellen'),
'description' => __('Verfassen Sie Ihre erste Mitteilung und senden Sie sie zur Prüfung.'),
'href' => route('me.press-releases.create'),
'action' => __('Mitteilung erstellen'),
'done' => $pressReleaseTotal > 0,
],
];
}
/**
* @return list<array{icon: string, title: string, description: string, href: string, action: string, percent?: int}>
*/
private function qualityHints(User $user, ?Profile $profile, ?BillingAddress $billingAddress, ?Company $selectedCompany, Builder $pressReleaseQuery): array
{
$hints = [];
$profilePercent = $this->profileCompleteness($profile);
if ($profilePercent < 100) {
$hints[] = [
'icon' => 'user',
'title' => __('Profil unvollständig'),
'description' => __('Ergänzen Sie Salutation, Telefon und Adresse für eine saubere Kundenakte.'),
'href' => route('me.profile') . '#profil',
'action' => __('Profil öffnen'),
'percent' => $profilePercent,
];
}
$billingPercent = $this->billingCompleteness($billingAddress);
if ($billingPercent < 100) {
$hints[] = [
'icon' => 'archive-box',
'title' => $billingAddress ? __('Rechnungsadresse unvollständig') : __('Rechnungsadresse fehlt'),
'description' => __('Hinterlegen Sie eine Rechnungsadresse, damit spätere Buchungen sauber abgerechnet werden können.'),
'href' => route('me.profile') . '#rechnungsadresse',
'action' => __('Rechnungsadresse ergänzen'),
'percent' => $billingPercent,
];
}
if ($selectedCompany) {
$contactsCount = Contact::forCompany($selectedCompany->id)->count();
if ($contactsCount === 0) {
$hints[] = [
'icon' => 'user-group',
'title' => __('Keine Pressekontakte hinterlegt'),
'description' => __('Ergänzen Sie Pressekontakte für diese Firma.'),
'href' => route('me.press-kits.show', $selectedCompany->id),
'action' => __('Firma öffnen'),
];
}
} else {
$unassignedPressReleasesCount = (clone $pressReleaseQuery)->whereNull('company_id')->count();
if ($unassignedPressReleasesCount > 0) {
$hints[] = [
'icon' => 'newspaper',
'title' => trans_choice(':count Pressemitteilung ohne Firma|:count Pressemitteilungen ohne Firma', $unassignedPressReleasesCount, ['count' => $unassignedPressReleasesCount]),
'description' => __('Ordnen Sie Legacy-Pressemitteilungen einer Firma zu, damit Portal und Pressekontakte eindeutig sind.'),
'href' => route('me.press-releases.index', ['company' => 'unassigned']),
'action' => __('Pressemitteilungen prüfen'),
];
}
}
return $hints;
}
}; ?>
<div class="space-y-8">
{{-- ============== PAGE HEADER ============== --}}
<header class="page-header">
<div class="min-w-0">
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
<span class="badge hub dot">{{ __('User Backend') }}</span>
<span class="eyebrow muted">{{ __('Mein Bereich · A · 01') }}</span>
</div>
<h1 class="text-[34px] font-bold tracking-[-0.7px] leading-[1.1] m-0 text-[color:var(--color-ink)]">
{{ __('Mein 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)]">{{ $user->name }}</strong>.
@if ($selectedCompany)
{{ __('Übersicht für :company', ['company' => $selectedCompany->name]) }} —
@endif
{{ __('Hier sehen Sie Status und Reichweite Ihres Kundenkontos für presseecho und businessportal24.') }}
</p>
</div>
<div class="flex items-center gap-3 flex-shrink-0">
@if ($selectedCompany)
<span class="badge hub dot">
{{ __('Aktive Firma:') }} <strong class="font-semibold">{{ $selectedCompany->name }}</strong>
</span>
@else
<a href="{{ route('me.press-kits.index') }}" wire:navigate
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-[4px] text-[12px] font-semibold whitespace-nowrap bg-[color:var(--color-warn-soft)] border border-[color:var(--color-warn)]/30 text-[color:var(--color-accent-deep)] hover:bg-[color:var(--color-warn-soft)]/80 transition">
<flux:icon.exclamation-triangle class="size-[13px] flex-shrink-0" />
{{ __('Keine Firma zugeordnet') }}
<span
class="underline underline-offset-[3px] decoration-[color:var(--color-accent-deep)]/40">{{ __('zuordnen') }}
→</span>
</a>
@endif
</div>
</header>
{{-- ============== ERSTE SCHRITTE (Onboarding, aufklappbar) ============== --}}
@if ($onboardingCompleted < $onboardingTotal)
<x-portal.onboarding-guide :steps="$onboardingSteps" :completed="$onboardingCompleted" :total="$onboardingTotal" />
@endif
{{-- ============== BUCHUNGS-GATE (WS-5) ============== --}}
@if ($bookingRequired)
<section
class="rounded-[6px] border border-[color:var(--color-warn)]/40 bg-[color:var(--color-warn-soft)] px-5 py-4">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex items-start gap-3 min-w-0">
<span
class="flex-shrink-0 mt-0.5 w-9 h-9 rounded-[5px] flex items-center justify-center
bg-white/60 border border-[color:var(--color-warn)]/30 text-[color:var(--color-accent-deep)]">
<flux:icon.lock-closed class="size-[17px]" />
</span>
<div class="min-w-0">
<div class="text-[13.5px] font-semibold text-[color:var(--color-ink)]">
{{ __('Buchung erforderlich, um zu veröffentlichen') }}
</div>
<p class="text-[12px] leading-[1.55] mt-1 m-0 text-[color:var(--color-ink-2)]">
{{ __('Sie können Pressemitteilungen wie gewohnt erstellen und speichern. Zum Einreichen zur Prüfung wird eine aktive Buchung benötigt.') }}
</p>
</div>
</div>
<div class="flex-shrink-0">
<flux:button variant="primary" icon="credit-card" href="{{ route('me.bookings.index') }}"
wire:navigate>
{{ __('Buchung auswählen') }}
</flux:button>
</div>
</div>
</section>
@endif
{{-- ============== STAT-CARDS — KPI-Reihe ============== --}}
<section class="grid grid-cols-2 gap-4 xl:grid-cols-4">
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="$stats['total']">
<x-slot:meta>{{ now()->format('Y') }}</x-slot:meta>
<x-slot:trend>
@php
$delta = $stats['deltaMonth'];
$deltaLabel =
$delta === 0
? __('keine Veränderung ggü. Vormonat')
: trans_choice(':sign:abs ggü. Vormonat', abs($delta), [
'sign' => $delta > 0 ? '+' : '−',
'abs' => abs($delta),
]);
@endphp
<span class="flex items-center gap-1">
@if ($delta > 0)
<flux:icon.arrow-trending-up class="size-[11px] text-[color:var(--color-ok)]" />
@elseif ($delta < 0)
<flux:icon.arrow-trending-down class="size-[11px] text-[color:var(--color-loss)]" />
@else
<flux:icon.minus class="size-[11px] text-[color:var(--color-ink-4)]" />
@endif
{{ $deltaLabel }}
</span>
</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="ok" :label="__('Veröffentlicht')" :value="$stats['published']">
<x-slot:meta>
<span class="badge ok" style="font-size:9.5px;padding:1px 6px;">{{ __('live') }}</span>
</x-slot:meta>
<x-slot:trend>{{ __('auf beiden Portalen') }}</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="warn" :label="__('In Prüfung')" :value="$stats['review']">
<x-slot:meta>{{ __('Ø 4 h') }}</x-slot:meta>
<x-slot:trend>{{ __('Inhaltsprüfung') }}</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="muted" :label="__('Entwürfe')" :value="$stats['draft']">
<x-slot:meta>{{ __('privat') }}</x-slot:meta>
<x-slot:trend>{{ __('gespeichert, nicht eingereicht') }}</x-slot:trend>
</x-portal.stat-card>
</section>
{{-- ============== ZWEISPALTEN-GRID ============== --}}
<section class="grid gap-6 lg:grid-cols-[2fr_1fr]">
{{-- LINKS: Pressemitteilungen-Liste / Empty-State --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Meine letzten Pressemitteilungen') }}</span>
<div class="flex items-center gap-3">
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
{{ $recent->count() }} {{ __('von') }} {{ $stats['total'] }}
</span>
<a href="{{ route('me.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>
@forelse ($recent as $pr)
@php
$badgeClass = $pr->status->badgeClass();
$portal = $pr->portal?->value ?? 'both';
$showPe = in_array($portal, ['presseecho', 'both'], true);
$showBp = in_array($portal, ['businessportal24', 'both'], true);
$primaryDate =
$pr->status === PressReleaseStatus::Published && $pr->published_at
? $pr->published_at
: $pr->created_at;
@endphp
<a href="{{ route('me.press-releases.show', $pr->id) }}" wire:navigate
class="flex items-center justify-between gap-4 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">
PM-{{ $pr->id }}
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
{{ $pr->company?->name ?? __('Ohne Firma') }}
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
{{ $primaryDate?->format('d.m.Y') }}
</p>
</div>
<div class="flex items-center gap-1.5 shrink-0">
@if ($showPe)
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
@endif
@if ($showBp)
<span class="portal-pill bp"><span class="pdot"></span>businessportal24</span>
@endif
<span class="badge {{ $badgeClass }} dot">{{ $pr->status->label() }}</span>
</div>
</a>
@empty
<div class="px-10 py-14 flex flex-col items-center text-center">
<div
class="w-16 h-16 rounded-[6px] flex items-center justify-center mb-5 relative
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
<flux:icon.newspaper class="size-7" />
<span
class="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full text-[10px] font-bold flex items-center justify-center font-mono
bg-[color:var(--color-accent)] text-white">0</span>
</div>
<div class="text-[16px] font-semibold m-0 text-[color:var(--color-ink)]">
{{ __('Noch keine Pressemitteilungen') }}
</div>
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[460px] text-[color:var(--color-ink-3)]">
{{ __('Starten Sie mit einer ersten Mitteilung für die aktive Firma oder Ihr Kundenkonto. Veröffentlichung erfolgt nach bestandener Inhaltsprüfung auf beiden Portalen.') }}
</p>
<div class="mt-6 flex items-center gap-2.5">
<flux:button variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}"
wire:navigate>
{{ __('Erste Pressemitteilung erstellen') }}
</flux:button>
</div>
<div class="mt-9 grid gap-3 w-full max-w-[560px] grid-cols-1 sm:grid-cols-3">
@foreach ([['num' => '01', 'label' => __('Firma zuordnen')], ['num' => '02', 'label' => __('Mitteilung verfassen')], ['num' => '03', 'label' => __('Zur Prüfung senden')]] as $step)
<div
class="text-left px-3 py-2.5 rounded-[3px]
bg-[color:var(--color-bg-elev)] border border-[color:var(--color-bg-rule)]">
<div
class="font-mono text-[9.5px] tracking-[0.16em] font-bold mb-1 text-[color:var(--color-accent-deep)]">
{{ $step['num'] }}
</div>
<div class="text-[11.5px] font-semibold leading-tight text-[color:var(--color-ink)]">
{{ $step['label'] }}
</div>
</div>
@endforeach
</div>
</div>
@endforelse
<div
class="px-5 py-3 border-t border-[color:var(--color-bg-rule)] flex items-center gap-2.5 text-[11.5px]
bg-[color:var(--color-bg-elev)] text-[color:var(--color-ink-3)]">
<flux:icon.shield-check class="size-[12px] text-[color:var(--color-hub)] flex-shrink-0" />
{{ __('Tipp: Geprüfte Mitteilungen erscheinen i. d. R. binnen') }}
<strong class="font-semibold text-[color:var(--color-ink-2)]">{{ __('wenigen Minuten') }}</strong>
{{ __('auf den Portalen.') }}
</div>
</article>
{{-- RECHTS: Datenqualität --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Datenqualität') }}</span>
@if (count($qualityHints) > 0)
<span class="badge warn dot">{{ count($qualityHints) }} {{ __('offen') }}</span>
@else
<span class="badge ok dot">{{ __('vollständig') }}</span>
@endif
</div>
<div class="px-5 py-5">
<p class="text-[12px] leading-[1.55] m-0 mb-4 text-[color:var(--color-ink-3)]">
{{ __('Diese Hinweise helfen, Ihr User Backend vollständig und sauber zu halten.') }}
</p>
@if (count($qualityHints) > 0)
<div class="space-y-3">
@foreach ($qualityHints as $hint)
<x-portal.hint-card :icon="$hint['icon']" :title="$hint['title']" :percent="$hint['percent'] ?? null"
:href="$hint['href']" :action="$hint['action']">
{{ $hint['description'] }}
</x-portal.hint-card>
@endforeach
</div>
@else
<div class="flex flex-col items-center text-center py-6">
<flux:icon.check-badge class="size-8 text-[color:var(--color-ok)] mb-2" />
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
{{ __('Alles im grünen Bereich') }}
</div>
<p class="text-[11.5px] text-[color:var(--color-ink-3)] mt-1 m-0">
{{ __('Profil, Rechnungsadresse und Firmen-Daten sind vollständig.') }}
</p>
</div>
@endif
</div>
</article>
</section>
{{-- ============== UNTERER GRID: FIRMEN + BRAND-BRIDGE ============== --}}
<section class="grid gap-6 lg:grid-cols-[2fr_1fr]">
{{-- Firmen --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Meine Firmen') }}</span>
<div class="flex items-center gap-3">
<span class="badge hub" style="font-size:9.5px;padding:1px 6px;">
{{ $companiesTotal }} {{ __('zugeordnet') }}
</span>
<a href="{{ route('me.press-kits.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 Firmen anzeigen') }} →
</a>
</div>
</div>
<div class="p-6">
@if ($companies->isNotEmpty())
<div class="grid gap-3 grid-cols-1 md:grid-cols-2">
@foreach ($companies as $company)
<a href="{{ route('me.press-kits.show', $company->id) }}" wire:navigate
class="block relative rounded-[5px] p-5 transition-colors
bg-[color:var(--color-bg-elev)] border border-[color:var(--color-bg-rule)]
hover:border-[color:var(--color-hub)]/50">
<div class="flex items-center gap-3 mb-2">
<span
class="w-10 h-10 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)] text-[12px] font-bold">
{{ \Illuminate\Support\Str::of($company->name)->take(2)->upper() }}
</span>
<div class="min-w-0">
<div
class="text-[13.5px] font-semibold truncate text-[color:var(--color-ink)]">
{{ $company->name }}
</div>
<div class="text-[11px] mt-0.5 text-[color:var(--color-ink-3)]">
{{ __('Presse-Kit öffnen') }}
</div>
</div>
</div>
</a>
@endforeach
</div>
@if ($companiesTotal > $companies->count())
<div class="mt-4 text-[11.5px] leading-[1.5] text-[color:var(--color-ink-3)]">
{{ __('Die zehn neuesten Firmen werden hier als Vorschau angezeigt.') }}
<a href="{{ route('me.press-kits.index') }}" wire:navigate
class="font-semibold text-[color:var(--color-hub)] hover:underline underline-offset-[3px] decoration-[color:var(--color-hub)]/30">
{{ __('Zur vollständigen Firmenliste') }} →
</a>
</div>
@endif
@else
<div class="grid gap-3 grid-cols-1 md:grid-cols-2">
<div
class="relative rounded-[5px] p-5 transition-colors
border border-dashed border-[color:var(--color-bg-rule)] hover:bg-[color:var(--color-bg-elev)]">
<div class="flex items-center gap-3 mb-3">
<span
class="w-10 h-10 rounded-[4px] flex items-center justify-center
border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)]
text-[color:var(--color-ink-4)]">
<flux:icon.building-office class="size-[18px]" />
</span>
<div>
<div class="text-[13.5px] font-semibold text-[color:var(--color-ink-2)]">
{{ __('Firma hinzufügen') }}
</div>
<div class="text-[11px] mt-0.5 text-[color:var(--color-ink-3)]">
{{ __('Slot frei') }}
</div>
</div>
</div>
<p class="text-[11.5px] leading-[1.5] m-0 text-[color:var(--color-ink-3)]">
{{ __('Pressestellen, für die Sie Mitteilungen erstellen — mit eigenem Logo, Kontaktperson und Themen-Tags.') }}
</p>
</div>
<div
class="p-5 rounded-[5px]
bg-[color:var(--color-bg-elev)] border border-[color:var(--color-bg-rule)]">
<div class="eyebrow muted mb-2">{{ __('Hinweis') }}</div>
<div class="text-[13px] leading-[1.55] m-0 text-[color:var(--color-ink-2)]">
{{ __('Keine Firmen zugeordnet. Wenn hier eine Firma fehlen sollte, prüfen Sie bitte die Firmenverwaltung oder wenden Sie sich an den Support.') }}
</div>
<div class="mt-3">
<flux:button size="sm" variant="filled"
href="{{ route('me.press-kits.index') }}" wire:navigate>
{{ __('Firmen öffnen') }}
</flux:button>
</div>
</div>
</div>
@endif
</div>
</article>
{{-- Brand-Bridge (dark) --}}
<article class="panel-dark">
<div class="panel-head">
<span class="eyebrow on-dark">{{ __('Brand-Bridge') }}</span>
<span
class="font-mono text-[10px] tracking-[0.14em] uppercase text-[color:var(--color-ink-on-dark-3)]">A
· B</span>
</div>
<div class="px-5 py-5">
<div class="text-[12.5px] leading-[1.55] m-0 mb-4 text-[color:var(--color-ink-on-dark-2)]">
{{ __('Ein Konto, beide Portale — Veröffentlichungen werden parallel auf presseecho und businessportal24 ausgespielt.') }}
</div>
<div class="grid gap-3 grid-cols-2">
{{-- Konstantes dunkles Hub-Blau (Phase 5): bleibt in beiden Modi gleich. --}}
<div class="rounded-[4px] px-3.5 py-3 border bg-[color:var(--color-panel-dark-2)] border-white/5">
<div class="flex items-center gap-2 mb-1.5">
<span class="dot-pe"></span>
<span
class="text-[11px] font-bold tracking-[0.14em] uppercase text-white/85">presseecho</span>
</div>
<div class="font-mono text-[15px] font-semibold text-white tabular-nums">
{{ __($bridgeStatus['presseecho']['state']) }}
</div>
<div class="text-[10.5px] mt-0.5 text-[color:var(--color-ink-on-dark-3)]">
{{ $bridgeStatus['presseecho']['subline'] }}
</div>
</div>
<div class="rounded-[4px] px-3.5 py-3 border bg-[color:var(--color-panel-dark-2)] border-white/5">
<div class="flex items-center gap-2 mb-1.5">
<span class="dot-bp"></span>
<span
class="text-[11px] font-bold tracking-[0.14em] uppercase text-white/85">businessportal24</span>
</div>
<div class="font-mono text-[15px] font-semibold text-white tabular-nums">
{{ __($bridgeStatus['businessportal24']['state']) }}
</div>
<div class="text-[10.5px] mt-0.5 text-[color:var(--color-ink-on-dark-3)]">
{{ $bridgeStatus['businessportal24']['subline'] }}
</div>
</div>
</div>
<hr class="mt-5 mb-4 border-0 h-px bg-white/10" />
<div class="space-y-2 text-[11.5px] text-[color:var(--color-ink-on-dark-2)]">
<div class="flex items-center justify-between">
<span>{{ __('API-Status') }}</span>
<span class="flex items-center gap-1.5 text-white">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
{{ __('operational') }}
</span>
</div>
<div class="flex items-center justify-between">
<span>{{ __('Tarif') }}</span>
<span class="font-mono text-white">{{ __('Starter') }}</span>
</div>
</div>
</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.com · Publisher-Hub</span>
<span class="flex items-center gap-5">
<a href="{{ route('me.security') }}" wire:navigate
class="hover:text-[color:var(--color-hub)]">{{ __('Sicherheit') }}</a>
<a href="{{ route('me.tokens.index') }}" wire:navigate
class="hover:text-[color:var(--color-hub)]">{{ __('API & Tokens') }}</a>
<a href="{{ route('me.profile') }}" wire:navigate
class="hover:text-[color:var(--color-hub)]">{{ __('Profil') }}</a>
</span>
</footer>
</div>