🎨 Add dynamic category-based filtering for news and improve UI interactions

- 🆕 Introduce `selectedCategory` state with URL binding for category filtering
- 🪄 Add computed `filteredNews` property to handle filtered results efficiently
- 🎛️ Implement category toggle buttons and "Clear Filter" functionality in UI
- 🌟 Improve category display with badges and contextual feedback for empty states
- 🔄 Refactor repeated news loading into a single `loadNews` method
This commit is contained in:
HolgerHatGarKeineNode
2026-01-25 19:25:22 +01:00
parent 10dac9d02b
commit 1391808793

View File

@@ -4,8 +4,10 @@ use App\Enums\NewsCategory;
use App\Models\Notification; use App\Models\Notification;
use App\Support\NostrAuth; use App\Support\NostrAuth;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Attributes\Title; use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component; use Livewire\Component;
use Livewire\WithFileUploads; use Livewire\WithFileUploads;
@@ -17,6 +19,9 @@ class extends Component {
public Collection|array $news = []; public Collection|array $news = [];
#[Url(as: 'kategorie')]
public ?int $selectedCategory = null;
public array $form = [ public array $form = [
'category' => '', 'category' => '',
'name' => '', 'name' => '',
@@ -49,13 +54,40 @@ class extends Component {
$this->canEdit = true; $this->canEdit = true;
} }
$this->news = \App\Models\Notification::query() $this->loadNews();
->with(['einundzwanzigPleb.profile'])
->latest()
->get();
} }
} }
#[Computed]
public function filteredNews(): Collection|array
{
if ($this->selectedCategory === null) {
return $this->news;
}
return collect($this->news)->filter(
fn ($item) => $item->category->value === $this->selectedCategory
);
}
public function filterByCategory(?int $category): void
{
$this->selectedCategory = $this->selectedCategory === $category ? null : $category;
}
public function clearFilter(): void
{
$this->selectedCategory = null;
}
private function loadNews(): void
{
$this->news = Notification::query()
->with(['einundzwanzigPleb.profile'])
->latest()
->get();
}
public function save(): void public function save(): void
{ {
$this->validate([ $this->validate([
@@ -81,11 +113,7 @@ class extends Component {
} }
$this->reset(['form', 'file']); $this->reset(['form', 'file']);
$this->loadNews();
$this->news = \App\Models\Notification::query()
->with(['einundzwanzigPleb.profile'])
->latest()
->get();
} }
public function confirmDelete(int $id): void public function confirmDelete(int $id): void
@@ -97,11 +125,7 @@ class extends Component {
{ {
$news = Notification::query()->findOrFail($this->confirmDeleteId); $news = Notification::query()->findOrFail($this->confirmDeleteId);
$news->delete(); $news->delete();
$this->loadNews();
$this->news = \App\Models\Notification::query()
->with(['einundzwanzigPleb.profile'])
->latest()
->get();
} }
public function removeFile(): void public function removeFile(): void
@@ -143,17 +167,38 @@ class extends Component {
<div> <div>
<div <div
class="text-xs font-semibold text-zinc-400 dark:text-zinc-500 uppercase mb-3 md:sr-only"> class="text-xs font-semibold text-zinc-400 dark:text-zinc-500 uppercase mb-3 md:sr-only">
Menu Kategorien
</div> </div>
<ul class="flex flex-nowrap md:block mr-3 md:mr-0"> <ul class="flex flex-nowrap md:block mr-3 md:mr-0">
<li class="mr-0.5 md:mr-0 md:mb-0.5" wire:key="category_all">
<button
type="button"
wire:click="clearFilter"
@class([
'inline-flex items-center px-2.5 py-1 rounded-md text-sm font-medium transition-colors cursor-pointer',
'bg-amber-500 text-white' => $selectedCategory === null,
'bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-600' => $selectedCategory !== null,
])
>
<i class="fa-sharp-duotone fa-solid fa-layer-group shrink-0 fill-current mr-2"></i>
<span>Alle</span>
</button>
</li>
@foreach(\App\Enums\NewsCategory::selectOptions() as $category) @foreach(\App\Enums\NewsCategory::selectOptions() as $category)
<li class="mr-0.5 md:mr-0 md:mb-0.5" <li class="mr-0.5 md:mr-0 md:mb-0.5"
wire:key="category_{{ $category['value'] }}"> wire:key="category_{{ $category['value'] }}">
<flux:badge> <button
<i class="fa-sharp-duotone fa-solid fa-{{ $category['icon'] }} shrink-0 fill-current text-amber-500 mr-2"></i> type="button"
<span wire:click="filterByCategory({{ $category['value'] }})"
class="text-sm font-medium text-amber-500">{{ $category['label'] }}</span> @class([
</flux:badge> 'inline-flex items-center px-2.5 py-1 rounded-md text-sm font-medium transition-colors cursor-pointer',
'bg-amber-500 text-white' => $selectedCategory === $category['value'],
'bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-600' => $selectedCategory !== $category['value'],
])
>
<i class="fa-sharp-duotone fa-solid fa-{{ $category['icon'] }} shrink-0 fill-current mr-2"></i>
<span>{{ $category['label'] }}</span>
</button>
</li> </li>
@endforeach @endforeach
</ul> </ul>
@@ -168,7 +213,7 @@ class extends Component {
<div class="md:py-8"> <div class="md:py-8">
<div class="space-y-2"> <div class="space-y-2">
@forelse($news as $post) @forelse($this->filteredNews as $post)
<flux:card wire:key="post_{{ $post->id }}"> <flux:card wire:key="post_{{ $post->id }}">
<!-- Avatar --> <!-- Avatar -->
<div class="shrink-0 mt-1.5"> <div class="shrink-0 mt-1.5">
@@ -179,6 +224,17 @@ class extends Component {
</div> </div>
<!-- Content --> <!-- Content -->
<div class="grow"> <div class="grow">
<!-- Category Badge -->
<div class="mb-2">
<button
type="button"
wire:click="filterByCategory({{ $post->category->value }})"
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
>
<i class="fa-sharp-duotone fa-solid fa-{{ $post->category->icon() }} mr-1"></i>
{{ $post->category->label() }}
</button>
</div>
<!-- Title --> <!-- Title -->
<h2 class="font-semibold text-zinc-800 dark:text-zinc-100 mb-2"> <h2 class="font-semibold text-zinc-800 dark:text-zinc-100 mb-2">
{{ $post->name }} {{ $post->name }}
@@ -252,7 +308,14 @@ class extends Component {
</flux:card> </flux:card>
@empty @empty
<flux:card> <flux:card>
<p>Keine News vorhanden.</p> @if($selectedCategory !== null)
<p>Keine News in dieser Kategorie vorhanden.</p>
<flux:button wire:click="clearFilter" size="sm" class="mt-2">
Alle anzeigen
</flux:button>
@else
<p>Keine News vorhanden.</p>
@endif
</flux:card> </flux:card>
@endforelse @endforelse
</div> </div>