7d854ffb0a
- Translation system: lang/es/ PHP files (auth, validation, pagination, passwords)
- Rappasoft vendor translations published (lang/vendor/livewire-tables/es/)
- JSON files synced to 391 keys (EN + ES, full parity)
- APP_LOCALE changed to 'es', users.locale column default changed to 'es'
- Language switcher fixed: JS event + window.location.reload() avoids /livewire/update redirect
- SetLocale middleware fallback uses config('app.locale') instead of hardcoded 'en'
- setSortingPillsEnabled(false) on ProjectTable, CompanyTable, UserTable
- Translated 17 blade views: project-map, template-manager, layer-manager,
company-management, phase-list, media-manager, reports-dashboard,
client-projects, layer-upload, project-form, project-map-editor-tab,
admin/users, projects/media, projects/templates, layouts/client
- Navigation 'Empresas' link uses __('Companies')
- Fixed typo key 'Fases and layers' -> 'Phases and layers'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
271 lines
14 KiB
PHP
271 lines
14 KiB
PHP
<div class="card bg-base-100 shadow">
|
|
<div class="card-body p-8">
|
|
|
|
<form wire:submit.prevent="save">
|
|
|
|
@if($errors->any())
|
|
<div class="alert alert-error text-sm mb-6">
|
|
<x-heroicon-o-exclamation-circle class="w-5 h-5 shrink-0" />
|
|
<ul class="list-disc pl-3 space-y-0.5">
|
|
@foreach($errors->all() as $e) <li>{{ $e }}</li> @endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- ══════════════════════════════════════════════════════════════════
|
|
1. IDENTIFICACIÓN
|
|
══════════════════════════════════════════════════════════════════ --}}
|
|
<div class="pb-6 mb-6 border-b border-base-200">
|
|
<h3 class="text-xs font-semibold text-gray-400 uppercase tracking-widest mb-4">Identificación</h3>
|
|
<div class="space-y-4">
|
|
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">
|
|
Nombre <span class="text-error">*</span>
|
|
</label>
|
|
<div class="flex-1">
|
|
<input type="text" wire:model="name"
|
|
class="input input-bordered w-full"
|
|
placeholder="Edificio Residencial Las Palmas"
|
|
autofocus />
|
|
@error('name') <p class="text-error text-xs mt-1">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">
|
|
Referencia
|
|
<p class="text-xs text-gray-400 font-normal mt-0.5">Código interno o expediente</p>
|
|
</label>
|
|
<div class="flex-1">
|
|
<input type="text" wire:model="reference"
|
|
class="input input-bordered w-full max-w-xs"
|
|
placeholder="OBR-2026-001" />
|
|
@error('reference') <p class="text-error text-xs mt-1">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
@if($project)
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">Estado</label>
|
|
<div class="flex-1">
|
|
<select wire:model="status" class="select select-bordered w-full max-w-xs">
|
|
<option value="planning">Planificación</option>
|
|
<option value="in_progress">En progreso</option>
|
|
<option value="paused">Pausado</option>
|
|
<option value="completed">Completado</option>
|
|
</select>
|
|
@error('status') <p class="text-error text-xs mt-1">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ══════════════════════════════════════════════════════════════════
|
|
2. UBICACIÓN
|
|
══════════════════════════════════════════════════════════════════ --}}
|
|
<div class="pb-6 mb-6 border-b border-base-200">
|
|
<h3 class="text-xs font-semibold text-gray-400 uppercase tracking-widest mb-4">Ubicación</h3>
|
|
|
|
{{-- Search box --}}
|
|
<div class="flex gap-2 mb-3">
|
|
<label class="input input-bordered input-sm flex items-center gap-2 flex-1">
|
|
<x-heroicon-o-magnifying-glass class="w-4 h-4 opacity-40 shrink-0" />
|
|
<input type="text" id="map-search-input" class="grow"
|
|
placeholder="Buscar dirección, ciudad, lugar…"
|
|
autocomplete="off" />
|
|
</label>
|
|
<button type="button" id="map-search-btn"
|
|
class="btn btn-outline btn-sm gap-1 shrink-0">
|
|
<x-heroicon-o-magnifying-glass class="w-4 h-4" />
|
|
Buscar
|
|
</button>
|
|
</div>
|
|
|
|
{{-- Geocode status message --}}
|
|
<p id="geocode-status" class="text-xs text-gray-400 mb-2 min-h-[1rem]"></p>
|
|
|
|
{{-- Map (wire:ignore prevents Livewire morphing from destroying Leaflet) --}}
|
|
<div wire:ignore
|
|
id="project-location-map"
|
|
data-lat="{{ $lat }}"
|
|
data-lng="{{ $lng }}"
|
|
style="height: 380px; border-radius: 0.5rem; overflow: hidden; z-index: 1;"
|
|
class="border border-base-300 shadow-sm mb-4">
|
|
</div>
|
|
|
|
<p class="text-xs text-gray-400 mb-4 flex items-center gap-1">
|
|
<x-heroicon-o-cursor-arrow-rays class="w-3.5 h-3.5 opacity-60" />
|
|
Pulsa en el mapa o arrastra el marcador para actualizar la ubicación.
|
|
</p>
|
|
|
|
<div class="space-y-4">
|
|
|
|
{{-- Lat/Lng (read-only, filled by map) --}}
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">
|
|
Coordenadas
|
|
<p class="text-xs text-gray-400 font-normal mt-0.5">Auto al pulsar el mapa</p>
|
|
</label>
|
|
<div class="flex-1 flex items-center gap-3">
|
|
<div class="flex-1">
|
|
<label class="label-text text-xs mb-0.5">Latitud</label>
|
|
<input type="text" wire:model="lat" readonly
|
|
id="input-lat"
|
|
class="input input-bordered input-sm w-full bg-base-200 font-mono"
|
|
placeholder="40.41680000" />
|
|
@error('lat') <p class="text-error text-xs mt-0.5">{{ $message }}</p> @enderror
|
|
</div>
|
|
<span class="text-gray-300 mt-5">/</span>
|
|
<div class="flex-1">
|
|
<label class="label-text text-xs mb-0.5">Longitud</label>
|
|
<input type="text" wire:model="lng" readonly
|
|
id="input-lng"
|
|
class="input input-bordered input-sm w-full bg-base-200 font-mono"
|
|
placeholder="-3.70380000" />
|
|
@error('lng') <p class="text-error text-xs mt-0.5">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Dirección --}}
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">
|
|
Dirección <span class="text-error">*</span>
|
|
</label>
|
|
<div class="flex-1">
|
|
<textarea wire:model="address" rows="2"
|
|
class="textarea textarea-bordered w-full"
|
|
placeholder="Calle Gran Vía 28, 28013 Madrid, España"></textarea>
|
|
@error('address') <p class="text-error text-xs mt-1">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
{{-- País — custom dropdown with flag images (native <select> can't render emoji on Windows) --}}
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">País</label>
|
|
<div class="flex-1 max-w-xs">
|
|
<div x-data="{ open: false, q: '' }"
|
|
@click.outside="open = false; q = ''"
|
|
class="relative">
|
|
|
|
{{-- Trigger button --}}
|
|
<button type="button"
|
|
@click="open = !open; if(open) $nextTick(() => $refs.qs?.focus())"
|
|
class="btn btn-outline w-full justify-start gap-2 font-normal h-12">
|
|
@if($country && isset($countryList[$country]))
|
|
<img src="https://flagcdn.com/w20/{{ $country }}.png"
|
|
class="w-6 h-4 object-cover rounded-sm shrink-0"
|
|
onerror="this.style.display='none'" />
|
|
<span>{{ $countryList[$country] }}</span>
|
|
@else
|
|
<span class="text-gray-400">— Sin especificar —</span>
|
|
@endif
|
|
<x-heroicon-o-chevron-up-down class="w-4 h-4 ml-auto opacity-40 shrink-0" />
|
|
</button>
|
|
|
|
{{-- Dropdown panel --}}
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-100"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
class="absolute z-50 mt-1 w-full bg-base-100 border border-base-300 rounded-xl shadow-xl overflow-hidden"
|
|
style="display:none">
|
|
|
|
{{-- Search --}}
|
|
<div class="p-2 border-b border-base-200">
|
|
<input x-ref="qs" x-model="q" type="text"
|
|
placeholder="Buscar país…"
|
|
class="input input-sm input-bordered w-full"
|
|
@keydown.escape="open = false; q = ''" />
|
|
</div>
|
|
|
|
{{-- Clear option --}}
|
|
<button type="button"
|
|
@click="$wire.set('country', ''); open = false; q = ''"
|
|
class="flex items-center gap-2 w-full px-3 py-2 hover:bg-base-200 text-sm text-gray-400 border-b border-base-200">
|
|
— Sin especificar —
|
|
</button>
|
|
|
|
{{-- Country list --}}
|
|
<ul class="overflow-y-auto max-h-52 py-1">
|
|
@foreach($countryList as $code => $cName)
|
|
<li>
|
|
<button type="button"
|
|
x-show="q === '' || '{{ strtolower(addslashes($cName)) }}'.includes(q.toLowerCase())"
|
|
@click="$wire.set('country', '{{ $code }}'); open = false; q = ''"
|
|
class="flex items-center gap-2.5 w-full px-3 py-1.5 hover:bg-base-200 text-sm text-left {{ $country === $code ? 'bg-primary/10 font-semibold text-primary' : '' }}">
|
|
<img src="https://flagcdn.com/w20/{{ $code }}.png"
|
|
class="w-6 h-4 object-cover rounded-sm shrink-0"
|
|
loading="lazy"
|
|
onerror="this.style.display='none'" />
|
|
{{ $cName }}
|
|
@if($country === $code)
|
|
<x-heroicon-o-check class="w-3.5 h-3.5 ml-auto shrink-0" />
|
|
@endif
|
|
</button>
|
|
</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
@error('country') <p class="text-error text-xs mt-1">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ══════════════════════════════════════════════════════════════════
|
|
3. PLANIFICACIÓN
|
|
══════════════════════════════════════════════════════════════════ --}}
|
|
<div class="pb-6 mb-6 border-b border-base-200">
|
|
<h3 class="text-xs font-semibold text-gray-400 uppercase tracking-widest mb-4">Planificación</h3>
|
|
<div class="space-y-4">
|
|
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">
|
|
Fecha inicio <span class="text-error">*</span>
|
|
</label>
|
|
<div class="flex-1">
|
|
<input type="date" wire:model="startDate"
|
|
class="input input-bordered w-full max-w-xs" />
|
|
@error('startDate') <p class="text-error text-xs mt-1">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start gap-4">
|
|
<label class="w-48 shrink-0 pt-2 text-sm font-medium text-gray-700">
|
|
Fecha fin estimada
|
|
<p class="text-xs text-gray-400 font-normal mt-0.5">Vacío = sin fecha límite</p>
|
|
</label>
|
|
<div class="flex-1">
|
|
<input type="date" wire:model="endDateEstimated"
|
|
class="input input-bordered w-full max-w-xs" />
|
|
@error('endDateEstimated') <p class="text-error text-xs mt-1">{{ $message }}</p> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ── Botones ─────────────────────────────────────────────────────── --}}
|
|
<div class="flex items-center justify-between pt-2">
|
|
<a href="{{ route('projects.index') }}" class="btn btn-outline gap-1" wire:navigate>
|
|
<x-heroicon-o-x-mark class="w-4 h-4" />
|
|
Cancelar
|
|
</a>
|
|
<button type="submit" class="btn btn-primary gap-2"
|
|
wire:loading.attr="disabled" wire:target="save">
|
|
<span wire:loading wire:target="save" class="loading loading-spinner loading-sm"></span>
|
|
<x-heroicon-o-check class="w-4 h-4" />
|
|
{{ $project ? 'Guardar cambios' : 'Crear proyecto' }}
|
|
</button>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|