project = $project; // Cargar fases con sus capas y los features de esas capas (para mostrarlos en el mapa) $this->phases = $project->phases()->with(['layers' => function ($q) { $q->withCount('features'); }, 'layers.features'])->get(); // Por defecto mostrar todas las capas activas (todas las fases que tengan alguna capa con features) $this->activeLayers = $this->phases->pluck('id')->toArray(); $this->loadTemplates(); } public function loadTemplates() { $this->templates = InspectionTemplate::where('project_id', $this->project->id)->get(); } public function toggleLayer($phaseId) { if (in_array($phaseId, $this->activeLayers)) { $this->activeLayers = array_diff($this->activeLayers, [$phaseId]); } else { $this->activeLayers[] = $phaseId; } $this->dispatch('layersUpdated', $this->activeLayers); } public function openLayerModal() { $this->showLayerModal = true; } public function closeLayerModal() { $this->showLayerModal = false; } /** * Actualizar el progreso de un Feature y recalcular el progreso de la fase. */ public function updateProgress($featureId, $newProgress, $comment = null) { $feature = Feature::findOrFail($featureId); $user = Auth::user(); if (!$user->can('update progress') && !$user->hasRole('Admin')) { $this->dispatch('notify', 'Sin permisos'); return; } $oldProgress = $feature->progress; $feature->progress = min(100, max(0, $newProgress)); $feature->save(); // Recalcular el progreso de la fase (promedio de todos sus features) $phase = Phase::find($feature->layer->phase_id); $phase->progress_percent = $phase->features()->avg('progress') ?: 0; $phase->save(); // Registrar la actualización en progress_updates $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'); // Si el feature seleccionado es el mismo, actualizar la propiedad local if ($this->selectedFeature && $this->selectedFeature->id == $featureId) { $this->selectedFeature->progress = $feature->progress; $this->editProgress = $feature->progress; } } /** * Seleccionar un Feature al hacer clic en el mapa. */ public function selectFeature($featureId) { $this->selectedFeature = null; $feature = Feature::with('template')->find($featureId); if (!$feature) return; $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->loadInspectionHistory(); $this->resetInspectionForm(); $this->dispatch('featureSelected', $featureId); } /** * Cargar el historial de inspecciones del feature seleccionado. */ public function loadInspectionHistory() { if (!$this->selectedFeature) { $this->inspectionHistory = []; return; } $this->inspectionHistory = Inspection::where('feature_id', $this->selectedFeature->id) ->with('user', 'template') ->orderBy('created_at', 'desc') ->get(); } /** * Reiniciar el formulario de inspección según el template seleccionado. */ public function resetInspectionForm() { $this->inspectionFormData = []; if ($this->selectedTemplateId) { $template = InspectionTemplate::find($this->selectedTemplateId); if ($template) { foreach ($template->fields as $field) { $this->inspectionFormData[$field['name']] = ''; } } } } /** * Guardar una nueva inspección. */ public function saveInspection() { if (!$this->selectedFeature || !$this->selectedTemplateId) { $this->dispatch('notify', 'Selecciona un elemento y un template.'); return; } $this->validate([ 'selectedTemplateId' => 'required|exists:inspection_templates,id', ]); $template = InspectionTemplate::find($this->selectedTemplateId); foreach ($template->fields as $field) { if (($field['required'] ?? false) && empty($this->inspectionFormData[$field['name']])) { $this->dispatch('notify', "El campo {$field['label']} es obligatorio."); return; } } $inspection = Inspection::create([ 'project_id' => $this->project->id, 'layer_id' => $this->selectedFeature->layer_id, 'feature_id' => $this->selectedFeature->id, 'template_id' => $this->selectedTemplateId, 'user_id' => auth()->id(), 'data' => $this->inspectionFormData, ]); // Si el template tiene un campo llamado 'progress', actualizar el progreso del feature if (isset($this->inspectionFormData['progress'])) { $this->updateProgress($this->selectedFeature->id, (int)$this->inspectionFormData['progress'], 'Inspección registrada'); } $this->loadInspectionHistory(); $this->resetInspectionForm(); $this->dispatch('notify', 'Inspección guardada correctamente'); } /** * Asignar un template al feature seleccionado. */ public function assignTemplateToFeature($templateId) { if (!$this->selectedFeature) return; $this->selectedFeature->template_id = $templateId; $this->selectedFeature->save(); $this->selectedTemplateId = $templateId; $this->resetInspectionForm(); $this->dispatch('notify', 'Template asignado al elemento'); } /** * Guardar progreso y responsable del feature seleccionado. */ public function saveFeatureProgress() { if (!$this->selectedFeature) return; $this->selectedFeature->progress = min(100, max(0, (int)$this->editProgress)); $this->selectedFeature->responsible = $this->editResponsible; $this->selectedFeature->save(); // Recalcular progreso de la fase $phase = Phase::find($this->selectedFeature->layer->phase_id); $phase->progress_percent = $phase->features()->avg('progress') ?: 0; $phase->save(); $this->dispatch('progressUpdated', $phase->id, $phase->progress_percent); $this->dispatch('notify', 'Progreso guardado'); } /** * Cuando cambia el template seleccionado, reiniciar el formulario. */ public function onTemplateChange() { $this->resetInspectionForm(); } /** * Toggle mostrar imágenes en el mapa. */ public function toggleFeatureImages() { $this->showFeatureImages = !$this->showFeatureImages; $this->loadFeatureImageMarkers(); $this->dispatch('featureImagesToggled', $this->showFeatureImages, $this->featureImageMarkers); } /** * Cargar marcadores de imágenes para el mapa. */ 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', 'LineString'])) { $coords = [ 'lat' => $geo['coordinates'][0][1] ?? null, 'lng' => $geo['coordinates'][0][0] ?? null, ]; } } 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 render() { return view('livewire.projects.project-map', [ 'project' => $this->project, 'phases' => $this->phases, ]); } }