Files
Nexora/resources/views/projects/create.blade.php
Javi 88e526cf6c
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
mejoras en la gestión de nombres y códigos de proyectos y documentos según la norma ISO 19650
2025-10-25 11:30:59 +02:00

544 lines
25 KiB
PHP

<!-- resources/views/projects/create.blade.php -->
<x-layouts.app title="{{ (isset($project) && $project->id) ? __('Edit User') : __('Create User') }}"
:showSidebar={{ $showSidebar }}>
<!-- Header -->
<div class="mb-8">
<div class="flex items-center gap-4 mb-4">
<svg class="h-10 w-10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<!-- Base abstract shape -->
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 4.5L20 8v8l-8 3.5L4 16V8l8-3.5z"/>
<!-- Progress indicator -->
<path stroke-linecap="round"
d="M12 6v12"
class="text-blue-500"/>
<!-- Connection dots -->
<circle cx="12" cy="6" r="1" fill="currentColor"/>
<circle cx="12" cy="18" r="1" fill="currentColor"/>
<!-- Decorative elements -->
<path stroke-linecap="round" stroke-linejoin="round"
d="M8 10l4 2 4-2"
class="text-green-500"/>
</svg>
<h1 class="text-3xl font-bold text-gray-800">
{{ (isset($project) && $project->id) ? 'Editar Proyecto' : 'Nuevo Proyecto' }}
</h1>
</div>
<p class="text-gray-600 text-sm">
@if(isset($project) && $project->id)
Modifique los campos necesarios para actualizar la información del usuario.
@else
Complete todos los campos obligatorios para registrar un nuevo usuario en el sistema.
@endisset
</p>
</div>
@if(session('error'))
<div id="error-message" class="mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
{{ session('error') }}
</div>
@endif
<!-- Formulario -->
<form method="POST" action="{{ (isset($project) && $project->id) ? route('projects.update', $project) : route('projects.store') }}" enctype="multipart/form-data">
@csrf
@if(isset($project) && $project->id)
@method('PUT')
@endif
<!-- Separador -->
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center">
<span class="px-4 bg-white text-sm text-gray-500">Datos Generales</span>
</div>
</div>
<div class="bg-white py-6">
<table class="w-full mb-8">
<tbody>
<!-- Empresa -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label for="company_id" :value="__('Empresa propietaria del Proyecto')" />
</td>
<td class="py-3">
<select id="company_id" name="company_id"
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none" required>
<option value="">Seleccione una empresa...</option>
@foreach($companies as $company)
<option value="{{ $company->id }}"
{{ old('company_id', $project->company_id ?? '') == $company->id ? 'selected' : '' }}>
{{ $company->name }}
</option>
@endforeach
</select>
@error('company_id')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
<!-- Referencia -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label for="reference" :value="__('Referencia')" />
</td>
<td class="py-3">
<input type="text" name="reference" id="reference"
value="{{ old('reference', $project->reference ?? '') }}"
class="w-[250px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
maxlength="12"
autofocus
required>
<flux:tooltip content="Máximo: 12 caracteres" position="right">
<flux:button icon="information-circle" size="sm" variant="ghost" />
</flux:tooltip>
@error('reference')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
<!-- Nombre -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label for="name" :value="__('Nombre')" />
</td>
<td class="py-3">
<input type="text" name="name"
value="{{ old('name', $project->name ?? '') }}"
class="w-[500px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
required>
@error('name')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
<!-- Estado -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label for="status" :value="__('Estado del Proyecto')" />
</td>
<td class="py-3">
<select id="status" name="status"
class="w-[150px] block mt-1 border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
<option :value="active" {{ old('status') == 'active' ? 'selected' : '' }}>Activo</option>
<option :value="inactive" {{ old('status') == 'inactive' ? 'selected' : '' }}>Inactivo</option>
</select>
@error('status')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
<!-- Descripción Rich Editor -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label for="description" :value="__('Descripción')" />
</td>
<td class="py-3">
<!-- Editor Container -->
<div id="rich-editor" style="height: 120px;">
{!! old('description', $project->description ?? '') !!}
</div>
<!-- Campo oculto para enviar el contenido -->
<input type="hidden" id="description" name="description"
value="{{ old('description', $project->description ?? '') }}">
@error('description')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
<!-- Fechas Importantes -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label for="start_date" :value="__('Fechas')" />
</td>
<td class="py-3">
<div class="flex gap-4">
<span class="text-gray-700">de</span>
<input type="date"
id="start_date"
name="start_date"
value="{{ old('start_date') }}"
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
<span class="text-gray-700">a</span>
<input type="date"
id="deadline"
name="deadline"
value="{{ old('end_date') }}"
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center">
<span class="px-4 bg-white text-sm text-gray-500">Ubicación </span>
</div>
</div>
<div class="bg-white py-6">
<table class="w-full mb-8">
<tbody>
<!-- Dirección -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label :value="__('Dirección')" />
</td>
<td class="py-3">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-1">
<div>
<textarea id="address"
name="address"
rows="3"
class="w-full border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">{{ old('address') }}
</textarea>
@error('address')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<input type="text"
id="postal_code"
name="postal_code"
value="{{ old('postal_code') }}"
placeholder="Código Postal"
class="w-[120px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
@error('postal_code')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<input type="text"
id="province"
name="province"
value="{{ old('province') }}"
placeholder="Provincia"
class="w-[300px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
@error('province')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<div>
<livewire:country-select :initialCountry="old('country')" />
@error('country')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
</td>
</tr>
<!-- Mapa para Coordenadas -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label :value="__('Seleccione Ubicación')" />
</td>
<td class="py-3">
<div id="map" class="h-80 border-2 border-gray-200"></div>
<div class="grid grid-cols-2 gap-4 mt-2">
<div>
<x-label for="latitude" :value="__('Latitud')" />
<input type="number"
id="latitude"
name="latitude"
value="{{ old('latitude') }}"
step="any"
class="border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
</div>
<div>
<x-label for="longitude" :value="__('Longitud')" />
<input type="number"
id="longitude"
name="longitude"
value="{{ old('longitude') }}"
step="any"
class="border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none">
</div>
</div>
@error('latitude')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
@error('longitude')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
</tbody>
</table>
</div>
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center">
<span class="px-4 bg-white text-sm text-gray-500">Otros datos</span>
</div>
</div>
<div class="bg-white py-6">
<table class="w-full mb-8">
<tbody>
<!-- Imagen de Referencia -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label for="reference_image" :value="__('Imagen de Referencia')" />
</td>
<td class="py-3">
@livewire('image-uploader', [
'fieldName' => 'image_path', // Nombre del campo en la BD
'label' => 'Imagen principal' // Etiqueta personalizada
])
<!-- Campo oculto para la ruta de la imagen -->
<input type="hidden" name="project_image_path" id="PhotoPathInput"
value="{{ old('project_image_path', optional($project)->project_image_path ?? '') }}">
@error('project_image')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
<!-- Categorías -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label :value="__('Identificación Visual')" />
</td>
<td class="py-3">
<div>
<x-label for="categories" :value="__('Categorías')" />
<x-multiselect
class="w-[150px] border-b-1 border-gray-300 focus:border-blue-500 focus:outline-none"
name="categories[]"
:options="$categories"
:selected="old('categories', [])"
placeholder="Seleccione categorías"
/>
</div>
</td>
</tr>
<!-- Miembros del Equipo -->
<tr>
<td class="w-1/4 py-3 pr-4 align-top">
<x-label :value="__('Miembros del Equipo')" />
</td>
<td class="py-3">
<div class="grid grid-cols-1 mt-2 gap-y-2 gap-x-4 sm:grid-cols-2">
@foreach($users as $user)
<label class="flex items-center space-x-2">
<input type="checkbox"
name="team[]"
value="{{ $user->id }}"
{{ in_array($user->id, old('team', [])) ? 'checked' : '' }}
class="rounded border-gray-300 text-blue-600 shadow-sm focus:ring-blue-500">
<span class="text-sm text-gray-700">{{ $user->first_name}} {{ $user->last_name}}</span>
</label>
@endforeach
</div>
@error('team')
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
@enderror
</td>
</tr>
</tbody>
</table>
</div>
<!-- Botones de Acción -->
<div class="flex justify-end mt-8 space-x-4">
<a href="{{ route('projects.index') }}"
class="px-4 py-2 text-gray-600 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50">
{{ __('Cancelar') }}
</a>
<button type="submit"
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors">
{{ (isset($project) && $project->id) ? 'Actualizar' : 'Crear' }}
</button>
</div>
</form>
@push('scripts')
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.min.js"></script>
@endpush
<script>
// Editor Quill
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
['link', 'formula'],
//[{ 'header': 1 }, { 'header': 2 }], // custom button :values
[{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
[{ 'script': 'sub'}, { 'script': 'super' }], // superscript/subscript
[{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
[{ 'direction': 'rtl' }], // text direction
//[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
[{ 'font': [] }],
[{ 'align': [] }],
['clean'] // remove formatting button
];
const quill = new Quill('#rich-editor', {
theme: 'snow',
modules: {
toolbar: toolbarOptions,
},
placeholder: 'Escribe la descripción del proyecto...'
});
// Actualizar el input hidden con el contenido HTML
quill.on('text-change', function() {
document.getElementById('description').value = quill.root.innerHTML;
});
// Inicializar con contenido existente si hay
quill.clipboard.dangerouslyPasteHTML(
document.getElementById('description').value
);
</script>
<script>
// Escuchar el evento de Livewire y actualizar el campo oculto
document.addEventListener('imageUploaded', (event) => {
document.getElementById('PhotoPathInput').value = event.detail.path;
});
document.addEventListener('imageRemoved', (event) => {
document.getElementById('PhotoPathInput').value = '';
});
</script>
<script>
let map;
let marker;
function initMap() {
// Obtener valores iniciales de los inputs o usar defaults
const initialLat = parseFloat(document.getElementById('latitude').value) || 40.4168;
const initialLng = parseFloat(document.getElementById('longitude').value) || -3.7038;
map = L.map('map').setView([initialLat, initialLng], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// Marcador inicial si hay valores válidos
if (!isNaN(initialLat) && !isNaN(initialLng)) {
marker = L.marker([initialLat, initialLng]).addTo(map);
}
// Actualizar inputs al hacer clic en el mapa
map.on('click', function(e) {
updateInputs(e.latlng.lat, e.latlng.lng);
updateMarker(e.latlng);
});
}
function updateMarker(latlng) {
if (marker) {
marker.setLatLng(latlng);
} else {
marker = L.marker(latlng).addTo(map);
}
map.panTo(latlng);
}
function updateInputs(lat, lng) {
document.getElementById('latitude').value = lat.toFixed(6);
document.getElementById('longitude').value = lng.toFixed(6);
}
function updateMapFromInputs() {
const lat = parseFloat(document.getElementById('latitude').value);
const lng = parseFloat(document.getElementById('longitude').value);
if (!isNaN(lat) && !isNaN(lng) &&
lat >= -90 && lat <= 90 &&
lng >= -180 && lng <= 180) {
const newLatLng = L.latLng(lat, lng);
updateMarker(newLatLng);
}
}
// Inicializar el mapa
window.onload = function() {
initMap();
// Escuchar cambios en los inputs
document.getElementById('latitude').addEventListener('input', updateMapFromInputs);
document.getElementById('longitude').addEventListener('input', updateMapFromInputs);
};
</script>
<!-- Sidebar menu -->
@push('sidebar-menu')
<flux:navlist variant="outline">
<!-- Sección de Proyectos -->
<flux:navlist.group :heading="__('Projects')">
<flux:navlist.item
icon="folder"
:href="route('projects.index')"
wire:navigate
>
{{ __('List Projects') }}
</flux:navlist.item>
<flux:navlist.item
icon="plus"
:href="route('projects.create')"
wire:navigate
>
{{ __('Create Project') }}
</flux:navlist.item>
</flux:navlist.group>
</flux:navlist>
@endpush
</x-layouts.app>