Files
construprogress/resources/views/livewire/projects/project-form.blade.php
T
javier 941dbd5997 restore: bring back f8a1310 (security review) state
Restores all files to the f8a1310 security-review snapshot as requested,
plus the 2 boot-critical fixes from a24c8a2 (config/session.php env()
instead of app()->environment(), and removal of the duplicate $activeTab
in ProjectMap.php) so the application actually boots.

Forward commit, no history rewrite. The 7d854ff state remains in history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:36:44 +02:00

188 lines
8.1 KiB
PHP

<div>
<div class="max-w-2xl mx-auto p-4">
<h1 class="text-2xl font-bold mb-6">{{ $projectId ? __('Edit Project') : __('New Project') }}</h1>
@if($errors->any())
<div class="alert alert-error text-sm mb-4">
<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
<form wire:submit.prevent="save" class="space-y-6">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label class="block text-sm font-medium mb-2">{{ __('Name') }}</label>
<input type="text" wire:model="name" class="input input-bordered w-full {{ $errors->has('name') ? 'input-error' : '' }}" placeholder="{{ __('Project name') }}" required>
@error('name') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-2">{{ __('Address') }}</label>
<input type="text" wire:model="address" class="input input-bordered w-full" placeholder="{{ __('Street address, city, etc.') }}">
@error('address') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium mb-2">{{ __('Country') }}</label>
<input type="text" wire:model="country" class="input input-bordered w-full" placeholder="{{ __('Country (auto-filled)') }}" readonly>
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium mb-2">{{ __('Start Date') }}</label>
<input type="date" wire:model="start_date" class="input input-bordered w-full {{ $errors->has('start_date') ? 'input-error' : '' }}" required>
@error('start_date') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium mb-2">{{ __('End Date (estimated)') }}</label>
<input type="date" wire:model="end_date_estimated" class="input input-bordered w-full {{ $errors->has('end_date_estimated') ? 'input-error' : '' }}">
@error('end_date_estimated') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium mb-2">{{ __('Status') }}</label>
<select wire:model="status" class="select select-bordered w-full">
<option value="planning">{{ __('Planning') }}</option>
<option value="in_progress">{{ __('In Progress') }}</option>
<option value="paused">{{ __('Paused') }}</option>
<option value="completed">{{ __('Completed') }}</option>
</select>
@error('status') <span class="text-error text-xs mt-1">{{ $message }}</span> @enderror
</div>
</div>
<div class="border rounded-lg p-4">
<h2 class="text-xl font-bold mb-4">{{ __('Project Location') }}</h2>
<p class="text-sm text-gray-500 mb-2">
{{ __('Click on the map to set the project location. The address and country will be filled automatically.') }}
</p>
<div id="projectMap" style="height: 400px; width: 100%; background: #e2e8f0; border-radius: 0.5rem;"></div>
<input type="hidden" wire:model="lat">
<input type="hidden" wire:model="lng">
</div>
<div class="flex justify-end space-x-4">
<button type="button" wire:click="resetForm" class="btn btn-outline">
{{ __('Reset') }}
</button>
<button type="submit" class="btn btn-primary">
{{ $projectId ? __('Update') : __('Create') }}
</button>
</div>
</form>
@if(session()->has('message'))
<div class="mt-4 p-4 bg-green-50 border-l-4 border-green-400 text-green-700">
{{ session('message') }}
</div>
@endif
</div>
</div>
@push('scripts')
<script>
let map;
let marker;
// Initialize Leaflet map
function initMap() {
if (map) return;
// Default coordinates (can be overridden)
const defaultLat = @json($lat ?? 0);
const defaultLng = @json($lng ?? 0);
const center = defaultLat && defaultLng ? [defaultLat, defaultLng] : [0, 0];
map = L.map('projectMap').setView(center, 13);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OSM</a> & CartoDB'
}).addTo(map);
// Add marker if we have coordinates
if (defaultLat && defaultLng) {
marker = L.marker([defaultLat, defaultLng], {
draggable: true
}).addTo(map);
marker.on('dragend', function(e) {
const pos = marker.getLatLng();
updateCoordinates(pos.lat, pos.lng);
});
}
// Handle map clicks to place marker
map.on('click', function(e) {
const pos = e.latlng;
if (marker) {
marker.setLatLng(pos);
} else {
marker = L.marker(pos, {
draggable: true
}).addTo(map);
marker.on('dragend', function(e) {
const pos = marker.getLatLng();
updateCoordinates(pos.lat, pos.lng);
});
}
updateCoordinates(pos.lat, pos.lng);
});
}
// Update coordinates and trigger reverse geocoding
function updateCoordinates(lat, lng) {
// Update hidden inputs
document.querySelector('input[name="lat"]').value = lat;
document.querySelector('input[name="lng"]').value = lng;
// Trigger Livewire event to update coordinates
@this.setCoordinates(lat, lng);
// Reverse geocode to get address and country
reverseGeocode(lat, lng);
}
// Reverse geocode using Nominatim (OpenStreetMap)
async function reverseGeocode(lat, lng) {
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`,
{
headers: {
'User-Agent': 'OpenClaw/1.0 (construprogress)'
}
}
);
if (!response.ok) throw new Error('Geocoding request failed');
const data = await response.json();
// Update address field
const addressInput = document.querySelector('input[name="address"]');
if (data.display_name) {
addressInput.value = data.display_name;
}
// Update country field
const countryInput = document.querySelector('input[name="country"]');
if (data.address && data.address.country) {
countryInput.value = data.address.country;
}
} catch (error) {
console.error('Error reverse geocoding:', error);
// Don't fail the UI if geocoding fails
}
}
// Initialize map when component is ready
document.addEventListener('Livewire:load', function() {
initMap();
});
// Also initialize on DOMContentLoaded as fallback
document.addEventListener('DOMContentLoaded', function() {
initMap();
});
</script>
@endpush