Add Advanced Search and filtering to Posts
- Add full-text search index on posts.title and posts.body - Add search box with live filtering - Add filter dropdowns for type, status, and site - Add Clear Filters button - Display post count in filter bar - All filters work together (AND logic) Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -40,9 +40,39 @@ class Index extends Component
|
|||||||
|
|
||||||
public ?string $publishNotice = null;
|
public ?string $publishNotice = null;
|
||||||
|
|
||||||
|
public string $search = '';
|
||||||
|
|
||||||
|
public string $typeFilter = '';
|
||||||
|
|
||||||
|
public string $statusFilter = '';
|
||||||
|
|
||||||
|
public string $siteFilter = '';
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$posts = Post::with(['site', 'socialAccounts'])->orderByDesc('created_at')->get();
|
$query = Post::with(['site', 'socialAccounts']);
|
||||||
|
|
||||||
|
// Full-text search on title and body
|
||||||
|
if ($this->search) {
|
||||||
|
$query->whereFullText(['title', 'body'], $this->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by type
|
||||||
|
if ($this->typeFilter) {
|
||||||
|
$query->where('type', $this->typeFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by status
|
||||||
|
if ($this->statusFilter) {
|
||||||
|
$query->where('status', $this->statusFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by site
|
||||||
|
if ($this->siteFilter) {
|
||||||
|
$query->where('site_id', $this->siteFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$posts = $query->orderByDesc('created_at')->get();
|
||||||
|
|
||||||
return view('livewire.posts.index', [
|
return view('livewire.posts.index', [
|
||||||
'posts' => $posts,
|
'posts' => $posts,
|
||||||
@@ -78,6 +108,14 @@ class Index extends Component
|
|||||||
$this->showForm = true;
|
$this->showForm = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function clearFilters(): void
|
||||||
|
{
|
||||||
|
$this->search = '';
|
||||||
|
$this->typeFilter = '';
|
||||||
|
$this->statusFilter = '';
|
||||||
|
$this->siteFilter = '';
|
||||||
|
}
|
||||||
|
|
||||||
public function createFromTemplate(int $templateId): void
|
public function createFromTemplate(int $templateId): void
|
||||||
{
|
{
|
||||||
$template = Template::findOrFail($templateId);
|
$template = Template::findOrFail($templateId);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('posts', function (Blueprint $table) {
|
||||||
|
$table->fullText(['title', 'body']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('posts', function (Blueprint $table) {
|
||||||
|
$table->dropFullText(['title', 'body']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,6 +8,59 @@
|
|||||||
<div class="mb-4 p-3 bg-amber-50 border border-amber-200 text-amber-800 rounded text-sm">{{ $publishNotice }}</div>
|
<div class="mb-4 p-3 bg-amber-50 border border-amber-200 text-amber-800 rounded text-sm">{{ $publishNotice }}</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<!-- Search and Filters -->
|
||||||
|
<div class="bg-gray-50 p-4 rounded mb-4">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 items-end">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Search</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
wire:model.live="search"
|
||||||
|
placeholder="Search posts..."
|
||||||
|
class="w-full border rounded p-2"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Type</label>
|
||||||
|
<select wire:model.live="typeFilter" class="w-full border rounded p-2">
|
||||||
|
<option value="">All Types</option>
|
||||||
|
@foreach ($types as $t)
|
||||||
|
<option value="{{ $t }}">{{ ucfirst($t) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Status</label>
|
||||||
|
<select wire:model.live="statusFilter" class="w-full border rounded p-2">
|
||||||
|
<option value="">All Statuses</option>
|
||||||
|
@foreach ($statuses as $s)
|
||||||
|
<option value="{{ $s }}">{{ ucfirst($s) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Site</label>
|
||||||
|
<select wire:model.live="siteFilter" class="w-full border rounded p-2">
|
||||||
|
<option value="">All Sites</option>
|
||||||
|
@foreach ($sites as $site)
|
||||||
|
<option value="{{ $site->id }}">{{ $site->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
wire:click="clearFilters"
|
||||||
|
class="w-full bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded text-sm"
|
||||||
|
>
|
||||||
|
Clear Filters
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 text-xs text-gray-500">
|
||||||
|
Showing {{ $posts->count() }} post{{ $posts->count() !== 1 ? 's' : '' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="w-full bg-white border border-gray-200 rounded">
|
<table class="w-full bg-white border border-gray-200 rounded">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left border-b border-gray-200">
|
<tr class="text-left border-b border-gray-200">
|
||||||
|
|||||||
Reference in New Issue
Block a user