Optimierung 5.1: Tabellen-Concerns (Sortierung, Volltext-Heuristik, Stats)
- HasSortableTable::sort() ersetzt 5 byte-identische sort()-Kopien in den Admin-Listen (Benutzer, Firmen, Kategorien, Kontakte, Pressemitteilungen). $sortBy/$sortDir-Defaults bleiben je Komponente, da sie divergieren. - HasFullTextSearch::supportsFullTextSearch() ersetzt 9 byte-identische Kopien (4 Index-Listen + 5 Firmen-Comboboxen); ungenutzte DB-Imports entfernt. - AdminPerformanceCache::activeInactiveStats(Builder) buendelt den identischen SUM(CASE WHEN is_active)-Block der Benutzer- und Firmenliste. Zurueckgestellt (real divergent): HasResettableFilters (updatedXxxFilter normalisieren teils zusaetzlich) und eine gemeinsame hydrateRelationCounts(). Tests: erweiterte AdminPerformanceCacheTest; 99 Admin-Tests bleiben gruen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8e0aedaf95
commit
a8958c176e
15 changed files with 111 additions and 145 deletions
|
|
@ -5,7 +5,6 @@ use App\Models\Category;
|
|||
use App\Models\PressRelease;
|
||||
use App\Services\Admin\AdminPerformanceCache;
|
||||
use App\Services\CurrentPortalContext;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Volt\Component;
|
||||
|
|
@ -13,6 +12,8 @@ use Livewire\WithPagination;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Kategorien')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
use \App\Livewire\Concerns\HasSortableTable;
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
|
|
@ -21,17 +22,6 @@ new #[Layout('components.layouts.app'), Title('Kategorien')] class extends Compo
|
|||
|
||||
public string $sortDir = 'asc';
|
||||
|
||||
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 updatedSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
|
|
@ -134,11 +124,6 @@ new #[Layout('components.layouts.app'), Title('Kategorien')] class extends Compo
|
|||
});
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-8">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ use App\Models\PressRelease;
|
|||
use App\Models\User;
|
||||
use App\Services\Admin\AdminPerformanceCache;
|
||||
use App\Services\CurrentPortalContext;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Attributes\Url;
|
||||
|
|
@ -16,6 +15,8 @@ use Livewire\WithPagination;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
use \App\Livewire\Concerns\HasSortableTable;
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
|
|
@ -42,17 +43,6 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
|
||||
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 with(): array
|
||||
{
|
||||
$sortable = ['name', 'email', 'is_active', 'press_releases_count', 'contacts_count', 'created_at'];
|
||||
|
|
@ -124,20 +114,11 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
$portal = CurrentPortalContext::get()?->value ?? 'all';
|
||||
$cache = app(AdminPerformanceCache::class);
|
||||
|
||||
return $cache->remember($cache->companiesStatsKey($portal), AdminPerformanceCache::StatsTtl, function (): array {
|
||||
$stats = Company::query()
|
||||
->toBase()
|
||||
->selectRaw('COUNT(*) as total')
|
||||
->selectRaw('SUM(CASE WHEN is_active = ? THEN 1 ELSE 0 END) as active', [true])
|
||||
->selectRaw('SUM(CASE WHEN is_active = ? THEN 1 ELSE 0 END) as inactive', [false])
|
||||
->first();
|
||||
|
||||
return [
|
||||
'total' => (int) ($stats->total ?? 0),
|
||||
'active' => (int) ($stats->active ?? 0),
|
||||
'inactive' => (int) ($stats->inactive ?? 0),
|
||||
];
|
||||
});
|
||||
return $cache->remember(
|
||||
$cache->companiesStatsKey($portal),
|
||||
AdminPerformanceCache::StatsTtl,
|
||||
fn (): array => $cache->activeInactiveStats(Company::query()),
|
||||
);
|
||||
}
|
||||
|
||||
private function hydrateCounts($companies): void
|
||||
|
|
@ -166,12 +147,6 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
use App\Enums\Portal;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
|
||||
public int $id;
|
||||
|
||||
public string $activeTab = 'overview';
|
||||
|
|
@ -175,12 +176,6 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
];
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
|
||||
private function portalBadgeColor(?Portal $portal): string
|
||||
{
|
||||
return match ($portal) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
use App\Enums\Portal;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
|
|
@ -11,6 +10,8 @@ use Livewire\Volt\Component;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
|
||||
public int|string|null $companyId = null;
|
||||
|
||||
public string $companySearch = '';
|
||||
|
|
@ -136,11 +137,6 @@ new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends
|
|||
];
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<form wire:submit="save" class="space-y-8">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
use App\Enums\Portal;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Locked;
|
||||
|
|
@ -12,6 +11,8 @@ use Livewire\Volt\Component;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
|
||||
#[Locked]
|
||||
public int $id;
|
||||
|
||||
|
|
@ -140,12 +141,6 @@ new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class exten
|
|||
];
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
|
||||
public function deleteContact(): void
|
||||
{
|
||||
$contact = Contact::query()->find($this->id);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ 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;
|
||||
|
|
@ -17,6 +16,8 @@ use Livewire\WithPagination;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
use \App\Livewire\Concerns\HasSortableTable;
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
|
|
@ -48,17 +49,6 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
|
||||
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'
|
||||
|
|
@ -259,12 +249,6 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
@ -616,7 +600,6 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
$contactCompanyName = $contact->company?->name ?? __('Unbekannte Firma');
|
||||
@endphp
|
||||
|
||||
|
||||
<flux:table.row :key="$contact->id">
|
||||
|
||||
<flux:table.cell>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ use App\Services\Admin\AdminPerformanceCache;
|
|||
use App\Services\PressRelease\BlacklistViolationException;
|
||||
use App\Services\PressRelease\PressReleaseService;
|
||||
use Flux\Flux;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Attributes\Url;
|
||||
|
|
@ -21,6 +20,8 @@ use Livewire\WithPagination;
|
|||
new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HandlesAdminPressReleaseActions;
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
use \App\Livewire\Concerns\HasSortableTable;
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
|
|
@ -57,17 +58,6 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
|
||||
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 updatedSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
|
|
@ -382,11 +372,6 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
->get();
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-8">
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ use Spatie\Permission\Models\Role;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasSortableTable;
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
|
|
@ -36,17 +37,6 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
|
||||
public ?int $viewingUserId = null;
|
||||
|
||||
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 with(): array
|
||||
{
|
||||
$sortable = ['name', 'email', 'portal', 'is_active', 'last_login_at', 'created_at'];
|
||||
|
|
@ -186,20 +176,13 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
*/
|
||||
private function stats(): array
|
||||
{
|
||||
return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::UserStats, AdminPerformanceCache::StatsTtl, function (): array {
|
||||
$stats = User::query()
|
||||
->toBase()
|
||||
->selectRaw('COUNT(*) as total')
|
||||
->selectRaw('SUM(CASE WHEN is_active = ? THEN 1 ELSE 0 END) as active', [true])
|
||||
->selectRaw('SUM(CASE WHEN is_active = ? THEN 1 ELSE 0 END) as inactive', [false])
|
||||
->first();
|
||||
$cache = app(AdminPerformanceCache::class);
|
||||
|
||||
return [
|
||||
'total' => (int) ($stats->total ?? 0),
|
||||
'active' => (int) ($stats->active ?? 0),
|
||||
'inactive' => (int) ($stats->inactive ?? 0),
|
||||
];
|
||||
});
|
||||
return $cache->remember(
|
||||
AdminPerformanceCache::UserStats,
|
||||
AdminPerformanceCache::StatsTtl,
|
||||
fn (): array => $cache->activeInactiveStats(User::query()),
|
||||
);
|
||||
}
|
||||
|
||||
private function hydrateCompanyCounts($users): void
|
||||
|
|
@ -576,7 +559,6 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
</flux:text>
|
||||
</flux:table.cell>
|
||||
|
||||
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use App\Models\Company;
|
|||
use App\Models\User;
|
||||
use App\Services\Admin\AdminPerformanceCache;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
|
|
@ -15,6 +14,8 @@ use Spatie\Permission\Models\Role;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Benutzer anlegen')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
|
||||
public string $name = '';
|
||||
|
||||
public string $email = '';
|
||||
|
|
@ -213,12 +214,6 @@ new #[Layout('components.layouts.app'), Title('Benutzer anlegen')] class extends
|
|||
->get(['id', 'name']));
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
|
||||
protected function billingIsComplete(): bool
|
||||
{
|
||||
return filled($this->billing['name'])
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ use App\Models\Contact;
|
|||
use App\Models\User;
|
||||
use App\Services\Admin\AdminPerformanceCache;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Locked;
|
||||
|
|
@ -17,6 +16,8 @@ use Spatie\Permission\Models\Role;
|
|||
|
||||
new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class extends Component
|
||||
{
|
||||
use \App\Livewire\Concerns\HasFullTextSearch;
|
||||
|
||||
#[Locked]
|
||||
public int $id;
|
||||
|
||||
|
|
@ -624,12 +625,6 @@ new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class exte
|
|||
->get(['id', 'name']));
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
|
||||
protected function syncContactFormsToSelectedCompanies(): void
|
||||
{
|
||||
$currentFormsById = collect($this->contactForms)->keyBy('id');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue