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,237 @@
<?php
use App\Enums\PressReleaseStatus;
use App\Models\Company;
use App\Models\Contact;
use App\Models\PressRelease;
use App\Models\User;
use App\Services\Customer\CustomerCompanyContext;
use Illuminate\Database\Eloquent\Builder;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Volt\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 = $context->selectedCompany($user);
$pressReleaseQuery = PressRelease::withoutGlobalScopes()
->where('user_id', $user->id)
->when($selectedCompanyId !== null, fn ($query) => $query->where('company_id', $selectedCompanyId));
$myPRs = (clone $pressReleaseQuery)
->selectRaw('status, count(*) as count')
->groupBy('status')
->pluck('count', 'status');
$recent = (clone $pressReleaseQuery)
->with('company:id,name')
->latest()
->limit(5)
->get(['id', 'title', 'status', 'company_id', 'created_at']);
return [
'user' => $user,
'selectedCompany' => $selectedCompany,
'stats' => [
'total' => (clone $pressReleaseQuery)->count(),
'published' => $myPRs->get('published', 0),
'review' => $myPRs->get('review', 0),
'draft' => $myPRs->get('draft', 0),
],
'qualityHints' => $this->qualityHints($user, $selectedCompany, $pressReleaseQuery),
'recent' => $recent,
'companies' => $context->companiesFor($user),
];
}
private function qualityHints(User $user, ?Company $selectedCompany, Builder $pressReleaseQuery): array
{
$hints = [];
if (! $user->profile()->exists()) {
$hints[] = [
'color' => 'amber',
'icon' => 'user',
'title' => __('Profil unvollständig'),
'description' => __('Ergänzen Sie Ihre Profildaten für eine sauberere Kundenakte.'),
'href' => route('me.profile').'#profil',
'action' => __('Profil öffnen'),
];
}
if (! $user->billingAddress()->exists()) {
$hints[] = [
'color' => 'amber',
'icon' => 'archive-box',
'title' => __('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'),
];
}
if ($selectedCompany) {
$contactsCount = Contact::withoutGlobalScopes()
->where('company_id', $selectedCompany->id)
->count();
if ($contactsCount === 0) {
$hints[] = [
'color' => 'blue',
'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[] = [
'color' => 'amber',
'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-6">
<flux:card>
<flux:heading size="lg">{{ __('Willkommen, :name', ['name' => $user->name]) }}</flux:heading>
<flux:subheading>
{{ $selectedCompany
? __('Übersicht für :company', ['company' => $selectedCompany->name])
: __('Übersicht Ihres Kundenkontos') }}
</flux:subheading>
</flux:card>
{{-- Statistiken --}}
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
<flux:card>
<flux:text class="text-xs text-zinc-500">{{ __('Gesamt') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
</flux:card>
<flux:card>
<flux:text class="text-xs text-green-600">{{ __('Veröffentlicht') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['published'] }}</flux:text>
</flux:card>
<flux:card>
<flux:text class="text-xs text-yellow-600">{{ __('In Prüfung') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['review'] }}</flux:text>
</flux:card>
<flux:card>
<flux:text class="text-xs text-zinc-500">{{ __('Entwürfe') }}</flux:text>
<flux:text size="xl" weight="bold">{{ $stats['draft'] }}</flux:text>
</flux:card>
</div>
@if($qualityHints)
<flux:card>
<div class="mb-4">
<flux:heading size="sm">{{ __('Datenqualität') }}</flux:heading>
<flux:text class="text-sm text-zinc-500">{{ __('Diese Hinweise helfen, Ihr User Backend vollständig und sauber zu halten.') }}</flux:text>
</div>
<div class="grid gap-3 lg:grid-cols-3">
@foreach($qualityHints as $hint)
<a href="{{ $hint['href'] }}" wire:navigate class="rounded-lg border border-zinc-200 p-4 transition hover:bg-zinc-50 dark:border-zinc-700 dark:hover:bg-zinc-900">
<div class="flex items-start gap-3">
<flux:badge color="{{ $hint['color'] }}" size="sm" icon="{{ $hint['icon'] }}" />
<div class="min-w-0 flex-1">
<flux:text weight="semibold">{{ $hint['title'] }}</flux:text>
<flux:text class="mt-1 text-sm text-zinc-500">{{ $hint['description'] }}</flux:text>
<flux:text class="mt-3 text-xs font-medium text-zinc-700 dark:text-zinc-300">
{{ $hint['action'] ?? __('Öffnen') }} &rarr;
</flux:text>
</div>
</div>
</a>
@endforeach
</div>
</flux:card>
@endif
<div class="grid gap-6 lg:grid-cols-[1fr,280px]">
{{-- Letzte Pressemitteilungen --}}
<flux:card class="p-0">
<div class="flex items-center justify-between border-b border-zinc-200 px-4 py-3 dark:border-zinc-700">
<flux:heading size="sm">{{ __('Meine letzten Pressemitteilungen') }}</flux:heading>
<flux:button size="sm" variant="ghost" href="{{ route('me.press-releases.index') }}" wire:navigate>
{{ __('Alle anzeigen') }}
</flux:button>
</div>
<div class="divide-y divide-zinc-100 dark:divide-zinc-800">
@forelse($recent as $pr)
<a href="{{ route('me.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>
<flux:badge color="{{ match($pr->status->value) {
'published' => 'green',
'review' => 'yellow',
'rejected' => 'red',
'archived' => 'blue',
default => 'zinc',
} }}" size="sm">
{{ $pr->status->label() }}
</flux:badge>
</a>
@empty
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
<flux:icon.newspaper class="size-10 text-zinc-300" />
<flux:text weight="semibold" class="mt-3">{{ __('Noch keine Pressemitteilungen') }}</flux:text>
<flux:text class="mt-1 max-w-md text-sm text-zinc-500">
{{ __('Starten Sie mit einer ersten Pressemitteilung für die aktive Firma oder für Ihr Kundenkonto.') }}
</flux:text>
<flux:button class="mt-4" variant="primary" href="{{ route('me.press-releases.create') }}" wire:navigate>
{{ __('Erste Pressemitteilung erstellen') }}
</flux:button>
</div>
@endforelse
</div>
</flux:card>
{{-- Zugeordnete Firmen --}}
<flux:card>
<flux:heading size="sm" class="mb-3">{{ __('Meine Firmen') }}</flux:heading>
@forelse($companies as $company)
<div class="py-2 text-sm">
<p class="font-medium">{{ $company->name }}</p>
</div>
@empty
<div class="rounded-lg border border-dashed border-zinc-200 p-4 text-sm text-zinc-500 dark:border-zinc-700">
{{ __('Keine Firmen zugeordnet. Wenn hier eine Firma fehlen sollte, prüfen Sie bitte Ihr Profil oder wenden Sie sich an den Support.') }}
</div>
@endforelse
<div class="mt-4 border-t border-zinc-100 pt-4 dark:border-zinc-800">
<flux:button size="sm" variant="ghost" href="{{ route('me.profile') }}" wire:navigate>
{{ __('Profil & Firma verwalten') }}
</flux:button>
</div>
</flux:card>
</div>
<flux:button variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
{{ __('Neue Pressemitteilung') }}
</flux:button>
</div>