Sistema de archivos multimedia: MediaManager, checkbox imágenes en mapa, modal visor, subida por feature/proyecto

This commit is contained in:
2026-05-09 22:28:20 +02:00
parent dabd35091a
commit 8f7b9aa09b
12 changed files with 586 additions and 1 deletions
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\On;
use App\Models\Media;
use App\Models\Project;
use App\Models\Phase;
use App\Models\Layer;
use App\Models\Feature;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class MediaManager extends Component
{
use WithFileUploads;
// Polimórfico: a qué entidad pertenece
public $mediableType;
public $mediableId;
public $entity; // instancia cargada
public $mediaItems = [];
// Subida
public $uploadFiles = [];
public $uploadDescription = '';
public $uploadCategory = 'image';
// Modal visor
public $showViewer = false;
public $viewingMedia = null;
protected $rules = [
'uploadFiles.*' => 'required|file|max:102400', // 100MB total
'uploadDescription' => 'nullable|string|max:500',
'uploadCategory' => 'required|in:image,document,other',
];
protected $messages = [
'uploadFiles.*.max' => 'Cada archivo debe pesar menos de 100MB.',
];
public function mount($mediableType, $mediableId)
{
$this->mediableType = $mediableType;
$this->mediableId = $mediableId;
$this->entity = $mediableType::findOrFail($mediableId);
$this->loadMedia();
}
public function loadMedia()
{
$this->mediaItems = Media::where('mediable_type', $this->mediableType)
->where('mediable_id', $this->mediableId)
->with('uploader')
->latest()
->get();
}
public function upload()
{
$user = Auth::user();
if (!$user->can('upload layers') && !$user->hasRole('Admin')) {
session()->flash('error', 'Sin permisos.');
return;
}
$this->validate();
if (empty($this->uploadFiles)) {
session()->flash('error', 'Selecciona al menos un archivo.');
return;
}
$uploaded = 0;
foreach ($this->uploadFiles as $file) {
$mime = $file->getMimeType();
$ext = $file->getClientOriginalExtension();
$size = $file->getSize();
$name = $file->getClientOriginalName();
// Determinar categoría automática
$category = $this->uploadCategory;
if (str_starts_with($mime, 'image/')) {
$category = 'image';
} elseif (in_array($mime, ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'])) {
$category = 'document';
}
// Guardar en disco
$entityType = class_basename($this->entity);
$dir = "uploads/{$entityType}s/{$this->mediableId}/media";
$path = $file->store($dir, 'public');
Media::create([
'mediable_type' => $this->mediableType,
'mediable_id' => $this->mediableId,
'name' => $name,
'file_path' => $path,
'file_type' => $mime,
'file_extension' => $ext,
'file_size' => $size,
'category' => $category,
'description' => $this->uploadDescription,
'uploaded_by' => $user->id,
]);
$uploaded++;
}
$this->reset(['uploadFiles', 'uploadDescription']);
$this->loadMedia();
// Notificar al mapa si corresponde
$this->dispatch('mediaUploaded', [
'mediableType' => $this->mediableType,
'mediableId' => $this->mediableId,
]);
session()->flash('message', "$uploaded archivo(s) subido(s) correctamente.");
}
public function deleteMedia($mediaId)
{
$media = Media::findOrFail($mediaId);
$user = Auth::user();
if (!$user->hasRole('Admin') && $media->uploaded_by !== $user->id) {
session()->flash('error', 'No puedes borrar archivos de otro usuario.');
return;
}
$media->delete();
$this->loadMedia();
session()->flash('message', 'Archivo eliminado.');
}
public function viewMedia($mediaId)
{
$media = Media::findOrFail($mediaId);
if (!$media->is_image) {
// Si no es imagen, abrir en nueva pestaña
$this->dispatch('openUrl', $media->url);
return;
}
$this->viewingMedia = $media;
$this->showViewer = true;
}
public function closeViewer()
{
$this->showViewer = false;
$this->viewingMedia = null;
}
public function render()
{
return view('livewire.media-manager', [
'entityName' => class_basename($this->entity) . ': ' . ($this->entity->name ?? $this->entity->id),
'images' => $this->mediaItems->filter(fn($m) => $m->is_image),
'documents' => $this->mediaItems->filter(fn($m) => !$m->is_image),
]);
}
}
+62
View File
@@ -33,6 +33,10 @@ class ProjectMap extends Component
public $inspectionFormData = [];
public $inspectionHistory = [];
// Imágenes en mapa
public $showFeatureImages = false;
public $featureImageMarkers = [];
public function mount(Project $project)
{
$this->project = $project;
@@ -246,6 +250,64 @@ class ProjectMap extends Component
$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;