From 2dccbe3a14c09a67cc4943a4d35835183a5abd50 Mon Sep 17 00:00:00 2001 From: javier Date: Fri, 19 Jun 2026 17:56:40 +0200 Subject: [PATCH] feat(map): filtros por columna en cabecera (elementos e inspecciones) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usa la cabecera secundaria de Rappasoft (setSecondaryHeaderEnabled + secondaryHeaderFilter): - Elementos: Elemento (texto), Capa (select), Fase (select). - Inspecciones: Fecha (date), Elemento (texto), Resultado (select), Usuario (select); Plantilla sin filtro. Tests: MapTablesTest amplía con filtro de capa funcional. Suite 75 passing. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/Livewire/Projects/FeatureTable.php | 33 ++++++++++++++------ app/Livewire/Projects/InspectionTable.php | 37 +++++++++++++++++++++++ tests/Feature/MapTablesTest.php | 20 ++++++++++++ 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/app/Livewire/Projects/FeatureTable.php b/app/Livewire/Projects/FeatureTable.php index 85e724e..0577598 100644 --- a/app/Livewire/Projects/FeatureTable.php +++ b/app/Livewire/Projects/FeatureTable.php @@ -3,12 +3,15 @@ namespace App\Livewire\Projects; use App\Models\Feature; +use App\Models\Layer; +use App\Models\Phase; use App\Models\Project; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Auth; use Rappasoft\LaravelLivewireTables\DataTableComponent; use Rappasoft\LaravelLivewireTables\Views\Column; use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter; +use Rappasoft\LaravelLivewireTables\Views\Filters\TextFilter; class FeatureTable extends DataTableComponent { @@ -21,6 +24,7 @@ class FeatureTable extends DataTableComponent $this->setPrimaryKey('id') ->setDefaultSort('name', 'asc') ->setSortingPillsEnabled(false) + ->setSecondaryHeaderEnabled() ->setAdditionalSelects(['features.id as id', 'features.layer_id as layer_id']); } @@ -44,13 +48,16 @@ class FeatureTable extends DataTableComponent Column::make('Elemento', 'name') ->sortable() ->searchable() + ->secondaryHeaderFilter('name') ->format(fn ($value) => '' . e($value) . '') ->html(), Column::make('Capa') + ->secondaryHeaderFilter('layer') ->label(fn ($row) => e($row->layer?->name ?? '—')), Column::make('Fase') + ->secondaryHeaderFilter('phase') ->label(fn ($row) => e($row->layer?->phase?->name ?? '—')), Column::make('Progreso', 'progress') @@ -76,17 +83,23 @@ class FeatureTable extends DataTableComponent public function filters(): array { + $layers = Layer::whereHas('phase', fn ($q) => $q->where('project_id', $this->projectId)) + ->orderBy('name')->pluck('name', 'id')->toArray(); + $phases = Phase::where('project_id', $this->projectId) + ->orderBy('order')->pluck('name', 'id')->toArray(); + return [ - SelectFilter::make('Progreso', 'progress') - ->options(['' => 'Progreso: todos', 'pending' => 'Sin iniciar', 'in_progress' => 'En curso', 'done' => 'Completado']) - ->filter(function (Builder $query, string $value) { - match ($value) { - 'pending' => $query->where('features.progress', '=', 0), - 'in_progress' => $query->where('features.progress', '>', 0)->where('features.progress', '<', 100), - 'done' => $query->where('features.progress', '>=', 100), - default => null, - }; - }), + TextFilter::make('Elemento', 'name') + ->config(['placeholder' => 'Buscar elemento…']) + ->filter(fn (Builder $query, string $value) => $query->where('features.name', 'like', '%' . $value . '%')), + + SelectFilter::make('Capa', 'layer') + ->options(['' => 'Todas'] + $layers) + ->filter(fn (Builder $query, string $value) => $query->where('features.layer_id', $value)), + + SelectFilter::make('Fase', 'phase') + ->options(['' => 'Todas'] + $phases) + ->filter(fn (Builder $query, string $value) => $query->whereHas('layer', fn ($l) => $l->where('phase_id', $value))), ]; } } diff --git a/app/Livewire/Projects/InspectionTable.php b/app/Livewire/Projects/InspectionTable.php index dcf83c2..774d175 100644 --- a/app/Livewire/Projects/InspectionTable.php +++ b/app/Livewire/Projects/InspectionTable.php @@ -4,10 +4,14 @@ namespace App\Livewire\Projects; use App\Models\Inspection; use App\Models\Project; +use App\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Auth; use Rappasoft\LaravelLivewireTables\DataTableComponent; use Rappasoft\LaravelLivewireTables\Views\Column; +use Rappasoft\LaravelLivewireTables\Views\Filters\DateFilter; +use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter; +use Rappasoft\LaravelLivewireTables\Views\Filters\TextFilter; class InspectionTable extends DataTableComponent { @@ -20,6 +24,7 @@ class InspectionTable extends DataTableComponent $this->setPrimaryKey('id') ->setDefaultSort('inspections.created_at', 'desc') ->setSortingPillsEnabled(false) + ->setSecondaryHeaderEnabled() ->setAdditionalSelects([ 'inspections.id as id', 'inspections.created_at as created_at', @@ -48,9 +53,11 @@ class InspectionTable extends DataTableComponent return [ Column::make('Fecha', 'created_at') ->sortable() + ->secondaryHeaderFilter('fecha') ->format(fn ($value, $row) => $row->created_at?->format('d/m/Y') ?? '—'), Column::make('Elemento') + ->secondaryHeaderFilter('elemento') ->label(fn ($row) => $row->feature?->name ? '' . e($row->feature->name) . '' : '') @@ -61,12 +68,14 @@ class InspectionTable extends DataTableComponent Column::make('Resultado', 'result') ->sortable() + ->secondaryHeaderFilter('resultado') ->format(fn ($value) => $value ? '' . e($value) . '' : '') ->html(), Column::make('Usuario') + ->secondaryHeaderFilter('usuario') ->label(fn ($row) => e($row->user?->name ?? '—')), Column::make('Acciones') @@ -80,4 +89,32 @@ class InspectionTable extends DataTableComponent ->html(), ]; } + + public function filters(): array + { + $results = Inspection::where('project_id', $this->projectId) + ->whereNotNull('result')->distinct()->orderBy('result') + ->pluck('result', 'result')->toArray(); + + $users = User::whereIn('id', Inspection::where('project_id', $this->projectId) + ->whereNotNull('user_id')->distinct()->pluck('user_id')) + ->orderBy('name')->pluck('name', 'id')->toArray(); + + return [ + DateFilter::make('Fecha', 'fecha') + ->filter(fn (Builder $query, string $value) => $query->whereDate('inspections.created_at', $value)), + + TextFilter::make('Elemento', 'elemento') + ->config(['placeholder' => 'Buscar elemento…']) + ->filter(fn (Builder $query, string $value) => $query->whereHas('feature', fn ($f) => $f->where('name', 'like', '%' . $value . '%'))), + + SelectFilter::make('Resultado', 'resultado') + ->options(['' => 'Todos'] + $results) + ->filter(fn (Builder $query, string $value) => $query->where('inspections.result', $value)), + + SelectFilter::make('Usuario', 'usuario') + ->options(['' => 'Todos'] + $users) + ->filter(fn (Builder $query, string $value) => $query->where('inspections.user_id', $value)), + ]; + } } diff --git a/tests/Feature/MapTablesTest.php b/tests/Feature/MapTablesTest.php index 00ef6c9..e4793ee 100644 --- a/tests/Feature/MapTablesTest.php +++ b/tests/Feature/MapTablesTest.php @@ -60,6 +60,26 @@ class MapTablesTest extends TestCase ->assertForbidden(); } + public function test_feature_table_layer_filter(): void + { + // Segunda capa con otro elemento en la misma fase. + $phaseId = $this->feature->layer->phase_id; + $layerB = Layer::create(['project_id' => $this->project->id, 'phase_id' => $phaseId, 'name' => 'Capa B', 'color' => '#222', 'uploaded_by' => $this->user->id]); + Feature::create([ + 'layer_id' => $layerB->id, 'name' => 'Viga 7', + 'geometry' => ['type' => 'Point', 'coordinates' => [-3.0, 40.0]], + 'progress' => 0, 'status' => 'planned', + ]); + + Livewire::actingAs($this->user) + ->test(FeatureTable::class, ['projectId' => $this->project->id]) + ->assertSee('Pilar 12') + ->assertSee('Viga 7') + ->set('filterComponents.layer', (string) $this->feature->layer_id) + ->assertSee('Pilar 12') + ->assertDontSee('Viga 7'); + } + public function test_inspection_table_lists_project_inspections(): void { Inspection::create([