237 lines
11 KiB
PHP
237 lines
11 KiB
PHP
<?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') }} →
|
||
</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>
|