techblog/resources/views/livewire/posts/index.blade.php
PeterChrz 75561faf25
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
initialize project and update gitignore
2026-03-19 09:35:42 -04:00

219 lines
9.2 KiB
PHP

<?php
use App\Livewire\IncludesSorting;
use App\Livewire\WithDeleteFilter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Livewire\Attributes\On;
use Livewire\Component;
use Livewire\Attributes\Computed;
use App\Models\Post;
use Livewire\WithPagination;
new class extends Component {
use IncludesSorting;
use WithDeleteFilter;
use WithPagination;
public array $filters = [
'search' => null,
'deleted' => null,
];
public function create()
{
$this->dispatch('post.create');
$this->modal('post-modal-form')->show();
}
public function edit($id): void
{
$this->dispatch('post.edit', $id);
$this->modal('post-modal-form')->show();
}
public function delete($id): void
{
$post = Post::find($id);
// if ($post->posts->count() > 0) {
// Flux::toast(variant: 'danger', heading: 'Delete Error', text: 'Post is being used!');
// } else {
$this->dispatch('show-delete-modal', [
'id' => $id,
'type' => 'post',
'message' => 'Are you sure you want to delete <span class="font-bold">' . $post->title . '</span>?',
]);
// }
}
#[On('confirmed-delete')]
public function handleConfirmedDelete($data): void
{
if ($data['type'] === 'post') {
$post = Post::findOrFail($data['id']);
$post->delete();
Flux::toast(variant: 'success', text: 'Post deleted');
}
}
public function restore($id): void
{
$post = Post::withTrashed()->findOrFail($id);
$post->restore();
$this->resetFilters();
}
public function forceDelete($id): void
{
$post = Post::withTrashed()->findOrFail($id);
$post->forceDelete();
$this->resetFilters();
Flux::toast(variant: 'success', text: 'Post completely deleted!');
}
public function resetFilters(): void
{
$this->filters = [
'search' => null,
'deleted' => null,
];
}
public function togglePublished($id, $published_at): void
{
Post::findOrFail($id)
->update(['published_at' => $published_at ? null : now() ]);
}
#[On('post.processed')]
public function handlePostProcessed(): void
{
$this->modal('post-modal-form')->close();
}
#[Computed]
public function posts(): LengthAwarePaginator
{
return Post::query()
->with('category', 'tags')
->join('categories', 'posts.category_id', '=', 'categories.id')
->selectRaw('posts.*, categories.name AS category_name')
->tap(fn($query) => $this->sortBy ? $query->orderBy($this->sortBy, $this->sortDirection) : $query->orderBy('title', 'asc'))
->tap(fn($query) => $this->filters['search'] ? $query->where(function ($q) {
$q->where('title', 'like', '%' . $this->filters['search'] . '%')
->orWhere('excerpt', 'like', '%' . $this->filters['search'] . '%');
}) : $query)
->tap(fn($query) => $this->handleDeletedFilter($query))
->paginate();
}
};
?>
<div x-cloak>
<div class="flex items-center justify-between">
<div>
<flux:heading size="xl">@lang('Posts')</flux:heading>
<flux:subheading>@lang('Manage all your posts for the blog.')</flux:subheading>
</div>
<flux:button size="sm" icon="plus" wire:click="create">@lang('Add Post')</flux:button>
</div>
<div class="mt-8">
<x-filters :filters="$filters">
<flux:select wire:model.live="filters.deleted" placeholder="{{ __('Deleted items') }}" size="sm">
<flux:select.option value="all">@lang('All')</flux:select.option>
<flux:select.option value="deleted">@lang('Deleted only')</flux:select.option>
<flux:select.option value="active">@lang('Active only')</flux:select.option>
</flux:select>
</x-filters>
</div>
@if ($this->posts->count() > 0)
<div class="mt-8">
<flux:table :paginate="$this->posts">
<flux:table.columns>
<flux:table.column sortable :sorted="$sortBy === 'title'" :direction="$sortDirection" wire:click="sort('title')">@lang('Title')</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'category_name'" :direction="$sortDirection" wire:click="sort('category_name')">@lang('Category')</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'slug'" :direction="$sortDirection" wire:click="sort('slug')">@lang('Slug')</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'published_at'" :direction="$sortDirection" wire:click="sort('published_at')">@lang('Published')</flux:table.column>
<flux:table.column>@lang('Tags')</flux:table.column>
<flux:table.column></flux:table.column>
</flux:table.columns>
<flux:table.rows>
@foreach ($this->posts as $post)
<flux:table.row :key="$post->id">
<flux:table.cell>{{ $post->title }}</flux:table.cell>
<flux:table.cell><x-category :category="$post->category"/></flux:table.cell>
<flux:table.cell>
<a href="" class="flex item-center hover:underline">
<span class="font-mono mr-2">{{ $post->slug }}</span>
<flux:icon class="size-5" name="link" />
</a>
</flux:table.cell>
<flux:table.cell>
@if ($post->published_at)
<button wire:click="togglePublished({{ $post->id }}, '{{ $post->published_at }}')">
<flux:icon size="4" icon="check-circle" class="text-green-600" />
</button>
@else
<button wire:click="togglePublished({{ $post->id }}, '{{ $post->published_at }}')">
<flux:icon size="4" icon="x-circle" class="text-red-600" />
</button>
@endif
</flux:table.cell>
<flux:table.cell>
@if ($post->tags->count() > 0)
<div class="flex items-center">
<span class="mr-2">{{ $post->tags->count() }}</span>
<flux:dropdown hover position="bottom">
<button type="button">
<flux:icon variant="solid" class="size-5 text-purple-300" name="tag" />
</button>
<flux:popover class="flex flex-col gap-3 rounded-md shadow-md">
@foreach ($post->tags as $tag)
{{ $tag->name }}<br />
@endforeach
</flux:popover>
</flux:dropdown>
</div>
@endif
</flux:table.cell>
<flux:table.cell>
<flux:dropdown align="end">
<flux:button size="sm" variant="ghost" icon="ellipsis-vertical"/>
<flux:menu>
<flux:menu.item wire:click="edit({{ $post->id }})" icon="pencil">@lang('Edit')</flux:menu.item>
@if (is_null($post->deleted_at))
<flux:menu.item wire:click="delete({{ $post->id }})" variant="danger" icon="trash">Delete</flux:menu.item>
@else
<flux:menu.item wire:click="restore({{ $post->id }})" variant="danger" icon="arrow-uturn-left">Restore</flux:menu.item>
<flux:menu.item wire:click="forceDelete({{ $post->id }})" variant="danger" icon="trash">Force delete</flux:menu.item>
@endif
</flux:menu>
</flux:dropdown>
</flux:table.cell>
</flux:table.row>
@endforeach
</flux:table.rows>
</flux:table>
</div>
@else
<flux:callout class="mt-8" color="blue" icon="information-circle" heading="{{ __('Cannot find any existing posts.') }}"/>
@endif
<flux:modal name="post-modal-form" class="min-w-3xl max-w-3xl">
<livewire:posts.form/>
</flux:modal>
<livewire:delete-confirm-modal />
</div>