mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 09:31:11 -05:00
Add comprehensive implementation prompts for Reviews and Rides listing pages with Django parity, Laravel/Livewire architecture, and screen-agnostic design principles
This commit is contained in:
551
memory-bank/prompts/ParksListingPagePrompt.md
Normal file
551
memory-bank/prompts/ParksListingPagePrompt.md
Normal file
@@ -0,0 +1,551 @@
|
||||
# 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
|
||||
<div class="parks-mobile-layout">
|
||||
<!-- GPS-Enabled Search Bar -->
|
||||
<div class="sticky top-0 bg-white dark:bg-gray-900 z-20 p-4">
|
||||
<livewire:parks-location-search :enable-gps="true" />
|
||||
<div class="flex items-center mt-2 space-x-2">
|
||||
<button wire:click="toggleNearbyMode" class="flex items-center space-x-1 px-3 py-1 bg-blue-100 dark:bg-blue-900 rounded-full">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">...</svg>
|
||||
<span class="text-sm">Near Me</span>
|
||||
</button>
|
||||
<button wire:click="toggleMapView" class="flex items-center space-x-1 px-3 py-1 bg-gray-100 dark:bg-gray-800 rounded-full">
|
||||
<span class="text-sm">{{ $showMap ? 'List' : 'Map' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Filters -->
|
||||
<div class="horizontal-scroll p-4 pb-2">
|
||||
<livewire:parks-quick-filters />
|
||||
</div>
|
||||
|
||||
@if($showMap)
|
||||
<!-- Mobile Map View -->
|
||||
<div class="h-64 relative">
|
||||
<livewire:parks-map-view :parks="$parks" :compact="true" />
|
||||
</div>
|
||||
<!-- Bottom Sheet Park List -->
|
||||
<div class="bg-white dark:bg-gray-900 rounded-t-xl shadow-lg mt-4">
|
||||
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<h3 class="text-lg font-semibold">{{ $parks->count() }} Parks Found</h3>
|
||||
</div>
|
||||
<div class="max-h-96 overflow-y-auto">
|
||||
@foreach($parks as $park)
|
||||
<livewire:park-mobile-card :park="$park" :show-distance="true" :key="$park->id" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- Park Cards -->
|
||||
<div class="space-y-4 p-4">
|
||||
@foreach($parks as $park)
|
||||
<livewire:park-mobile-card :park="$park" :show-distance="$nearbyMode" :key="$park->id" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Mobile Pagination -->
|
||||
<div class="sticky bottom-0 bg-white dark:bg-gray-900 p-4">
|
||||
{{ $parks->links('pagination.mobile') }}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 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
|
||||
<div class="parks-tablet-layout flex h-screen">
|
||||
<!-- Filter Sidebar -->
|
||||
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<livewire:parks-location-search :advanced="true" />
|
||||
<div class="mt-6">
|
||||
<livewire:parks-filters :expanded="true" :show-regional="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
<!-- View Toggle and Stats -->
|
||||
<div class="bg-white dark:bg-gray-900 p-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<h2 class="text-xl font-semibold">{{ $parks->total() }} Parks</h2>
|
||||
<livewire:parks-statistics-summary />
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button wire:click="setView('list')" class="px-3 py-2 {{ $view === 'list' ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700' }} rounded">
|
||||
List
|
||||
</button>
|
||||
<button wire:click="setView('map')" class="px-3 py-2 {{ $view === 'map' ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700' }} rounded">
|
||||
Map
|
||||
</button>
|
||||
<button wire:click="setView('split')" class="px-3 py-2 {{ $view === 'split' ? 'bg-blue-500 text-white' : 'bg-gray-200 dark:bg-gray-700' }} rounded">
|
||||
Split
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Area -->
|
||||
<div class="flex-1 flex">
|
||||
@if($view === 'list')
|
||||
<!-- Full List View -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
@foreach($parks as $park)
|
||||
<livewire:park-tablet-card :park="$park" :key="$park->id" />
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
{{ $parks->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@elseif($view === 'map')
|
||||
<!-- Full Map View -->
|
||||
<div class="flex-1">
|
||||
<livewire:parks-map-view :parks="$parks" :interactive="true" />
|
||||
</div>
|
||||
@else
|
||||
<!-- Split View -->
|
||||
<div class="flex-1">
|
||||
<livewire:parks-map-view :parks="$parks" :interactive="true" />
|
||||
</div>
|
||||
<div class="w-96 bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-gray-700 overflow-y-auto">
|
||||
<div class="p-4">
|
||||
@foreach($parks as $park)
|
||||
<livewire:park-compact-card :park="$park" :key="$park->id" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 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
|
||||
<div class="parks-desktop-layout flex h-screen">
|
||||
<!-- Advanced Filter Sidebar -->
|
||||
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<livewire:parks-location-search :advanced="true" :autocomplete="true" />
|
||||
<div class="mt-6">
|
||||
<livewire:parks-filters :expanded="true" :advanced="true" :show-statistics="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
<!-- Advanced Header -->
|
||||
<div class="bg-white dark:bg-gray-900 p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-6">
|
||||
<h1 class="text-2xl font-bold">{{ $parks->total() }} Theme Parks</h1>
|
||||
<livewire:parks-statistics-dashboard />
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<livewire:parks-sort-selector :options="$advancedSortOptions" />
|
||||
<livewire:parks-view-selector />
|
||||
<livewire:parks-export-options />
|
||||
</div>
|
||||
</div>
|
||||
<livewire:parks-advanced-search-bar />
|
||||
</div>
|
||||
|
||||
<!-- Content Area -->
|
||||
<div class="flex-1 flex">
|
||||
@if($view === 'grid')
|
||||
<!-- Advanced Grid View -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<div class="grid grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
@foreach($parks as $park)
|
||||
<livewire:park-desktop-card :park="$park" :detailed="true" :key="$park->id" />
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
{{ $parks->links('pagination.desktop') }}
|
||||
</div>
|
||||
</div>
|
||||
@elseif($view === 'map')
|
||||
<!-- Advanced Map View -->
|
||||
<div class="flex-1">
|
||||
<livewire:parks-advanced-map :parks="$parks" :clustering="true" :layers="true" />
|
||||
</div>
|
||||
@else
|
||||
<!-- Dashboard View -->
|
||||
<div class="flex-1 p-6">
|
||||
<livewire:parks-dashboard :parks="$parks" />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Info Panel -->
|
||||
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<livewire:parks-quick-info />
|
||||
<div class="mt-6">
|
||||
<livewire:parks-recent-activity />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 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.
|
||||
Reference in New Issue
Block a user