Skip to content

Commit

Permalink
feat: Added filters for backup tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen committed Sep 7, 2024
1 parent 057fa65 commit 44b5e1b
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 37 deletions.
91 changes: 82 additions & 9 deletions app/Livewire/BackupTasks/Tables/IndexTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,42 @@
namespace App\Livewire\BackupTasks\Tables;

use App\Models\BackupTask;
use App\Models\Tag;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use Toaster;

/**
* Manages the display of backup tasks in a table format.
* Manages the display and filtering of backup tasks in a table format.
*
* This component handles the rendering and pagination of backup tasks for the authenticated user.
* This component handles the rendering, pagination, and filtering of backup tasks for the authenticated user.
*/
class IndexTable extends Component
{
use WithPagination;

/** @var int The count of filtered backup tasks */
public int $filteredCount = 0;

/** @var int|null Selected tag ID for filtering */
#[Url]
public ?int $selectedTag = null;

/** @var string|null The status to filter by */
#[Url]
public ?string $status = null;

/** @var string The text input for label filtering */
#[Url]
public string $search = '';

/** @var array<string> Available statuses for filtering */
public array $statuses = ['running', 'ready'];

/**
* @var string[]
*/
Expand All @@ -27,16 +49,67 @@ class IndexTable extends Component
/**
* Render the backup tasks index table.
*
* Fetches and paginates backup tasks for the authenticated user, including related data.
* Fetches and paginates filtered backup tasks for the authenticated user, including related data.
*/
public function render(): View
{
$backupTasks = BackupTask::where('user_id', Auth::id())
->with(['remoteServer', 'backupDestination'])
->withAggregate('latestLog', 'created_at')
->orderBy('id', 'desc')
->paginate(Auth::user()?->getAttribute('pagination_count') ?? 15, pageName: 'backup-tasks');
$builder = $this->getFilteredQuery();

$lengthAwarePaginator = $builder->paginate(Auth::user()?->getAttribute('pagination_count') ?? 15, pageName: 'backup-tasks');

$this->filteredCount = $builder->count();

$tags = Tag::where('user_id', Auth::id())->get();

return view('livewire.backup-tasks.tables.index-table', ['backupTasks' => $backupTasks]);
return view('livewire.backup-tasks.tables.index-table', [
'backupTasks' => $lengthAwarePaginator,
'tags' => $tags,
]);
}

/**
* Reset all filters and clear URL parameters.
*/
public function resetFilters(): void
{
$this->reset(['selectedTag', 'status', 'search']);
$this->resetPage();
$this->clearUrlParameters();

Toaster::info('Cleared your Backup Tasks filter.');
}

/**
* Get the filtered query for backup tasks.
*
* @return Builder<BackupTask>
*/
private function getFilteredQuery(): Builder
{
$query = BackupTask::where('user_id', Auth::id())
->with(['remoteServer', 'backupDestination', 'tags'])
->withAggregate('latestLog', 'created_at');

if ($this->selectedTag !== null) {
$query->whereHas('tags', fn (Builder $builder) => $builder->where('id', $this->selectedTag));
}

if ($this->status !== null) {
$query->where('status', $this->status);
}

if ($this->search !== '') {
$query->where('label', 'like', "%{$this->search}%");
}

return $query->latest('id');
}

/**
* Clear URL parameters related to filtering.
*/
private function clearUrlParameters(): void
{
$this->dispatch('urlParametersCleared');
}
}
119 changes: 91 additions & 28 deletions resources/views/livewire/backup-tasks/tables/index-table.blade.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
<div>
<div class="mt-4">
@if ($backupTasks->isEmpty())
@if ($filteredCount === 0 &&Auth::user()->backupTasks()->exists())
<x-no-content withBackground>
<x-slot name="icon">
@svg('hugeicons-filter', 'inline h-16 w-16 text-primary-900 dark:text-white')
</x-slot>
<x-slot name="title">
{{ __('No backup tasks match your filters!') }}
</x-slot>
<x-slot name="description">
{{ __('Try adjusting your filter criteria or clear your filter.') }}
</x-slot>
<x-slot name="action">
<x-danger-button type="button" class="mt-4" wire:click="resetFilters">
{{ __('Reset Filters') }}
</x-danger-button>
</x-slot>
</x-no-content>
@elseif (! Auth::user()->backupTasks()->exists())
<x-no-content withBackground>
<x-slot name="icon">
@svg('hugeicons-archive-02', 'inline h-16 w-16 text-primary-900 dark:text-white')
Expand All @@ -27,42 +44,88 @@
<x-slot name="icon">
<x-hugeicons-archive-02 class="h-6 w-6 text-primary-600 dark:text-primary-400" />
</x-slot>
<x-table.table-header>
<div class="col-span-12 md:col-span-3">
{{ __('Task') }}

<div class="mb-4 flex flex-wrap items-center gap-4">
<div class="flex-grow">
<x-input-label for="search" :value="__('Search')" />
<x-text-input
id="search"
name="search"
type="text"
class="mt-1 block w-full"
wire:model.live="search"
:placeholder="__('Search by label')"
/>
</div>
<div class="col-span-12 md:col-span-3">
{{ __('Server & Destination') }}
<div>
<x-input-label for="status" :value="__('Status')" />
<x-select id="status" class="mt-1 block w-full" wire:model.live="status" name="status">
<option value="">{{ __('All') }}</option>
@foreach ($statuses as $statusOption)
<option value="{{ $statusOption }}">{{ __(ucfirst($statusOption)) }}</option>
@endforeach
</x-select>
</div>
<div class="col-span-12 md:col-span-4">
{{ __('Status & Schedule') }}
<div>
<x-input-label for="tag" :value="__('Tag')" />
<x-select id="tag" name="tag" class="mt-1 block w-full" wire:model.live="selectedTag">
<option value="">{{ __('All Tags') }}</option>
@foreach ($tags as $tag)
<option value="{{ $tag->id }}">{{ $tag->label }}</option>
@endforeach
</x-select>
</div>
<div class="col-span-12 md:col-span-2">
{{ __('Actions') }}
<div class="mt-7 flex items-end">
<x-secondary-button wire:click="resetFilters" iconOnly title="{{ __('Clear filter') }}">
@svg('hugeicons-filter-remove')
</x-secondary-button>
</div>
</x-table.table-header>
<x-table.table-body>
@foreach ($backupTasks as $backupTask)
<livewire:backup-tasks.tables.index-item
:backupTask="$backupTask"
:key="'index-item-' . $backupTask->id"
/>
@endforeach
</x-table.table-body>
</div>

@if ($backupTasks->isEmpty() && $filteredCount > 0)
<x-no-content>
<x-slot name="title">
{{ __('No backup tasks match your filters') }}
</x-slot>
<x-slot name="description">
{{ __('Try adjusting your search or filter criteria.') }}
</x-slot>
</x-no-content>
@else
<x-table.table-header>
<div class="col-span-12 md:col-span-3">
{{ __('Task') }}
</div>
<div class="col-span-12 md:col-span-3">
{{ __('Server & Destination') }}
</div>
<div class="col-span-12 md:col-span-4">
{{ __('Status & Schedule') }}
</div>
<div class="col-span-12 md:col-span-2">
{{ __('Actions') }}
</div>
</x-table.table-header>
<x-table.table-body>
@foreach ($backupTasks as $backupTask)
<livewire:backup-tasks.tables.index-item
:backupTask="$backupTask"
:key="'index-item-' . $backupTask->id"
/>
@endforeach
</x-table.table-body>
@endif
</x-table.table-wrapper>
<div class="mt-4 flex justify-end">
{{ $backupTasks->links() }}
</div>
@endif
</div>

@push('scripts')
<script>
document.addEventListener('livewire:init', function () {
Livewire.on('taskUpdated', (taskId) => {
Alpine.store('tagsTooltip').refresh(taskId);
});
<script>
document.addEventListener('livewire:initialized', () => {
Livewire.on('urlParametersCleared', () => {
history.replaceState(null, '', window.location.pathname);
});
</script>
@endpush
});
</script>
</div>

0 comments on commit 44b5e1b

Please sign in to comment.