complete story 5.2 with qa tests
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\PostStatus;
|
||||
use App\Models\AdminLog;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
@@ -13,7 +15,7 @@ new class extends Component
|
||||
public string $statusFilter = '';
|
||||
public string $sortBy = 'updated_at';
|
||||
public string $sortDir = 'desc';
|
||||
public int $perPage = 15;
|
||||
public int $perPage = 10;
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
@@ -47,16 +49,76 @@ new class extends Component
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function togglePublish(int $id): void
|
||||
{
|
||||
DB::transaction(function () use ($id) {
|
||||
$post = Post::lockForUpdate()->find($id);
|
||||
|
||||
if (! $post) {
|
||||
session()->flash('error', __('posts.post_not_found'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$oldStatus = $post->status->value;
|
||||
$newStatus = $post->status === PostStatus::Published ? PostStatus::Draft : PostStatus::Published;
|
||||
|
||||
$post->update([
|
||||
'status' => $newStatus,
|
||||
'published_at' => $newStatus === PostStatus::Published ? now() : null,
|
||||
]);
|
||||
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'status_change',
|
||||
'target_type' => 'post',
|
||||
'target_id' => $post->id,
|
||||
'old_values' => ['status' => $oldStatus],
|
||||
'new_values' => ['status' => $newStatus->value],
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
session()->flash('success', __('posts.post_status_updated'));
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
DB::transaction(function () use ($id) {
|
||||
$post = Post::lockForUpdate()->find($id);
|
||||
|
||||
if (! $post) {
|
||||
session()->flash('error', __('posts.post_not_found'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'delete',
|
||||
'target_type' => 'post',
|
||||
'target_id' => $post->id,
|
||||
'old_values' => $post->toArray(),
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$post->delete();
|
||||
|
||||
session()->flash('success', __('posts.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->whereRaw("JSON_EXTRACT(title, '$.\"{$locale}\"') LIKE ?", ["%{$this->search}%"])
|
||||
->orWhereRaw("JSON_EXTRACT(title, '$.\"ar\"') LIKE ?", ["%{$this->search}%"])
|
||||
->orWhereRaw("JSON_EXTRACT(title, '$.\"en\"') LIKE ?", ["%{$this->search}%"]);
|
||||
->when($this->search, fn ($q) => $q->where(function ($q) {
|
||||
$q->whereRaw("JSON_EXTRACT(title, '$.\"ar\"') LIKE ?", ["%{$this->search}%"])
|
||||
->orWhereRaw("JSON_EXTRACT(title, '$.\"en\"') LIKE ?", ["%{$this->search}%"])
|
||||
->orWhereRaw("JSON_EXTRACT(body, '$.\"ar\"') LIKE ?", ["%{$this->search}%"])
|
||||
->orWhereRaw("JSON_EXTRACT(body, '$.\"en\"') LIKE ?", ["%{$this->search}%"]);
|
||||
}))
|
||||
->when($this->statusFilter, fn ($q) => $q->where('status', $this->statusFilter))
|
||||
->orderBy($this->sortBy, $this->sortDir)
|
||||
@@ -111,7 +173,7 @@ new class extends Component
|
||||
|
||||
<flux:field>
|
||||
<flux:select wire:model.live="perPage">
|
||||
<option value="15">15 {{ __('admin.per_page') }}</option>
|
||||
<option value="10">10 {{ __('admin.per_page') }}</option>
|
||||
<option value="25">25 {{ __('admin.per_page') }}</option>
|
||||
<option value="50">50 {{ __('admin.per_page') }}</option>
|
||||
</flux:select>
|
||||
@@ -148,7 +210,7 @@ new class extends Component
|
||||
<flux:icon name="{{ $sortDir === 'asc' ? 'chevron-up' : 'chevron-down' }}" class="w-4 h-4" />
|
||||
@endif
|
||||
</button>
|
||||
<span class="w-32">{{ __('common.actions') }}</span>
|
||||
<span class="w-48">{{ __('common.actions') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Posts List -->
|
||||
@@ -188,7 +250,7 @@ new class extends Component
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="lg:w-32 flex gap-2">
|
||||
<div class="lg:w-48 flex flex-wrap gap-2">
|
||||
<flux:button
|
||||
href="{{ route('admin.posts.edit', $post) }}"
|
||||
variant="filled"
|
||||
@@ -197,6 +259,21 @@ new class extends Component
|
||||
>
|
||||
{{ __('common.edit') }}
|
||||
</flux:button>
|
||||
<flux:button
|
||||
wire:click="togglePublish({{ $post->id }})"
|
||||
variant="{{ $post->status === \App\Enums\PostStatus::Published ? 'ghost' : 'primary' }}"
|
||||
size="sm"
|
||||
>
|
||||
{{ $post->status === \App\Enums\PostStatus::Published ? __('posts.unpublish') : __('posts.publish') }}
|
||||
</flux:button>
|
||||
<flux:button
|
||||
wire:click="delete({{ $post->id }})"
|
||||
wire:confirm="{{ __('posts.confirm_delete') }}"
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
{{ __('common.delete') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user