# Parks Listing Page Implementation Prompt ## Django Parity Reference **Django Implementation**: `parks/views.py` - `ParkListView` (lines 135-150+) **Django Template**: `parks/templates/parks/park_list.html` **Django Features**: Location-based search, operator filtering, region filtering, park type filtering, statistics display, pagination with HTMX, map integration ## Core Implementation Requirements ### Laravel/Livewire Architecture Generate the parks listing system using ThrillWiki's custom generators: ```bash # Generate the main listing component with location optimization php artisan make:thrillwiki-livewire ParksListing --paginated --cached --with-tests # Generate location-aware search component php artisan make:thrillwiki-livewire ParksLocationSearch --reusable --with-tests # Generate operator-specific park filters php artisan make:thrillwiki-livewire ParksFilters --reusable --cached # Generate parks map view component php artisan make:thrillwiki-livewire ParksMapView --reusable --with-tests # Generate operator-specific park listings php artisan make:thrillwiki-livewire OperatorParksListing --paginated --cached --with-tests # Generate regional park listings php artisan make:thrillwiki-livewire RegionalParksListing --paginated --cached --with-tests ``` ### Django Parity Features #### 1. Location-Based Search Functionality **Django Implementation**: Multi-term search with location awareness across: - Park name (`name__icontains`) - Park description (`description__icontains`) - Location city/state (`location__city__icontains`, `location__state__icontains`) - Operator name (`operator__name__icontains`) - Park type (`park_type__icontains`) **Laravel Implementation**: ```php public function locationAwareSearch($query, $userLocation = null) { return Park::query() ->when($query, function ($q) use ($query) { $terms = explode(' ', $query); foreach ($terms as $term) { $q->where(function ($subQuery) use ($term) { $subQuery->where('name', 'ilike', "%{$term}%") ->orWhere('description', 'ilike', "%{$term}%") ->orWhere('park_type', 'ilike', "%{$term}%") ->orWhereHas('location', function($locQuery) use ($term) { $locQuery->where('city', 'ilike', "%{$term}%") ->orWhere('state', 'ilike', "%{$term}%") ->orWhere('country', 'ilike', "%{$term}%"); }) ->orWhereHas('operator', fn($opQuery) => $opQuery->where('name', 'ilike', "%{$term}%")); }); } }) ->when($userLocation, function ($q) use ($userLocation) { // Add distance-based ordering for location-aware results $q->selectRaw('parks.*, (6371 * acos(cos(radians(?)) * cos(radians(locations.latitude)) * cos(radians(locations.longitude) - radians(?)) + sin(radians(?)) * sin(radians(locations.latitude)))) AS distance', [$userLocation['lat'], $userLocation['lng'], $userLocation['lat']]) ->join('locations', 'parks.location_id', '=', 'locations.id') ->orderBy('distance'); }) ->with(['location', 'operator', 'photos', 'statistics']) ->withCount(['rides', 'reviews']); } ``` #### 2. Advanced Filtering with Geographic Context **Django Filters**: - Operator (operator__id) - Region/State (location__state) - Country (location__country) - Park type (park_type) - Opening year range - Size range (area_acres) - Ride count range - Distance from user location **Laravel Filters Implementation**: ```php public function applyFilters($query, $filters, $userLocation = null) { return $query ->when($filters['operator_id'] ?? null, fn($q, $operatorId) => $q->where('operator_id', $operatorId)) ->when($filters['region'] ?? null, fn($q, $region) => $q->whereHas('location', fn($locQ) => $locQ->where('state', $region))) ->when($filters['country'] ?? null, fn($q, $country) => $q->whereHas('location', fn($locQ) => $locQ->where('country', $country))) ->when($filters['park_type'] ?? null, fn($q, $type) => $q->where('park_type', $type)) ->when($filters['opening_year_from'] ?? null, fn($q, $year) => $q->where('opening_date', '>=', "{$year}-01-01")) ->when($filters['opening_year_to'] ?? null, fn($q, $year) => $q->where('opening_date', '<=', "{$year}-12-31")) ->when($filters['min_area'] ?? null, fn($q, $area) => $q->where('area_acres', '>=', $area)) ->when($filters['max_area'] ?? null, fn($q, $area) => $q->where('area_acres', '<=', $area)) ->when($filters['min_rides'] ?? null, fn($q, $count) => $q->whereHas('rides', fn($rideQ) => $rideQ->havingRaw('COUNT(*) >= ?', [$count]))) ->when($filters['max_distance'] ?? null && $userLocation, function($q) use ($filters, $userLocation) { $q->whereRaw('(6371 * acos(cos(radians(?)) * cos(radians(locations.latitude)) * cos(radians(locations.longitude) - radians(?)) + sin(radians(?)) * sin(radians(locations.latitude)))) <= ?', [$userLocation['lat'], $userLocation['lng'], $userLocation['lat'], $filters['max_distance']]); }); } ``` #### 3. Context-Aware Views with Statistics **Global Listing**: All parks worldwide with statistics **Operator-Specific Listing**: Parks filtered by specific operator with comparisons **Regional Listing**: Parks filtered by geographic region with local insights **Nearby Listing**: Location-based parks with distance calculations ### Screen-Agnostic Design Implementation #### Mobile Layout (320px - 767px) - **Single Column**: Full-width park cards with essential info - **Location Services**: GPS-enabled "Near Me" functionality - **Touch-Optimized Maps**: Pinch-to-zoom, tap-to-select functionality - **Swipe Navigation**: Horizontal scrolling for quick filters - **Bottom Sheet**: Map/list toggle with smooth transitions **Mobile Component Structure**: ```blade
@if($showMap)

