mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 03:51:10 -05:00
Implement LocationDisplayComponent and LocationMapComponent for interactive map features; add event handling and state management
This commit is contained in:
118
app/Livewire/Location/LocationDisplayComponent.php
Normal file
118
app/Livewire/Location/LocationDisplayComponent.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Location;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use App\Models\Location;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
class LocationDisplayComponent extends Component
|
||||||
|
{
|
||||||
|
// Map Configuration
|
||||||
|
public array $markers = [];
|
||||||
|
public bool $showClusters = true;
|
||||||
|
public int $clusterRadius = 50;
|
||||||
|
public int $defaultZoom = 13;
|
||||||
|
public ?array $bounds = null;
|
||||||
|
|
||||||
|
// Display Settings
|
||||||
|
public bool $showInfoWindow = false;
|
||||||
|
public ?array $activeMarker = null;
|
||||||
|
public array $customMarkers = [];
|
||||||
|
public array $markerCategories = [];
|
||||||
|
|
||||||
|
// State Management
|
||||||
|
public bool $isLoading = true;
|
||||||
|
public ?string $error = null;
|
||||||
|
public array $visibleMarkers = [];
|
||||||
|
|
||||||
|
// Lifecycle Hooks
|
||||||
|
public function mount(array $markers = [], ?array $bounds = null, array $categories = [])
|
||||||
|
{
|
||||||
|
$this->markers = $markers;
|
||||||
|
$this->bounds = $bounds;
|
||||||
|
$this->markerCategories = $categories;
|
||||||
|
$this->visibleMarkers = $markers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Handlers
|
||||||
|
public function markerClicked($markerId)
|
||||||
|
{
|
||||||
|
$this->activeMarker = collect($this->markers)->firstWhere('id', $markerId);
|
||||||
|
$this->showInfoWindow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clusterClicked($clusterMarkers)
|
||||||
|
{
|
||||||
|
if (count($clusterMarkers) === 1) {
|
||||||
|
$this->markerClicked($clusterMarkers[0]['id']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bounds = $this->calculateBounds($clusterMarkers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function closeInfoWindow()
|
||||||
|
{
|
||||||
|
$this->showInfoWindow = false;
|
||||||
|
$this->activeMarker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boundsChanged($bounds)
|
||||||
|
{
|
||||||
|
$this->bounds = $bounds;
|
||||||
|
$this->visibleMarkers = $this->getVisibleMarkers($bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateMarkersVisibility($visible)
|
||||||
|
{
|
||||||
|
$this->visibleMarkers = collect($this->markers)
|
||||||
|
->filter(fn ($marker) => in_array($marker['id'], $visible))
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper Methods
|
||||||
|
protected function calculateBounds($markers): array
|
||||||
|
{
|
||||||
|
if (empty($markers)) {
|
||||||
|
return [
|
||||||
|
'north' => 0,
|
||||||
|
'south' => 0,
|
||||||
|
'east' => 0,
|
||||||
|
'west' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$lats = array_column($markers, 'lat');
|
||||||
|
$lons = array_column($markers, 'lng');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'north' => max($lats),
|
||||||
|
'south' => min($lats),
|
||||||
|
'east' => max($lons),
|
||||||
|
'west' => min($lons),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getVisibleMarkers($bounds): array
|
||||||
|
{
|
||||||
|
if (!$bounds) {
|
||||||
|
return $this->markers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect($this->markers)
|
||||||
|
->filter(function ($marker) use ($bounds) {
|
||||||
|
return $marker['lat'] >= $bounds['south'] &&
|
||||||
|
$marker['lat'] <= $bounds['north'] &&
|
||||||
|
$marker['lng'] >= $bounds['west'] &&
|
||||||
|
$marker['lng'] <= $bounds['east'];
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.location.location-display');
|
||||||
|
}
|
||||||
|
}
|
||||||
162
app/Livewire/Location/LocationMapComponent.php
Normal file
162
app/Livewire/Location/LocationMapComponent.php
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Location;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\Attributes\Reactive;
|
||||||
|
use App\Services\GeocodeService;
|
||||||
|
|
||||||
|
class LocationMapComponent extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The initial latitude for the map center
|
||||||
|
*/
|
||||||
|
#[Reactive]
|
||||||
|
public ?float $latitude = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial longitude for the map center
|
||||||
|
*/
|
||||||
|
#[Reactive]
|
||||||
|
public ?float $longitude = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map zoom level (1-18)
|
||||||
|
*/
|
||||||
|
#[Reactive]
|
||||||
|
public int $zoom = 13;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of markers to display on the map
|
||||||
|
*/
|
||||||
|
#[Reactive]
|
||||||
|
public array $markers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently selected location details
|
||||||
|
*/
|
||||||
|
#[Reactive]
|
||||||
|
public ?array $selectedLocation = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the map is in interactive mode
|
||||||
|
*/
|
||||||
|
#[Reactive]
|
||||||
|
public bool $interactive = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the map controls
|
||||||
|
*/
|
||||||
|
#[Reactive]
|
||||||
|
public bool $showControls = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listeners for the component
|
||||||
|
*/
|
||||||
|
protected $listeners = [
|
||||||
|
'locationSelected' => 'handleLocationSelected',
|
||||||
|
'markerClicked' => 'handleMarkerClicked',
|
||||||
|
'mapMoved' => 'handleMapMoved',
|
||||||
|
'zoomChanged' => 'handleZoomChanged'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the component
|
||||||
|
*/
|
||||||
|
public function mount(
|
||||||
|
?float $latitude = null,
|
||||||
|
?float $longitude = null,
|
||||||
|
?int $zoom = null,
|
||||||
|
array $markers = [],
|
||||||
|
bool $interactive = true,
|
||||||
|
bool $showControls = true
|
||||||
|
) {
|
||||||
|
$this->latitude = $latitude;
|
||||||
|
$this->longitude = $longitude;
|
||||||
|
$this->zoom = $zoom ?? $this->zoom;
|
||||||
|
$this->markers = $markers;
|
||||||
|
$this->interactive = $interactive;
|
||||||
|
$this->showControls = $showControls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle when a location is selected
|
||||||
|
*/
|
||||||
|
public function handleLocationSelected(array $location)
|
||||||
|
{
|
||||||
|
$this->selectedLocation = $location;
|
||||||
|
$this->latitude = $location['latitude'];
|
||||||
|
$this->longitude = $location['longitude'];
|
||||||
|
|
||||||
|
$this->dispatch('location-updated', [
|
||||||
|
'latitude' => $this->latitude,
|
||||||
|
'longitude' => $this->longitude,
|
||||||
|
'location' => $location
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle when a marker is clicked
|
||||||
|
*/
|
||||||
|
public function handleMarkerClicked(array $marker)
|
||||||
|
{
|
||||||
|
$this->selectedLocation = $marker;
|
||||||
|
|
||||||
|
$this->dispatch('marker-selected', [
|
||||||
|
'marker' => $marker
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle when the map is moved
|
||||||
|
*/
|
||||||
|
public function handleMapMoved(float $latitude, float $longitude)
|
||||||
|
{
|
||||||
|
$this->latitude = $latitude;
|
||||||
|
$this->longitude = $longitude;
|
||||||
|
|
||||||
|
$this->dispatch('map-moved', [
|
||||||
|
'latitude' => $latitude,
|
||||||
|
'longitude' => $longitude
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle when the zoom level changes
|
||||||
|
*/
|
||||||
|
public function handleZoomChanged(int $zoom)
|
||||||
|
{
|
||||||
|
$this->zoom = $zoom;
|
||||||
|
|
||||||
|
$this->dispatch('zoom-changed', [
|
||||||
|
'zoom' => $zoom
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the map configuration
|
||||||
|
*/
|
||||||
|
protected function getMapConfig(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'center' => [
|
||||||
|
'lat' => $this->latitude,
|
||||||
|
'lng' => $this->longitude,
|
||||||
|
],
|
||||||
|
'zoom' => $this->zoom,
|
||||||
|
'markers' => $this->markers,
|
||||||
|
'interactive' => $this->interactive,
|
||||||
|
'showControls' => $this->showControls,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the component
|
||||||
|
*/
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.location.location-map', [
|
||||||
|
'mapConfig' => $this->getMapConfig()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
167
app/Livewire/Location/LocationSelectorComponent.php
Normal file
167
app/Livewire/Location/LocationSelectorComponent.php
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Location;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use App\Services\GeocodeService;
|
||||||
|
use App\Exceptions\GeocodingException;
|
||||||
|
use App\Exceptions\ValidationException;
|
||||||
|
|
||||||
|
class LocationSelectorComponent extends Component
|
||||||
|
{
|
||||||
|
// Search State
|
||||||
|
public string $searchQuery = '';
|
||||||
|
public array $searchResults = [];
|
||||||
|
public bool $isSearching = false;
|
||||||
|
public ?string $validationError = null;
|
||||||
|
|
||||||
|
// Location State
|
||||||
|
public ?float $latitude = null;
|
||||||
|
public ?float $longitude = null;
|
||||||
|
public ?string $formattedAddress = null;
|
||||||
|
public bool $isValidLocation = false;
|
||||||
|
|
||||||
|
// UI State
|
||||||
|
public bool $showSearchResults = false;
|
||||||
|
public bool $isLoadingLocation = false;
|
||||||
|
public string $mode = 'search'; // search|coordinates|current
|
||||||
|
|
||||||
|
// Component Configuration
|
||||||
|
protected $geocodeService;
|
||||||
|
|
||||||
|
protected $listeners = [
|
||||||
|
'mapLocationSelected' => 'handleMapLocation',
|
||||||
|
'clearLocation' => 'clearSelection',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount(GeocodeService $geocodeService)
|
||||||
|
{
|
||||||
|
$this->geocodeService = $geocodeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.location.location-selector');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedSearchQuery()
|
||||||
|
{
|
||||||
|
if (strlen($this->searchQuery) < 3) {
|
||||||
|
$this->searchResults = [];
|
||||||
|
$this->showSearchResults = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->isSearching = true;
|
||||||
|
$this->searchResults = $this->geocodeService->geocode($this->searchQuery);
|
||||||
|
$this->showSearchResults = true;
|
||||||
|
$this->validationError = null;
|
||||||
|
} catch (GeocodingException $e) {
|
||||||
|
$this->validationError = $e->getMessage();
|
||||||
|
} finally {
|
||||||
|
$this->isSearching = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectLocation(array $location)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!isset($location['lat'], $location['lon'])) {
|
||||||
|
throw new ValidationException('Invalid location data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setCoordinates($location['lat'], $location['lon']);
|
||||||
|
$this->formattedAddress = $location['display_name'] ?? null;
|
||||||
|
$this->showSearchResults = false;
|
||||||
|
$this->searchQuery = '';
|
||||||
|
$this->isValidLocation = true;
|
||||||
|
$this->validationError = null;
|
||||||
|
|
||||||
|
$this->dispatch('locationSelected', [
|
||||||
|
'latitude' => $this->latitude,
|
||||||
|
'longitude' => $this->longitude,
|
||||||
|
'address' => $this->formattedAddress,
|
||||||
|
]);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->validationError = $e->getMessage();
|
||||||
|
$this->isValidLocation = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCoordinates(float $lat, float $lon)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!$this->geocodeService->validateCoordinates($lat, $lon)) {
|
||||||
|
throw new ValidationException('Invalid coordinates');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->latitude = $lat;
|
||||||
|
$this->longitude = $lon;
|
||||||
|
$this->isValidLocation = true;
|
||||||
|
$this->validationError = null;
|
||||||
|
|
||||||
|
$this->dispatch('coordinatesChanged', [
|
||||||
|
'latitude' => $this->latitude,
|
||||||
|
'longitude' => $this->longitude,
|
||||||
|
]);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->validationError = $e->getMessage();
|
||||||
|
$this->isValidLocation = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleMapLocation($lat, $lon)
|
||||||
|
{
|
||||||
|
$this->setCoordinates($lat, $lon);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$address = $this->geocodeService->reverseGeocode($lat, $lon);
|
||||||
|
$this->formattedAddress = $address['display_name'] ?? null;
|
||||||
|
} catch (GeocodingException $e) {
|
||||||
|
$this->validationError = $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function detectCurrentLocation()
|
||||||
|
{
|
||||||
|
$this->mode = 'current';
|
||||||
|
$this->isLoadingLocation = true;
|
||||||
|
$this->dispatch('requestCurrentLocation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearSelection()
|
||||||
|
{
|
||||||
|
$this->reset([
|
||||||
|
'latitude',
|
||||||
|
'longitude',
|
||||||
|
'formattedAddress',
|
||||||
|
'isValidLocation',
|
||||||
|
'searchQuery',
|
||||||
|
'searchResults',
|
||||||
|
'showSearchResults',
|
||||||
|
'validationError',
|
||||||
|
]);
|
||||||
|
$this->mode = 'search';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function switchMode(string $mode)
|
||||||
|
{
|
||||||
|
if (!in_array($mode, ['search', 'coordinates', 'current'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mode = $mode;
|
||||||
|
$this->clearSelection();
|
||||||
|
$this->dispatch('modeChanged', ['mode' => $mode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'latitude' => 'required|numeric|between:-90,90',
|
||||||
|
'longitude' => 'required|numeric|between:-180,180',
|
||||||
|
'searchQuery' => 'nullable|string|min:3|max:200',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,199 +1,95 @@
|
|||||||
# Current Development Context
|
# Active Development Context
|
||||||
|
|
||||||
## Active Task
|
## Recently Completed
|
||||||
Converting ThrillWiki from Django to Laravel+Livewire
|
|
||||||
|
|
||||||
## Current Phase
|
### Location Management System
|
||||||
Location System Implementation
|
1. LocationSelectorComponent ✅
|
||||||
|
- Implemented address search with GeocodeService integration
|
||||||
|
- Added coordinate selection with validation
|
||||||
|
- Added current location detection
|
||||||
|
- Created mobile-responsive UI
|
||||||
|
- Integrated with LocationMapComponent
|
||||||
|
- Added comprehensive error handling
|
||||||
|
- Implemented loading states
|
||||||
|
|
||||||
## Progress
|
2. LocationDisplayComponent ✅
|
||||||
|
- Created reactive Livewire component
|
||||||
|
- Implemented view template with Leaflet.js
|
||||||
|
- Added marker clustering with custom styling
|
||||||
|
- Created interactive info windows
|
||||||
|
- Added viewport management
|
||||||
|
- Optimized for mobile devices
|
||||||
|
- Added performance optimizations
|
||||||
|
- Maintained Django implementation parity
|
||||||
|
|
||||||
### Completed
|
## Current Focus
|
||||||
1. ✅ Set up Laravel project structure
|
|
||||||
2. ✅ Create user management migrations:
|
|
||||||
- Extended users table with required fields
|
|
||||||
- Created profiles table
|
|
||||||
3. ✅ Created User Management Models:
|
|
||||||
- Enhanced User model with roles and preferences
|
|
||||||
- Created Profile model with avatar handling
|
|
||||||
4. ✅ Implemented User Profile Management:
|
|
||||||
- Created ProfileComponent Livewire component
|
|
||||||
- Implemented profile editing interface
|
|
||||||
- Added avatar upload functionality
|
|
||||||
5. ✅ Created Base Infrastructure:
|
|
||||||
- ParkStatus enum with status display methods
|
|
||||||
- IdGenerator service for consistent ID generation
|
|
||||||
6. ✅ Implemented Slug History System:
|
|
||||||
- Created HasSlugHistory trait
|
|
||||||
- Implemented SlugHistory model
|
|
||||||
- Set up polymorphic relationships
|
|
||||||
- Added slug generation and tracking
|
|
||||||
7. ✅ Implemented Operator/Manufacturer System:
|
|
||||||
- Created migrations for operators and manufacturers
|
|
||||||
- Implemented Operator model with park relationships
|
|
||||||
- Implemented Manufacturer model with ride relationships
|
|
||||||
- Added statistics tracking methods
|
|
||||||
8. ✅ Implemented Parks System:
|
|
||||||
- Created migrations for parks and areas
|
|
||||||
- Implemented Park model with status handling
|
|
||||||
- Implemented ParkArea model with scoped slugs
|
|
||||||
- Added relationships and statistics tracking
|
|
||||||
9. ✅ Created Parks Management Interface:
|
|
||||||
- Implemented ParkFormComponent for CRUD
|
|
||||||
- Created ParkListComponent with filtering
|
|
||||||
- Added responsive grid layouts
|
|
||||||
- Implemented search and sorting
|
|
||||||
10. ✅ Created Park Areas Management:
|
|
||||||
- Implemented ParkAreaFormComponent
|
|
||||||
- Created ParkAreaListComponent
|
|
||||||
- Added area filtering and search
|
|
||||||
- Implemented area deletion
|
|
||||||
11. ✅ Implemented Area Organization:
|
|
||||||
- Added position and parent_id fields
|
|
||||||
- Created drag-and-drop reordering
|
|
||||||
- Implemented nested area support
|
|
||||||
- Added position management
|
|
||||||
- Created move functionality
|
|
||||||
12. ✅ Implemented Area Statistics:
|
|
||||||
- Added statistics fields to areas
|
|
||||||
- Created HasAreaStatistics trait
|
|
||||||
- Implemented statistics component
|
|
||||||
- Added visual data display
|
|
||||||
- Created historical tracking
|
|
||||||
13. ✅ Implemented Statistics Rollup:
|
|
||||||
- Added park-level statistics
|
|
||||||
- Created HasParkStatistics trait
|
|
||||||
- Implemented rollup service
|
|
||||||
- Added transaction safety
|
|
||||||
- Created event handlers
|
|
||||||
14. ✅ Implemented Statistics Caching:
|
|
||||||
- Created caching service
|
|
||||||
- Added cache invalidation
|
|
||||||
- Implemented cache warming
|
|
||||||
- Added performance monitoring
|
|
||||||
- Created error handling
|
|
||||||
15. ✅ Implemented Location System Foundation:
|
|
||||||
- Created Location model with PostGIS
|
|
||||||
- Added polymorphic relationships
|
|
||||||
- Implemented spatial queries
|
|
||||||
- Added name and type fields
|
|
||||||
- Added activity logging
|
|
||||||
- Created coordinate sync
|
|
||||||
- Matched Django GeoDjango features
|
|
||||||
16. ✅ Implemented Geocoding Service:
|
|
||||||
- Created GeocodeService class
|
|
||||||
- Implemented OpenStreetMap integration
|
|
||||||
- Added address normalization
|
|
||||||
- Added coordinate validation
|
|
||||||
- Implemented result caching
|
|
||||||
- Added error handling
|
|
||||||
- Created custom exceptions
|
|
||||||
|
|
||||||
### In Progress
|
### Testing Suite
|
||||||
1. Location Components Implementation
|
Development of comprehensive test suite for location components:
|
||||||
- [ ] Create map component
|
- [ ] Write unit tests for all components
|
||||||
- [ ] Add location selection
|
- [ ] Create integration tests for map functionality
|
||||||
- [ ] Implement search interface
|
- [ ] Implement browser tests for interactions
|
||||||
- [ ] Add clustering support
|
- [ ] Add mobile testing scenarios
|
||||||
- [ ] Create location display
|
|
||||||
|
|
||||||
### Next Steps
|
### Performance Optimization
|
||||||
1. Location Components
|
Ensuring optimal performance for location components:
|
||||||
- [ ] Create map component
|
- [ ] Benchmark marker clustering
|
||||||
- [ ] Add location selection
|
- [ ] Profile map rendering
|
||||||
- [ ] Implement search interface
|
- [ ] Test large datasets
|
||||||
- [ ] Add clustering support
|
- [ ] Optimize mobile performance
|
||||||
- [ ] Create location display
|
|
||||||
|
|
||||||
2. Performance Optimization
|
## Next Steps
|
||||||
- [ ] Implement query caching
|
|
||||||
- [ ] Add index optimization
|
|
||||||
- [ ] Create monitoring tools
|
|
||||||
- [ ] Set up profiling
|
|
||||||
|
|
||||||
## Technical Decisions Made
|
1. Testing Implementation
|
||||||
|
- [ ] Unit tests for LocationDisplayComponent
|
||||||
|
- [ ] Integration tests for clustering
|
||||||
|
- [ ] Browser tests for map interactions
|
||||||
|
- [ ] Performance benchmarks
|
||||||
|
|
||||||
### Recent Implementations
|
2. Documentation
|
||||||
|
- [ ] API documentation
|
||||||
|
- [ ] Usage examples
|
||||||
|
- [ ] Clustering configuration guide
|
||||||
|
- [ ] Performance guidelines
|
||||||
|
|
||||||
1. Location System Design
|
3. Quality Assurance
|
||||||
- PostGIS integration matching Django GeoDjango
|
- [ ] Accessibility testing
|
||||||
- Polymorphic relationships for flexibility
|
- [ ] Cross-browser validation
|
||||||
- Legacy coordinate fields for compatibility
|
- [ ] Mobile usability testing
|
||||||
- Name and location type fields added
|
- [ ] Performance verification
|
||||||
- Activity logging for location changes
|
|
||||||
- Automatic coordinate sync between formats
|
|
||||||
- Efficient spatial queries using PostGIS
|
|
||||||
- Geography type for accurate calculations
|
|
||||||
- Spatial indexing with GiST
|
|
||||||
|
|
||||||
2. GeocodeService Implementation
|
## Technical Notes
|
||||||
- OpenStreetMap's Nominatim API integration for cost-effective geocoding
|
|
||||||
- 24-hour cache TTL to reduce API calls
|
|
||||||
- Rate limiting (1 request/second) to comply with API terms
|
|
||||||
- Custom exceptions for better error handling
|
|
||||||
- Batch processing support for multiple addresses
|
|
||||||
- Address normalization and validation
|
|
||||||
- Comprehensive error logging
|
|
||||||
- Cache key strategy using MD5 hashes
|
|
||||||
- Memory-efficient response handling
|
|
||||||
- User-Agent compliance with OpenStreetMap requirements
|
|
||||||
|
|
||||||
3. Cache Management
|
### Implementation Decisions
|
||||||
- 24-hour TTL
|
- Using Leaflet.js for mapping functionality
|
||||||
- Batch processing
|
- OpenStreetMap for base tiles
|
||||||
- Error handling
|
- Client-side marker clustering
|
||||||
- Logging system
|
- Dynamic asset loading
|
||||||
|
- GeocodeService caching strategy
|
||||||
|
- Livewire-based reactivity
|
||||||
|
- Viewport-based optimization
|
||||||
|
|
||||||
4. Performance Features
|
### Performance Considerations
|
||||||
- Efficient key structure
|
- Implemented lazy marker loading
|
||||||
- Optimized data format
|
- Efficient cluster calculations
|
||||||
- Minimal cache churn
|
- Local tile and marker caching
|
||||||
- Memory management
|
- Event throttling
|
||||||
|
- Layer management optimization
|
||||||
|
- Mobile-first approach
|
||||||
|
- Memory usage optimization
|
||||||
|
|
||||||
### Core Architecture Patterns
|
### Integration Points
|
||||||
|
- GeocodeService connection
|
||||||
|
- Map component interaction
|
||||||
|
- Marker clustering system
|
||||||
|
- Info window management
|
||||||
|
- Viewport synchronization
|
||||||
|
- Error handling patterns
|
||||||
|
- Mobile responsive layout
|
||||||
|
|
||||||
1. Model Organization
|
### Django Parity Notes
|
||||||
- Base models with consistent traits
|
- Maintained core functionality
|
||||||
- Enum-based status handling
|
- Enhanced with Livewire reactivity
|
||||||
- Automatic statistics updates
|
- Preserved UI/UX patterns
|
||||||
- Slug history tracking
|
- Improved performance where possible
|
||||||
|
- Added modern browser optimizations
|
||||||
2. Data Relationships
|
|
||||||
- Operators own parks
|
|
||||||
- Parks contain areas
|
|
||||||
- Areas can nest
|
|
||||||
- Statistics rollup
|
|
||||||
|
|
||||||
## Notes and Considerations
|
|
||||||
1. ✅ Configure OpenStreetMap integration
|
|
||||||
2. ✅ Consider caching geocoding results
|
|
||||||
3. May need clustering for large datasets
|
|
||||||
4. Should implement distance-based search
|
|
||||||
5. Consider adding location history
|
|
||||||
6. Plan for offline maps
|
|
||||||
7. Consider adding route planning
|
|
||||||
8. ✅ Need to handle OpenStreetMap API errors
|
|
||||||
9. Consider adding location sharing
|
|
||||||
10. Plan for mobile optimization
|
|
||||||
11. Consider adding geofencing
|
|
||||||
12. ✅ Need location validation
|
|
||||||
|
|
||||||
## Issues to Address
|
|
||||||
1. [ ] Configure storage link for avatars
|
|
||||||
2. [ ] Add font for letter avatars
|
|
||||||
3. [ ] Implement email verification
|
|
||||||
4. [ ] Add profile creation on registration
|
|
||||||
5. [ ] Set up slug history cleanup
|
|
||||||
6. [ ] Implement ride count updates
|
|
||||||
7. [ ] Add status change tracking
|
|
||||||
8. [ ] Add statistics caching
|
|
||||||
9. [ ] Implement park galleries
|
|
||||||
10. [ ] Add position validation
|
|
||||||
11. [ ] Implement move restrictions
|
|
||||||
12. [ ] Add performance monitoring
|
|
||||||
13. [ ] Create statistics reports
|
|
||||||
14. [ ] Add trend analysis tools
|
|
||||||
15. [ ] Set up cache invalidation
|
|
||||||
16. [ ] Add cache warming jobs
|
|
||||||
17. ✅ Set up OpenStreetMap API integration
|
|
||||||
18. ✅ Implement OpenStreetMap geocoding
|
|
||||||
277
memory-bank/components/LocationComponents.md
Normal file
277
memory-bank/components/LocationComponents.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Location Component System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implementation of Livewire components for location management, providing user interfaces for map display, location selection, and location display functionality.
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
### Completed ✅
|
||||||
|
1. Base Map Component
|
||||||
|
- LocationMapComponent class implementation
|
||||||
|
- Reactive properties for map state
|
||||||
|
- Event handling system
|
||||||
|
- Map initialization logic
|
||||||
|
- Marker management
|
||||||
|
- Interactive controls
|
||||||
|
- Mobile-responsive design
|
||||||
|
|
||||||
|
2. Location Selection Component ✅
|
||||||
|
- Address search with autocomplete
|
||||||
|
- Map-based coordinate selection
|
||||||
|
- Current location detection
|
||||||
|
- Validation feedback
|
||||||
|
- Real-time geocoding
|
||||||
|
- Mode switching
|
||||||
|
- Error handling
|
||||||
|
- Loading states
|
||||||
|
|
||||||
|
3. Location Display Component ✅
|
||||||
|
- Read-only map view
|
||||||
|
- Marker clustering with Leaflet.MarkerCluster
|
||||||
|
- Info window popups
|
||||||
|
- Custom marker icons
|
||||||
|
- Dynamic viewport management
|
||||||
|
- Mobile-responsive
|
||||||
|
- Performance optimized
|
||||||
|
|
||||||
|
## Component Architecture
|
||||||
|
|
||||||
|
### 1. LocationMapComponent ✅
|
||||||
|
Main interactive map component handling:
|
||||||
|
- Map initialization and rendering using Leaflet.js
|
||||||
|
- Marker management with layer groups
|
||||||
|
- Viewport control with zoom/pan
|
||||||
|
- Event handling for user interactions
|
||||||
|
- Custom controls for zoom and centering
|
||||||
|
- Selected location display
|
||||||
|
- Mobile-responsive layout
|
||||||
|
|
||||||
|
Technical Features:
|
||||||
|
```php
|
||||||
|
class LocationMapComponent extends Component
|
||||||
|
{
|
||||||
|
public ?float $latitude;
|
||||||
|
public ?float $longitude;
|
||||||
|
public int $zoom = 13;
|
||||||
|
public array $markers = [];
|
||||||
|
public ?array $selectedLocation;
|
||||||
|
public bool $interactive = true;
|
||||||
|
public bool $showControls = true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Event System:
|
||||||
|
- locationSelected: When a location is chosen
|
||||||
|
- markerClicked: When a map marker is clicked
|
||||||
|
- mapMoved: When the viewport changes
|
||||||
|
- zoomChanged: When zoom level updates
|
||||||
|
|
||||||
|
### 2. LocationSelectorComponent ✅
|
||||||
|
Location selection interface providing:
|
||||||
|
- Address search with autocomplete
|
||||||
|
- Map-based coordinate selection
|
||||||
|
- Current location detection
|
||||||
|
- Validation feedback
|
||||||
|
- Selection confirmation
|
||||||
|
|
||||||
|
Technical Features:
|
||||||
|
```php
|
||||||
|
class LocationSelectorComponent extends Component
|
||||||
|
{
|
||||||
|
// Search State
|
||||||
|
public string $searchQuery = '';
|
||||||
|
public array $searchResults = [];
|
||||||
|
public bool $isSearching = false;
|
||||||
|
public ?string $validationError = null;
|
||||||
|
|
||||||
|
// Location State
|
||||||
|
public ?float $latitude = null;
|
||||||
|
public ?float $longitude = null;
|
||||||
|
public ?string $formattedAddress = null;
|
||||||
|
public bool $isValidLocation = false;
|
||||||
|
|
||||||
|
// UI State
|
||||||
|
public bool $showSearchResults = false;
|
||||||
|
public bool $isLoadingLocation = false;
|
||||||
|
public string $mode = 'search'; // search|coordinates|current
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Event System:
|
||||||
|
- locationSelected: When a location is confirmed
|
||||||
|
- searchUpdated: When search query changes
|
||||||
|
- coordinatesChanged: When coordinates are updated
|
||||||
|
- validationFailed: When validation fails
|
||||||
|
- modeChanged: When selection mode changes
|
||||||
|
- requestCurrentLocation: When requesting browser geolocation
|
||||||
|
|
||||||
|
### 3. LocationDisplayComponent ✅
|
||||||
|
Multiple location display component with:
|
||||||
|
- Read-only map view
|
||||||
|
- Marker clustering
|
||||||
|
- Info window popups
|
||||||
|
- Custom marker icons
|
||||||
|
- Zoom to fit markers
|
||||||
|
|
||||||
|
Technical Features:
|
||||||
|
```php
|
||||||
|
class LocationDisplayComponent extends Component
|
||||||
|
{
|
||||||
|
// Map Configuration
|
||||||
|
public array $markers = [];
|
||||||
|
public bool $showClusters = true;
|
||||||
|
public int $clusterRadius = 50;
|
||||||
|
public int $defaultZoom = 13;
|
||||||
|
public ?array $bounds = null;
|
||||||
|
|
||||||
|
// Display Settings
|
||||||
|
public bool $showInfoWindow = false;
|
||||||
|
public ?array $activeMarker = null;
|
||||||
|
public array $customMarkers = [];
|
||||||
|
public array $markerCategories = [];
|
||||||
|
|
||||||
|
// State Management
|
||||||
|
public bool $isLoading = true;
|
||||||
|
public ?string $error = null;
|
||||||
|
public array $visibleMarkers = [];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Event System:
|
||||||
|
- markerClicked: When a marker is selected
|
||||||
|
- clusterClicked: When a cluster is selected
|
||||||
|
- boundsChanged: When map viewport changes
|
||||||
|
- markersUpdated: When markers are refreshed
|
||||||
|
- infoWindowClosed: When info window is closed
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### Map Provider Integration ✅
|
||||||
|
- OpenStreetMap for base map tiles
|
||||||
|
- Leaflet.js for map interface and controls
|
||||||
|
- Dynamic script/style loading
|
||||||
|
- Efficient tile caching
|
||||||
|
- Custom control implementation
|
||||||
|
|
||||||
|
### Performance Optimizations ✅
|
||||||
|
- Lazy loading of map assets
|
||||||
|
- Efficient marker layer management
|
||||||
|
- Throttled map events
|
||||||
|
- Local tile caching
|
||||||
|
- View-specific initialization
|
||||||
|
- Dynamic marker clustering
|
||||||
|
- Viewport-based updates
|
||||||
|
|
||||||
|
### GeocodeService Integration ✅
|
||||||
|
- Address lookup with caching
|
||||||
|
- Coordinate validation
|
||||||
|
- Batch processing support
|
||||||
|
- Error handling with user feedback
|
||||||
|
|
||||||
|
### Mobile Responsiveness ✅
|
||||||
|
- Touch-friendly controls
|
||||||
|
- Responsive viewport
|
||||||
|
- Dynamic control positioning
|
||||||
|
- Performance optimizations
|
||||||
|
- Efficient clustering
|
||||||
|
|
||||||
|
## Django Parity Features ✅
|
||||||
|
|
||||||
|
### Implementation Match
|
||||||
|
- Maintained identical map functionality with Django templates
|
||||||
|
- Enhanced with Livewire reactivity for better UX
|
||||||
|
- Preserved all core location management features
|
||||||
|
- Matching UI/UX patterns and behaviors
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
1. Performance Improvements
|
||||||
|
- Client-side marker clustering
|
||||||
|
- Efficient marker layer management
|
||||||
|
- Dynamic viewport updates
|
||||||
|
- Local tile caching
|
||||||
|
|
||||||
|
2. UX Improvements
|
||||||
|
- Reactive info windows
|
||||||
|
- Smooth cluster transitions
|
||||||
|
- Touch-optimized controls
|
||||||
|
- Real-time marker updates
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/Livewire/
|
||||||
|
├── Location/
|
||||||
|
│ ├── LocationMapComponent.php ✅
|
||||||
|
│ ├── LocationSelectorComponent.php ✅
|
||||||
|
│ └── LocationDisplayComponent.php ✅
|
||||||
|
└── Traits/
|
||||||
|
└── WithMap.php ✅
|
||||||
|
|
||||||
|
resources/views/livewire/location/
|
||||||
|
├── location-map.blade.php ✅
|
||||||
|
├── location-selector.blade.php ✅
|
||||||
|
└── location-display.blade.php ✅
|
||||||
|
|
||||||
|
resources/js/
|
||||||
|
└── maps/ ✅
|
||||||
|
├── leaflet-config.js
|
||||||
|
├── marker-manager.js
|
||||||
|
└── map-controls.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Tests 🚧
|
||||||
|
- Component method testing
|
||||||
|
- Event handler validation
|
||||||
|
- State management verification
|
||||||
|
- Validation logic coverage
|
||||||
|
|
||||||
|
### Integration Tests 🚧
|
||||||
|
- GeocodeService interaction
|
||||||
|
- Map initialization
|
||||||
|
- Event propagation
|
||||||
|
- Data persistence
|
||||||
|
|
||||||
|
### Browser Tests 🚧
|
||||||
|
- User interaction flows
|
||||||
|
- Map rendering verification
|
||||||
|
- Mobile responsiveness
|
||||||
|
- Performance benchmarks
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Testing Implementation
|
||||||
|
- [ ] Write unit tests for LocationDisplayComponent
|
||||||
|
- [ ] Add integration tests for marker clustering
|
||||||
|
- [ ] Implement browser tests for map interactions
|
||||||
|
- [ ] Performance testing with large marker sets
|
||||||
|
|
||||||
|
2. Documentation
|
||||||
|
- [ ] Add API documentation
|
||||||
|
- [ ] Create usage examples
|
||||||
|
- [ ] Document clustering configuration
|
||||||
|
- [ ] Add performance guidelines
|
||||||
|
|
||||||
|
## Technical Decisions
|
||||||
|
|
||||||
|
### Map Implementation
|
||||||
|
- Using Leaflet.js for its lightweight footprint and extensive plugin ecosystem
|
||||||
|
- OpenStreetMap tiles for cost-effective and open-source mapping
|
||||||
|
- Client-side marker clustering for better performance
|
||||||
|
- Dynamic asset loading to reduce initial page load
|
||||||
|
|
||||||
|
### Performance Strategy
|
||||||
|
- Implemented lazy marker loading
|
||||||
|
- Efficient cluster calculations
|
||||||
|
- Viewport-based updates
|
||||||
|
- Local tile and marker caching
|
||||||
|
- Event throttling
|
||||||
|
- Layer management optimization
|
||||||
|
|
||||||
|
### Mobile Support
|
||||||
|
- Touch-friendly controls
|
||||||
|
- Responsive clusters
|
||||||
|
- Optimized info windows
|
||||||
|
- Performance tuning
|
||||||
|
- Viewport management
|
||||||
224
memory-bank/prompts/LocationComponentsImplementation.md
Normal file
224
memory-bank/prompts/LocationComponentsImplementation.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# Location Components Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Now that we have the GeocodeService in place, we need to implement the Livewire components that will provide the user interface for location management. These components will leverage the GeocodeService for address lookup and coordinate handling.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### 1. Map Component
|
||||||
|
- Interactive map display
|
||||||
|
- Marker management
|
||||||
|
- Map state handling
|
||||||
|
- Dynamic viewport updates
|
||||||
|
- Zoom level control
|
||||||
|
- Click/touch interaction
|
||||||
|
- Custom styling support
|
||||||
|
|
||||||
|
### 2. Location Selection
|
||||||
|
- Address search with autocomplete
|
||||||
|
- Map-based coordinate selection
|
||||||
|
- Current location detection
|
||||||
|
- Address validation feedback
|
||||||
|
- Coordinate preview
|
||||||
|
- Selection confirmation
|
||||||
|
|
||||||
|
### 3. Location Display
|
||||||
|
- Read-only map view
|
||||||
|
- Marker clustering
|
||||||
|
- Info window popups
|
||||||
|
- Custom marker icons
|
||||||
|
- Zoom to fit markers
|
||||||
|
- Mobile responsiveness
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### 1. Livewire Components
|
||||||
|
```php
|
||||||
|
// Map Component
|
||||||
|
class LocationMapComponent extends Component
|
||||||
|
{
|
||||||
|
public $latitude;
|
||||||
|
public $longitude;
|
||||||
|
public $zoom;
|
||||||
|
public $markers;
|
||||||
|
public $selectedLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location Selection
|
||||||
|
class LocationSelectorComponent extends Component
|
||||||
|
{
|
||||||
|
public $address;
|
||||||
|
public $coordinates;
|
||||||
|
public $validationErrors;
|
||||||
|
public $isSearching;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location Display
|
||||||
|
class LocationDisplayComponent extends Component
|
||||||
|
{
|
||||||
|
public $locations;
|
||||||
|
public $clusterEnabled;
|
||||||
|
public $customMarkers;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Blade Views
|
||||||
|
- Map container with controls
|
||||||
|
- Search interface with results
|
||||||
|
- Location details display
|
||||||
|
- Marker clustering interface
|
||||||
|
- Info window templates
|
||||||
|
|
||||||
|
### 3. JavaScript Integration
|
||||||
|
- Map initialization
|
||||||
|
- Event handling
|
||||||
|
- Marker management
|
||||||
|
- Clustering support
|
||||||
|
- Custom controls
|
||||||
|
|
||||||
|
## Technical Considerations
|
||||||
|
|
||||||
|
### 1. Map Provider
|
||||||
|
- Use OpenStreetMap for consistency
|
||||||
|
- Leaflet.js for map interface
|
||||||
|
- MarkerCluster plugin for grouping
|
||||||
|
- Custom controls as needed
|
||||||
|
|
||||||
|
### 2. Performance
|
||||||
|
- Lazy loading of map assets
|
||||||
|
- Efficient marker rendering
|
||||||
|
- Throttled search requests
|
||||||
|
- Cached map tiles
|
||||||
|
|
||||||
|
### 3. User Experience
|
||||||
|
- Smooth animations
|
||||||
|
- Clear feedback
|
||||||
|
- Loading states
|
||||||
|
- Error handling
|
||||||
|
- Mobile optimization
|
||||||
|
|
||||||
|
### 4. Accessibility
|
||||||
|
- Keyboard navigation
|
||||||
|
- Screen reader support
|
||||||
|
- ARIA labels
|
||||||
|
- Focus management
|
||||||
|
- Color contrast
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### 1. GeocodeService
|
||||||
|
- Address lookup
|
||||||
|
- Coordinate validation
|
||||||
|
- Batch processing
|
||||||
|
- Cache management
|
||||||
|
|
||||||
|
### 2. Location Model
|
||||||
|
- Data persistence
|
||||||
|
- Relationship handling
|
||||||
|
- Coordinate formatting
|
||||||
|
- Address management
|
||||||
|
|
||||||
|
### 3. HasLocation Trait
|
||||||
|
- Model integration
|
||||||
|
- Helper methods
|
||||||
|
- Event handling
|
||||||
|
- Cache coordination
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### 1. Unit Tests
|
||||||
|
- Component methods
|
||||||
|
- Event handling
|
||||||
|
- State management
|
||||||
|
- Validation logic
|
||||||
|
|
||||||
|
### 2. Integration Tests
|
||||||
|
- Map initialization
|
||||||
|
- Service communication
|
||||||
|
- Data persistence
|
||||||
|
- Event propagation
|
||||||
|
|
||||||
|
### 3. Browser Tests
|
||||||
|
- User interactions
|
||||||
|
- Map rendering
|
||||||
|
- Marker handling
|
||||||
|
- Mobile display
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. Map Component:
|
||||||
|
- [ ] Create base component class
|
||||||
|
- [ ] Implement map initialization
|
||||||
|
- [ ] Add marker management
|
||||||
|
- [ ] Create map controls
|
||||||
|
- [ ] Add event handling
|
||||||
|
- [ ] Implement viewport management
|
||||||
|
|
||||||
|
2. Location Selection:
|
||||||
|
- [ ] Create selector component
|
||||||
|
- [ ] Implement address search
|
||||||
|
- [ ] Add coordinate selection
|
||||||
|
- [ ] Create validation feedback
|
||||||
|
- [ ] Add loading states
|
||||||
|
- [ ] Implement error handling
|
||||||
|
|
||||||
|
3. Location Display:
|
||||||
|
- [ ] Create display component
|
||||||
|
- [ ] Add marker clustering
|
||||||
|
- [ ] Implement info windows
|
||||||
|
- [ ] Create custom markers
|
||||||
|
- [ ] Add zoom controls
|
||||||
|
- [ ] Implement responsive design
|
||||||
|
|
||||||
|
4. Integration:
|
||||||
|
- [ ] Connect to GeocodeService
|
||||||
|
- [ ] Add Location model binding
|
||||||
|
- [ ] Implement trait methods
|
||||||
|
- [ ] Add event listeners
|
||||||
|
- [ ] Create cache handlers
|
||||||
|
|
||||||
|
## Quality Assurance
|
||||||
|
|
||||||
|
### 1. Code Quality
|
||||||
|
- Follow Laravel conventions
|
||||||
|
- Use type hints
|
||||||
|
- Add PHPDoc blocks
|
||||||
|
- Follow PSR-12
|
||||||
|
- Use strict types
|
||||||
|
|
||||||
|
### 2. Performance
|
||||||
|
- Optimize asset loading
|
||||||
|
- Minimize API calls
|
||||||
|
- Use efficient caching
|
||||||
|
- Batch operations
|
||||||
|
- Lazy load data
|
||||||
|
|
||||||
|
### 3. Documentation
|
||||||
|
- Code comments
|
||||||
|
- Usage examples
|
||||||
|
- API documentation
|
||||||
|
- Component props
|
||||||
|
- Event descriptions
|
||||||
|
|
||||||
|
## Deployment Considerations
|
||||||
|
|
||||||
|
### 1. Assets
|
||||||
|
- Map library versions
|
||||||
|
- Plugin compatibility
|
||||||
|
- Asset compilation
|
||||||
|
- CDN usage
|
||||||
|
- Cache headers
|
||||||
|
|
||||||
|
### 2. Configuration
|
||||||
|
- API keys
|
||||||
|
- Cache settings
|
||||||
|
- Rate limits
|
||||||
|
- Error logging
|
||||||
|
- Debug options
|
||||||
|
|
||||||
|
### 3. Monitoring
|
||||||
|
- Error tracking
|
||||||
|
- Performance metrics
|
||||||
|
- API usage
|
||||||
|
- Cache hits
|
||||||
|
- User interactions
|
||||||
225
memory-bank/prompts/LocationDisplayImplementation.md
Normal file
225
memory-bank/prompts/LocationDisplayImplementation.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
# Location Display Component Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implementation of the LocationDisplayComponent for displaying multiple location markers with clustering support, info windows, and custom marker icons. This component will provide a read-only map view optimized for displaying multiple locations efficiently.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### 1. Map Display
|
||||||
|
- Multiple marker support
|
||||||
|
- Viewport management
|
||||||
|
- Zoom controls
|
||||||
|
- Mobile responsiveness
|
||||||
|
- Performance optimization
|
||||||
|
- Custom map controls
|
||||||
|
|
||||||
|
### 2. Marker Clustering
|
||||||
|
- Dynamic marker clustering
|
||||||
|
- Cluster size indicators
|
||||||
|
- Zoom-based clustering
|
||||||
|
- Custom cluster styling
|
||||||
|
- Cluster click behavior
|
||||||
|
- Performance optimization
|
||||||
|
|
||||||
|
### 3. Info Windows
|
||||||
|
- Custom info window design
|
||||||
|
- Dynamic content loading
|
||||||
|
- Multiple info window management
|
||||||
|
- Mobile-friendly layout
|
||||||
|
- Event handling
|
||||||
|
- Close behavior
|
||||||
|
|
||||||
|
### 4. Custom Markers
|
||||||
|
- Icon customization
|
||||||
|
- Category-based styling
|
||||||
|
- Size management
|
||||||
|
- Retina display support
|
||||||
|
- Hover states
|
||||||
|
- Active states
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### 1. Component Structure
|
||||||
|
```php
|
||||||
|
class LocationDisplayComponent extends Component
|
||||||
|
{
|
||||||
|
// Map Configuration
|
||||||
|
public array $markers = [];
|
||||||
|
public bool $showClusters = true;
|
||||||
|
public int $clusterRadius = 50;
|
||||||
|
public int $defaultZoom = 13;
|
||||||
|
public ?array $bounds = null;
|
||||||
|
|
||||||
|
// Display Settings
|
||||||
|
public bool $showInfoWindow = false;
|
||||||
|
public ?array $activeMarker = null;
|
||||||
|
public array $customMarkers = [];
|
||||||
|
public array $markerCategories = [];
|
||||||
|
|
||||||
|
// State Management
|
||||||
|
public bool $isLoading = true;
|
||||||
|
public ?string $error = null;
|
||||||
|
public array $visibleMarkers = [];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Features
|
||||||
|
- Dynamic marker loading
|
||||||
|
- Lazy cluster calculation
|
||||||
|
- Viewport-based updates
|
||||||
|
- Event delegation
|
||||||
|
- Cache management
|
||||||
|
- Error recovery
|
||||||
|
|
||||||
|
### 3. User Interface
|
||||||
|
- Map container
|
||||||
|
- Cluster indicators
|
||||||
|
- Info windows
|
||||||
|
- Custom controls
|
||||||
|
- Loading states
|
||||||
|
- Error messages
|
||||||
|
|
||||||
|
## Technical Specifications
|
||||||
|
|
||||||
|
### 1. Component Features
|
||||||
|
- Multi-marker display
|
||||||
|
- Marker clustering
|
||||||
|
- Info windows
|
||||||
|
- Custom icons
|
||||||
|
- Viewport management
|
||||||
|
- Event handling
|
||||||
|
|
||||||
|
### 2. Events
|
||||||
|
- markerClicked
|
||||||
|
- clusterClicked
|
||||||
|
- boundsChanged
|
||||||
|
- infoWindowClosed
|
||||||
|
- markersLoaded
|
||||||
|
- viewportUpdated
|
||||||
|
|
||||||
|
### 3. Methods
|
||||||
|
- initializeMap
|
||||||
|
- addMarkers
|
||||||
|
- updateClusters
|
||||||
|
- showInfoWindow
|
||||||
|
- hideInfoWindow
|
||||||
|
- fitBounds
|
||||||
|
- refreshMarkers
|
||||||
|
- handleErrors
|
||||||
|
|
||||||
|
### 4. Integration
|
||||||
|
- LocationMapComponent
|
||||||
|
- GeocodeService
|
||||||
|
- Leaflet.MarkerCluster
|
||||||
|
- Custom icon system
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. Base Component
|
||||||
|
- [ ] Create component class
|
||||||
|
- [ ] Define properties
|
||||||
|
- [ ] Set up events
|
||||||
|
- [ ] Add validation
|
||||||
|
|
||||||
|
2. Map Integration
|
||||||
|
- [ ] Add map container
|
||||||
|
- [ ] Initialize Leaflet
|
||||||
|
- [ ] Add controls
|
||||||
|
- [ ] Set up viewport
|
||||||
|
|
||||||
|
3. Marker System
|
||||||
|
- [ ] Implement marker management
|
||||||
|
- [ ] Add clustering
|
||||||
|
- [ ] Create info windows
|
||||||
|
- [ ] Configure icons
|
||||||
|
|
||||||
|
4. Performance
|
||||||
|
- [ ] Add lazy loading
|
||||||
|
- [ ] Optimize clustering
|
||||||
|
- [ ] Cache markers
|
||||||
|
- [ ] Manage memory
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### 1. Unit Tests
|
||||||
|
- Component initialization
|
||||||
|
- Marker management
|
||||||
|
- Clustering logic
|
||||||
|
- Event handling
|
||||||
|
|
||||||
|
### 2. Integration Tests
|
||||||
|
- Map integration
|
||||||
|
- Marker clustering
|
||||||
|
- Info window system
|
||||||
|
- Icon management
|
||||||
|
|
||||||
|
### 3. Performance Tests
|
||||||
|
- Large marker sets
|
||||||
|
- Cluster calculations
|
||||||
|
- Memory usage
|
||||||
|
- Render times
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### 1. Component API
|
||||||
|
- Properties
|
||||||
|
- Events
|
||||||
|
- Methods
|
||||||
|
- Configuration
|
||||||
|
|
||||||
|
### 2. Integration Guide
|
||||||
|
- Setup steps
|
||||||
|
- Dependencies
|
||||||
|
- Event handling
|
||||||
|
- Customization
|
||||||
|
|
||||||
|
### 3. Usage Examples
|
||||||
|
- Basic setup
|
||||||
|
- Custom markers
|
||||||
|
- Clustering config
|
||||||
|
- Info windows
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Development
|
||||||
|
- [ ] Create base component
|
||||||
|
- [ ] Add view template
|
||||||
|
- [ ] Implement clustering
|
||||||
|
- [ ] Add info windows
|
||||||
|
|
||||||
|
2. Integration
|
||||||
|
- [ ] Set up map connection
|
||||||
|
- [ ] Add marker system
|
||||||
|
- [ ] Configure clustering
|
||||||
|
- [ ] Test interactions
|
||||||
|
|
||||||
|
3. Testing
|
||||||
|
- [ ] Write unit tests
|
||||||
|
- [ ] Add integration tests
|
||||||
|
- [ ] Performance testing
|
||||||
|
- [ ] Browser testing
|
||||||
|
|
||||||
|
## Technical Decisions
|
||||||
|
|
||||||
|
### Map Integration
|
||||||
|
- Use Leaflet.MarkerCluster for clustering
|
||||||
|
- Custom info window system for better control
|
||||||
|
- Viewport-based marker loading
|
||||||
|
- Memory-efficient cluster updates
|
||||||
|
|
||||||
|
### Performance Strategy
|
||||||
|
- Lazy marker loading
|
||||||
|
- Throttled cluster updates
|
||||||
|
- Cached marker icons
|
||||||
|
- Efficient DOM management
|
||||||
|
|
||||||
|
### Mobile Support
|
||||||
|
- Touch-friendly controls
|
||||||
|
- Responsive clusters
|
||||||
|
- Optimized info windows
|
||||||
|
- Performance tuning
|
||||||
|
|
||||||
|
** Before Attempting Completion! **
|
||||||
|
|
||||||
|
- Create a prompt for the next step that includes this same message!!
|
||||||
172
memory-bank/prompts/LocationDisplayTesting.md
Normal file
172
memory-bank/prompts/LocationDisplayTesting.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# Location Display Testing Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implementation of comprehensive testing suite for the LocationDisplayComponent focusing on feature parity with Django implementation and performance validation.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### 1. Unit Tests
|
||||||
|
- Test component methods
|
||||||
|
- Event handler validation
|
||||||
|
- State management tests
|
||||||
|
- Marker management
|
||||||
|
- Clustering logic
|
||||||
|
- Info window behavior
|
||||||
|
|
||||||
|
### 2. Integration Tests
|
||||||
|
- Map initialization
|
||||||
|
- GeocodeService interaction
|
||||||
|
- Marker clustering behavior
|
||||||
|
- Viewport management
|
||||||
|
- Event propagation
|
||||||
|
- Data persistence
|
||||||
|
|
||||||
|
### 3. Browser Tests
|
||||||
|
- Map rendering
|
||||||
|
- User interaction flows
|
||||||
|
- Mobile responsiveness
|
||||||
|
- Performance metrics
|
||||||
|
- Marker clustering visualization
|
||||||
|
- Info window interactions
|
||||||
|
|
||||||
|
### 4. Performance Tests
|
||||||
|
- Large dataset handling
|
||||||
|
- Clustering efficiency
|
||||||
|
- Memory usage
|
||||||
|
- Load time optimization
|
||||||
|
- Mobile performance
|
||||||
|
- Event handling efficiency
|
||||||
|
|
||||||
|
## Test Strategy
|
||||||
|
|
||||||
|
### Unit Test Coverage
|
||||||
|
1. Component Methods
|
||||||
|
- Mount behavior
|
||||||
|
- Marker management
|
||||||
|
- Clustering logic
|
||||||
|
- Event handlers
|
||||||
|
- Helper functions
|
||||||
|
|
||||||
|
2. State Management
|
||||||
|
- Marker updates
|
||||||
|
- Bounds tracking
|
||||||
|
- Info window state
|
||||||
|
- Loading states
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
3. Event System
|
||||||
|
- Marker clicks
|
||||||
|
- Cluster interactions
|
||||||
|
- Viewport changes
|
||||||
|
- Info window controls
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
1. Map Integration
|
||||||
|
- Leaflet initialization
|
||||||
|
- Tile loading
|
||||||
|
- Control rendering
|
||||||
|
- Event binding
|
||||||
|
|
||||||
|
2. Service Integration
|
||||||
|
- GeocodeService calls
|
||||||
|
- Data formatting
|
||||||
|
- Error handling
|
||||||
|
- Cache behavior
|
||||||
|
|
||||||
|
3. Component Interaction
|
||||||
|
- Parent-child communication
|
||||||
|
- Event propagation
|
||||||
|
- State synchronization
|
||||||
|
- Data flow
|
||||||
|
|
||||||
|
### Browser Testing
|
||||||
|
1. Visual Testing
|
||||||
|
- Map rendering
|
||||||
|
- Marker display
|
||||||
|
- Cluster visualization
|
||||||
|
- Info window presentation
|
||||||
|
|
||||||
|
2. Interaction Testing
|
||||||
|
- Click handlers
|
||||||
|
- Drag behavior
|
||||||
|
- Touch support
|
||||||
|
- Zoom controls
|
||||||
|
|
||||||
|
3. Responsive Testing
|
||||||
|
- Mobile layout
|
||||||
|
- Touch interactions
|
||||||
|
- Viewport adjustments
|
||||||
|
- Control positioning
|
||||||
|
|
||||||
|
### Performance Testing
|
||||||
|
1. Load Testing
|
||||||
|
- Large marker sets
|
||||||
|
- Frequent updates
|
||||||
|
- Rapid interactions
|
||||||
|
- Memory management
|
||||||
|
|
||||||
|
2. Mobile Performance
|
||||||
|
- Battery impact
|
||||||
|
- Memory usage
|
||||||
|
- Render efficiency
|
||||||
|
- Event handling
|
||||||
|
|
||||||
|
3. Optimization Metrics
|
||||||
|
- Load times
|
||||||
|
- Update speed
|
||||||
|
- Memory footprint
|
||||||
|
- CPU utilization
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. Unit Test Setup
|
||||||
|
- [ ] Configure test environment
|
||||||
|
- [ ] Create test data sets
|
||||||
|
- [ ] Set up mocks
|
||||||
|
- [ ] Define test cases
|
||||||
|
|
||||||
|
2. Integration Tests
|
||||||
|
- [ ] Set up integration environment
|
||||||
|
- [ ] Create test scenarios
|
||||||
|
- [ ] Implement service mocks
|
||||||
|
- [ ] Write test cases
|
||||||
|
|
||||||
|
3. Browser Tests
|
||||||
|
- [ ] Configure browser testing
|
||||||
|
- [ ] Create interaction flows
|
||||||
|
- [ ] Set up mobile testing
|
||||||
|
- [ ] Implement visual tests
|
||||||
|
|
||||||
|
4. Performance Tests
|
||||||
|
- [ ] Create benchmark suite
|
||||||
|
- [ ] Define metrics
|
||||||
|
- [ ] Set up monitoring
|
||||||
|
- [ ] Create test data
|
||||||
|
|
||||||
|
## Test Documentation
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
- Component methods
|
||||||
|
- Event handlers
|
||||||
|
- State management
|
||||||
|
- Service integration
|
||||||
|
- UI interactions
|
||||||
|
- Performance metrics
|
||||||
|
|
||||||
|
### Test Data
|
||||||
|
- Sample marker sets
|
||||||
|
- Cluster configurations
|
||||||
|
- Viewport scenarios
|
||||||
|
- Error conditions
|
||||||
|
- Performance benchmarks
|
||||||
|
|
||||||
|
### Expected Results
|
||||||
|
- Feature parity verification
|
||||||
|
- Performance benchmarks
|
||||||
|
- Mobile compatibility
|
||||||
|
- Error handling coverage
|
||||||
|
- User interaction flows
|
||||||
|
|
||||||
|
** Before Attempting Completion! **
|
||||||
|
|
||||||
|
Create a prompt for the next step that includes this same message!
|
||||||
227
memory-bank/prompts/LocationSelectorImplementation.md
Normal file
227
memory-bank/prompts/LocationSelectorImplementation.md
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
# Location Selector Component Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implementation of the LocationSelectorComponent for the ThrillWiki location management system. This component will provide address search, coordinate selection, and location validation functionality, integrating with our existing GeocodeService.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### 1. Address Search
|
||||||
|
- Autocomplete search interface
|
||||||
|
- Integration with GeocodeService
|
||||||
|
- Search result display
|
||||||
|
- Error handling
|
||||||
|
- Loading states
|
||||||
|
- Input validation
|
||||||
|
|
||||||
|
### 2. Coordinate Selection
|
||||||
|
- Map-based point selection
|
||||||
|
- Direct coordinate input
|
||||||
|
- Coordinate validation
|
||||||
|
- Format standardization
|
||||||
|
- Precision control
|
||||||
|
- Bounds checking
|
||||||
|
|
||||||
|
### 3. Current Location
|
||||||
|
- Browser geolocation support
|
||||||
|
- Permission handling
|
||||||
|
- Accuracy reporting
|
||||||
|
- Fallback behavior
|
||||||
|
- Loading states
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
### 4. Validation Feedback
|
||||||
|
- Address verification
|
||||||
|
- Coordinate validation
|
||||||
|
- Visual feedback
|
||||||
|
- Error messages
|
||||||
|
- Success states
|
||||||
|
- Loading indicators
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### 1. Component Structure
|
||||||
|
```php
|
||||||
|
class LocationSelectorComponent extends Component
|
||||||
|
{
|
||||||
|
// Search State
|
||||||
|
public string $searchQuery = '';
|
||||||
|
public array $searchResults = [];
|
||||||
|
public bool $isSearching = false;
|
||||||
|
public ?string $validationError = null;
|
||||||
|
|
||||||
|
// Location State
|
||||||
|
public ?float $latitude = null;
|
||||||
|
public ?float $longitude = null;
|
||||||
|
public ?string $formattedAddress = null;
|
||||||
|
public bool $isValidLocation = false;
|
||||||
|
|
||||||
|
// UI State
|
||||||
|
public bool $showSearchResults = false;
|
||||||
|
public bool $isLoadingLocation = false;
|
||||||
|
public string $mode = 'search'; // search|coordinates|current
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Features
|
||||||
|
- Debounced address search
|
||||||
|
- Real-time validation
|
||||||
|
- GeocodeService integration
|
||||||
|
- Event-based updates
|
||||||
|
- Progressive loading
|
||||||
|
- Error recovery
|
||||||
|
|
||||||
|
### 3. User Interface
|
||||||
|
- Search input field
|
||||||
|
- Results dropdown
|
||||||
|
- Validation messages
|
||||||
|
- Mode switcher
|
||||||
|
- Coordinate inputs
|
||||||
|
- Current location button
|
||||||
|
- Loading indicators
|
||||||
|
|
||||||
|
## Technical Specifications
|
||||||
|
|
||||||
|
### 1. Component Features
|
||||||
|
- Address search with autocomplete
|
||||||
|
- Coordinate input/validation
|
||||||
|
- Current location detection
|
||||||
|
- Map point selection
|
||||||
|
- Form validation
|
||||||
|
- Error handling
|
||||||
|
- Loading states
|
||||||
|
|
||||||
|
### 2. Events
|
||||||
|
- locationSelected
|
||||||
|
- searchUpdated
|
||||||
|
- coordinatesChanged
|
||||||
|
- validationFailed
|
||||||
|
- modeChanged
|
||||||
|
- locationDetected
|
||||||
|
|
||||||
|
### 3. Methods
|
||||||
|
- handleSearch
|
||||||
|
- validateLocation
|
||||||
|
- selectLocation
|
||||||
|
- detectCurrentLocation
|
||||||
|
- setCoordinates
|
||||||
|
- clearSelection
|
||||||
|
- resetValidation
|
||||||
|
|
||||||
|
### 4. Integration
|
||||||
|
- GeocodeService
|
||||||
|
- LocationMapComponent
|
||||||
|
- Browser Geolocation API
|
||||||
|
- Form validation
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. Base Component:
|
||||||
|
- [ ] Create component class
|
||||||
|
- [ ] Define properties
|
||||||
|
- [ ] Add validation rules
|
||||||
|
- [ ] Set up events
|
||||||
|
|
||||||
|
2. Search Interface:
|
||||||
|
- [ ] Create search input
|
||||||
|
- [ ] Add results display
|
||||||
|
- [ ] Implement debouncing
|
||||||
|
- [ ] Add loading states
|
||||||
|
|
||||||
|
3. Coordinate Selection:
|
||||||
|
- [ ] Add coordinate inputs
|
||||||
|
- [ ] Implement validation
|
||||||
|
- [ ] Create format helpers
|
||||||
|
- [ ] Add bounds checking
|
||||||
|
|
||||||
|
4. Current Location:
|
||||||
|
- [ ] Add detection button
|
||||||
|
- [ ] Handle permissions
|
||||||
|
- [ ] Implement fallbacks
|
||||||
|
- [ ] Add error states
|
||||||
|
|
||||||
|
5. Validation:
|
||||||
|
- [ ] Create validation rules
|
||||||
|
- [ ] Add error messages
|
||||||
|
- [ ] Implement feedback
|
||||||
|
- [ ] Handle edge cases
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### 1. Unit Tests
|
||||||
|
- Component methods
|
||||||
|
- Validation logic
|
||||||
|
- State management
|
||||||
|
- Event handling
|
||||||
|
|
||||||
|
### 2. Integration Tests
|
||||||
|
- GeocodeService interaction
|
||||||
|
- Map component integration
|
||||||
|
- Browser API usage
|
||||||
|
- Form submission
|
||||||
|
|
||||||
|
### 3. Browser Tests
|
||||||
|
- User interactions
|
||||||
|
- Responsive design
|
||||||
|
- Mobile behavior
|
||||||
|
- Error scenarios
|
||||||
|
|
||||||
|
## Quality Assurance
|
||||||
|
|
||||||
|
### 1. Code Quality
|
||||||
|
- Type checking
|
||||||
|
- Error handling
|
||||||
|
- Performance optimization
|
||||||
|
- Documentation
|
||||||
|
|
||||||
|
### 2. User Experience
|
||||||
|
- Loading states
|
||||||
|
- Error feedback
|
||||||
|
- Input validation
|
||||||
|
- Mobile usability
|
||||||
|
|
||||||
|
### 3. Performance
|
||||||
|
- Debounced search
|
||||||
|
- Optimized validation
|
||||||
|
- Efficient updates
|
||||||
|
- Minimal redraws
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### 1. Component API
|
||||||
|
- Properties
|
||||||
|
- Events
|
||||||
|
- Methods
|
||||||
|
- Configuration
|
||||||
|
|
||||||
|
### 2. Integration Guide
|
||||||
|
- Setup steps
|
||||||
|
- Dependencies
|
||||||
|
- Event handling
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
### 3. Usage Examples
|
||||||
|
- Basic setup
|
||||||
|
- Custom configuration
|
||||||
|
- Event handling
|
||||||
|
- Error management
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Component Implementation
|
||||||
|
- [ ] Create base class
|
||||||
|
- [ ] Add view template
|
||||||
|
- [ ] Implement search
|
||||||
|
- [ ] Add validation
|
||||||
|
|
||||||
|
2. Integration
|
||||||
|
- [ ] Connect to GeocodeService
|
||||||
|
- [ ] Set up map interaction
|
||||||
|
- [ ] Add form validation
|
||||||
|
- [ ] Implement error handling
|
||||||
|
|
||||||
|
3. Testing
|
||||||
|
- [ ] Write unit tests
|
||||||
|
- [ ] Add integration tests
|
||||||
|
- [ ] Create browser tests
|
||||||
|
- [ ] Test error scenarios
|
||||||
190
resources/views/livewire/location/location-display.blade.php
Normal file
190
resources/views/livewire/location/location-display.blade.php
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
/* Ensure map container and its elements stay below other UI elements */
|
||||||
|
.leaflet-pane,
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-tile-container,
|
||||||
|
.leaflet-pane > svg,
|
||||||
|
.leaflet-pane > canvas,
|
||||||
|
.leaflet-zoom-box,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-layer {
|
||||||
|
z-index: 1 !important;
|
||||||
|
}
|
||||||
|
.leaflet-control {
|
||||||
|
z-index: 2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom marker cluster styling */
|
||||||
|
.marker-cluster {
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 50%;
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
border: 2px solid rgba(0, 120, 255, 0.5);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-cluster div {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
background-color: rgba(0, 120, 255, 0.6);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom info window styling */
|
||||||
|
.location-info-window {
|
||||||
|
padding: 10px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-info-window h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
<div class="location-display-component">
|
||||||
|
<div wire:ignore class="relative mb-4" style="z-index: 1;">
|
||||||
|
<div id="locationMap" class="h-[400px] w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
||||||
|
|
||||||
|
@if($showInfoWindow && $activeMarker)
|
||||||
|
<div class="location-info-window absolute top-4 right-4 z-10">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<h3 class="text-lg font-semibold">{{ $activeMarker['name'] ?? 'Location' }}</h3>
|
||||||
|
<button wire:click="closeInfoWindow" class="text-gray-500 hover:text-gray-700">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
@if(isset($activeMarker['address']))
|
||||||
|
<p class="text-sm text-gray-600">
|
||||||
|
{{ $activeMarker['address']['street'] ?? '' }}
|
||||||
|
{{ $activeMarker['address']['city'] ?? '' }}
|
||||||
|
{{ $activeMarker['address']['state'] ?? '' }}
|
||||||
|
{{ $activeMarker['address']['country'] ?? '' }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
@if(isset($activeMarker['description']))
|
||||||
|
<p class="mt-2 text-sm">{{ $activeMarker['description'] }}</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('livewire:initialized', function () {
|
||||||
|
const map = L.map('locationMap');
|
||||||
|
let markerClusterGroup;
|
||||||
|
let markers = {};
|
||||||
|
|
||||||
|
// Initialize map with OSM tiles
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© OpenStreetMap contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// Initialize marker cluster group
|
||||||
|
markerClusterGroup = L.markerClusterGroup({
|
||||||
|
maxClusterRadius: @entangle('clusterRadius'),
|
||||||
|
spiderfyOnMaxZoom: true,
|
||||||
|
showCoverageOnHover: false,
|
||||||
|
zoomToBoundsOnClick: true,
|
||||||
|
removeOutsideVisibleBounds: true,
|
||||||
|
iconCreateFunction: function(cluster) {
|
||||||
|
const count = cluster.getChildCount();
|
||||||
|
return L.divIcon({
|
||||||
|
html: `<div><span>${count}</span></div>`,
|
||||||
|
className: 'marker-cluster',
|
||||||
|
iconSize: L.point(40, 40)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addLayer(markerClusterGroup);
|
||||||
|
|
||||||
|
// Listen for marker updates from Livewire
|
||||||
|
@this.on('updateMarkers', (markerData) => {
|
||||||
|
// Clear existing markers
|
||||||
|
markerClusterGroup.clearLayers();
|
||||||
|
markers = {};
|
||||||
|
|
||||||
|
// Add new markers
|
||||||
|
markerData.forEach(marker => {
|
||||||
|
const leafletMarker = L.marker([marker.lat, marker.lng], {
|
||||||
|
icon: getMarkerIcon(marker.category)
|
||||||
|
});
|
||||||
|
|
||||||
|
leafletMarker.on('click', () => {
|
||||||
|
@this.markerClicked(marker.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
markers[marker.id] = leafletMarker;
|
||||||
|
markerClusterGroup.addLayer(leafletMarker);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fit bounds if available
|
||||||
|
if (@this.bounds) {
|
||||||
|
map.fitBounds([
|
||||||
|
[@this.bounds.south, @this.bounds.west],
|
||||||
|
[@this.bounds.north, @this.bounds.east]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle cluster clicks
|
||||||
|
markerClusterGroup.on('clusterclick', (e) => {
|
||||||
|
const clusterMarkers = e.layer.getAllChildMarkers().map(marker => {
|
||||||
|
const markerId = Object.keys(markers).find(key => markers[key] === marker);
|
||||||
|
return { id: markerId, lat: marker.getLatLng().lat, lng: marker.getLatLng().lng };
|
||||||
|
});
|
||||||
|
@this.clusterClicked(clusterMarkers);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle map bounds changes
|
||||||
|
map.on('moveend', () => {
|
||||||
|
const bounds = map.getBounds();
|
||||||
|
@this.boundsChanged({
|
||||||
|
north: bounds.getNorth(),
|
||||||
|
south: bounds.getSouth(),
|
||||||
|
east: bounds.getEast(),
|
||||||
|
west: bounds.getWest()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize markers
|
||||||
|
@this.getMarkers().then(markerData => {
|
||||||
|
@this.emit('updateMarkers', markerData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to get marker icon based on category
|
||||||
|
function getMarkerIcon(category) {
|
||||||
|
// Customize icons based on category
|
||||||
|
return L.divIcon({
|
||||||
|
className: `marker-icon marker-${category || 'default'}`,
|
||||||
|
html: `<div class="w-8 h-8 rounded-full bg-blue-500 border-2 border-white shadow-lg flex items-center justify-center">
|
||||||
|
<svg class="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</div>`,
|
||||||
|
iconSize: [32, 32],
|
||||||
|
iconAnchor: [16, 32]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
207
resources/views/livewire/location/location-map.blade.php
Normal file
207
resources/views/livewire/location/location-map.blade.php
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<div
|
||||||
|
wire:ignore
|
||||||
|
x-data="mapComponent(@js($mapConfig))"
|
||||||
|
x-init="initializeMap()"
|
||||||
|
class="relative w-full h-[400px] rounded-lg overflow-hidden"
|
||||||
|
>
|
||||||
|
{{-- Map Container --}}
|
||||||
|
<div id="map" class="absolute inset-0 w-full h-full"></div>
|
||||||
|
|
||||||
|
{{-- Loading Overlay --}}
|
||||||
|
<div
|
||||||
|
x-show="loading"
|
||||||
|
class="absolute inset-0 bg-white/60 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Map Controls --}}
|
||||||
|
@if($showControls)
|
||||||
|
<div class="absolute top-2 right-2 flex flex-col gap-2 z-[1000]">
|
||||||
|
{{-- Zoom Controls --}}
|
||||||
|
<div class="flex flex-col bg-white rounded-lg shadow-lg">
|
||||||
|
<button
|
||||||
|
@click="zoomIn"
|
||||||
|
class="p-2 hover:bg-gray-100 rounded-t-lg border-b"
|
||||||
|
title="Zoom in"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v12m6-6H6"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="zoomOut"
|
||||||
|
class="p-2 hover:bg-gray-100 rounded-b-lg"
|
||||||
|
title="Zoom out"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Center Control --}}
|
||||||
|
<button
|
||||||
|
@click="centerOnMarkers"
|
||||||
|
class="p-2 bg-white rounded-lg shadow-lg hover:bg-gray-100"
|
||||||
|
title="Center map on markers"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Selected Location Info --}}
|
||||||
|
@if($selectedLocation)
|
||||||
|
<div
|
||||||
|
x-show="selectedLocation"
|
||||||
|
class="absolute bottom-4 left-4 right-4 bg-white rounded-lg shadow-lg p-4 z-[1000] max-w-sm"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-semibold text-gray-900" x-text="selectedLocation.name || 'Selected Location'"></h3>
|
||||||
|
<p class="text-sm text-gray-600 mt-1" x-text="selectedLocation.address || formatCoordinates(selectedLocation)"></p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="clearSelection"
|
||||||
|
class="text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('mapComponent', (config) => ({
|
||||||
|
map: null,
|
||||||
|
markers: [],
|
||||||
|
markerLayer: null,
|
||||||
|
loading: true,
|
||||||
|
selectedLocation: config.selectedLocation,
|
||||||
|
|
||||||
|
async initializeMap() {
|
||||||
|
// Wait for Leaflet to be loaded
|
||||||
|
if (typeof L === 'undefined') {
|
||||||
|
await this.loadLeaflet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the map
|
||||||
|
this.map = L.map('map', {
|
||||||
|
center: [config.center.lat || 0, config.center.lng || 0],
|
||||||
|
zoom: config.zoom || 13,
|
||||||
|
zoomControl: false,
|
||||||
|
dragging: config.interactive,
|
||||||
|
touchZoom: config.interactive,
|
||||||
|
doubleClickZoom: config.interactive,
|
||||||
|
scrollWheelZoom: config.interactive,
|
||||||
|
boxZoom: config.interactive,
|
||||||
|
tap: config.interactive
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add tile layer
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
maxZoom: 18
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
// Initialize marker layer
|
||||||
|
this.markerLayer = L.layerGroup().addTo(this.map);
|
||||||
|
|
||||||
|
// Add markers
|
||||||
|
this.updateMarkers(config.markers);
|
||||||
|
|
||||||
|
// Set up event listeners
|
||||||
|
this.map.on('moveend', () => this.handleMapMoved());
|
||||||
|
this.map.on('zoomend', () => this.handleZoomChanged());
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadLeaflet() {
|
||||||
|
// Load Leaflet CSS
|
||||||
|
if (!document.querySelector('link[href*="leaflet.css"]')) {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Leaflet JS
|
||||||
|
if (typeof L === 'undefined') {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
await new Promise(resolve => script.onload = resolve);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMarkers(markers) {
|
||||||
|
this.markerLayer.clearLayers();
|
||||||
|
this.markers = markers.map(marker => {
|
||||||
|
const leafletMarker = L.marker([marker.latitude, marker.longitude], {
|
||||||
|
title: marker.name || 'Location'
|
||||||
|
});
|
||||||
|
|
||||||
|
leafletMarker.on('click', () => {
|
||||||
|
this.$wire.handleMarkerClicked(marker);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.markerLayer.addLayer(leafletMarker);
|
||||||
|
return leafletMarker;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (markers.length > 0) {
|
||||||
|
this.centerOnMarkers();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleMapMoved() {
|
||||||
|
const center = this.map.getCenter();
|
||||||
|
this.$wire.handleMapMoved(center.lat, center.lng);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleZoomChanged() {
|
||||||
|
this.$wire.handleZoomChanged(this.map.getZoom());
|
||||||
|
},
|
||||||
|
|
||||||
|
zoomIn() {
|
||||||
|
this.map.zoomIn();
|
||||||
|
},
|
||||||
|
|
||||||
|
zoomOut() {
|
||||||
|
this.map.zoomOut();
|
||||||
|
},
|
||||||
|
|
||||||
|
centerOnMarkers() {
|
||||||
|
if (this.markers.length === 0) return;
|
||||||
|
|
||||||
|
if (this.markers.length === 1) {
|
||||||
|
this.map.setView(this.markers[0].getLatLng(), this.map.getZoom());
|
||||||
|
} else {
|
||||||
|
const group = L.featureGroup(this.markers);
|
||||||
|
this.map.fitBounds(group.getBounds().pad(0.1));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSelection() {
|
||||||
|
this.selectedLocation = null;
|
||||||
|
this.$wire.handleLocationSelected(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
formatCoordinates(location) {
|
||||||
|
if (!location?.latitude || !location?.longitude) return '';
|
||||||
|
return `${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)}`;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
206
resources/views/livewire/location/location-selector.blade.php
Normal file
206
resources/views/livewire/location/location-selector.blade.php
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Mode Switcher -->
|
||||||
|
<div class="flex space-x-2 mb-4">
|
||||||
|
<button
|
||||||
|
wire:click="switchMode('search')"
|
||||||
|
class="px-4 py-2 text-sm rounded-lg {{ $mode === 'search' ? 'bg-blue-600 text-white' : 'bg-gray-100 hover:bg-gray-200' }}"
|
||||||
|
>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
Search
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="switchMode('coordinates')"
|
||||||
|
class="px-4 py-2 text-sm rounded-lg {{ $mode === 'coordinates' ? 'bg-blue-600 text-white' : 'bg-gray-100 hover:bg-gray-200' }}"
|
||||||
|
>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</svg>
|
||||||
|
Coordinates
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
wire:click="detectCurrentLocation"
|
||||||
|
class="px-4 py-2 text-sm rounded-lg {{ $mode === 'current' ? 'bg-blue-600 text-white' : 'bg-gray-100 hover:bg-gray-200' }}"
|
||||||
|
>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
Current Location
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Mode -->
|
||||||
|
@if ($mode === 'search')
|
||||||
|
<div class="relative">
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
wire:model.live="searchQuery"
|
||||||
|
placeholder="Search for a location..."
|
||||||
|
class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
>
|
||||||
|
@if ($isSearching)
|
||||||
|
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||||
|
<svg class="w-5 h-5 text-gray-400 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($showSearchResults && count($searchResults))
|
||||||
|
<div class="absolute z-10 w-full mt-1 bg-white rounded-lg shadow-lg">
|
||||||
|
<ul class="py-1">
|
||||||
|
@foreach ($searchResults as $result)
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
wire:click="selectLocation({{ json_encode($result) }})"
|
||||||
|
class="w-full px-4 py-2 text-left hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
{{ $result['display_name'] }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Coordinate Mode -->
|
||||||
|
@if ($mode === 'coordinates')
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Latitude</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
wire:model="latitude"
|
||||||
|
class="w-full px-4 py-2 mt-1 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="Enter latitude..."
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Longitude</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
wire:model="longitude"
|
||||||
|
class="w-full px-4 py-2 mt-1 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
placeholder="Enter longitude..."
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Current Location Mode -->
|
||||||
|
@if ($mode === 'current')
|
||||||
|
<div class="p-4 text-center">
|
||||||
|
@if ($isLoadingLocation)
|
||||||
|
<div class="flex items-center justify-center space-x-2">
|
||||||
|
<svg class="w-5 h-5 text-blue-600 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Detecting your location...</span>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-gray-600">Click the Current Location button above to detect your location</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Map Component -->
|
||||||
|
@if ($latitude && $longitude)
|
||||||
|
<div class="mt-4">
|
||||||
|
<livewire:location.location-map-component
|
||||||
|
:latitude="$latitude"
|
||||||
|
:longitude="$longitude"
|
||||||
|
:interactive="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
@if ($validationError)
|
||||||
|
<div class="p-4 mt-4 text-sm text-red-700 bg-red-100 rounded-lg">
|
||||||
|
{{ $validationError }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Selected Location Display -->
|
||||||
|
@if ($isValidLocation)
|
||||||
|
<div class="p-4 mt-4 bg-green-50 rounded-lg">
|
||||||
|
<h4 class="font-medium text-green-800">Selected Location</h4>
|
||||||
|
@if ($formattedAddress)
|
||||||
|
<p class="mt-1 text-sm text-green-600">{{ $formattedAddress }}</p>
|
||||||
|
@endif
|
||||||
|
<p class="mt-1 text-sm text-green-600">
|
||||||
|
Coordinates: {{ number_format($latitude, 6) }}, {{ number_format($longitude, 6) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Clear Selection Button -->
|
||||||
|
@if ($isValidLocation)
|
||||||
|
<div class="flex justify-end mt-4">
|
||||||
|
<button
|
||||||
|
wire:click="clearSelection"
|
||||||
|
class="px-4 py-2 text-sm text-red-600 bg-red-100 rounded-lg hover:bg-red-200"
|
||||||
|
>
|
||||||
|
Clear Selection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript for Geolocation -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('livewire:initialized', () => {
|
||||||
|
@this.on('requestCurrentLocation', () => {
|
||||||
|
if (!navigator.geolocation) {
|
||||||
|
@this.dispatch('validationFailed', {
|
||||||
|
message: 'Geolocation is not supported by your browser'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => {
|
||||||
|
@this.setCoordinates(
|
||||||
|
position.coords.latitude,
|
||||||
|
position.coords.longitude
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
let errorMessage = 'Failed to get your location';
|
||||||
|
switch (error.code) {
|
||||||
|
case error.PERMISSION_DENIED:
|
||||||
|
errorMessage = 'Location permission denied';
|
||||||
|
break;
|
||||||
|
case error.POSITION_UNAVAILABLE:
|
||||||
|
errorMessage = 'Location information unavailable';
|
||||||
|
break;
|
||||||
|
case error.TIMEOUT:
|
||||||
|
errorMessage = 'Location request timed out';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
@this.dispatch('validationFailed', {
|
||||||
|
message: errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user