resetPage(); } public function setSavedView(string $view): void { $allowed = ['all', 'active', 'drafts', 'inactive', 'shared']; $this->savedView = in_array($view, $allowed, true) ? $view : 'all'; $this->resetPage(); } public function setPortalFilter(string $portal): void { $allowed = ['', 'presseecho', 'businessportal24']; $this->portalFilter = in_array($portal, $allowed, true) ? $portal : ''; $this->resetPage(); } public function setRoleFilter(string $role): void { $allowed = ['all', 'owner', 'responsible', 'member']; $this->roleFilter = in_array($role, $allowed, true) ? $role : 'all'; $this->resetPage(); } public function setViewMode(string $mode): void { $this->viewMode = $mode === 'list' ? 'list' : 'cards'; } public function resetFilters(): void { $this->search = ''; $this->savedView = 'all'; $this->portalFilter = ''; $this->roleFilter = 'all'; $this->resetPage(); } /** * @return Builder */ private function baseQuery(User $user): Builder { return app(CustomerCompanyContext::class) ->accessibleCompanyQuery($user); } /** * Wendet die "Saved View"-Logik auf eine Query an. * * @param Builder $query */ private function applySavedView(Builder $query, User $user, string $view): void { match ($view) { 'active' => $query->where('is_active', true), 'inactive' => $query->where('is_active', false), 'drafts' => $query->whereRaw('1 = 0'), 'shared' => $query->where('owner_user_id', '!=', $user->id), default => null, }; } /** * @param Builder $query */ private function applySharedFilters(Builder $query): void { if (filled($this->portalFilter)) { $query->where(function ($query) { $query->where('portal', $this->portalFilter) ->orWhere('portal', 'both'); }); } if (filled($this->search)) { $search = trim($this->search); $query->where(function ($query) use ($search): void { $query->where('name', 'like', '%'.$search.'%') ->orWhere('email', 'like', '%'.$search.'%') ->orWhere('address', 'like', '%'.$search.'%') ->orWhere('slug', 'like', '%'.$search.'%'); }); } } /** * @param Builder $query */ private function applyRoleFilter(Builder $query, User $user, string $role): void { if ($role === 'all') { return; } if ($role === 'owner') { $query->where('owner_user_id', $user->id); return; } $query->where('owner_user_id', '!=', $user->id) ->whereHas('users', function ($query) use ($user, $role): void { $query->where('users.id', $user->id) ->where('company_user.role', $role); }); } /** * Sammelt alle Counter-Werte in genau drei Queries: * 1) aggregiertes COUNT/SUM CASE auf companies * 2) COUNT auf press_releases * 3) COUNT auf contacts * * @return array{ * counters: array{companies: int, active: int, press_releases: int, contacts: int}, * saved_views: array{all: int, active: int, drafts: int, inactive: int, shared: int}, * } */ private function buildAggregateCounts(User $user): array { $row = $this->baseQuery($user) ->selectRaw( 'COUNT(*) as total_companies, SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active_companies, SUM(CASE WHEN is_active = 0 THEN 1 ELSE 0 END) as inactive_companies, SUM(CASE WHEN owner_user_id <> ? THEN 1 ELSE 0 END) as shared_companies', [$user->id] ) ->first(); $totalCompanies = (int) ($row->total_companies ?? 0); $activeCompanies = (int) ($row->active_companies ?? 0); $inactiveCompanies = (int) ($row->inactive_companies ?? 0); $sharedCompanies = (int) ($row->shared_companies ?? 0); if ($totalCompanies === 0) { return [ 'counters' => [ 'companies' => 0, 'active' => 0, 'press_releases' => 0, 'contacts' => 0, ], 'saved_views' => [ 'all' => 0, 'active' => 0, 'drafts' => 0, 'inactive' => 0, 'shared' => 0, ], ]; } $companyIdsQuery = $this->baseQuery($user)->select('companies.id'); $pressReleaseCount = (int) \App\Models\PressRelease::query() ->withoutGlobalScopes() ->whereIn('company_id', $companyIdsQuery) ->count(); $contactsCount = (int) \App\Models\Contact::query() ->withoutGlobalScopes() ->whereIn('company_id', $companyIdsQuery) ->count(); return [ 'counters' => [ 'companies' => $totalCompanies, 'active' => $activeCompanies, 'press_releases' => $pressReleaseCount, 'contacts' => $contactsCount, ], 'saved_views' => [ 'all' => $totalCompanies, 'active' => $activeCompanies, 'drafts' => 0, 'inactive' => $inactiveCompanies, 'shared' => $sharedCompanies, ], ]; } /** * Bestimmt deterministisch einen Logo-Token (lg-*) anhand der Company-Id. */ public function logoVariant(Company $company): string { $variants = ['lg-brew', 'lg-mv', 'lg-soft', 'lg-warm']; if (blank($company->name)) { return 'lg-blank'; } return $variants[$company->id % count($variants)]; } /** * Initialen aus dem Firmennamen (max. 2 Zeichen, Großbuchstaben). */ public function logoInitials(Company $company): string { $name = trim((string) $company->name); if (blank($name)) { return '–'; } $words = preg_split('/\s+/u', $name) ?: []; $letters = ''; foreach ($words as $word) { $first = mb_substr($word, 0, 1); if ($first !== '') { $letters .= $first; } if (mb_strlen($letters) >= 2) { break; } } if ($letters === '') { $letters = mb_substr($name, 0, 2); } return mb_strtoupper($letters); } /** * Liefert eine kompakte Meta-Line: Stadt · Typ. */ public function metaLine(Company $company): string { $parts = []; $address = trim((string) ($company->address ?? '')); if (filled($address)) { $lastLine = collect(preg_split('/\r?\n/', $address)) ->map(fn ($line) => trim((string) $line)) ->filter() ->last(); if (is_string($lastLine) && filled($lastLine)) { $parts[] = $lastLine; } } $type = $company->type?->label(); if (is_string($type) && filled($type)) { $parts[] = $type; } return implode(' · ', $parts); } /** * Rolle des aktuellen Users für die Karte (admin|member). */ public function userRoleKey(Company $company, User $user): string { if ($company->owner_user_id === $user->id) { return 'owner'; } return (string) ($company->getAttribute('current_user_role') ?? $company->pivot?->role ?? 'member'); } public function isAdminRole(string $roleKey): bool { return in_array($roleKey, ['owner', 'responsible'], true); } public function roleLabel(string $roleKey): string { return match ($roleKey) { 'owner' => __('Owner'), 'responsible' => __('Verantwortlich'), default => __('Mitglied'), }; } public function fastLogoUrl(Company $company): ?string { if (blank($company->logo_path)) { return null; } $logoPath = trim((string) $company->logo_path); if (Str::startsWith($logoPath, ['http://', 'https://']) && blank($company->legacy_portal)) { return $logoPath; } if (Str::startsWith($logoPath, '/storage/')) { return asset($logoPath); } if (filled($company->legacy_portal)) { return null; } if (! Str::startsWith($logoPath, ['http://', 'https://'])) { return asset('storage/'.ltrim($logoPath, '/')); } return null; } public function with(): array { $user = auth()->user(); $query = $this->baseQuery($user) ->select([ 'companies.id', 'companies.owner_user_id', 'companies.portal', 'companies.type', 'companies.name', 'companies.address', 'companies.logo_path', 'companies.legacy_portal', 'companies.is_active', ]) ->addSelect([ 'current_user_role' => DB::table('company_user') ->select('role') ->whereColumn('company_user.company_id', 'companies.id') ->where('company_user.user_id', $user->id) ->limit(1), ]) ->withCount([ 'contacts' => fn ($q) => $q->withoutGlobalScopes(), 'pressReleases' => fn ($q) => $q->withoutGlobalScopes(), ]) ->withMax(['pressReleases' => fn ($q) => $q->withoutGlobalScopes()], 'published_at'); $this->applySavedView($query, $user, $this->savedView); $this->applySharedFilters($query); $this->applyRoleFilter($query, $user, $this->roleFilter); $pressKits = $query ->orderBy('name') ->paginate(50) ->through(function (Company $company) use ($user): Company { $roleKey = $this->userRoleKey($company, $user); $lastPublishedAt = $company->press_releases_max_published_at ? Carbon::parse($company->press_releases_max_published_at) : null; $company->setAttribute('panel_role_key', $roleKey); $company->setAttribute('panel_is_admin', $this->isAdminRole($roleKey)); $company->setAttribute('panel_role_label', $this->roleLabel($roleKey)); $company->setAttribute('panel_logo_url', $this->fastLogoUrl($company)); $company->setAttribute('panel_logo_variant', $this->logoVariant($company)); $company->setAttribute('panel_logo_initials', $this->logoInitials($company)); $company->setAttribute('panel_meta_line', $this->metaLine($company)); $company->setAttribute( 'panel_last_press_release_short', $lastPublishedAt?->format('d.m.') ?? '—' ); $company->setAttribute( 'panel_last_press_release_date', $lastPublishedAt?->format('d.m.Y') ?? '—' ); return $company; }); $aggregates = $this->buildAggregateCounts($user); return [ 'pressKits' => $pressKits, 'user' => $user, 'hasActiveFilters' => filled($this->search) || $this->savedView !== 'all' || filled($this->portalFilter) || $this->roleFilter !== 'all', 'counters' => $aggregates['counters'], 'savedViewCounts' => $aggregates['saved_views'], ]; } }; ?>
{{-- ============== PAGE HEADER ============== --}}
{{ __('User Backend') }} {{ __('Mein Bereich · Firmen') }}

