presseportale/resources/views/livewire/admin/contacts/index.blade.php
Kevin Adametz 036a53499f Responsive-Härtung: Seiten-Header, Kontextleiste, Stat-Cards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:08:08 +00:00

751 lines
32 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\Portal;
use App\Models\Company;
use App\Models\Contact;
use App\Models\User;
use App\Models\UserFilterPreset;
use App\Services\Admin\AdminPerformanceCache;
use App\Services\CurrentPortalContext;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Component
{
use WithPagination;
public string $search = '';
#[Url(as: 'company', except: 'all')]
public string $companyFilter = 'all';
public string $companySearch = '';
#[Url(as: 'user', except: 'all')]
public string $userFilter = 'all';
public string $userSearch = '';
#[Url(as: 'data', except: 'all')]
public string $qualityFilter = 'all';
public string $portalFilter = 'all';
public ?int $selectedPresetId = null;
public string $presetName = '';
public string $notification = '';
public string $notificationType = 'success';
public string $sortBy = 'created_at';
public string $sortDir = 'desc';
public function sort(string $column): void
{
if ($this->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',
};
}
}; ?>
<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">{{ __('Admin Backend') }}</span>
<span class="eyebrow muted">{{ __('Administration · Pressekontakte') }}</span>
</div>
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
{{ __('Kontakte') }}
</h1>
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
{{ __('Pressekontakte aller Firmen über alle Portale hinweg.') }}
</p>
</div>
<div class="flex items-center gap-2 flex-shrink-0">
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.create'))
<flux:button variant="primary" icon="plus" href="{{ route('admin.contacts.create') }}" wire:navigate>
{{ __('Neuer Kontakt') }}
</flux:button>
@else
<flux:button variant="primary" icon="plus" disabled>
{{ __('Neuer Kontakt') }}
</flux:button>
@endif
</div>
</header>
@if ($notification)
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 3000)" x-show="show" x-transition
@class([
'px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2',
'bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-ink-2)]' => $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')
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0" />
@else
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
@endif
{{ $notification }}
</div>
@endif
{{-- ============== KPI-Reihe ============== --}}
<section class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="number_format($stats['total'])">
<x-slot:meta>{{ __('Pressekontakte') }}</x-slot:meta>
<x-slot:trend>{{ __('alle Portale') }}</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="ok" :label="__('Firmen mit Kontakten')" :value="number_format($stats['companies_with_contacts'])">
<x-slot:meta>{{ __('aktiv versorgt') }}</x-slot:meta>
<x-slot:trend>{{ __('mind. ein Kontakt') }}</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="muted" :label="__('Ø pro Firma')" :value="number_format($stats['avg_per_company'], 1)">
<x-slot:meta>{{ __('Pflegegrad') }}</x-slot:meta>
<x-slot:trend>{{ __('Kontakte / Firma') }}</x-slot:trend>
</x-portal.stat-card>
</section>
{{-- ============== FILTER-PANEL ============== --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
</div>
<div class="p-5 flex flex-col gap-4">
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
<flux:input wire:model.live.debounce.300ms="search"
placeholder="{{ __('Name, Email oder Firma suchen...') }}" icon="magnifying-glass" class="flex-1" />
<div class="flex w-full gap-2 xl:w-64">
<flux:select wire:model.live="companyFilter" variant="combobox" :filter="false" clearable
placeholder="{{ __('Alle Firmen') }}" class="min-w-0 flex-1">
<x-slot name="input">
<flux:select.input wire:model.live.debounce.300ms="companySearch"
placeholder="{{ __('Firma suchen…') }}" />
</x-slot>
@foreach ($filterCompanies as $company)
<flux:select.option :value="$company->id" wire:key="fc-{{ $company->id }}">
{{ $company->name }}
</flux:select.option>
@endforeach
<x-slot name="empty">
<flux:select.option.empty>
@if (blank(trim($companySearch)))
{{ __('Name eingeben…') }}
@else
{{ __('Keine Firma gefunden.') }}
@endif
</flux:select.option.empty>
</x-slot>
</flux:select>
<flux:button
type="button"
size="sm"
variant="filled"
icon="x-mark"
wire:click="clearCompanySearch"
title="{{ __('Firmensuche zurücksetzen') }}"
/>
</div>
<div class="flex w-full gap-2 xl:w-64">
<flux:select wire:model.live="userFilter" variant="combobox" :filter="false" clearable
placeholder="{{ __('Alle User') }}" class="min-w-0 flex-1">
<x-slot name="input">
<flux:select.input wire:model.live.debounce.300ms="userSearch"
placeholder="{{ __('User suchen…') }}" />
</x-slot>
@foreach ($filterUsers as $user)
<flux:select.option :value="$user->id" wire:key="contact-user-{{ $user->id }}">
{{ $user->name }}
<span class="ml-1 text-zinc-400">· {{ $user->email }}</span>
</flux:select.option>
@endforeach
<x-slot name="empty">
<flux:select.option.empty>
@if (blank(trim($userSearch)))
{{ __('Usernamen oder E-Mail eingeben…') }}
@else
{{ __('Kein User gefunden.') }}
@endif
</flux:select.option.empty>
</x-slot>
</flux:select>
<flux:button
type="button"
size="sm"
variant="filled"
icon="x-mark"
wire:click="clearUserSearch"
title="{{ __('Usersuche zurücksetzen') }}"
/>
</div>
<flux:select wire:model.live="qualityFilter" class="w-full xl:w-56">
<option value="all">{{ __('Alle Datenstände') }}</option>
<option value="with_press_releases">{{ __('Mit Pressemitteilungen') }}</option>
<option value="without_press_releases">{{ __('Ohne Pressemitteilungen') }}</option>
</flux:select>
<flux:select wire:model.live="portalFilter" class="w-full xl:w-48">
<option value="all">{{ __('Alle Portale') }}</option>
@foreach ($portalOptions as $portalOption)
<option value="{{ $portalOption->value }}">{{ $portalOption->label() }}</option>
@endforeach
</flux:select>
</div>
</div>
</article>
{{-- ============== PRESET-PANEL ============== --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Filter-Presets') }}</span>
</div>
<div class="p-5 space-y-3">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex flex-1 gap-3">
<flux:input wire:model="presetName" placeholder="{{ __('Neues Preset speichern...') }}"
class="flex-1" />
<flux:button wire:click="savePreset" variant="filled" icon="bookmark">
{{ __('Preset speichern') }}
</flux:button>
</div>
<div class="flex gap-2 flex-wrap">
<flux:select wire:model="selectedPresetId" class="w-64">
<option value="">{{ __('Preset auswählen') }}</option>
@foreach ($presets as $preset)
<option value="{{ $preset->id }}">
{{ $preset->name }}{{ $preset->is_default ? ' (Standard)' : '' }}
</option>
@endforeach
</flux:select>
<flux:button wire:click="applyPreset" variant="filled">{{ __('Anwenden') }}</flux:button>
<flux:button wire:click="setDefaultPreset" variant="filled">{{ __('Als Standard') }}</flux:button>
<flux:button wire:click="deletePreset" variant="danger">{{ __('Löschen') }}</flux:button>
</div>
</div>
<flux:error name="presetName" />
</div>
</article>
{{-- ============== TABELLE ============== --}}
<article class="panel overflow-hidden">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Alle Kontakte') }}</span>
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
{{ __(':count Einträge', ['count' => $contacts->count()]) }}
</span>
</div>
<flux:table>
<flux:table.columns>
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'last_name'" :direction="$sortDir"
wire:click="sort('last_name')">{{ __('Name') }}</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'email'" :direction="$sortDir"
wire:click="sort('email')">{{ __('Kontakt') }}</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'company_id'" :direction="$sortDir"
wire:click="sort('company_id')">{{ __('Firma') }}</flux:table.column>
<flux:table.column>{{ __('Portal') }}</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'press_releases_count'" :direction="$sortDir"
wire:click="sort('press_releases_count')">{{ __('PMs') }}</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'created_at'" :direction="$sortDir"
wire:click="sort('created_at')">{{ __('Hinzugefügt') }}</flux:table.column>
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@forelse($contacts as $contact)
@php
$contactDisplayName =
trim(($contact->first_name ?? '') . ' ' . ($contact->last_name ?? '')) ?:
__('Kontakt ohne Name');
$contactCompanyName = $contact->company?->name ?? __('Unbekannte Firma');
@endphp
<flux:table.row :key="$contact->id">
<flux:table.cell>
<div class="flex gap-2">
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.edit'))
<flux:button size="sm" variant="filled" icon="pencil"
href="{{ route('admin.contacts.edit', $contact->id) }}" wire:navigate />
@endif
@if ($contact->company && \Illuminate\Support\Facades\Route::has('admin.companies.show'))
<flux:button size="sm" variant="filled" icon="building-office"
href="{{ route('admin.companies.show', $contact->company_id) }}"
wire:navigate />
@endif
</div>
</flux:table.cell>
<flux:table.cell>
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] truncate">
{{ $contactDisplayName }}
</div>
</flux:table.cell>
<flux:table.cell>
<div class="space-y-0.5">
<div class="text-[12.5px]">
<a href="mailto:{{ $contact->email }}"
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
{{ $contact->email ?: __('Keine E-Mail') }}
</a>
</div>
<div class="text-[12px] text-[color:var(--color-ink-3)]">
{{ $contact->phone ?: __('Kein Telefon') }}
</div>
</div>
</flux:table.cell>
<flux:table.cell>
@if ($contact->company && \Illuminate\Support\Facades\Route::has('admin.companies.show'))
<a href="{{ route('admin.companies.show', $contact->company_id) }}" wire:navigate
class="text-[12.5px] text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
{{ \Illuminate\Support\Str::limit($contact->company->name, 60) }}
</a>
@else
<span class="text-[12.5px] text-[color:var(--color-ink)]">
{{ \Illuminate\Support\Str::limit($contact->company?->name ?? __('Unbekannte Firma'), 80) }}
</span>
@endif
</flux:table.cell>
<flux:table.cell>
<span class="badge hub">{{ $contact->portal?->label() ?? __('Unbekannt') }}</span>
</flux:table.cell>
<flux:table.cell>
@if ($contact->press_releases_count > 0)
<flux:button
size="sm"
variant="filled"
href="{{ route('admin.press-releases.index', ['contact' => $contact->id]) }}"
wire:navigate
>
{{ $contact->press_releases_count }} {{ __('PMs') }}
</flux:button>
@else
<span class="badge dot">0</span>
@endif
</flux:table.cell>
<flux:table.cell>
<span class="text-[12px] text-[color:var(--color-ink-3)]">
{{ $contact->created_at?->format('d.m.Y H:i') ?? '-' }}
</span>
</flux:table.cell>
<flux:table.cell>
<div class="flex gap-2">
<flux:modal.trigger name="confirm-contact-delete-{{ $contact->id }}">
<flux:button size="sm" variant="filled" icon="trash" type="button"
x-data=""
x-on:click.prevent="$dispatch('open-modal', 'confirm-contact-delete-{{ $contact->id }}')" />
</flux:modal.trigger>
<flux:button size="sm" variant="filled" icon="envelope"
href="mailto:{{ $contact->email }}" />
</div>
<flux:modal name="confirm-contact-delete-{{ $contact->id }}" class="max-w-lg">
<div class="space-y-6">
<div>
<flux:heading size="lg">{{ __('Kontakt wirklich löschen?') }}
</flux:heading>
<flux:subheading>
{{ __('Du löschst: :contact (Firma: :company). Dieser Kontakt wird archiviert (Soft Delete) und aus den Standardlisten entfernt.', ['contact' => $contactDisplayName, 'company' => $contactCompanyName]) }}
</flux:subheading>
</div>
<div class="flex justify-end space-x-2 rtl:space-x-reverse">
<flux:modal.close>
<flux:button variant="filled">{{ __('Abbrechen') }}</flux:button>
</flux:modal.close>
<flux:button variant="danger"
wire:click="deleteContactFromIndex({{ $contact->id }})">
{{ __('Löschung bestätigen') }}
</flux:button>
</div>
</div>
</flux:modal>
</flux:table.cell>
</flux:table.row>
@empty
<flux:table.row>
<flux:table.cell colspan="8">
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
<flux:icon.user-group class="size-6" />
</div>
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
{{ __('Keine Kontakte gefunden') }}
</div>
</div>
</flux:table.cell>
</flux:table.row>
@endforelse
</flux:table.rows>
</flux:table>
<div class="border-t border-[color:var(--color-bg-rule)] p-4">
{{ $contacts->links('components.portal.pagination') }}
</div>
</article>
</div>