14 KiB
Rides Listing Page Implementation Prompt
Django Parity Reference
Django Implementation: rides/views.py - RideListView (lines 215-278)
Django Template: rides/templates/rides/ride_list.html
Django Features: Multi-term search, category filtering, manufacturer filtering, status filtering, pagination with HTMX, eager loading optimization
Core Implementation Requirements
Laravel/Livewire Architecture
Generate the rides listing system using ThrillWiki's custom generators:
# Generate the main listing component with optimizations
php artisan make:thrillwiki-livewire RidesListing --paginated --cached --with-tests
# Generate reusable search suggestions component
php artisan make:thrillwiki-livewire RidesSearchSuggestions --reusable --with-tests
# Generate advanced filters component
php artisan make:thrillwiki-livewire RidesFilters --reusable --cached
# Generate context-aware listing for park-specific rides
php artisan make:thrillwiki-livewire ParkRidesListing --paginated --cached --with-tests
Django Parity Features
1. Search Functionality
Django Implementation: Multi-term search across:
- Ride name (
name__icontains) - Ride description (
description__icontains) - Park name (
park__name__icontains) - Manufacturer name (
manufacturer__name__icontains) - Designer name (
designer__name__icontains)
Laravel Implementation:
public function search($query)
{
return Ride::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}%")
->orWhereHas('park', fn($q) => $q->where('name', 'ilike', "%{$term}%"))
->orWhereHas('manufacturer', fn($q) => $q->where('name', 'ilike', "%{$term}%"))
->orWhereHas('designer', fn($q) => $q->where('name', 'ilike', "%{$term}%"));
});
}
})
->with(['park', 'manufacturer', 'designer', 'photos'])
->orderBy('name');
}
2. Advanced Filtering
Django Filters:
- Category (ride_type)
- Status (status)
- Manufacturer (manufacturer__id)
- Opening year range
- Height restrictions
- Park context (when viewing park-specific rides)
Laravel Filters Implementation:
public function applyFilters($query, $filters)
{
return $query
->when($filters['category'] ?? null, fn($q, $category) =>
$q->where('ride_type', $category))
->when($filters['status'] ?? null, fn($q, $status) =>
$q->where('status', $status))
->when($filters['manufacturer_id'] ?? null, fn($q, $manufacturerId) =>
$q->where('manufacturer_id', $manufacturerId))
->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_height'] ?? null, fn($q, $height) =>
$q->where('height_requirement', '>=', $height))
->when($filters['max_height'] ?? null, fn($q, $height) =>
$q->where('height_requirement', '<=', $height));
}
3. Context-Aware Views
Global Listing: All rides across all parks Park-Specific Listing: Rides filtered by specific park Category-Specific Listing: Rides filtered by ride type/category
Screen-Agnostic Design Implementation
Mobile Layout (320px - 767px)
- Single Column: Full-width ride cards
- Touch Targets: Minimum 44px touch areas
- Gesture Support: Pull-to-refresh, swipe navigation
- Bottom Navigation: Sticky filters and search
- Thumb Navigation: Search and filter controls within thumb reach
Mobile Component Structure:
<div class="rides-mobile-layout">
<!-- Sticky Search Bar -->
<div class="sticky top-0 bg-white dark:bg-gray-900 z-10 p-4">
<livewire:rides-search-suggestions />
</div>
<!-- Quick Filters -->
<div class="horizontal-scroll p-4">
<livewire:rides-quick-filters />
</div>
<!-- Ride Cards -->
<div class="space-y-4 p-4">
@foreach($rides as $ride)
<livewire:ride-mobile-card :ride="$ride" :key="$ride->id" />
@endforeach
</div>
<!-- Mobile Pagination -->
<div class="sticky bottom-0 bg-white dark:bg-gray-900 p-4">
{{ $rides->links('pagination.mobile') }}
</div>
</div>
Tablet Layout (768px - 1023px)
- Dual-Pane: Filter sidebar + main content
- Grid Layout: 2-column ride cards
- Advanced Filters: Expandable filter panels
- Touch + Keyboard: Support both interaction modes
Tablet Component Structure:
<div class="rides-tablet-layout flex">
<!-- Filter Sidebar -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 p-6">
<livewire:rides-filters :expanded="true" />
</div>
<!-- Main Content -->
<div class="flex-1 p-6">
<!-- Search and Sort -->
<div class="mb-6 flex items-center space-x-4">
<livewire:rides-search-suggestions class="flex-1" />
<livewire:rides-sort-selector />
</div>
<!-- Grid Layout -->
<div class="grid grid-cols-2 gap-6 mb-6">
@foreach($rides as $ride)
<livewire:ride-tablet-card :ride="$ride" :key="$ride->id" />
@endforeach
</div>
<!-- Pagination -->
{{ $rides->links() }}
</div>
</div>
Desktop Layout (1024px - 1919px)
- Three-Pane: Filter sidebar + main content + quick info panel
- Advanced Grid: 3-4 column layout
- Keyboard Navigation: Full keyboard shortcuts
- Mouse Interactions: Hover effects, context menus
Desktop Component Structure:
<div class="rides-desktop-layout flex">
<!-- Filter Sidebar -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 p-6">
<livewire:rides-filters :expanded="true" :advanced="true" />
</div>
<!-- Main Content -->
<div class="flex-1 p-8">
<!-- Advanced Search Bar -->
<div class="mb-8 flex items-center space-x-6">
<livewire:rides-search-suggestions class="flex-1" :advanced="true" />
<livewire:rides-sort-selector :options="$advancedSortOptions" />
<livewire:rides-view-selector />
</div>
<!-- Grid Layout -->
<div class="grid grid-cols-3 xl:grid-cols-4 gap-6 mb-8">
@foreach($rides as $ride)
<livewire:ride-desktop-card :ride="$ride" :key="$ride->id" />
@endforeach
</div>
<!-- Advanced Pagination -->
{{ $rides->links('pagination.desktop') }}
</div>
<!-- Quick Info Panel -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 p-6">
<livewire:rides-quick-info />
</div>
</div>
Large Screen Layout (1920px+)
- Dashboard Style: Multi-column layout with statistics
- Ultra-Wide Optimization: Up to 6-column grid
- Advanced Analytics: Statistics panels and data visualization
- Multi-Monitor Support: Optimized for extended displays
Performance Optimization Strategy
Caching Implementation
public function mount()
{
$this->cachedFilters = Cache::remember(
"rides.filters.{$this->currentUser->id}",
now()->addHours(1),
fn() => $this->loadFilterOptions()
);
}
public function getRidesProperty()
{
$cacheKey = "rides.listing." . md5(serialize([
'search' => $this->search,
'filters' => $this->filters,
'sort' => $this->sort,
'page' => $this->page
]));
return Cache::remember($cacheKey, now()->addMinutes(15), function() {
return $this->search($this->search)
->applyFilters($this->filters)
->orderBy($this->sort['column'], $this->sort['direction'])
->paginate(24);
});
}
Database Optimization
// Query optimization with eager loading
public function optimizedQuery()
{
return Ride::select([
'id', 'name', 'description', 'ride_type', 'status',
'park_id', 'manufacturer_id', 'designer_id', 'opening_date',
'height_requirement', 'created_at', 'updated_at'
])
->with([
'park:id,name,slug',
'manufacturer:id,name,slug',
'designer:id,name,slug',
'photos' => fn($q) => $q->select(['id', 'ride_id', 'url', 'thumbnail_url'])->limit(1)
])
->withCount(['reviews', 'favorites']);
}
Component Reuse Strategy
Shared Components
RidesSearchSuggestions: Reusable across all ride-related pagesRidesFilters: Extensible filter component with device-aware UIRideCard: Responsive ride display componentRideQuickView: Modal/sidebar quick view component
Context Variations
GlobalRidesListing: All rides across all parksParkRidesListing: Park-specific rides (extends base listing)CategoryRidesListing: Category-specific rides (extends base listing)UserFavoriteRides: User's favorite rides (extends base listing)
Testing Requirements
Feature Tests
/** @test */
public function can_search_rides_across_multiple_fields()
{
// Test multi-term search across name, description, park, manufacturer
$ride = Ride::factory()->create(['name' => 'Space Mountain']);
$park = $ride->park;
$park->update(['name' => 'Magic Kingdom']);
Livewire::test(RidesListing::class)
->set('search', 'Space Magic')
->assertSee($ride->name)
->assertSee($park->name);
}
/** @test */
public function filters_rides_by_multiple_criteria()
{
$coaster = Ride::factory()->create(['ride_type' => 'roller-coaster']);
$kiddie = Ride::factory()->create(['ride_type' => 'kiddie']);
Livewire::test(RidesListing::class)
->set('filters.category', 'roller-coaster')
->assertSee($coaster->name)
->assertDontSee($kiddie->name);
}
/** @test */
public function maintains_django_parity_performance()
{
Ride::factory()->count(100)->create();
$start = microtime(true);
Livewire::test(RidesListing::class);
$end = microtime(true);
$this->assertLessThan(0.5, $end - $start); // < 500ms initial load
}
Cross-Device Tests
/** @test */
public function renders_appropriately_on_mobile()
{
$this->browse(function (Browser $browser) {
$browser->resize(375, 667) // iPhone dimensions
->visit('/rides')
->assertVisible('.rides-mobile-layout')
->assertMissing('.rides-desktop-layout');
});
}
/** @test */
public function supports_touch_gestures_on_tablet()
{
$this->browse(function (Browser $browser) {
$browser->resize(768, 1024) // iPad dimensions
->visit('/rides')
->assertVisible('.rides-tablet-layout')
->swipeLeft('.horizontal-scroll')
->assertMissing('.rides-mobile-layout');
});
}
Performance Targets
Universal Performance Standards
- Initial Load: < 500ms (Django parity requirement)
- Filter Response: < 200ms
- Search Response: < 300ms
- 3G Network: < 3 seconds total page load
- First Contentful Paint: < 1.5 seconds across all devices
Device-Specific Targets
- Mobile (3G): Core functionality in < 3 seconds
- Tablet (WiFi): Full functionality in < 2 seconds
- Desktop (Broadband): Advanced features in < 1 second
- Large Screen: Dashboard mode in < 1.5 seconds
Success Criteria Checklist
Django Parity Verification
- Multi-term search matches Django behavior exactly
- All Django filters implemented and functional
- Pagination performance matches or exceeds Django
- Eager loading prevents N+1 queries like Django
- Context-aware views work identically to Django
Screen-Agnostic Compliance
- Mobile layout optimized for 320px+ screens
- Tablet layout utilizes dual-pane effectively
- Desktop layout provides advanced functionality
- Large screen layout maximizes available space
- All touch targets meet 44px minimum requirement
- Keyboard navigation works on all layouts
Performance Benchmarks
- Initial load under 500ms (matches Django target)
- Filter/search responses under 200ms
- 3G network performance under 3 seconds
- Memory usage optimized with proper caching
- Database queries optimized with eager loading
Component Reusability
- Search component reusable across ride-related pages
- Filter component extensible for different contexts
- Card components work across all screen sizes
- Modal/sidebar quick view components functional
Testing Coverage
- All Django functionality covered by feature tests
- Performance tests validate speed requirements
- Cross-device browser tests pass
- Component integration tests complete
- User interaction tests cover all form factors
Implementation Priority Order
-
Generate Base Components (Day 1)
- Use ThrillWiki generators for rapid scaffolding
- Implement core search and filter functionality
- Set up responsive layouts
-
Django Parity Implementation (Day 2)
- Implement exact search behavior
- Add all Django filter options
- Optimize database queries
-
Screen-Agnostic Optimization (Day 3)
- Fine-tune responsive layouts
- Implement device-specific features
- Add touch and keyboard interactions
-
Performance Optimization (Day 4)
- Implement caching strategies
- Optimize database queries
- Add lazy loading where appropriate
-
Testing and Validation (Day 5)
- Complete test suite implementation
- Validate Django parity
- Verify performance targets
This prompt ensures complete Django parity while leveraging Laravel/Livewire advantages and maintaining ThrillWiki's screen-agnostic design principles.