217 lines
7.8 KiB
PHP
217 lines
7.8 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Livewire\Projects\Traits;
|
||
|
|
|
||
|
|
use App\Models\Feature;
|
||
|
|
use App\Models\InspectionTemplate;
|
||
|
|
use App\Models\Issue;
|
||
|
|
use App\Notifications\InspectionCompletedNotification;
|
||
|
|
use App\Notifications\InspectionDeletedNotification;
|
||
|
|
use Illuminate\Support\Facades\Auth;
|
||
|
|
use Illuminate\Support\Str;
|
||
|
|
use Livewire\Attributes\On;
|
||
|
|
|
||
|
|
trait MapFeatures
|
||
|
|
{
|
||
|
|
// Editor properties
|
||
|
|
public $selectedFeature = null;
|
||
|
|
|
||
|
|
public $selectedPhaseId = null;
|
||
|
|
|
||
|
|
public $editProgress = 0;
|
||
|
|
|
||
|
|
public $editComment = '';
|
||
|
|
|
||
|
|
public $editResponsible = '';
|
||
|
|
|
||
|
|
public $editPhotos = [];
|
||
|
|
|
||
|
|
public $formFullscreen = false;
|
||
|
|
|
||
|
|
// Tab management
|
||
|
|
public $activeTab = 'edit';
|
||
|
|
|
||
|
|
public $allFeatures;
|
||
|
|
|
||
|
|
public $allInspections;
|
||
|
|
|
||
|
|
// Imágenes en mapa
|
||
|
|
public $showFeatureImages = false;
|
||
|
|
|
||
|
|
public $featureImageMarkers = [];
|
||
|
|
|
||
|
|
// Feature status ─────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function editFeatureStatus($status)
|
||
|
|
{
|
||
|
|
if (! $this->selectedFeature) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
$feature = Feature::with('layer.phase')->findOrFail($this->selectedFeature->id);
|
||
|
|
if ($feature->layer->phase->project_id !== $this->project->id) {
|
||
|
|
abort(403);
|
||
|
|
}
|
||
|
|
$feature->status = $status;
|
||
|
|
if ($status === 'completed') {
|
||
|
|
$feature->progress = 100;
|
||
|
|
}
|
||
|
|
if ($status === 'planned') {
|
||
|
|
$feature->progress = 0;
|
||
|
|
}
|
||
|
|
$feature->save();
|
||
|
|
$this->selectedFeature = $feature;
|
||
|
|
$this->editProgress = $feature->progress;
|
||
|
|
$this->allFeatures = $this->allFeatures->map(fn ($f) => $f->id === $feature->id ? $feature : $f);
|
||
|
|
$this->dispatch('featureStatusChanged', $feature->id, $feature->status, $feature->status_color);
|
||
|
|
$this->dispatch('notify', 'Estado actualizado');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function updateProgress($featureId, $newProgress, $comment = null)
|
||
|
|
{
|
||
|
|
$feature = Feature::with('layer.phase')->findOrFail($featureId);
|
||
|
|
$user = Auth::user();
|
||
|
|
if (! $user->can('update progress')) {
|
||
|
|
$this->dispatch('notify', 'Sin permisos');
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if ($feature->layer->phase->project_id !== $this->project->id) {
|
||
|
|
abort(403);
|
||
|
|
}
|
||
|
|
$feature->progress = min(100, max(0, $newProgress));
|
||
|
|
$feature->save();
|
||
|
|
$phase = $feature->layer->phase;
|
||
|
|
$phase->progress_percent = $phase->features()->avg('progress') ?: 0;
|
||
|
|
$phase->save();
|
||
|
|
$phase->progressUpdates()->create([
|
||
|
|
'user_id' => $user->id,
|
||
|
|
'progress_percent' => $phase->progress_percent,
|
||
|
|
'comment' => $comment,
|
||
|
|
]);
|
||
|
|
$this->dispatch('progressUpdated', $featureId, $feature->progress);
|
||
|
|
$this->dispatch('notify', 'Progreso actualizado');
|
||
|
|
if ($this->selectedFeature && $this->selectedFeature->id == $featureId) {
|
||
|
|
$this->selectedFeature->progress = $feature->progress;
|
||
|
|
$this->editProgress = $feature->progress;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[On('map-select-feature')]
|
||
|
|
public function selectFeature($featureId)
|
||
|
|
{
|
||
|
|
\Log::info('[ProjectMap] map-select-feature received', ['payload' => $featureId, 'type' => gettype($featureId)]);
|
||
|
|
|
||
|
|
// Handle both formats: direct ID or { featureId: X }
|
||
|
|
if (is_array($featureId) && isset($featureId['featureId'])) {
|
||
|
|
$featureId = $featureId['featureId'];
|
||
|
|
\Log::info('[ProjectMap] Extracted featureId from array', ['featureId' => $featureId]);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->selectedFeature = null;
|
||
|
|
$feature = Feature::with(['template', 'layer.phase'])->find($featureId);
|
||
|
|
if (! $feature) {
|
||
|
|
\Log::warning('[ProjectMap] Feature not found', ['featureId' => $featureId]);
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if ($feature->layer->phase->project_id !== $this->project->id) {
|
||
|
|
\Log::warning('[ProjectMap] Feature not in project', ['featureId' => $featureId, 'projectId' => $this->project->id]);
|
||
|
|
abort(403);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->selectedFeature = $feature;
|
||
|
|
$this->selectedPhaseId = $feature->layer->phase_id;
|
||
|
|
$this->editProgress = $feature->progress;
|
||
|
|
$this->editResponsible = $feature->responsible ?? '';
|
||
|
|
$this->editPhotos = $feature->properties['photos'] ?? [];
|
||
|
|
$this->selectedTemplateId = $feature->template_id;
|
||
|
|
$this->activeTab = 'edit';
|
||
|
|
|
||
|
|
$this->loadInspectionHistory();
|
||
|
|
$this->resetInspectionForm();
|
||
|
|
|
||
|
|
$this->dispatch('featureSelected', $featureId, $feature->name);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function saveFeatureProgress()
|
||
|
|
{
|
||
|
|
if (! $this->selectedFeature) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
$feature = Feature::with('layer.phase')->findOrFail($this->selectedFeature->id);
|
||
|
|
if ($feature->layer->phase->project_id !== $this->project->id) {
|
||
|
|
abort(403);
|
||
|
|
}
|
||
|
|
$feature->progress = min(100, max(0, (int) $this->editProgress));
|
||
|
|
$feature->responsible = $this->editResponsible;
|
||
|
|
$feature->save();
|
||
|
|
$this->selectedFeature = $feature;
|
||
|
|
$phase = $feature->layer->phase;
|
||
|
|
$phase->progress_percent = $phase->features()->avg('progress') ?: 0;
|
||
|
|
$phase->save();
|
||
|
|
$this->dispatch('progressUpdated', $phase->id, $phase->progress_percent);
|
||
|
|
$this->dispatch('notify', 'Progreso guardado');
|
||
|
|
}
|
||
|
|
|
||
|
|
// ─── Feature Images ────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function toggleFeatureImages()
|
||
|
|
{
|
||
|
|
$this->showFeatureImages = ! $this->showFeatureImages;
|
||
|
|
$this->loadFeatureImageMarkers();
|
||
|
|
$this->dispatch('featureImagesToggled', $this->showFeatureImages, $this->featureImageMarkers);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function loadFeatureImageMarkers()
|
||
|
|
{
|
||
|
|
if (! $this->showFeatureImages) {
|
||
|
|
$this->featureImageMarkers = [];
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
$markers = [];
|
||
|
|
foreach ($this->phases as $phase) {
|
||
|
|
foreach ($phase->layers as $layer) {
|
||
|
|
foreach ($layer->features as $feature) {
|
||
|
|
$image = $feature->images->first();
|
||
|
|
if ($image) {
|
||
|
|
$geo = $feature->geometry;
|
||
|
|
$coords = null;
|
||
|
|
if ($geo && isset($geo['coordinates'])) {
|
||
|
|
if ($geo['type'] === 'Point') {
|
||
|
|
$coords = ['lat' => $geo['coordinates'][1], 'lng' => $geo['coordinates'][0]];
|
||
|
|
} elseif (in_array($geo['type'], ['Polygon', 'MultiPolygon'])) {
|
||
|
|
$firstRing = $geo['type'] === 'Polygon' ? $geo['coordinates'][0] : $geo['coordinates'][0][0];
|
||
|
|
$coords = ['lat' => $firstRing[0][1], 'lng' => $firstRing[0][0]];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ($coords && $coords['lat'] && $coords['lng']) {
|
||
|
|
$markers[] = [
|
||
|
|
'feature_id' => $feature->id,
|
||
|
|
'name' => $feature->name,
|
||
|
|
'lat' => $coords['lat'],
|
||
|
|
'lng' => $coords['lng'],
|
||
|
|
'image_url' => $image->url,
|
||
|
|
'image_name' => $image->name,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$this->featureImageMarkers = $markers;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function toggleFullscreen()
|
||
|
|
{
|
||
|
|
$this->formFullscreen = ! $this->formFullscreen;
|
||
|
|
if (! $this->formFullscreen) {
|
||
|
|
$this->dispatch('mapResize');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setActiveTab($tab)
|
||
|
|
{
|
||
|
|
$this->activeTab = $tab;
|
||
|
|
}
|
||
|
|
}
|