- 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>
234 lines
12 KiB
PHP
234 lines
12 KiB
PHP
<div @if ($hasPublishing) wire:poll.3s @endif>
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h1 class="text-xl font-semibold">Posts</h1>
|
|
<button wire:click="create" class="bg-blue-600 text-white px-4 py-2 rounded">Add Post</button>
|
|
</div>
|
|
|
|
@if ($publishNotice)
|
|
<div class="mb-4 p-3 bg-amber-50 border border-amber-200 text-amber-800 rounded text-sm">{{ $publishNotice }}</div>
|
|
@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">
|
|
<thead>
|
|
<tr class="text-left border-b border-gray-200">
|
|
<th class="p-3">Image</th>
|
|
<th class="p-3">Site</th>
|
|
<th class="p-3">Type</th>
|
|
<th class="p-3">Title</th>
|
|
<th class="p-3">Status</th>
|
|
<th class="p-3">Scheduled</th>
|
|
<th class="p-3">Account</th>
|
|
<th class="p-3"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($posts as $post)
|
|
<tr wire:key="post-{{ $post->id }}" class="border-b border-gray-100">
|
|
<td class="p-3">
|
|
@if ($post->image_url)
|
|
<img src="{{ $post->image_url }}" alt="" class="w-10 h-10 object-cover rounded">
|
|
@else
|
|
<span class="text-gray-400">—</span>
|
|
@endif
|
|
</td>
|
|
<td class="p-3">{{ $post->site->name }}</td>
|
|
<td class="p-3">{{ ucfirst($post->type) }}</td>
|
|
<td class="p-3">{{ $post->title ?: \Illuminate\Support\Str::limit($post->body, 40) }}</td>
|
|
<td class="p-3">
|
|
@if ($post->status === 'published' && $post->published_url)
|
|
<a href="{{ $post->published_url }}" target="_blank" class="text-green-700 hover:underline">Published</a>
|
|
@else
|
|
{{ ucfirst($post->status) }}
|
|
@endif
|
|
@if ($post->status === 'failed' && $post->publish_error)
|
|
<div class="text-xs text-red-600 mt-0.5">{{ $post->publish_error }}</div>
|
|
@endif
|
|
</td>
|
|
<td class="p-3">{{ $post->scheduled_at?->format('Y-m-d H:i') ?? '—' }}</td>
|
|
<td class="p-3">
|
|
@if ($post->socialAccounts->count() > 0)
|
|
<span class="flex flex-wrap gap-1">
|
|
@foreach ($post->socialAccounts as $account)
|
|
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded">{{ $account->username }}</span>
|
|
@endforeach
|
|
</span>
|
|
@else
|
|
<span class="text-gray-400">—</span>
|
|
@endif
|
|
</td>
|
|
<td class="p-3 text-right space-x-2">
|
|
@if (in_array($post->type, ['blog', 'reddit', 'twitter', 'linkedin']) && in_array($post->status, ['draft', 'scheduled', 'failed']))
|
|
<button wire:click="publish({{ $post->id }})" class="text-green-700">Publish</button>
|
|
@endif
|
|
<button wire:click="edit({{ $post->id }})" class="text-blue-600">Edit</button>
|
|
<button wire:click="delete({{ $post->id }})" wire:confirm="Delete this post?" class="text-red-600">Delete</button>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
|
|
@if ($showForm)
|
|
<div class="fixed inset-0 bg-black/30 flex items-center justify-center">
|
|
<div class="bg-white p-6 rounded w-full max-w-md space-y-4 max-h-[90vh] overflow-y-auto">
|
|
<h2 class="text-lg font-semibold">{{ $editingId ? 'Edit Post' : 'Add Post' }}</h2>
|
|
|
|
@if (count($templates) > 0 && !$editingId)
|
|
<div>
|
|
<label class="block text-sm font-medium">Template (optional)</label>
|
|
<select wire:model.live="templateId" class="w-full border rounded p-2">
|
|
<option value="">No template</option>
|
|
@foreach ($templates as $template)
|
|
<option value="{{ $template->id }}">{{ $template->name }} ({{ $template->type ? ucfirst($template->type) : 'any' }})</option>
|
|
@endforeach
|
|
</select>
|
|
@if ($templateId)
|
|
<button wire:click="applyTemplate" class="mt-2 text-sm text-blue-600">Apply Template</button>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Site</label>
|
|
<select wire:model="siteId" class="w-full border rounded p-2">
|
|
<option value="">Select site</option>
|
|
@foreach ($sites as $site)
|
|
<option value="{{ $site->id }}">{{ $site->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
@error('siteId') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Social Accounts (optional - select multiple for cross-posting)</label>
|
|
<div class="border border-gray-200 rounded p-2 max-h-32 overflow-y-auto bg-gray-50">
|
|
@foreach ($accounts as $account)
|
|
<label class="flex items-center gap-2 p-1 hover:bg-white rounded cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
wire:model="selectedAccountIds"
|
|
value="{{ $account->id }}"
|
|
class="h-4 w-4 rounded"
|
|
>
|
|
<span class="text-sm">{{ $account->username }} ({{ ucfirst($account->network) }})</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
@error('selectedAccountIds') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Type</label>
|
|
<select wire:model="type" class="w-full border rounded p-2">
|
|
<option value="">Select type</option>
|
|
@foreach ($types as $t)
|
|
<option value="{{ $t }}">{{ ucfirst($t) }}</option>
|
|
@endforeach
|
|
</select>
|
|
@error('type') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Title (optional)</label>
|
|
<input type="text" wire:model="title" class="w-full border rounded p-2">
|
|
@error('title') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Body</label>
|
|
<textarea wire:model="body" rows="4" class="w-full border rounded p-2"></textarea>
|
|
@error('body') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Target (optional — e.g. subreddit for Reddit posts)</label>
|
|
<input type="text" wire:model="target" class="w-full border rounded p-2">
|
|
@error('target') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">
|
|
Image URL (optional{{ $type === 'blog' ? ' — used as the featured image on WordPress if set' : '' }})
|
|
</label>
|
|
<input type="url" wire:model="imageUrl" placeholder="https://..." class="w-full border rounded p-2">
|
|
@error('imageUrl') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Status</label>
|
|
<select wire:model="status" class="w-full border rounded p-2">
|
|
@foreach ($statuses as $s)
|
|
<option value="{{ $s }}">{{ ucfirst($s) }}</option>
|
|
@endforeach
|
|
</select>
|
|
@error('status') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium">Scheduled At (optional)</label>
|
|
<input type="datetime-local" wire:model="scheduledAt" class="w-full border rounded p-2">
|
|
@error('scheduledAt') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
</div>
|
|
|
|
<div class="flex justify-end gap-2">
|
|
<button wire:click="$set('showForm', false)" class="px-4 py-2">Cancel</button>
|
|
<button wire:click="save" class="bg-blue-600 text-white px-4 py-2 rounded">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|