sortBy === $column) { $this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc'; } else { $this->sortBy = $column; $this->sortDir = 'asc'; } $this->resetPage(); } public function updatedCompanyFilter(): void { // Flux clearable setzt den Wert auf null – normalisieren auf 'all' if (blank($this->companyFilter)) { $this->companyFilter = 'all'; } $this->companySearch = ''; $this->resetPage(); } public function updatedCompanySearch(): void { $this->resetPage(); } public function updatedUserFilter(): void { if (blank($this->userFilter)) { $this->userFilter = 'all'; } $this->userSearch = ''; $this->resetPage(); } public function updatedUserSearch(): void { $this->resetPage(); } public function clearCompanySearch(): void { $this->companyFilter = 'all'; $this->companySearch = ''; $this->resetPage(); } public function clearUserSearch(): void { $this->userFilter = 'all'; $this->userSearch = ''; $this->resetPage(); } public function updatedQualityFilter(): void { $this->resetPage(); } public function mount(): void { $currentUser = auth()->user(); if (! $currentUser) { return; } $defaultPreset = UserFilterPreset::query()->where('user_id', $currentUser->id)->where('page', 'admin.contacts.index')->where('is_default', true)->first(); if (! $defaultPreset) { return; } $this->selectedPresetId = (int) $defaultPreset->id; $filters = $defaultPreset->filters ?? []; $this->search = (string) ($filters['search'] ?? ''); $this->companyFilter = (string) ($filters['company_filter'] ?? 'all'); $this->userFilter = (string) ($filters['user_filter'] ?? 'all'); $this->qualityFilter = (string) ($filters['quality_filter'] ?? 'all'); $this->portalFilter = (string) ($filters['portal_filter'] ?? 'all'); } public function with(): array { $currentUser = auth()->user(); $contacts = Contact::query() ->with('company:id,name') ->withCount('pressReleases') ->when($this->search, function ($query): void { $term = trim($this->search); if ($this->supportsFullTextSearch($term)) { $query->where(function ($query) use ($term): void { $query->whereFullText(['first_name', 'last_name', 'email', 'responsibility'], $term) ->orWhereHas('company', fn ($companyQuery) => $companyQuery->whereFullText(['name', 'email', 'slug'], $term)); }); return; } $query->where(function ($searchQuery): void { $searchQuery ->where('first_name', 'like', '%'.$this->search.'%') ->orWhere('last_name', 'like', '%'.$this->search.'%') ->orWhere('email', 'like', '%'.$this->search.'%') ->orWhereHas('company', function ($companyQuery): void { $companyQuery->where('name', 'like', '%'.$this->search.'%'); }); }); }) ->when($this->companyFilter !== 'all', function ($query): void { $query->where('company_id', (int) $this->companyFilter); }) ->when($this->userFilter !== 'all', function ($query): void { $query->whereHas('users', fn ($userQuery) => $userQuery->where('users.id', (int) $this->userFilter)); }) ->when($this->qualityFilter !== 'all', function ($query): void { match ($this->qualityFilter) { 'with_press_releases' => $query->whereHas('pressReleases'), 'without_press_releases' => $query->whereDoesntHave('pressReleases'), default => null, }; }) ->when($this->portalFilter !== 'all', function ($query): void { $query->where('portal', $this->portalFilter); }) ->orderBy(in_array($this->sortBy, ['last_name', 'email', 'company_id', 'press_releases_count', 'created_at'], true) ? $this->sortBy : 'created_at', $this->sortDir) ->paginate(50); // Firmen-Filter: nur Live-Suche, nie alle laden $term = trim($this->companySearch); $selectedCompanyId = $this->companyFilter !== 'all' ? (int) $this->companyFilter : null; $filterCompanies = Company::withoutGlobalScopes() ->when(filled($term), function ($q) use ($term): void { if ($this->supportsFullTextSearch($term)) { $q->whereFullText(['name', 'email', 'slug'], $term); return; } $q->where('name', 'like', '%'.$term.'%'); }) ->when(blank($term) && $selectedCompanyId, fn ($q) => $q->whereIn('id', [$selectedCompanyId])) ->when(blank($term) && ! $selectedCompanyId, fn ($q) => $q->whereRaw('0 = 1')) ->orderBy('name') ->limit(50) ->get(['id', 'name']); $userTerm = trim($this->userSearch); $selectedUserId = $this->userFilter !== 'all' ? (int) $this->userFilter : null; $filterUsers = User::query() ->select(['id', 'name', 'email']) ->where(function ($query) use ($userTerm, $selectedUserId): void { if ($selectedUserId) { $query->where('id', $selectedUserId); } if ($userTerm !== '') { $query->orWhere(function ($searchQuery) use ($userTerm): void { $searchQuery ->where('name', 'like', '%'.$userTerm.'%') ->orWhere('email', 'like', '%'.$userTerm.'%'); }); } }) ->when($userTerm === '' && ! $selectedUserId, fn ($query) => $query->whereRaw('0 = 1')) ->orderBy('name') ->limit(20) ->get(); return [ 'contacts' => $contacts, 'filterCompanies' => $filterCompanies, 'filterUsers' => $filterUsers, 'portalOptions' => Portal::cases(), 'presets' => $currentUser ? UserFilterPreset::query() ->where('user_id', $currentUser->id) ->where('page', 'admin.contacts.index') ->orderByDesc('is_default') ->orderByDesc('last_used_at') ->orderBy('name') ->get(['id', 'name', 'is_default', 'last_used_at', 'filters']) : collect(), 'stats' => $this->stats(), ]; } /** * @return array{total: int, companies_with_contacts: int, avg_per_company: float} */ private function stats(): array { $portal = CurrentPortalContext::get()?->value ?? 'all'; $cache = app(AdminPerformanceCache::class); return $cache->remember($cache->contactsStatsKey($portal), AdminPerformanceCache::StatsTtl, function (): array { $total = Contact::count(); $companiesWithContacts = Contact::query() ->distinct() ->count('company_id'); return [ 'total' => $total, 'companies_with_contacts' => $companiesWithContacts, 'avg_per_company' => $companiesWithContacts > 0 ? round($total / $companiesWithContacts, 1) : 0.0, ]; }); } private function supportsFullTextSearch(string $term): bool { return mb_strlen($term) >= 3 && in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true); } public function updatedSearch(): void { $this->resetPage(); } public function updatedPortalFilter(): void { $this->resetPage(); } public function savePreset(): void { $currentUser = auth()->user(); if (! $currentUser) { return; } $validated = $this->validate([ 'presetName' => ['required', 'string', 'min:2', 'max:120', Rule::unique('user_filter_presets', 'name')->where(fn ($query) => $query->where('user_id', $currentUser->id)->where('page', 'admin.contacts.index'))], ]); UserFilterPreset::query()->create([ 'user_id' => $currentUser->id, 'page' => 'admin.contacts.index', 'name' => $validated['presetName'], 'last_used_at' => now(), 'filters' => [ 'search' => $this->search, 'company_filter' => $this->companyFilter, 'user_filter' => $this->userFilter, 'quality_filter' => $this->qualityFilter, 'portal_filter' => $this->portalFilter, ], ]); $this->presetName = ''; $this->notification = __('Filter-Preset wurde gespeichert.'); $this->notificationType = 'success'; } public function applyPreset(): void { $currentUser = auth()->user(); if (! $currentUser || ! $this->selectedPresetId) { return; } $preset = UserFilterPreset::query()->where('user_id', $currentUser->id)->where('page', 'admin.contacts.index')->find($this->selectedPresetId); if (! $preset) { return; } $filters = $preset->filters ?? []; $this->search = (string) ($filters['search'] ?? ''); $this->companyFilter = (string) ($filters['company_filter'] ?? 'all'); $this->userFilter = (string) ($filters['user_filter'] ?? 'all'); $this->qualityFilter = (string) ($filters['quality_filter'] ?? 'all'); $this->portalFilter = (string) ($filters['portal_filter'] ?? 'all'); $preset->update(['last_used_at' => now()]); $this->resetPage(); } public function deletePreset(): void { $currentUser = auth()->user(); if (! $currentUser || ! $this->selectedPresetId) { return; } UserFilterPreset::query()->where('user_id', $currentUser->id)->where('page', 'admin.contacts.index')->whereKey($this->selectedPresetId)->delete(); $this->selectedPresetId = null; $this->notification = __('Filter-Preset wurde gelöscht.'); $this->notificationType = 'success'; } public function setDefaultPreset(): void { $currentUser = auth()->user(); if (! $currentUser || ! $this->selectedPresetId) { return; } UserFilterPreset::query() ->where('user_id', $currentUser->id) ->where('page', 'admin.contacts.index') ->update(['is_default' => false]); UserFilterPreset::query() ->where('user_id', $currentUser->id) ->where('page', 'admin.contacts.index') ->whereKey($this->selectedPresetId) ->update(['is_default' => true]); $this->notification = __('Standard-Preset wurde gesetzt.'); $this->notificationType = 'success'; } public function deleteContactFromIndex(int $contactId): void { $contact = Contact::query()->find($contactId); if (! $contact) { $this->notification = __('Der angeforderte Kontakt wurde nicht gefunden.'); $this->notificationType = 'error'; return; } $contact->delete(); $this->notification = __('Kontakt wurde gelöscht.'); $this->notificationType = 'success'; $this->resetPage(); } private function portalBadgeColor(?Portal $portal): string { return match ($portal) { Portal::Presseecho => 'blue', Portal::Businessportal24 => 'purple', Portal::Both => 'zinc', default => 'zinc', }; } }; ?>
{{-- ============== PAGE HEADER ============== --}} @if ($notification)
$notificationType === 'error', 'bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]' => $notificationType !== 'error', ])> @if ($notificationType === 'error') @else @endif {{ $notification }}
@endif {{-- ============== KPI-Reihe ============== --}}
{{ __('Pressekontakte') }} {{ __('alle Portale') }} {{ __('aktiv versorgt') }} {{ __('mind. ein Kontakt') }} {{ __('Pflegegrad') }} {{ __('Kontakte / Firma') }}
{{-- ============== FILTER-PANEL ============== --}}
{{ __('Filter & Suche') }}
@foreach ($filterCompanies as $company) {{ $company->name }} @endforeach @if (blank(trim($companySearch))) {{ __('Name eingeben…') }} @else {{ __('Keine Firma gefunden.') }} @endif
@foreach ($filterUsers as $user) {{ $user->name }} · {{ $user->email }} @endforeach @if (blank(trim($userSearch))) {{ __('Usernamen oder E-Mail eingeben…') }} @else {{ __('Kein User gefunden.') }} @endif
@foreach ($portalOptions as $portalOption) @endforeach
{{-- ============== PRESET-PANEL ============== --}}
{{ __('Filter-Presets') }}
{{ __('Preset speichern') }}
@foreach ($presets as $preset) @endforeach {{ __('Anwenden') }} {{ __('Als Standard') }} {{ __('Löschen') }}
{{-- ============== TABELLE ============== --}}
{{ __('Alle Kontakte') }} {{ __(':count Einträge', ['count' => $contacts->count()]) }}
{{ __('Aktionen') }} {{ __('Name') }} {{ __('Kontakt') }} {{ __('Firma') }} {{ __('Portal') }} {{ __('PMs') }} {{ __('Hinzugefügt') }} {{ __('Aktionen') }} @forelse($contacts as $contact) @php $contactDisplayName = trim(($contact->first_name ?? '') . ' ' . ($contact->last_name ?? '')) ?: __('Kontakt ohne Name'); $contactCompanyName = $contact->company?->name ?? __('Unbekannte Firma'); @endphp
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.edit')) @endif @if ($contact->company && \Illuminate\Support\Facades\Route::has('admin.companies.show')) @endif
{{ $contactDisplayName }}
{{ $contact->phone ?: __('Kein Telefon') }}
@if ($contact->company && \Illuminate\Support\Facades\Route::has('admin.companies.show')) {{ \Illuminate\Support\Str::limit($contact->company->name, 60) }} @else {{ \Illuminate\Support\Str::limit($contact->company?->name ?? __('Unbekannte Firma'), 80) }} @endif {{ $contact->portal?->label() ?? __('Unbekannt') }} @if ($contact->press_releases_count > 0) {{ $contact->press_releases_count }} {{ __('PMs') }} @else 0 @endif {{ $contact->created_at?->format('d.m.Y H:i') ?? '-' }}
{{ __('Kontakt wirklich löschen?') }} {{ __('Du löschst: :contact (Firma: :company). Dieser Kontakt wird archiviert (Soft Delete) und aus den Standardlisten entfernt.', ['contact' => $contactDisplayName, 'company' => $contactCompanyName]) }}
{{ __('Abbrechen') }} {{ __('Löschung bestätigen') }}
@empty
{{ __('Keine Kontakte gefunden') }}
@endforelse
{{ $contacts->links('components.portal.pagination') }}