From 25a59e44133132a6fe3be0916f46aad67543f35f Mon Sep 17 00:00:00 2001 From: javier Date: Fri, 19 Jun 2026 17:44:20 +0200 Subject: [PATCH] feat(map): listas de elementos e inspecciones con tablas Rappasoft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FeatureTable e InspectionTable (DataTableComponent) sustituyen las tablas HTML de las pestañas "Elementos" e "Inspecciones" del mapa: búsqueda, orden, filtro (progreso) y acciones. - Selección de elemento e "ver inspección" se comunican al ProjectMap por eventos (map-select-feature / map-view-inspection, vía #[On]); seleccionar abre el panel de edición y centra el mapa igual que antes. - Las relaciones requieren sus FKs en additionalSelects (layer_id / feature_id, template_id, user_id) porque Rappasoft no selecciona '*'. Tests: MapTablesTest (3). Suite 74 passing. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/Livewire/Projects/FeatureTable.php | 92 +++++++++++++++++++ app/Livewire/Projects/InspectionTable.php | 83 +++++++++++++++++ app/Livewire/Projects/ProjectMap.php | 3 + .../livewire/projects/project-map.blade.php | 76 +-------------- tests/Feature/MapTablesTest.php | 81 ++++++++++++++++ 5 files changed, 263 insertions(+), 72 deletions(-) create mode 100644 app/Livewire/Projects/FeatureTable.php create mode 100644 app/Livewire/Projects/InspectionTable.php create mode 100644 tests/Feature/MapTablesTest.php diff --git a/app/Livewire/Projects/FeatureTable.php b/app/Livewire/Projects/FeatureTable.php new file mode 100644 index 0000000..85e724e --- /dev/null +++ b/app/Livewire/Projects/FeatureTable.php @@ -0,0 +1,92 @@ +setPrimaryKey('id') + ->setDefaultSort('name', 'asc') + ->setSortingPillsEnabled(false) + ->setAdditionalSelects(['features.id as id', 'features.layer_id as layer_id']); + } + + public function builder(): Builder + { + $user = Auth::user(); + abort_unless( + $user->can('manage all') || + Project::whereKey($this->projectId)->whereHas('users', fn ($q) => $q->where('user_id', $user->id))->exists(), + 403 + ); + + return Feature::query() + ->whereHas('layer.phase', fn ($q) => $q->where('project_id', $this->projectId)) + ->with(['layer.phase']); + } + + public function columns(): array + { + return [ + Column::make('Elemento', 'name') + ->sortable() + ->searchable() + ->format(fn ($value) => '' . e($value) . '') + ->html(), + + Column::make('Capa') + ->label(fn ($row) => e($row->layer?->name ?? '—')), + + Column::make('Fase') + ->label(fn ($row) => e($row->layer?->phase?->name ?? '—')), + + Column::make('Progreso', 'progress') + ->sortable() + ->format(function ($value) { + $cls = $value >= 100 ? 'badge-success' : ($value > 0 ? 'badge-warning' : 'badge-ghost'); + return '' . (int) $value . '%'; + }) + ->html(), + + Column::make('Acciones') + ->label(fn ($row) => + '
+ +
') + ->html(), + ]; + } + + public function filters(): array + { + 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, + }; + }), + ]; + } +} diff --git a/app/Livewire/Projects/InspectionTable.php b/app/Livewire/Projects/InspectionTable.php new file mode 100644 index 0000000..dcf83c2 --- /dev/null +++ b/app/Livewire/Projects/InspectionTable.php @@ -0,0 +1,83 @@ +setPrimaryKey('id') + ->setDefaultSort('inspections.created_at', 'desc') + ->setSortingPillsEnabled(false) + ->setAdditionalSelects([ + 'inspections.id as id', + 'inspections.created_at as created_at', + 'inspections.feature_id as feature_id', + 'inspections.template_id as template_id', + 'inspections.user_id as user_id', + ]); + } + + public function builder(): Builder + { + $user = Auth::user(); + abort_unless( + $user->can('manage all') || + Project::whereKey($this->projectId)->whereHas('users', fn ($q) => $q->where('user_id', $user->id))->exists(), + 403 + ); + + return Inspection::query() + ->where('inspections.project_id', $this->projectId) + ->with(['feature', 'template', 'user']); + } + + public function columns(): array + { + return [ + Column::make('Fecha', 'created_at') + ->sortable() + ->format(fn ($value, $row) => $row->created_at?->format('d/m/Y') ?? '—'), + + Column::make('Elemento') + ->label(fn ($row) => $row->feature?->name + ? '' . e($row->feature->name) . '' + : '') + ->html(), + + Column::make('Plantilla') + ->label(fn ($row) => e($row->template?->name ?? '—')), + + Column::make('Resultado', 'result') + ->sortable() + ->format(fn ($value) => $value + ? '' . e($value) . '' + : '') + ->html(), + + Column::make('Usuario') + ->label(fn ($row) => e($row->user?->name ?? '—')), + + Column::make('Acciones') + ->label(fn ($row) => + '
+ +
') + ->html(), + ]; + } +} diff --git a/app/Livewire/Projects/ProjectMap.php b/app/Livewire/Projects/ProjectMap.php index 8ca2fa3..dd6bfb3 100644 --- a/app/Livewire/Projects/ProjectMap.php +++ b/app/Livewire/Projects/ProjectMap.php @@ -3,6 +3,7 @@ namespace App\Livewire\Projects; use Livewire\Component; +use Livewire\Attributes\On; use Illuminate\Support\Facades\Auth; use App\Models\Project; use App\Models\Phase; @@ -207,6 +208,7 @@ class ProjectMap extends Component } } + #[On('map-select-feature')] public function selectFeature($featureId) { $this->selectedFeature = null; @@ -357,6 +359,7 @@ class ProjectMap extends Component // ─── Inspection viewer ─────────────────────────────────────────────────────── + #[On('map-view-inspection')] public function viewInspection($id) { $ins = Inspection::where('project_id', $this->project->id) diff --git a/resources/views/livewire/projects/project-map.blade.php b/resources/views/livewire/projects/project-map.blade.php index 8e74be5..b7cc607 100644 --- a/resources/views/livewire/projects/project-map.blade.php +++ b/resources/views/livewire/projects/project-map.blade.php @@ -276,79 +276,11 @@ @endif @elseif($activeTab === 'features') - - @if($allFeatures->isNotEmpty()) -
- - - - - - - - - - - - @foreach($allFeatures as $feature) - - - - - - - - @endforeach - -
{{ __('Feature') }}{{ __('Layer') }}{{ __('Phase') }}{{ __('Progress') }}
{{ $feature->name }}{{ $feature->layer?->name ?? '—' }}{{ $feature->layer?->phase?->name ?? '—' }} - {{ $feature->progress }}% - - -
-
- @else -
- -

{{ __('No elements in this project') }}

-
- @endif + + @elseif($activeTab === 'inspections') - - @if($allInspections->isNotEmpty()) -
- - - - - - - - - - - - @foreach($allInspections as $inspection) - - - - - - - - @endforeach - -
{{ __('Date') }}{{ __('Feature') }}{{ __('Template') }}{{ __('User') }}
{{ $inspection->created_at?->format('d/m/Y') ?? '—' }}{{ $inspection->feature?->name ?? '—' }}{{ $inspection->template?->name ?? '—' }}{{ $inspection->user?->name ?? '—' }} - -
-
- @else -
- -

{{ __('No inspections registered') }}

-
- @endif + + @elseif($activeTab === 'issues') @livewire('issues.issue-manager', ['project' => $project], key('issues-tab-' . $project->id)) diff --git a/tests/Feature/MapTablesTest.php b/tests/Feature/MapTablesTest.php new file mode 100644 index 0000000..00ef6c9 --- /dev/null +++ b/tests/Feature/MapTablesTest.php @@ -0,0 +1,81 @@ +user = User::factory()->create(); + $this->project = Project::create([ + 'reference' => 'MAP-1', 'name' => 'Proyecto Mapa', 'address' => 'x', + 'lat' => 40.0, 'lng' => -3.0, 'start_date' => now()->toDateString(), + 'end_date_estimated' => now()->addMonth()->toDateString(), + 'status' => 'in_progress', 'created_by' => $this->user->id, + ]); + $this->project->users()->attach($this->user->id, ['role_in_project' => 'supervisor']); + + $phase = Phase::create(['project_id' => $this->project->id, 'name' => 'Fase 1', 'order' => 1, 'color' => '#3b82f6', 'progress_percent' => 0]); + $layer = Layer::create(['project_id' => $this->project->id, 'phase_id' => $phase->id, 'name' => 'Capa A', 'color' => '#10b981', 'uploaded_by' => $this->user->id]); + $this->feature = Feature::create([ + 'layer_id' => $layer->id, 'name' => 'Pilar 12', + 'geometry' => ['type' => 'Point', 'coordinates' => [-3.0, 40.0]], + 'progress' => 50, 'status' => 'in_progress', + ]); + } + + public function test_feature_table_lists_project_features(): void + { + Livewire::actingAs($this->user) + ->test(FeatureTable::class, ['projectId' => $this->project->id]) + ->assertOk() + ->assertSee('Pilar 12') + ->assertSee('Capa A'); + } + + public function test_feature_table_forbidden_for_non_member(): void + { + $outsider = User::factory()->create(); + Livewire::actingAs($outsider) + ->test(FeatureTable::class, ['projectId' => $this->project->id]) + ->assertForbidden(); + } + + public function test_inspection_table_lists_project_inspections(): void + { + Inspection::create([ + 'project_id' => $this->project->id, + 'layer_id' => $this->feature->layer_id, + 'feature_id' => $this->feature->id, + 'user_id' => $this->user->id, + 'data' => ['ok' => true], + 'status' => 'completed', + 'result' => 'pass', + ]); + + Livewire::actingAs($this->user) + ->test(InspectionTable::class, ['projectId' => $this->project->id]) + ->assertOk() + ->assertSee('Pilar 12') + ->assertSee('pass'); + } +}