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
b0353abb97
commit
54bab7eff5
15 changed files with 111 additions and 145 deletions
20
app/Livewire/Concerns/HasFullTextSearch.php
Normal file
20
app/Livewire/Concerns/HasFullTextSearch.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Concerns;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Gemeinsame Heuristik, ob für einen Suchbegriff der DB-Volltextindex genutzt
|
||||
* werden kann: ab drei Zeichen und nur auf MySQL/PostgreSQL (SQLite im Test
|
||||
* fällt auf die LIKE-Suche zurück). Single Source of Truth für die Admin-Listen
|
||||
* und Firmen-Comboboxen.
|
||||
*/
|
||||
trait HasFullTextSearch
|
||||
{
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
}
|
||||
23
app/Livewire/Concerns/HasSortableTable.php
Normal file
23
app/Livewire/Concerns/HasSortableTable.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Concerns;
|
||||
|
||||
/**
|
||||
* Spalten-Sortierung für Admin-Tabellen. Erwartet die öffentlichen Properties
|
||||
* `$sortBy`/`$sortDir` (deren Defaults bleiben bewusst in der jeweiligen
|
||||
* Komponente, da sie je Tabelle divergieren) sowie `resetPage()` aus
|
||||
* `Livewire\WithPagination`.
|
||||
*/
|
||||
trait HasSortableTable
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ namespace App\Services\Admin;
|
|||
use App\Enums\Portal;
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\PressRelease;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AdminPerformanceCache
|
||||
|
|
@ -49,6 +50,29 @@ class AdminPerformanceCache
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktiv-/Inaktiv-Kennzahlen einer `is_active`-Tabelle in einer einzigen
|
||||
* Aggregat-Abfrage. Single Source of Truth für die identischen Stat-Blöcke
|
||||
* der Benutzer- und Firmen-Liste.
|
||||
*
|
||||
* @param Builder<covariant \Illuminate\Database\Eloquent\Model> $query
|
||||
* @return array{total: int, active: int, inactive: int}
|
||||
*/
|
||||
public function activeInactiveStats(Builder $query): array
|
||||
{
|
||||
$stats = $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),
|
||||
];
|
||||
}
|
||||
|
||||
public function pressReleaseReviewCount(): int
|
||||
{
|
||||
return (int) $this->remember(self::PressReleaseReviewCount, self::StatsTtl, function (): int {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ Keine Fachlogik-Änderung. Reduziert erreichbare Demo-Komponenten und Altlasten.
|
|||
|
||||
## Phase 5 — Admin-CRUD & Auth: Concerns + Blade-Components (risikoarm)
|
||||
|
||||
- [ ] **5.1 Tabellen-Concerns** — `HasSortableTable` (`sort()` 3× wortgleich), `HasFullTextSearch` (`supportsFullTextSearch()` 6×+ + `applyFuzzySearch`), `HasResettableFilters` (`updatedXxxFilter()`→`resetPage`). `stats()` mit `SUM(CASE WHEN is_active)` → `AdminPerformanceCache::activeInactiveStats()`; Count-Hydration → `hydrateRelationCounts()`.
|
||||
- [x] **5.1 Tabellen-Concerns** — `HasSortableTable::sort()` (5 byte-identische Kopien zentralisiert; `$sortBy`/`$sortDir`-Defaults bleiben in der Komponente, da sie divergieren) und `HasFullTextSearch::supportsFullTextSearch()` (9 byte-identische Kopien — 4 Index-Listen + 5 Firmen-Comboboxen; nicht mehr genutzte `DB`-Imports entfernt). `AdminPerformanceCache::activeInactiveStats(Builder)` bündelt den identischen `SUM(CASE WHEN is_active)`-Block der Benutzer- und Firmenliste. Tests: erweiterte `AdminPerformanceCacheTest`; abgesichert durch 99 `Admin/`-Tests. **Bewusst zurückgestellt:** `HasResettableFilters` (die `updatedXxxFilter()` divergieren — manche normalisieren zusätzlich Werte; ein generischer `updated()`-Hook wäre eine Verhaltensänderung) und eine gemeinsame `hydrateRelationCounts()` (Count-Hydration unterscheidet sich je Tabelle real).
|
||||
- [ ] **5.2 User create/edit entdoppeln** — `UserFormRules`-Provider + `UserUpsertService`/`SaveUserAction` (`save()` trägt ~175 Zeilen). Markup → `<x-admin.billing-fields>`, `<x-admin.company-link-picker>`, `<x-admin.role-checkboxes>`. Kontakt → `ValidatesContactData`-Concern + `<x-contact-fields>`.
|
||||
- [ ] **5.3 Formatierungs-Components/Enum** — `Portal::badgeColor()` + `<x-portal.portal-badge>`; `<x-portal.status-badge>`; `PressReleaseStatus`-Enum-Methoden; `<x-portal.money>`, `<x-portal.datetime>`, `Category::localizedName()`, `Contact::displayName()`.
|
||||
- [ ] **5.4 Seiten-Struktur-Components** — `<x-portal.page-header>`, `<x-portal.stat-cards>`, `<x-portal.table-panel>`, `<x-portal.table-pagination>`, `<x-portal.empty-state>`, `<x-portal.searchable-select>`. (Muster: bestehende `<x-portal.stat-card>`/`components.portal.pagination`.)
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -5,6 +5,19 @@ use App\Services\Admin\AdminPerformanceCache;
|
|||
use Illuminate\Support\Facades\Cache;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
test('activeInactiveStats aggregates total, active and inactive in one shape', function () {
|
||||
Company::factory()->count(2)->create(['is_active' => true]);
|
||||
Company::factory()->create(['is_active' => false]);
|
||||
|
||||
$stats = app(AdminPerformanceCache::class)->activeInactiveStats(Company::query());
|
||||
|
||||
expect($stats)->toBe([
|
||||
'total' => 3,
|
||||
'active' => 2,
|
||||
'inactive' => 1,
|
||||
]);
|
||||
});
|
||||
|
||||
test('admin performance cache is flushed when observed models change', function () {
|
||||
Cache::put(AdminPerformanceCache::DashboardStats, ['stale' => true], 600);
|
||||
Cache::put(AdminPerformanceCache::PressReleaseStats, ['stale' => true], 600);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue