Filtro, Ordenação e Busca em CRUD com Laravel 12: Nesta etapa, vamos aprimorar o CRUD de tarefas implementado anteriormente com recursos que facilitam a navegação e análise de dados. Vamos adicionar filtros dinâmicos, ordenação por colunas, paginação com limite de 10 tarefas por página e um campo de busca. Esses recursos são essenciais para qualquer painel administrativo moderno.
O que você vai aprender
- Aplicar filtros personalizados nas tarefas
- Ordenar resultados com base em diferentes colunas
- Implementar paginação eficiente
- Adicionar funcionalidade de busca no título e na descrição

1. Adicionando Filtros e Paginação no Controller
No TaskController, vamos atualizar o método index para aplicar os filtros, ordenação e paginação com base nos parâmetros da requisição.
app/Http/Controllers/TaskController.php:
// ... (Inicio do arquivo, imports e use AuthorizesRequests) ...
class TaskController extends Controller
{
use AuthorizesRequests;
public function index(Request $request) // Importa a classe Request
{
// Inicia a consulta para o usuário logado
$query = Task::where('user_id', Auth::id());
// Aplica filtro de busca se houver termo de busca
if ($request->filled('search')) {
$searchTerm = $request->input('search');
$query->where(function ($q) use ($searchTerm) {
$q->where('title', 'like', '%' . $searchTerm . '%')
->orWhere('description', 'like', '%' . $searchTerm . '%');
});
}
// Aplica filtro de status se houver
if ($request->filled('status')) {
if ($request->input('status') == 'completed') {
$query->where('is_completed', true);
} elseif ($request->input('status') == 'pending') {
$query->where('is_completed', false);
}
}
// Aplica ordenação
$sortBy = $request->input('sort_by', 'created_at'); // Padrão: created_at
$sortDirection = $request->input('sort_direction', 'desc'); // Padrão: decrescente
// Garante que as colunas de ordenação são válidas para evitar injeção SQL
$allowedSortColumns = ['created_at', 'title', 'is_completed'];
if (!in_array($sortBy, $allowedSortColumns)) {
$sortBy = 'created_at'; // Volta para o padrão se for inválido
}
if (!in_array($sortDirection, ['asc', 'desc'])) {
$sortDirection = 'desc'; // Volta para o padrão se for inválido
}
$query->orderBy($sortBy, $sortDirection);
// Pagina os resultados (10 itens por página)
$tasks = $query->paginate(10);
return view('tasks.index', compact('tasks'));
}
// ... (restante dos métodos: create, store, edit, update, destroy) ...
}
2. Ajustando a View com Filtros, Busca e Paginação
No arquivo resources/views/tasks/index.blade.php, vamos adicionar um pequeno formulário para filtros, ordenação e busca.
<x-app-layout>
<x-slot name="header">
<div class="flex justify-between items-center">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('My Tasks') }}
</h2>
<a href="{{ route('tasks.create') }}" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
{{ __('New Task') }}
</a>
</div>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
{{-- Success Message (Toast) --}}
@if (session('success'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-6 rounded-md shadow-sm" role="alert">
<p class="font-bold">{{ __('Success!') }}</p>
<p>{{ session('success') }}</p>
</div>
@endif
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
{{-- Filters, Search and Sorting Form --}}
<form method="GET" action="{{ route('tasks.index') }}" class="mb-6">
<div class="flex flex-col md:flex-row md:space-x-4 space-y-4 md:space-y-0">
{{-- Filter Section --}}
<div class="flex-1 border border-gray-200 p-4 rounded-md shadow-sm">
<h3 class="text-lg font-semibold text-gray-800 mb-4">{{ __('Filters') }}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="search" class="block text-sm font-medium text-gray-700">{{ __('Search') }}</label>
<input type="text" name="search" id="search" value="{{ request('search') }}" placeholder="Search by title or description..." class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div>
<label for="status" class="block text-sm font-medium text-gray-700">{{ __('Status') }}</label>
<select id="status" name="status" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="">{{ __('All Statuses') }}</option>
<option value="pending" {{ request('status') == 'pending' ? 'selected' : '' }}>{{ __('Pending') }}</option>
<option value="completed" {{ request('status') == 'completed' ? 'selected' : '' }}>{{ __('Completed') }}</option>
</select>
</div>
</div>
</div>
{{-- Sorting Section --}}
<div class="flex-1 border border-gray-200 p-4 rounded-md shadow-sm">
<h3 class="text-lg font-semibold text-gray-800 mb-4">{{ __('Sort By') }}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="sort_by" class="block text-sm font-medium text-gray-700">{{ __('Sort Column') }}</label>
<select id="sort_by" name="sort_by" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="created_at" {{ request('sort_by') == 'created_at' ? 'selected' : '' }}>{{ __('Creation Date') }}</option>
<option value="title" {{ request('sort_by') == 'title' ? 'selected' : '' }}>{{ __('Title') }}</option>
<option value="is_completed" {{ request('sort_by') == 'is_completed' ? 'selected' : '' }}>{{ __('Status') }}</option>
</select>
</div>
<div>
<label for="sort_direction" class="block text-sm font-medium text-gray-700">{{ __('Direction') }}</label>
<select id="sort_direction" name="sort_direction" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<option value="asc" {{ request('sort_direction') == 'asc' ? 'selected' : '' }}>{{ __('Ascending') }}</option>
<option value="desc" {{ request('sort_direction') == 'desc' ? 'selected' : '' }}>{{ __('Descending') }}</option>
</select>
</div>
</div>
</div>
</div>
<div class="mt-6 flex justify-end">
<button type="submit" class="inline-flex items-center px-4 py-2 bg-indigo-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-indigo-700 focus:bg-indigo-700 active:bg-indigo-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
{{ __('Apply Filters') }}
</button>
</div>
</form>
{{-- Task List --}}
<div class="mt-6 bg-gray-50 p-4 rounded-lg shadow-inner">
@forelse($tasks as $task)
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center bg-white p-4 mb-3 border border-gray-200 rounded-md shadow-sm">
<div class="flex-grow mb-2 sm:mb-0">
<strong class="text-lg text-gray-900">{{ $task->title }}</strong>
{{-- Display description --}}
@if($task->description)
<p class="text-gray-600 text-sm mt-1 break-words">{{ $task->description }}</p>
@endif
<p class="text-gray-500 text-xs mt-1">
{{ __('Created at:') }} {{ $task->created_at->format('d/m/Y H:i') }}
</p>
</div>
<div class="flex items-center space-x-3 mt-2 sm:mt-0">
@if($task->is_completed)
<span class="px-2 py-0.5 bg-green-100 text-green-800 text-xs font-semibold rounded-full">{{ __('Completed') }}</span>
@else
<span class="px-2 py-0.5 bg-yellow-100 text-yellow-800 text-xs font-semibold rounded-full">{{ __('Pending') }}</span>
@endif
<a href="{{ route('tasks.edit', $task) }}" class="text-sm text-indigo-600 hover:text-indigo-900 font-medium">
{{ __('Edit') }}
</a>
<form action="{{ route('tasks.destroy', $task) }}" method="POST" onsubmit="return confirm('{{ __('Are you sure you want to delete this task?') }}');">
@csrf
@method('DELETE')
<button type="submit" class="text-sm text-red-600 hover:text-red-900 font-medium">
{{ __('Delete') }}
</button>
</form>
</div>
</div>
@empty
<p class="text-gray-600 text-center py-4">{{ __('No tasks found. How about creating one?') }}</p>
@endforelse
</div>
{{-- Displaying Pagination --}}
<div class="mt-6">
{{ $tasks->links() }}
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
3. Resultado Esperado
Agora, ao acessar /tasks, você poderá:
- Filtrar/buscar tarefas por palavras-chave no título ou descrição e status
- Ordenar por título, status e data de criação
- Navegar entre as páginas de 10 em 10 tarefas
Conclusão
Com esta implementação, o sistema de tarefas ganha robustez e usabilidade. Os filtros, ordenações e a funcionalidade de busca tornam a gestão mais eficiente e profissional, principalmente em cenários com grande volume de dados. Lembrando que o foco aqui é educativo: diversas melhorias de UX/UI poderiam ser aplicadas para uma experiência mais refinada, mas essas otimizações fogem ao escopo deste guia.
Na próxima etapa, vamos alterar nosso dashboard (painel) para apresentar algumas informações consolidadas sobre as tarefas.
🔗 Leitura recomendada: CRUD com Laravel 12, Breeze e Fortify – Parte 5B.