complete story 5.2 with qa tests

This commit is contained in:
Naser Mansour
2025-12-27 01:45:07 +02:00
parent 8e3daddb1d
commit d29959d54d
6 changed files with 480 additions and 42 deletions
@@ -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>