feat: implementar modal gestión capas y limpieza de stubs duplicados
This commit is contained in:
@@ -1,210 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Livewire\Attributes\Layout;
|
||||
use App\Models\Project;
|
||||
use App\Models\Phase;
|
||||
use App\Models\Layer;
|
||||
use App\Services\SpatialFileConverter;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
#[Layout('layouts.app')]
|
||||
class LayerManager extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public Project $project;
|
||||
public Phase $phase;
|
||||
public $layers;
|
||||
public $selectedLayer = null;
|
||||
public $visibleLayers = []; // IDs de capas visibles
|
||||
|
||||
public $uploadFile = null;
|
||||
public $layerName = '';
|
||||
public $layerColor = '#3b82f6';
|
||||
public $manualGeojson = null;
|
||||
public $drawingMode = false;
|
||||
|
||||
protected $rules = [
|
||||
'uploadFile' => 'required|file|mimes:geojson,kmz,kml,shp,dwg,zip|max:51200',
|
||||
'layerName' => 'required|string|max:255',
|
||||
'layerColor' => 'nullable|string|size:7',
|
||||
];
|
||||
|
||||
public function mount(Project $project, Phase $phase)
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->phase = $phase;
|
||||
$this->loadLayers();
|
||||
if ($this->phase->project_id !== $this->project->id) {
|
||||
abort(404);
|
||||
}
|
||||
// Por defecto todas visibles
|
||||
$this->visibleLayers = $this->layers->pluck('id')->toArray();
|
||||
$this->emitInitialLayersData();
|
||||
}
|
||||
|
||||
public function loadLayers()
|
||||
{
|
||||
$this->layers = Layer::where('phase_id', $this->phase->id)->latest()->get();
|
||||
// Eliminar de visibles las que ya no existen
|
||||
$this->visibleLayers = array_intersect($this->visibleLayers, $this->layers->pluck('id')->toArray());
|
||||
}
|
||||
|
||||
private function emitInitialLayersData()
|
||||
{
|
||||
$layersData = $this->layers->map(function($layer) {
|
||||
return [
|
||||
'id' => $layer->id,
|
||||
'geojson' => $layer->geojson_data,
|
||||
'color' => $layer->geojson_data['style']['color'] ?? '#3b82f6',
|
||||
];
|
||||
});
|
||||
$this->dispatch('initialLayersData', [
|
||||
'layers' => $layersData,
|
||||
'visibleLayers' => $this->visibleLayers,
|
||||
'selectedLayerId' => $this->selectedLayer?->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleLayerVisibility($layerId)
|
||||
{
|
||||
if ($this->selectedLayer && $this->selectedLayer->id == $layerId) {
|
||||
session()->flash('info', 'No puedes ocultar la capa que estás editando.');
|
||||
return;
|
||||
}
|
||||
if (in_array($layerId, $this->visibleLayers)) {
|
||||
$this->visibleLayers = array_diff($this->visibleLayers, [$layerId]);
|
||||
} else {
|
||||
$this->visibleLayers[] = $layerId;
|
||||
}
|
||||
$this->dispatch('visibilityChanged', $this->visibleLayers);
|
||||
}
|
||||
|
||||
public function selectLayer($layerId)
|
||||
{
|
||||
$this->selectedLayer = Layer::find($layerId);
|
||||
if (!$this->selectedLayer) return;
|
||||
// Asegurar que la capa seleccionada está visible
|
||||
if (!in_array($layerId, $this->visibleLayers)) {
|
||||
$this->visibleLayers[] = $layerId;
|
||||
$this->dispatch('visibilityChanged', $this->visibleLayers);
|
||||
}
|
||||
$geojson = $this->selectedLayer->geojson_data;
|
||||
$this->dispatch('layerSelectedForEdit', [
|
||||
'layerId' => $layerId,
|
||||
'geojson' => $geojson,
|
||||
'color' => $geojson['style']['color'] ?? '#3b82f6',
|
||||
]);
|
||||
session()->flash('info', 'Editando capa: ' . $this->selectedLayer->name);
|
||||
}
|
||||
|
||||
public function importFile()
|
||||
{
|
||||
$this->validate();
|
||||
$user = Auth::user();
|
||||
if (!$user->can('upload layers') && !$user->hasRole('Admin')) {
|
||||
session()->flash('error', 'Sin permisos.');
|
||||
return;
|
||||
}
|
||||
|
||||
$projectDir = "uploads/projects/{$this->project->id}/layers";
|
||||
$originalPath = $this->uploadFile->store($projectDir, 'public');
|
||||
$geojson = SpatialFileConverter::convertToGeoJson($this->uploadFile);
|
||||
if (!$geojson) {
|
||||
session()->flash('error', 'Conversión fallida.');
|
||||
return;
|
||||
}
|
||||
$geojson['style'] = ['color' => $this->layerColor ?: '#3b82f6'];
|
||||
|
||||
$layer = Layer::create([
|
||||
'project_id' => $this->project->id,
|
||||
'phase_id' => $this->phase->id,
|
||||
'name' => $this->layerName,
|
||||
'geojson_data' => $geojson,
|
||||
'original_file' => $originalPath,
|
||||
'uploaded_by' => $user->id,
|
||||
]);
|
||||
|
||||
$this->loadLayers();
|
||||
$this->visibleLayers[] = $layer->id;
|
||||
$this->reset(['uploadFile', 'layerName']);
|
||||
$this->emitInitialLayersData();
|
||||
session()->flash('message', 'Capa importada.');
|
||||
}
|
||||
|
||||
public function createEmptyLayer()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$emptyGeojson = [
|
||||
'type' => 'FeatureCollection',
|
||||
'features' => [],
|
||||
'style' => ['color' => $this->layerColor ?: '#3b82f6']
|
||||
];
|
||||
$layer = Layer::create([
|
||||
'project_id' => $this->project->id,
|
||||
'phase_id' => $this->phase->id,
|
||||
'name' => $this->layerName ?: 'Nueva capa',
|
||||
'geojson_data' => $emptyGeojson,
|
||||
'original_file' => null,
|
||||
'uploaded_by' => $user->id,
|
||||
]);
|
||||
$this->loadLayers();
|
||||
$this->visibleLayers[] = $layer->id;
|
||||
$this->selectLayer($layer->id);
|
||||
$this->emitInitialLayersData();
|
||||
session()->flash('message', 'Capa vacía creada y seleccionada.');
|
||||
}
|
||||
|
||||
public function saveManualGeojson($geojsonString)
|
||||
{
|
||||
if (!$this->selectedLayer) {
|
||||
session()->flash('error', 'No hay capa seleccionada.');
|
||||
return;
|
||||
}
|
||||
$geojson = json_decode($geojsonString, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
session()->flash('error', 'GeoJSON inválido.');
|
||||
return;
|
||||
}
|
||||
$geojson['style'] = ['color' => $this->layerColor ?: ($this->selectedLayer->geojson_data['style']['color'] ?? '#3b82f6')];
|
||||
$this->selectedLayer->geojson_data = $geojson;
|
||||
$this->selectedLayer->save();
|
||||
|
||||
$this->loadLayers(); // recargar por si acaso
|
||||
$this->selectLayer($this->selectedLayer->id);
|
||||
$this->emitInitialLayersData();
|
||||
session()->flash('message', 'Capa guardada.');
|
||||
}
|
||||
|
||||
public function deleteLayer($layerId)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user->can('delete layers') && !$user->hasRole('Admin')) abort(403);
|
||||
$layer = Layer::find($layerId);
|
||||
if (!$layer) return;
|
||||
if ($layer->original_file) Storage::disk('public')->delete($layer->original_file);
|
||||
$layer->delete();
|
||||
$this->loadLayers();
|
||||
if ($this->selectedLayer && $this->selectedLayer->id == $layerId) {
|
||||
$this->selectedLayer = null;
|
||||
}
|
||||
$this->emitInitialLayersData();
|
||||
session()->flash('message', 'Capa eliminada.');
|
||||
}
|
||||
|
||||
public function cancelEditing()
|
||||
{
|
||||
$this->selectedLayer = null;
|
||||
$this->dispatch('layerSelectedForEdit', null);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.layer-manager');
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,9 @@ class LayerManager extends Component
|
||||
private function emitInitialLayersData()
|
||||
{
|
||||
$layersData = $this->layers->map(function($layer) {
|
||||
// Usar el color guardado en BD o el color del formulario
|
||||
$color = $layer->color ?: ($this->layerColor ?: '#3b82f6');
|
||||
|
||||
// Construir FeatureCollection a partir de los features de esta capa
|
||||
$features = $layer->features->map(function($feature) {
|
||||
return [
|
||||
@@ -76,13 +79,13 @@ class LayerManager extends Component
|
||||
$geojson = [
|
||||
'type' => 'FeatureCollection',
|
||||
'features' => $features,
|
||||
'style' => ['color' => $this->layerColor ?: '#3b82f6'] // Podrías guardar el color en la tabla layers
|
||||
'style' => ['color' => $color]
|
||||
];
|
||||
|
||||
return [
|
||||
'id' => $layer->id,
|
||||
'geojson' => $geojson,
|
||||
'color' => $geojson['style']['color'],
|
||||
'color' => $color,
|
||||
];
|
||||
});
|
||||
|
||||
@@ -132,16 +135,17 @@ class LayerManager extends Component
|
||||
];
|
||||
})->values()->toArray();
|
||||
|
||||
$color = $this->selectedLayer->color ?: ($this->layerColor ?: '#3b82f6');
|
||||
$geojson = [
|
||||
'type' => 'FeatureCollection',
|
||||
'features' => $features,
|
||||
'style' => ['color' => $this->layerColor ?: '#3b82f6']
|
||||
'style' => ['color' => $color]
|
||||
];
|
||||
|
||||
$this->dispatch('layerSelectedForEdit', [
|
||||
'layerId' => $layerId,
|
||||
'geojson' => $geojson,
|
||||
'color' => $geojson['style']['color'],
|
||||
'color' => $color,
|
||||
]);
|
||||
session()->flash('info', 'Editando capa: ' . $this->selectedLayer->name);
|
||||
}
|
||||
@@ -192,13 +196,14 @@ class LayerManager extends Component
|
||||
return;
|
||||
}
|
||||
|
||||
$geojson['style'] = ['color' => $this->layerColor ?: '#3b82f6'];
|
||||
$layerColor = $this->layerColor ?: '#3b82f6';
|
||||
$geojson['style'] = ['color' => $layerColor];
|
||||
|
||||
$layer = Layer::create([
|
||||
'project_id' => $this->project->id,
|
||||
'phase_id' => $this->phase->id,
|
||||
'name' => $this->layerName,
|
||||
//'geojson_data' => $geojson,
|
||||
'color' => $layerColor,
|
||||
'original_file' => $originalPath,
|
||||
'uploaded_by' => $user->id,
|
||||
]);
|
||||
@@ -232,9 +237,9 @@ class LayerManager extends Component
|
||||
'project_id' => $this->project->id,
|
||||
'phase_id' => $this->phase->id,
|
||||
'name' => $this->layerName ?: 'Nueva capa',
|
||||
'color' => $this->layerColor ?: '#3b82f6',
|
||||
'original_file' => null,
|
||||
'uploaded_by' => $user->id,
|
||||
// Opcional: guarda el color en una columna 'color' de la tabla layers
|
||||
]);
|
||||
$this->loadLayers();
|
||||
$this->visibleLayers[] = $layer->id;
|
||||
|
||||
@@ -32,7 +32,7 @@ class ProjectList extends Component
|
||||
if ($this->statusFilter) {
|
||||
$query->where('status', $this->statusFilter);
|
||||
}
|
||||
$projects = $query->latest()->paginate(10);
|
||||
$projects = $query->with('phases')->latest()->paginate(10);
|
||||
return view('livewire.projects.project-list', ['projects' => $projects]);
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Phase;
|
||||
use App\Models\Inspection;
|
||||
use App\Models\InspectionTemplate;
|
||||
use App\Models\Feature;
|
||||
|
||||
class ProjectMap extends Component
|
||||
{
|
||||
public Project $project;
|
||||
public $phases;
|
||||
public $activeLayers = []; // Array of phase IDs to show
|
||||
|
||||
// Editor properties
|
||||
public $selectedFeature = null;
|
||||
public $selectedPhaseId = null;
|
||||
public $editProgress = 0;
|
||||
public $editComment = '';
|
||||
public $editResponsible = '';
|
||||
public $editPhotos = [];
|
||||
public $formFullscreen = false;
|
||||
|
||||
// Propiedades para templates e inspecciones
|
||||
public $templates = [];
|
||||
public $selectedTemplateId = null;
|
||||
public $inspectionFormData = [];
|
||||
public $inspectionHistory = [];
|
||||
public $showInspectionForm = true;
|
||||
|
||||
public function mount(Project $project)
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->phases = $project->phases()->with('currentLayer')->get();
|
||||
$this->activeLayers = $this->phases->pluck('id')->toArray();
|
||||
|
||||
$this->loadTemplates();
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
$feature->progress = min(100, max(0, $newProgress));
|
||||
$feature->save();
|
||||
|
||||
// Actualizar progreso de la fase (sumar promedio)
|
||||
$phase = Phase::find($feature->layer->phase_id);
|
||||
$phase->progress_percent = $phase->features()->avg('progress');
|
||||
$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');
|
||||
$this->editProgress = $feature->progress;
|
||||
}
|
||||
|
||||
public function loadTemplates()
|
||||
{
|
||||
$this->templates = InspectionTemplate::where('project_id', $this->project->id)->get();
|
||||
}
|
||||
|
||||
public function selectFeature($featureId, $featureProps)
|
||||
{
|
||||
$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->selectedTemplateId = $feature->template_id;
|
||||
|
||||
$this->loadInspectionHistory();
|
||||
$this->resetInspectionForm();
|
||||
|
||||
$this->dispatch('featureSelected', $featureId);
|
||||
}
|
||||
|
||||
public function saveFeatureProgress()
|
||||
{
|
||||
if (!$this->selectedFeature || !$this->selectedPhaseId) {
|
||||
return;
|
||||
}
|
||||
$this->updateProgress($this->selectedPhaseId, $this->editProgress, $this->editComment);
|
||||
$this->editComment = '';
|
||||
}
|
||||
|
||||
public function resetInspectionForm()
|
||||
{
|
||||
$this->inspectionFormData = [];
|
||||
if ($this->selectedTemplateId) {
|
||||
$template = InspectionTemplate::find($this->selectedTemplateId);
|
||||
if ($template) {
|
||||
foreach ($template->fields as $field) {
|
||||
$this->inspectionFormData[$field['name']] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function loadInspectionHistory()
|
||||
{
|
||||
if (!$this->selectedFeature || !$this->selectedPhaseId) {
|
||||
$this->inspectionHistory = [];
|
||||
return;
|
||||
}
|
||||
$layer = Phase::find($this->selectedPhaseId)->currentLayer;
|
||||
if ($layer) {
|
||||
$this->inspectionHistory = Inspection::where('layer_id', $layer->id)
|
||||
->where('feature_id', $this->selectedFeature['id'])
|
||||
->with('user', 'template')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
||||
public function saveInspection()
|
||||
{
|
||||
if (!$this->selectedFeature || !$this->selectedPhaseId) {
|
||||
return;
|
||||
}
|
||||
$this->validate([
|
||||
'selectedTemplateId' => 'required|exists:inspection_templates,id',
|
||||
]);
|
||||
$layer = Phase::find($this->selectedPhaseId)->currentLayer;
|
||||
if (!$layer) 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,
|
||||
]);
|
||||
|
||||
// Opcional: actualizar el progreso del elemento en el GeoJSON
|
||||
if (isset($this->inspectionFormData['progress'])) {
|
||||
$this->updateProgress($this->selectedPhaseId, $this->inspectionFormData['progress'], 'Inspección registrada');
|
||||
}
|
||||
|
||||
$this->loadInspectionHistory();
|
||||
$this->resetInspectionForm();
|
||||
$this->dispatch('notify', 'Inspección guardada');
|
||||
}
|
||||
|
||||
public function updateFeatureTemplate($templateId)
|
||||
{
|
||||
// Actualizar el template asociado al elemento (podrías guardarlo en la capa GeoJSON)
|
||||
if ($this->selectedFeature && $this->selectedPhaseId) {
|
||||
$layer = Phase::find($this->selectedPhaseId)->currentLayer;
|
||||
if ($layer) {
|
||||
$geojson = $layer->geojson_data;
|
||||
foreach ($geojson['features'] as &$feature) {
|
||||
if ($feature['properties']['id'] == $this->selectedFeature['id']) {
|
||||
$feature['properties']['template_id'] = $templateId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$layer->geojson_data = $geojson;
|
||||
$layer->save();
|
||||
}
|
||||
}
|
||||
$this->selectedTemplateId = $templateId;
|
||||
$this->resetInspectionForm();
|
||||
}
|
||||
|
||||
public function toggleFullscreen()
|
||||
{
|
||||
$this->formFullscreen = !$this->formFullscreen;
|
||||
if (!$this->formFullscreen) {
|
||||
$this->dispatch('mapResize');
|
||||
}
|
||||
}
|
||||
|
||||
// Añadir al final de la clase ProjectMap
|
||||
public function refreshTemplates()
|
||||
{
|
||||
$this->templates = InspectionTemplate::where('project_id', $this->project->id)->get();
|
||||
}
|
||||
|
||||
// Asignar template ahora actualiza el campo template_id
|
||||
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');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.projects.project-map', [
|
||||
'project' => $this->project,
|
||||
'phases' => $this->phases,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ class ProjectMap extends Component
|
||||
public Project $project;
|
||||
public $phases;
|
||||
public $activeLayers = [];
|
||||
public $showLayerModal = false;
|
||||
|
||||
// Editor properties
|
||||
public $selectedFeature = null; // será instancia de Feature
|
||||
@@ -57,6 +58,16 @@ class ProjectMap extends Component
|
||||
$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.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class Layer extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'project_id', 'phase_id', 'name', 'geojson_data', 'original_file', 'uploaded_by'
|
||||
'project_id', 'phase_id', 'name', 'color', 'geojson_data', 'original_file', 'uploaded_by'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
||||
Reference in New Issue
Block a user