From fe57388f05d01a15323c94c65b52078612d63867 Mon Sep 17 00:00:00 2001 From: javier Date: Wed, 17 Jun 2026 13:42:42 +0200 Subject: [PATCH] feat(project-form): wire the rich data form (labels-left) + edit tabs The edit/create project page used a stripped-down inline form. Rewired it to the existing-but-orphaned pieces: - Project Data uses the rich partial project-data-form (labels-left/field-right layout, sections Identification/Location/Planning, address search + Leaflet map with draggable marker + reverse/forward geocoding, country dropdown) - When editing, tabs added for Phases / Users / Companies (nested Livewire components phase-list / project-users / project-companies) - ProjectForm now provides $countryList (the partial's country dropdown needs it) - Added the map JS the partial was missing: inits #project-location-map, search box, and calls $this->setLocation(lat,lng,address,country) so the wire:model fields update Co-Authored-By: Claude Opus 4.8 (1M context) --- app/Livewire/ProjectForm.php | 24 +- .../livewire/projects/project-form.blade.php | 281 ++++++++---------- 2 files changed, 146 insertions(+), 159 deletions(-) diff --git a/app/Livewire/ProjectForm.php b/app/Livewire/ProjectForm.php index f2a0193..8b6c399 100644 --- a/app/Livewire/ProjectForm.php +++ b/app/Livewire/ProjectForm.php @@ -114,6 +114,28 @@ class ProjectForm extends Component public function render() { - return view('livewire.projects.project-form'); + return view('livewire.projects.project-form', [ + 'countryList' => $this->countryList(), + ]); + } + + /** + * ISO alpha-2 (lowercase, matches flagcdn) => display name. + */ + private function countryList(): array + { + return [ + 'es' => 'España', 'pt' => 'Portugal', 'fr' => 'Francia', 'it' => 'Italia', + 'de' => 'Alemania', 'gb' => 'Reino Unido', 'ie' => 'Irlanda', 'nl' => 'Países Bajos', + 'be' => 'Bélgica', 'ch' => 'Suiza', 'at' => 'Austria', 'lu' => 'Luxemburgo', + 'se' => 'Suecia', 'no' => 'Noruega', 'dk' => 'Dinamarca', 'fi' => 'Finlandia', + 'pl' => 'Polonia', 'cz' => 'Chequia', 'gr' => 'Grecia', 'ro' => 'Rumanía', + 'us' => 'Estados Unidos', 'ca' => 'Canadá', 'mx' => 'México', 'gt' => 'Guatemala', + 'cr' => 'Costa Rica', 'pa' => 'Panamá', 'co' => 'Colombia', 've' => 'Venezuela', + 'ec' => 'Ecuador', 'pe' => 'Perú', 'bo' => 'Bolivia', 'cl' => 'Chile', + 'ar' => 'Argentina', 'uy' => 'Uruguay', 'py' => 'Paraguay', 'br' => 'Brasil', + 'do' => 'República Dominicana', 'ma' => 'Marruecos', 'gq' => 'Guinea Ecuatorial', + 'ao' => 'Angola', 'cv' => 'Cabo Verde', 'us' => 'Estados Unidos', + ]; } } diff --git a/resources/views/livewire/projects/project-form.blade.php b/resources/views/livewire/projects/project-form.blade.php index 1b6b632..58eb0a6 100644 --- a/resources/views/livewire/projects/project-form.blade.php +++ b/resources/views/livewire/projects/project-form.blade.php @@ -1,173 +1,138 @@
-
-

{{ $project ? __('Edit Project') : __('New Project') }}

- -
-
-
- - +
+
+

{{ $project ? __('Edit Project') : __('New Project') }}

+ + {{ __('Back') }} + +
+ + @if($project) + {{-- Editor con pestañas para el resto de parámetros del proyecto --}} +
+
+ + + +
-
- - + +
+ @include('livewire.projects.partials.project-data-form')
-
- - +
+
-
- - +
+
-
- - -
-
- - +
+
- -
-

{{ __('Location') }}

-

- {{ __('Click on the map or drag the marker to update the location') }} -

-
- - -
- -
- - -
- - - @if(session()->has('message')) -
- {{ session('message') }} -
+ @else + {{-- Alta de proyecto: solo el formulario de datos --}} + @include('livewire.projects.partials.project-data-form') @endif
-
-@push('scripts') - -@endpush + + async function pickLocation(lat, lng) { + setStatus('{{ __('Loading...') }}'); + let address = '', country = ''; + try { + const r = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`); + if (r.ok) { + const d = await r.json(); + address = d.display_name || ''; + country = (d.address && d.address.country_code) ? d.address.country_code : ''; + } + } catch (e) { /* geocoding optional */ } + @this.setLocation(String(lat), String(lng), address, country); + setStatus(''); + } + + async function searchLocation(q) { + if (!q || !q.trim() || !pmap) return; + setStatus('{{ __('Searching...') }}'); + try { + const r = await fetch(`https://nominatim.openstreetmap.org/search?format=json&limit=1&addressdetails=1&q=${encodeURIComponent(q)}`); + const arr = await r.json(); + if (arr && arr.length) { + const lat = parseFloat(arr[0].lat), lng = parseFloat(arr[0].lon); + pmap.setView([lat, lng], 16); + placeMarker(lat, lng); + const address = arr[0].display_name || ''; + const country = (arr[0].address && arr[0].address.country_code) ? arr[0].address.country_code : ''; + @this.setLocation(String(lat), String(lng), address, country); + setStatus(''); + } else { + setStatus('{{ __('No results') }}'); + } + } catch (e) { + setStatus('{{ __('No results') }}'); + } + } + + function initProjectLocationMap() { + const el = document.getElementById('project-location-map'); + if (!el || el._leafletInit) return; + el._leafletInit = true; + + const dLat = parseFloat(el.dataset.lat); + const dLng = parseFloat(el.dataset.lng); + const hasCoords = !isNaN(dLat) && !isNaN(dLng) && (dLat !== 0 || dLng !== 0); + const lat = hasCoords ? dLat : 40.4168; + const lng = hasCoords ? dLng : -3.7038; + + pmap = L.map('project-location-map').setView([lat, lng], hasCoords ? 16 : 5); + L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { + attribution: '© OSM & CartoDB' + }).addTo(pmap); + + if (hasCoords) placeMarker(lat, lng); + + pmap.on('click', (e) => { + placeMarker(e.latlng.lat, e.latlng.lng); + pickLocation(e.latlng.lat, e.latlng.lng); + }); + + const input = document.getElementById('map-search-input'); + const btn = document.getElementById('map-search-btn'); + if (btn) btn.addEventListener('click', () => searchLocation(input ? input.value : '')); + if (input) input.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { e.preventDefault(); searchLocation(input.value); } + }); + + setTimeout(() => pmap.invalidateSize(), 200); + } + + document.addEventListener('livewire:navigated', initProjectLocationMap); + document.addEventListener('DOMContentLoaded', initProjectLocationMap); + setTimeout(initProjectLocationMap, 300); + })(); + + @endpush +