{{ __('Meine Firmen') }}

{{ $counters['companies'] }} {{ __('Firmen') }} {{ $counters['active'] }} {{ __('aktiv') }} {{ $counters['press_releases'] }} {{ __('Pressemitteilungen gesamt') }} {{ $counters['contacts'] }} {{ __('Pressekontakte hinterlegt') }}

{{ __('Eine Firma ist der Container für Pressemitteilungen: Stammdaten, Boilerplate, Pressekontakte. Anlage ohne separate Freigabe — die redaktionelle Prüfung erfolgt erst bei der Pressemitteilung.') }}

{{ __('Export') }} {{ __('bald') }} {{ __('Firma anlegen') }}
{{-- ============== SAVED VIEW TABS ============== --}} {{-- ============== FILTER + SUCHE ============== --}}
{{ __('Alle Portale') }} presseecho businessportal24 {{ __('Alle Rollen') }} {{ __('Owner') }} {{ __('Verantwortlich') }} {{ __('Mitglied') }}
{{-- View-Toggle Karten/Liste --}}
{{-- ============== CONTENT-HOST ============== --}}
@if ($pressKits->isEmpty()) {{-- Empty States --}} @if ($hasActiveFilters) {{-- Empty: Filter ohne Treffer --}}

{{ __('Keine Firmen mit diesen Filtern') }}

