feat(project-map): clearer editor tabs + per-phase and per-layer visibility
1. Editor tabs restyled as spaced DaisyUI buttons (btn-primary when active,
btn-ghost otherwise) — fixes cramped labels and missing active indicator.
2. Layer visibility now works at two levels:
- Phase toggle calls togglePhase() and shows/hides ALL its layers
(checked only when every layer of the phase is active)
- Each layer has its own independent toggle calling toggleLayer()
Map JS regrouped to build one Leaflet group per LAYER (keyed by layer id)
instead of per phase, so activeLayers (layer ids) drives visibility
correctly per layer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,23 +22,34 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
@foreach($phases as $phase)
|
@foreach($phases as $phase)
|
||||||
<div class="border rounded-lg p-2 {{ in_array($phase->id, $activeLayers) ? 'bg-base-200' : '' }}">
|
@php
|
||||||
|
$phaseLayerIds = $phase->layers->pluck('id')->map(fn($i) => (int) $i)->all();
|
||||||
|
$phaseAllActive = count($phaseLayerIds) > 0 && collect($phaseLayerIds)->every(fn($i) => in_array($i, $activeLayers));
|
||||||
|
@endphp
|
||||||
|
<div class="border rounded-lg p-2 {{ $phaseAllActive ? 'bg-base-200' : '' }}">
|
||||||
|
{{-- Fase: el toggle muestra/oculta TODAS sus capas --}}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
wire:change="toggleLayer({{ $phase->id }})"
|
wire:change="togglePhase({{ $phase->id }})"
|
||||||
@if(in_array($phase->id, $activeLayers)) checked @endif
|
@if($phaseAllActive) checked @endif
|
||||||
class="toggle toggle-xs toggle-primary">
|
class="toggle toggle-xs toggle-primary"
|
||||||
|
title="{{ __('Show/hide all layers of this phase') }}">
|
||||||
<span style="color: {{ $phase->color }};" class="text-lg">⬤</span>
|
<span style="color: {{ $phase->color }};" class="text-lg">⬤</span>
|
||||||
<span class="flex-1 font-medium text-sm truncate">{{ $phase->name }}</span>
|
<span class="flex-1 font-medium text-sm truncate">{{ $phase->name }}</span>
|
||||||
<span class="badge badge-sm {{ $phase->progress_percent >= 100 ? 'badge-success' : 'badge-ghost' }}">{{ $phase->progress_percent }}</span>
|
<span class="badge badge-sm {{ $phase->progress_percent >= 100 ? 'badge-success' : 'badge-ghost' }}">{{ $phase->progress_percent }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Capas de esta fase --}}
|
{{-- Capas de esta fase: cada una con su propio toggle independiente --}}
|
||||||
@if($phase->layers->isNotEmpty())
|
@if($phase->layers->isNotEmpty())
|
||||||
<div class="ml-7 mt-1 space-y-1">
|
<div class="ml-7 mt-1 space-y-1">
|
||||||
@foreach($phase->layers as $layer)
|
@foreach($phase->layers as $layer)
|
||||||
<div class="flex items-center gap-1 text-xs text-gray-600">
|
<div class="flex items-center gap-2 text-xs text-gray-600">
|
||||||
<span class="w-2 h-2 rounded-full inline-block" style="background: {{ $layer->color ?? '#ccc' }}"></span>
|
<input type="checkbox"
|
||||||
|
wire:change="toggleLayer({{ $layer->id }})"
|
||||||
|
@if(in_array((int) $layer->id, $activeLayers)) checked @endif
|
||||||
|
class="toggle toggle-xs toggle-primary"
|
||||||
|
title="{{ __('Show/hide layer') }}">
|
||||||
|
<span class="w-2 h-2 rounded-full inline-block shrink-0" style="background: {{ $layer->color ?? '#ccc' }}"></span>
|
||||||
<span class="flex-1 truncate">{{ $layer->name }}</span>
|
<span class="flex-1 truncate">{{ $layer->name }}</span>
|
||||||
<span class="badge badge-xs">{{ $layer->features_count ?? $layer->features->count() }} {{ __('elem.') }}</span>
|
<span class="badge badge-xs">{{ $layer->features_count ?? $layer->features->count() }} {{ __('elem.') }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,11 +102,11 @@
|
|||||||
<div class="card-body overflow-y-auto flex-1">
|
<div class="card-body overflow-y-auto flex-1">
|
||||||
<div class="flex justify-between items-center gap-2 mb-4">
|
<div class="flex justify-between items-center gap-2 mb-4">
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="tabs tabs-boxed flex-wrap">
|
<div class="flex flex-wrap gap-1">
|
||||||
<button wire:click="setActiveTab('edit')" class="tab {{ $activeTab === 'edit' ? 'tab-active' : '' }}">{{ __('Edit') }}</button>
|
<button wire:click="setActiveTab('edit')" class="btn btn-sm {{ $activeTab === 'edit' ? 'btn-primary' : 'btn-ghost' }}">{{ __('Edit') }}</button>
|
||||||
<button wire:click="setActiveTab('features')" class="tab {{ $activeTab === 'features' ? 'tab-active' : '' }}">{{ __('Features') }}</button>
|
<button wire:click="setActiveTab('features')" class="btn btn-sm {{ $activeTab === 'features' ? 'btn-primary' : 'btn-ghost' }}">{{ __('Features') }}</button>
|
||||||
<button wire:click="setActiveTab('inspections')" class="tab {{ $activeTab === 'inspections' ? 'tab-active' : '' }}">{{ __('Inspections') }}</button>
|
<button wire:click="setActiveTab('inspections')" class="btn btn-sm {{ $activeTab === 'inspections' ? 'btn-primary' : 'btn-ghost' }}">{{ __('Inspections') }}</button>
|
||||||
<button wire:click="setActiveTab('issues')" class="tab {{ $activeTab === 'issues' ? 'tab-active' : '' }} gap-1">
|
<button wire:click="setActiveTab('issues')" class="btn btn-sm gap-1 {{ $activeTab === 'issues' ? 'btn-primary' : 'btn-ghost' }}">
|
||||||
{{ __('Issues') }}
|
{{ __('Issues') }}
|
||||||
@if($openIssuesCount > 0)
|
@if($openIssuesCount > 0)
|
||||||
<span class="badge badge-error badge-xs">{{ $openIssuesCount }}</span>
|
<span class="badge badge-error badge-xs">{{ $openIssuesCount }}</span>
|
||||||
@@ -443,53 +454,54 @@
|
|||||||
baseLayers['{{ __('Streets') }}'].addTo(map);
|
baseLayers['{{ __('Streets') }}'].addTo(map);
|
||||||
L.control.layers(baseLayers, null, { position: 'topleft' }).addTo(map);
|
L.control.layers(baseLayers, null, { position: 'topleft' }).addTo(map);
|
||||||
|
|
||||||
// Cargar fases y sus features
|
// Cargar capas y sus features (cada capa = un grupo Leaflet independiente)
|
||||||
@foreach($phases as $phase)
|
@foreach($phases as $phase)
|
||||||
@php
|
@foreach($phase->layers as $layer)
|
||||||
$phaseFeatures = $phase->features()->with('layer.phase')->get();
|
@php
|
||||||
$fc = [
|
$fc = [
|
||||||
'type' => 'FeatureCollection',
|
'type' => 'FeatureCollection',
|
||||||
'features' => $phaseFeatures->map(function($f) {
|
'features' => $layer->features->map(function($f) {
|
||||||
return [
|
return [
|
||||||
'type' => 'Feature',
|
'type' => 'Feature',
|
||||||
'id' => $f->id,
|
'id' => $f->id,
|
||||||
'geometry' => $f->geometry,
|
'geometry' => $f->geometry,
|
||||||
'properties' => array_merge($f->properties ?? [], [
|
'properties' => array_merge($f->properties ?? [], [
|
||||||
'name' => $f->name,
|
'name' => $f->name,
|
||||||
'progress' => $f->progress,
|
'progress' => $f->progress,
|
||||||
'responsible' => $f->responsible,
|
'responsible' => $f->responsible,
|
||||||
'template_id' => $f->template_id,
|
'template_id' => $f->template_id,
|
||||||
'_feature_id' => $f->id,
|
'_feature_id' => $f->id,
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
})->values()->toArray()
|
})->values()->toArray()
|
||||||
];
|
];
|
||||||
@endphp
|
@endphp
|
||||||
(function() {
|
(function() {
|
||||||
const data = @json($fc);
|
const data = @json($fc);
|
||||||
if (data && data.features && data.features.length > 0) {
|
if (data && data.features && data.features.length > 0) {
|
||||||
const phaseLayer = L.geoJSON(data, {
|
const layerGroup = L.geoJSON(data, {
|
||||||
style: { color: '{{ $phase->color }}', weight: 3, opacity: 0.8, fillOpacity: 0.3 },
|
style: { color: '{{ $layer->color ?? $phase->color }}', weight: 3, opacity: 0.8, fillOpacity: 0.3 },
|
||||||
onEachFeature: function(feature, layer) {
|
onEachFeature: function(feature, lyr) {
|
||||||
const props = feature.properties || {};
|
const props = feature.properties || {};
|
||||||
const featId = props._feature_id || feature.id;
|
const featId = props._feature_id || feature.id;
|
||||||
const safeName = escapeHtml(props.name || '{{ __('Feature') }}');
|
const safeName = escapeHtml(props.name || '{{ __('Feature') }}');
|
||||||
const safeProgress = escapeHtml(props.progress || 0);
|
const safeProgress = escapeHtml(props.progress || 0);
|
||||||
const safeResponsible = escapeHtml(props.responsible || '-');
|
const safeResponsible = escapeHtml(props.responsible || '-');
|
||||||
let content = `<b>${safeName}</b><br>
|
let content = `<b>${safeName}</b><br>
|
||||||
{{ __('Progress') }}: ${safeProgress}%<br>
|
{{ __('Progress') }}: ${safeProgress}%<br>
|
||||||
{{ __('Responsible') }}: ${safeResponsible}<br>
|
{{ __('Responsible') }}: ${safeResponsible}<br>
|
||||||
<button class="btn btn-xs btn-primary mt-1" onclick="selectFeature('${featId}')">✏️ {{ __('Edit') }}</button>`;
|
<button class="btn btn-xs btn-primary mt-1" onclick="selectFeature('${featId}')">{{ __('Edit') }}</button>`;
|
||||||
layer.bindPopup(content);
|
lyr.bindPopup(content);
|
||||||
layer.on('click', function() { selectFeature(featId); });
|
lyr.on('click', function() { selectFeature(featId); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
layers[{{ $phase->id }}] = phaseLayer;
|
layers[{{ $layer->id }}] = layerGroup;
|
||||||
@if(in_array($phase->id, $activeLayers))
|
@if(in_array((int) $layer->id, $activeLayers))
|
||||||
phaseLayer.addTo(map);
|
layerGroup.addTo(map);
|
||||||
@endif
|
@endif
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@endforeach
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
// Initialize combined bounds
|
// Initialize combined bounds
|
||||||
|
|||||||
Reference in New Issue
Block a user