feat: Add tabs to project map right column with element selector, inspection history and media viewer
This commit is contained in:
@@ -0,0 +1,122 @@
|
|||||||
|
{{-- Feature seleccionado --}}
|
||||||
|
@if($selectedFeature)
|
||||||
|
<div class="border rounded-lg p-3 mb-3 bg-base-200">
|
||||||
|
<h3 class="font-bold text-sm">{{ $selectedFeature->name ?? 'Elemento' }}</h3>
|
||||||
|
<p class="text-xs text-gray-500">Fase: {{ $selectedFeature->layer?->phase?->name ?? '—' }}</p>
|
||||||
|
<p class="text-xs text-gray-500">Capa: {{ $selectedFeature->layer?->name ?? '—' }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- {{ __("Progress") }} --}}
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label-text">{{ __("Progress") }}: {{ $editProgress }}%</label>
|
||||||
|
<input type="range" min="0" max="100" wire:model.live="editProgress" class="range range-primary range-sm" />
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span>0%</span><span>50%</span><span>100%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mb-3">
|
||||||
|
<label class="label-text">{{ __("Responsible") }}</label>
|
||||||
|
<input type="text" wire:model="editResponsible" class="input input-bordered input-sm" placeholder="Nombre" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button wire:click="saveFeatureProgress" class="btn btn-primary btn-sm w-full mb-3">
|
||||||
|
💾 {{ __("Save progress") }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{{-- Gestor de archivos del feature --}}
|
||||||
|
<details class="mb-3 border rounded-lg">
|
||||||
|
<summary class="text-xs font-semibold cursor-pointer p-2 bg-base-200 rounded-t-lg">
|
||||||
|
📎 {{ __("Files of element") }}
|
||||||
|
</summary>
|
||||||
|
<div class="p-2">
|
||||||
|
@livewire('media-manager', [
|
||||||
|
'mediableType' => 'App\\Models\\Feature',
|
||||||
|
'mediableId' => $selectedFeature->id,
|
||||||
|
], key('media-feature-' . $selectedFeature->id))
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
{{-- Templates / Inspecciones --}}
|
||||||
|
@if($templates->isNotEmpty())
|
||||||
|
<div class="divider text-xs">{{ __("Inspection") }}</div>
|
||||||
|
<div class="form-control mb-2">
|
||||||
|
<label class="label-text">Plantilla</label>
|
||||||
|
<select wire:model.live="selectedTemplateId" wire:change="onTemplateChange" class="select select-bordered select-sm">
|
||||||
|
<option value="">Seleccionar plantilla...</option>
|
||||||
|
@foreach($templates as $t)
|
||||||
|
<option value="{{ $t->id }}">{{ $t->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($selectedTemplateId && !empty($inspectionFormData))
|
||||||
|
@php $template = $templates->firstWhere('id', $selectedTemplateId); @endphp
|
||||||
|
@if($template)
|
||||||
|
@foreach($template->fields as $field)
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="label-text text-xs">{{ $field['label'] }} @if($field['required'] ?? false)<span class="text-error">*</span>@endif</label>
|
||||||
|
@switch($field['type'] ?? 'text')
|
||||||
|
@case('percentage')
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<input type="number" wire:model="inspectionFormData.{{ $field['name'] }}" min="0" max="100" class="input input-bordered input-sm w-16" />
|
||||||
|
<span class="text-xs">%</span>
|
||||||
|
<input type="range" min="0" max="100" wire:model.live="inspectionFormData.{{ $field['name'] }}" class="range range-primary range-xs flex-1" />
|
||||||
|
</div>
|
||||||
|
@break
|
||||||
|
@case('boolean')
|
||||||
|
<input type="checkbox" wire:model="inspectionFormData.{{ $field['name'] }}" class="checkbox checkbox-sm" />
|
||||||
|
@break
|
||||||
|
@case('select')
|
||||||
|
<select wire:model="inspectionFormData.{{ $field['name'] }}" class="select select-bordered select-sm w-full">
|
||||||
|
<option value="">Seleccionar</option>
|
||||||
|
@foreach(explode(',', $field['options'] ?? '') as $opt)
|
||||||
|
<option value="{{ trim($opt) }}">{{ trim($opt) }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@break
|
||||||
|
@case('textarea')
|
||||||
|
<textarea wire:model="inspectionFormData.{{ $field['name'] }}" rows="2" class="textarea textarea-bordered textarea-sm w-full"></textarea>
|
||||||
|
@break
|
||||||
|
@default
|
||||||
|
<input type="{{ $field['type'] ?? 'text' }}" wire:model="inspectionFormData.{{ $field['name'] }}" class="input input-bordered input-sm w-full" />
|
||||||
|
@endswitch
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
<button wire:click="saveInspection" class="btn btn-primary btn-xs w-full mt-1">{{ __("Register inspection") }}</button>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- {{ __("History") }} de inspecciones --}}
|
||||||
|
@if($inspectionHistory->isNotEmpty())
|
||||||
|
<div class="divider text-xs">{{ __("History") }}</div>
|
||||||
|
<div class="space-y-1 max-h-40 overflow-y-auto">
|
||||||
|
@foreach($inspectionHistory as $ins)
|
||||||
|
<div class="border rounded p-2 text-xs">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="font-medium">{{ $ins->template?->name ?? '{{ __("Inspection") }}' }}</span>
|
||||||
|
<span class="text-gray-400">{{ $ins->created_at->diffForHumans() }}</span>
|
||||||
|
</div>
|
||||||
|
@if($ins->user)<span class="text-gray-500">por {{ $ins->user->name }}</span>@endif
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<div role="alert" class="alert alert-vertical sm:alert-horizontal">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info h-6 w-6 shrink-0">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold">{{ __("No templates yet") }}</h3>
|
||||||
|
<div class="text-xs">{{ __("Create an inspection template") }}.</div>
|
||||||
|
</div>
|
||||||
|
<a href="{{ route('projects.templates', $project) }}" class="btn btn-primary btn-sm">{{ __("Create") }}</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<div class="text-center text-gray-400 py-8">
|
||||||
|
<p class="text-lg">👆</p>
|
||||||
|
<p>Haz clic en un elemento del mapa para editarlo</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Selector de feature -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="label-text">{{ __("Select Element") }}</label>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
wire:model="searchFeatures"
|
||||||
|
placeholder="{{ __("Search by name, layer or phase...") }}"
|
||||||
|
class="input input-bordered input-sm flex-1"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
wire:click="searchFeatures = ''"
|
||||||
|
class="btn btn-sm btn-outline"
|
||||||
|
>
|
||||||
|
{{ __("Clear") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de features filtrados -->
|
||||||
|
<div class="max-h-96 overflow-y-auto border rounded border-base-200">
|
||||||
|
@if($filteredFeatures->isEmpty())
|
||||||
|
<div class="text-center text-gray-400 py-4">
|
||||||
|
<p>{{ __("No elements found") }}</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="space-y-1">
|
||||||
|
@foreach($filteredFeatures as $feature)
|
||||||
|
<div
|
||||||
|
wire:click="selectFeatureFromList({{ $feature->id }})"
|
||||||
|
class="cursor-pointer px-3 py-2 border-b border-base-100 hover:bg-base-50"
|
||||||
|
:class="{'bg-base-200': selectedFeature && selectedFeature->id == {{$feature->id}}}"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<strong class="text-sm">{{ $feature->name }}</strong><br>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
{{ optional(optional($feature->layer)->phase)->name ?? '—' }} >
|
||||||
|
{{ optional($feature->layer)->name ?? '—' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-right">
|
||||||
|
<span class="badge badge-sm {{ $feature->progress >= 100 ? 'badge-success' : 'badge-ghost' }}">
|
||||||
|
{{ $feature->progress }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Historial de inspecciones --}}
|
||||||
|
@if($selectedFeature)
|
||||||
|
<div class="mt-6">
|
||||||
|
<h3 class="font-semibold text-lg mb-3">{{ __("Inspection History") }}</h3>
|
||||||
|
@if($inspectionHistory->isEmpty())
|
||||||
|
<div class="text-center text-gray-400 py-4">
|
||||||
|
<p>{{ __("No inspections yet for this element") }}</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||||
|
@foreach($inspectionHistory as $inspection)
|
||||||
|
<div
|
||||||
|
wire:click="openInspectionViewer({{ $inspection->id }})"
|
||||||
|
class="cursor-pointer border rounded p-3 hover:shadow-md transition-shadow"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<div class="flex-1">
|
||||||
|
<strong>{{ $inspection->template->name ?? 'Inspection' }}</strong><br>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
{{ $inspection->created_at->diffForHumans() }} {{ __("ago") }}
|
||||||
|
</span>
|
||||||
|
@if($inspection->user)
|
||||||
|
<span class="text-xs text-gray-400">{{ __("by") }} {{ $inspection->user->name }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<span class="badge badge-sm">{{ __("View") }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Selector de feature -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="label-text">{{ __("Select Element") }}</label>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
wire:model="searchFeatures"
|
||||||
|
placeholder="{{ __("Search by name, layer or phase...") }}"
|
||||||
|
class="input input-bordered input-sm flex-1"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
wire:click="searchFeatures = ''"
|
||||||
|
class="btn btn-sm btn-outline"
|
||||||
|
>
|
||||||
|
{{ __("Clear") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista de features filtrados -->
|
||||||
|
<div class="max-h-96 overflow-y-auto border rounded border-base-200">
|
||||||
|
@if($filteredFeatures->isEmpty())
|
||||||
|
<div class="text-center text-gray-400 py-4">
|
||||||
|
<p>{{ __("No elements found") }}</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="space-y-1">
|
||||||
|
@foreach($filteredFeatures as $feature)
|
||||||
|
<div
|
||||||
|
wire:click="selectFeatureFromList({{ $feature->id }})"
|
||||||
|
class="cursor-pointer px-3 py-2 border-b border-base-100 hover:bg-base-50"
|
||||||
|
:class="{'bg-base-200': selectedFeature && selectedFeature->id == {{$feature->id}}}"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<strong class="text-sm">{{ $feature->name }}</strong><br>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
{{ optional(optional($feature->layer)->phase)->name ?? '—' }} >
|
||||||
|
{{ optional($feature->layer)->name ?? '—' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-right">
|
||||||
|
<span class="badge badge-sm {{ $feature->progress >= 100 ? 'badge-success' : 'badge-ghost' }}">
|
||||||
|
{{ $feature->progress }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Media del proyecto --}}
|
||||||
|
@if($selectedFeature)
|
||||||
|
<div class="mt-6">
|
||||||
|
<h3 class="font-semibold text-lg mb-3">{{ __("Media for this element") }}</h3>
|
||||||
|
@if($selectedFeature->media()->exists())
|
||||||
|
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||||
|
@foreach($selectedFeature->media as $media)
|
||||||
|
<div
|
||||||
|
wire:click="openMediaViewer({{ $media->id }})"
|
||||||
|
class="cursor-pointer border rounded p-3 hover:shadow-md transition-shadow"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<strong>{{ $media->name }}</strong><br>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
{{ $media->mime_type }} • {{ $media->created_at->diffForHumans() }} {{ __("ago") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<span class="badge badge-sm">{{ __("View") }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="text-center text-gray-400 py-4">
|
||||||
|
<p>{{ __("No media for this element yet") }}</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="mt-6">
|
||||||
|
<h3 class="font-semibold text-lg mb-3">{{ __("Project Media") }}</h3>
|
||||||
|
@if($projectMedia->isEmpty())
|
||||||
|
<div class="text-center text-gray-400 py-4">
|
||||||
|
<p>{{ __("No project media yet") }}</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||||
|
@foreach($projectMedia as $media)
|
||||||
|
<div
|
||||||
|
wire:click="openMediaViewer({{ $media->id }})"
|
||||||
|
class="cursor-pointer border rounded p-3 hover:shadow-md transition-shadow"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div class="flex-1">
|
||||||
|
<strong>{{ $media->name }}</strong><br>
|
||||||
|
<span class="text-xs text-gray-500">
|
||||||
|
{{ $media->mime_type }} • {{ $media->created_at->diffForHumans() }} {{ __("ago") }}
|
||||||
|
</span>
|
||||||
|
@if($media->model_type === 'App\\Models\\Feature')
|
||||||
|
<span class="text-xs text-gray-400">{{ __("Feature:") }} {{ optional($media->model)->name ?? '' }}</span>
|
||||||
|
@elseif($media->model_type === 'App\\Models\\Inspection')
|
||||||
|
<span class="text-xs text-gray-400">{{ __("Inspection:") }} {{ optional($media->model)->template->name ?? '' }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<span class="badge badge-sm">{{ __("View") }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
Reference in New Issue
Block a user