{{ __('Aktive Filter passen auf keine Einträge. Filter zurücksetzen oder weiter fassen.') }}

{{ __('Alle Filter zurücksetzen') }}
@else {{-- Empty: noch keine Firma --}}

{{ __('Noch keine Firma angelegt') }}

{{ __('Lege deine erste Firma an. Du kannst direkt im Anschluss eine Pressemitteilung darauf veröffentlichen — eine separate Freigabe der Firma ist nicht erforderlich.') }}

{{ __('Erste Firma anlegen') }}
01
{{ __('Stammdaten erfassen') }}
02
{{ __('Boilerplate schreiben') }}
03
{{ __('Pressekontakte zuordnen') }}
@endif @elseif ($viewMode === 'cards') {{-- Karten-Ansicht --}}
@foreach ($pressKits as $company)
@if ($company->is_active) {{ __('Aktiv') }} @else {{ __('Inaktiv') }} @endif

{{ $company->name }}

@if (filled($company->panel_meta_line))
{{ $company->panel_meta_line }}
@endif
@if ($company->portal === \App\Enums\Portal::Both) presseecho businessportal24 @elseif ($company->portal === \App\Enums\Portal::Presseecho) presseecho @elseif ($company->portal === \App\Enums\Portal::Businessportal24) businessportal24 @endif {{ $company->panel_role_label }}
{{ $company->press_releases_count }} {{ __('PMs') }}
{{ $company->contacts_count }} {{ __('Kontakte') }}
{{ $company->panel_last_press_release_short }} {{ __('letzte PM') }}
@endforeach {{-- Add-Tile am Ende des Grids, nur auf der letzten Seite --}} @if ($pressKits->currentPage() === $pressKits->lastPage()) {{ __('Neue Firma anlegen') }} {{ __('Stammdaten und Boilerplate. Die Anlage benötigt keine separate Freigabe.') }} @endif
@else {{-- Listen-Ansicht --}}
@foreach ($pressKits as $company) @endforeach
{{ __('Firma') }} {{ __('Portal') }} {{ __('Rolle') }} {{ __('Status') }} {{ __('PMs') }} {{ __('Kontakte') }} {{ __('Letzte PM') }}
{{ $company->name }} @if (filled($company->panel_meta_line))
{{ $company->panel_meta_line }}
@endif
@if ($company->portal === \App\Enums\Portal::Both) presseecho businessportal24 @elseif ($company->portal === \App\Enums\Portal::Presseecho) presseecho @elseif ($company->portal === \App\Enums\Portal::Businessportal24) businessportal24 @endif
{{ $company->panel_role_label }} @if ($company->is_active) {{ __('Aktiv') }} @else {{ __('Inaktiv') }} @endif {{ $company->press_releases_count }} {{ $company->contacts_count }} {{ $company->panel_last_press_release_date }}
@endif
{{-- Pagination --}} @if ($pressKits->hasPages())
{{ $pressKits->links('components.portal.pagination', ['scrollTo' => '[data-state-host]']) }}
@endif {{-- ============== ROLLEN-LEGENDE ============== --}}
{{ __('Rollen pro Firma') }}

{{ __('Mehrere Personen können einer Firma zugeordnet sein. Die Rolle steuert, was im Backend möglich ist.') }}

{{ __('Owner') }}
  • {{ __('Stammdaten & Boilerplate') }}
  • {{ __('Pressekontakte verwalten') }}
  • {{ __('PMs erstellen, einreichen, archivieren') }}
  • {{ __('Weitere Mitglieder einladen') }}
{{ __('Verantwortlich') }}
  • {{ __('Stammdaten & Boilerplate') }}
  • {{ __('Pressekontakte verwalten') }}
  • {{ __('PMs erstellen & einreichen') }}
  • {{ __('keine Mitglieder-Verwaltung') }}
{{ __('Mitglied') }} · {{ __('bald erweitert') }}
  • {{ __('PMs einsehen') }}
  • {{ __('Stammdaten lesen') }}
  • {{ __('keine Bearbeitung') }}