Files
thrillwiki_laravel/memory-bank/prompts/RidesListingPagePrompt.md

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

/** @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

  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.