# Story 5.2: Post Management Dashboard ## Epic Reference **Epic 5:** Posts/Blog System ## User Story As an **admin**, I want **a dashboard to manage all blog posts**, So that **I can organize, publish, and maintain content efficiently**. ## Story Context ### Existing System Integration - **Integrates with:** posts table, admin_logs table - **Technology:** Livewire Volt with pagination - **Follows pattern:** Admin list pattern (see similar components in `resources/views/livewire/admin/`) - **Touch points:** Post CRUD operations, AdminLog audit trail - **Note:** HTML sanitization uses `clean()` helper (from mews/purifier package, configured in Story 5.1) ## Acceptance Criteria ### List View - [ ] Display all posts with: - Title (in current admin language) - Status (draft/published) - Created date - Last updated date - [ ] Pagination (10/25/50 per page) ### Filtering & Search - [ ] Filter by status (draft/published/all) - [ ] Search by title or body content - [ ] Sort by date (newest/oldest) ### Quick Actions - [ ] Edit post - [ ] Delete (with confirmation) - [ ] Publish/unpublish toggle - [ ] Bulk delete option (optional) ### Quality Requirements - [ ] Bilingual labels - [ ] Audit log for delete and status change actions - [ ] Authorization check (admin only) on all actions - [ ] Tests for list operations ### Edge Cases - [ ] Handle post not found gracefully (race condition on delete) - [ ] Per-page selector visible in UI (10/25/50 options) - [ ] Bulk delete is **out of scope** for this story (marked optional) ## Technical Notes ### File Location Create Volt component at: `resources/views/livewire/admin/posts/index.blade.php` ### Route ```php // routes/web.php (admin group) Route::get('/admin/posts', function () { return view('livewire.admin.posts.index'); })->middleware(['auth', 'admin'])->name('admin.posts.index'); ``` ### Volt Component ```php resetPage(); } public function sort(string $column): void { if ($this->sortBy === $column) { $this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc'; } else { $this->sortBy = $column; $this->sortDir = 'desc'; } } public function togglePublish(int $id): void { $post = Post::findOrFail($id); $newStatus = $post->status === 'published' ? 'draft' : 'published'; $post->update(['status' => $newStatus]); AdminLog::create([ 'admin_id' => auth()->id(), 'action_type' => 'status_change', 'target_type' => 'post', 'target_id' => $post->id, 'old_values' => ['status' => $post->getOriginal('status')], 'new_values' => ['status' => $newStatus], 'ip_address' => request()->ip(), ]); session()->flash('success', __('messages.post_status_updated')); } public function delete(int $id): void { $post = Post::findOrFail($id); AdminLog::create([ 'admin_id' => auth()->id(), 'action_type' => 'delete', 'target_type' => 'post', 'target_id' => $post->id, 'old_values' => $post->toArray(), 'ip_address' => request()->ip(), ]); $post->delete(); session()->flash('success', __('messages.post_deleted')); } public function with(): array { $locale = app()->getLocale(); return [ 'posts' => Post::query() ->when($this->search, fn($q) => $q->where(function($q) use ($locale) { $q->where("title_{$locale}", 'like', "%{$this->search}%") ->orWhere("body_{$locale}", 'like', "%{$this->search}%"); })) ->when($this->statusFilter, fn($q) => $q->where('status', $this->statusFilter)) ->orderBy($this->sortBy, $this->sortDir) ->paginate($this->perPage), ]; } }; ``` ### Template ```blade
| {{ __('admin.title') }} | {{ __('admin.status') }} | {{ __('admin.created') }} | {{ __('admin.updated') }} | {{ __('admin.actions') }} |
|---|---|---|---|---|
| {{ $post->title }} |
|
{{ $post->created_at->format('d/m/Y') }} | {{ $post->updated_at->diffForHumans() }} |
|
| {{ __('admin.no_posts') }} | ||||