220 lines
9.2 KiB
PHP
220 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>
|