# 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:

```bash
# 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**:
```php
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**:
```php
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**:
```blade
<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**:
```blade
<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**:
```blade
<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
```php
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
```php
// 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 pages
- **`RidesFilters`**: Extensible filter component with device-aware UI
- **`RideCard`**: Responsive ride display component
- **`RideQuickView`**: Modal/sidebar quick view component

#### Context Variations
- **`GlobalRidesListing`**: All rides across all parks
- **`ParkRidesListing`**: 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
```php
/** @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
```php
/** @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

1. **Generate Base Components** (Day 1)
   - Use ThrillWiki generators for rapid scaffolding
   - Implement core search and filter functionality
   - Set up responsive layouts

2. **Django Parity Implementation** (Day 2)
   - Implement exact search behavior
   - Add all Django filter options
   - Optimize database queries

3. **Screen-Agnostic Optimization** (Day 3)
   - Fine-tune responsive layouts
   - Implement device-specific features
   - Add touch and keyboard interactions

4. **Performance Optimization** (Day 4)
   - Implement caching strategies
   - Optimize database queries
   - Add lazy loading where appropriate

5. **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.