{{ $parks->count() }} Parks Found

@foreach($parks as $park) @endforeach
@else
@foreach($parks as $park) @endforeach
@endif
{{ $parks->links('pagination.mobile') }}
``` #### Tablet Layout (768px - 1023px) - **Dual-Pane with Map**: Filter sidebar + map/list split view - **Advanced Filtering**: Expandable regional and operator filters - **Split-Screen Mode**: Map on one side, detailed list on the other - **Touch + External Input**: Keyboard shortcuts for power users **Tablet Component Structure**: ```blade

{{ $parks->total() }} Parks

@if($view === 'list')
@foreach($parks as $park) @endforeach
{{ $parks->links() }}
@elseif($view === 'map')
@else
@foreach($parks as $park) @endforeach
@endif
``` #### Desktop Layout (1024px - 1919px) - **Three-Pane Layout**: Filters + map/list + park details - **Advanced Map Integration**: Multiple layers, clustering, detailed overlays - **Keyboard Navigation**: Full keyboard shortcuts and accessibility - **Multi-Window Support**: Optimal for external monitor setups **Desktop Component Structure**: ```blade

{{ $parks->total() }} Theme Parks

@if($view === 'grid')
@foreach($parks as $park) @endforeach
{{ $parks->links('pagination.desktop') }}
@elseif($view === 'map')
@else
@endif
``` #### Large Screen Layout (1920px+) - **Dashboard-Style Interface**: Multi-column with comprehensive analytics - **Ultra-Wide Map Integration**: Immersive geographic visualization - **Advanced Data Visualization**: Charts, graphs, and statistical overlays - **Multi-Monitor Optimization**: Designed for extended desktop setups ### Performance Optimization Strategy #### Location-Aware Caching ```php public function mount() { $this->userLocation = $this->getUserLocation(); $this->cachedFilters = Cache::remember( "parks.filters.{$this->userLocation['region']}", now()->addHours(2), fn() => $this->loadRegionalFilterOptions() ); } public function getParksProperty() { $cacheKey = "parks.listing." . md5(serialize([ 'search' => $this->search, 'filters' => $this->filters, 'location' => $this->userLocation, 'sort' => $this->sort, 'page' => $this->page ])); return Cache::remember($cacheKey, now()->addMinutes(20), function() { return $this->locationAwareSearch($this->search, $this->userLocation) ->applyFilters($this->filters, $this->userLocation) ->orderBy($this->sort['column'], $this->sort['direction']) ->paginate(18); }); } ``` #### Geographic Query Optimization ```php // Optimized query with spatial indexing public function optimizedLocationQuery() { return Park::select([ 'parks.*', DB::raw('(6371 * acos(cos(radians(?)) * cos(radians(locations.latitude)) * cos(radians(locations.longitude) - radians(?)) + sin(radians(?)) * sin(radians(locations.latitude)))) AS distance ') ]) ->join('locations', 'parks.location_id', '=', 'locations.id') ->with([ 'location:id,city,state,country,latitude,longitude', 'operator:id,name,slug', 'photos' => fn($q) => $q->select(['id', 'park_id', 'url', 'thumbnail_url'])->limit(3), 'statistics:park_id,total_rides,total_reviews,average_rating' ]) ->withCount(['rides', 'reviews', 'favorites']) ->addBinding([$this->userLat, $this->userLng, $this->userLat], 'select'); } ``` ### Component Reuse Strategy #### Shared Components - **`ParksLocationSearch`**: GPS-enabled search with autocomplete - **`ParksFilters`**: Regional and operator filtering with statistics - **`ParksMapView`**: Interactive map with clustering and layers - **`ParkCard`**: Responsive park display with distance calculations #### Context Variations - **`GlobalParksListing`**: All parks worldwide with regional grouping - **`OperatorParksListing`**: Operator-specific parks with comparisons - **`RegionalParksListing`**: Geographic region parks with local insights - **`NearbyParksListing`**: Location-based parks with travel information ### Testing Requirements #### Feature Tests ```php /** @test */ public function can_search_parks_with_location_awareness() { $magicKingdom = Park::factory()->create(['name' => 'Magic Kingdom']); $magicKingdom->location()->create([ 'city' => 'Orlando', 'state' => 'Florida', 'latitude' => 28.3772, 'longitude' => -81.5707 ]); Livewire::test(ParksListing::class) ->set('search', 'Magic Orlando') ->set('userLocation', ['lat' => 28.4, 'lng' => -81.6]) ->assertSee($magicKingdom->name) ->assertSee('Orlando'); } /** @test */ public function filters_parks_by_distance_from_user_location() { $nearPark = Park::factory()->create(['name' => 'Near Park']); $nearPark->location()->create(['latitude' => 28.3772, 'longitude' => -81.5707]); $farPark = Park::factory()->create(['name' => 'Far Park']); $farPark->location()->create(['latitude' => 40.7128, 'longitude' => -74.0060]); Livewire::test(ParksListing::class) ->set('userLocation', ['lat' => 28.4, 'lng' => -81.6]) ->set('filters.max_distance', 50) ->assertSee($nearPark->name) ->assertDontSee($farPark->name); } /** @test */ public function maintains_django_parity_performance_with_location() { Park::factory()->count(100)->create(); $start = microtime(true); Livewire::test(ParksListing::class) ->set('userLocation', ['lat' => 28.4, 'lng' => -81.6]); $end = microtime(true); $this->assertLessThan(0.5, $end - $start); // < 500ms with location } ``` #### Location-Specific Tests ```php /** @test */ public function calculates_accurate_distances_between_parks_and_user() { $park = Park::factory()->create(); $park->location()->create([ 'latitude' => 28.3772, // Magic Kingdom coordinates 'longitude' => -81.5707 ]); $component = Livewire::test(ParksListing::class) ->set('userLocation', ['lat' => 28.4, 'lng' => -81.6]); $distance = $component->get('parks')->first()->distance; $this->assertLessThan(5, $distance); // Should be less than 5km } /** @test */ public function handles_gps_permission_denied_gracefully() { Livewire::test(ParksListing::class) ->set('gpsPermissionDenied', true) ->assertSee('Enter your location manually') ->assertDontSee('Near Me'); } ``` ### Performance Targets #### Universal Performance Standards with Location - **Initial Load**: < 500ms (matches Django with location services) - **GPS Location Acquisition**: < 2 seconds - **Distance Calculation**: < 100ms for 100 parks - **Map Rendering**: < 1 second for initial load - **Filter Response**: < 200ms with location context #### Location-Aware Caching Strategy - **Regional Filter Cache**: 2 hours (changes infrequently) - **Distance Calculations**: 30 minutes (user location dependent) - **Map Tile Cache**: 24 hours (geographic data stable) - **Nearby Parks Cache**: 15 minutes (location and time sensitive) ### Success Criteria Checklist #### Django Parity Verification - [ ] Location-based search matches Django behavior exactly - [ ] All geographic filters implemented and functional - [ ] Distance calculations accurate within 1% of Django results - [ ] Regional grouping works identically to Django - [ ] Statistics display matches Django formatting #### Screen-Agnostic Compliance - [ ] Mobile layout optimized with GPS integration - [ ] Tablet layout provides effective split-screen experience - [ ] Desktop layout maximizes map and data visualization - [ ] Large screen layout provides comprehensive dashboard - [ ] All layouts handle location permissions gracefully #### Performance Benchmarks - [ ] Initial load under 500ms including location services - [ ] GPS acquisition under 2 seconds - [ ] Map rendering under 1 second - [ ] Distance calculations under 100ms - [ ] Regional caching reduces server load by 70% #### Geographic Feature Completeness - [ ] GPS location services work on all supported devices - [ ] Distance calculations accurate across all coordinate systems - [ ] Map integration functional on all screen sizes - [ ] Regional filtering provides meaningful results - [ ] Location search provides relevant autocomplete suggestions This prompt ensures complete Django parity while adding location-aware enhancements that leverage modern browser capabilities and maintain ThrillWiki's screen-agnostic design principles.