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:
pacnpal
2025-06-23 10:21:54 -04:00
parent ecf237d592
commit c2f3532469
9 changed files with 2995 additions and 183 deletions

View File

@@ -0,0 +1,596 @@
# Operators Listing Page Implementation Prompt
## Django Parity Reference
**Django Implementation**: `companies/views.py` - `CompanyListView` & `ManufacturerListView` (lines 62-126)
**Django Template**: `companies/templates/companies/company_list.html`
**Django Features**: Dual-role filtering (park operators vs ride manufacturers), industry statistics, portfolio showcases, corporate hierarchy display, market analysis
## Core Implementation Requirements
### Laravel/Livewire Architecture
Generate the operators listing system using ThrillWiki's custom generators:
```bash
# Generate unified operators listing with dual-role support
php artisan make:thrillwiki-livewire OperatorsListing --paginated --cached --with-tests
# Generate role-specific filtering component
php artisan make:thrillwiki-livewire OperatorsRoleFilter --reusable --with-tests
# Generate portfolio showcase component
php artisan make:thrillwiki-livewire OperatorPortfolioCard --reusable --with-tests
# Generate industry statistics dashboard
php artisan make:thrillwiki-livewire OperatorsIndustryStats --reusable --cached
# Generate corporate hierarchy visualization
php artisan make:thrillwiki-livewire OperatorHierarchyView --reusable --with-tests
# Generate market analysis component
php artisan make:thrillwiki-livewire OperatorsMarketAnalysis --reusable --cached
```
### Django Parity Features
#### 1. Dual-Role Search Functionality
**Django Implementation**: Multi-role search across:
- Operator name (`name__icontains`)
- Company description (`description__icontains`)
- Founded year range (`founded_year__range`)
- Headquarters location (`headquarters__city__icontains`)
- Role-specific filtering (park_operator, ride_manufacturer, or both)
- Industry sector (`industry_sector__icontains`)
**Laravel Implementation**:
```php
public function dualRoleSearch($query, $roles = [])
{
return Operator::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('industry_sector', 'ilike', "%{$term}%")
->orWhereHas('location', function($locQuery) use ($term) {
$locQuery->where('city', 'ilike', "%{$term}%")
->orWhere('state', 'ilike', "%{$term}%")
->orWhere('country', 'ilike', "%{$term}%");
});
});
}
})
->when($roles, function ($q) use ($roles) {
$q->where(function ($roleQuery) use ($roles) {
if (in_array('park_operator', $roles)) {
$roleQuery->whereExists(function ($exists) {
$exists->select(DB::raw(1))
->from('parks')
->whereRaw('parks.operator_id = operators.id');
});
}
if (in_array('ride_manufacturer', $roles)) {
$roleQuery->orWhereExists(function ($exists) {
$exists->select(DB::raw(1))
->from('rides')
->whereRaw('rides.manufacturer_id = operators.id');
});
}
if (in_array('ride_designer', $roles)) {
$roleQuery->orWhereExists(function ($exists) {
$exists->select(DB::raw(1))
->from('rides')
->whereRaw('rides.designer_id = operators.id');
});
}
});
})
->with(['location', 'parks', 'manufactured_rides', 'designed_rides'])
->withCount(['parks', 'manufactured_rides', 'designed_rides']);
}
```
#### 2. Advanced Industry Filtering
**Django Filters**:
- Role type (park_operator, manufacturer, designer, mixed)
- Industry sector (entertainment, manufacturing, technology)
- Company size (small, medium, large, enterprise)
- Founded year range
- Geographic presence (regional, national, international)
- Market capitalization range
- Annual revenue range
**Laravel Filters Implementation**:
```php
public function applyIndustryFilters($query, $filters)
{
return $query
->when($filters['role_type'] ?? null, function ($q, $roleType) {
switch ($roleType) {
case 'park_operator_only':
$q->whereHas('parks')->whereDoesntHave('manufactured_rides');
break;
case 'manufacturer_only':
$q->whereHas('manufactured_rides')->whereDoesntHave('parks');
break;
case 'mixed':
$q->whereHas('parks')->whereHas('manufactured_rides');
break;
case 'designer':
$q->whereHas('designed_rides');
break;
}
})
->when($filters['industry_sector'] ?? null, fn($q, $sector) =>
$q->where('industry_sector', $sector))
->when($filters['company_size'] ?? null, function ($q, $size) {
$ranges = [
'small' => [1, 100],
'medium' => [101, 1000],
'large' => [1001, 10000],
'enterprise' => [10001, PHP_INT_MAX]
];
if (isset($ranges[$size])) {
$q->whereBetween('employee_count', $ranges[$size]);
}
})
->when($filters['founded_year_from'] ?? null, fn($q, $year) =>
$q->where('founded_year', '>=', $year))
->when($filters['founded_year_to'] ?? null, fn($q, $year) =>
$q->where('founded_year', '<=', $year))
->when($filters['geographic_presence'] ?? null, function ($q, $presence) {
switch ($presence) {
case 'regional':
$q->whereHas('parks', function ($parkQ) {
$parkQ->whereHas('location', function ($locQ) {
$locQ->havingRaw('COUNT(DISTINCT country) = 1');
});
});
break;
case 'international':
$q->whereHas('parks', function ($parkQ) {
$parkQ->whereHas('location', function ($locQ) {
$locQ->havingRaw('COUNT(DISTINCT country) > 1');
});
});
break;
}
})
->when($filters['min_revenue'] ?? null, fn($q, $revenue) =>
$q->where('annual_revenue', '>=', $revenue))
->when($filters['max_revenue'] ?? null, fn($q, $revenue) =>
$q->where('annual_revenue', '<=', $revenue));
}
```
#### 3. Portfolio and Statistics Display
**Portfolio Metrics**:
- Total parks operated
- Total rides manufactured/designed
- Geographic reach (countries, continents)
- Market share analysis
- Revenue and financial metrics
- Industry influence score
### Screen-Agnostic Design Implementation
#### Mobile Layout (320px - 767px)
- **Corporate Cards**: Compact operator cards with key metrics
- **Role Badges**: Visual indicators for operator/manufacturer/designer roles
- **Portfolio Highlights**: Key statistics prominently displayed
- **Industry Filters**: Simplified filtering for mobile users
**Mobile Component Structure**:
```blade
<div class="operators-mobile-layout">
<!-- Industry Search Bar -->
<div class="sticky top-0 bg-white dark:bg-gray-900 z-20 p-4">
<livewire:operators-industry-search />
<div class="flex items-center mt-2 space-x-2">
<button wire:click="filterByRole('park_operator')"
class="flex items-center space-x-1 px-3 py-1 {{ $activeRole === 'park_operator' ? 'bg-blue-500 text-white' : 'bg-blue-100 dark:bg-blue-900' }} rounded-full">
<span class="text-sm">Operators</span>
</button>
<button wire:click="filterByRole('manufacturer')"
class="flex items-center space-x-1 px-3 py-1 {{ $activeRole === 'manufacturer' ? 'bg-green-500 text-white' : 'bg-green-100 dark:bg-green-900' }} rounded-full">
<span class="text-sm">Manufacturers</span>
</button>
</div>
</div>
<!-- Industry Statistics Banner -->
<div class="bg-gradient-to-r from-blue-500 to-purple-600 text-white p-4 m-4 rounded-lg">
<livewire:operators-industry-stats :compact="true" />
</div>
<!-- Quick Filters -->
<div class="horizontal-scroll p-4 pb-2">
<livewire:operators-quick-filters />
</div>
<!-- Operator Cards -->
<div class="space-y-4 p-4">
@foreach($operators as $operator)
<livewire:operator-mobile-card :operator="$operator" :show-portfolio="true" :key="$operator->id" />
@endforeach
</div>
<!-- Mobile Pagination -->
<div class="sticky bottom-0 bg-white dark:bg-gray-900 p-4">
{{ $operators->links('pagination.mobile') }}
</div>
</div>
```
#### Tablet Layout (768px - 1023px)
- **Dual-Pane Layout**: Filter sidebar + operator grid
- **Portfolio Showcases**: Detailed portfolio cards for each operator
- **Industry Dashboard**: Real-time industry statistics and trends
- **Comparison Mode**: Side-by-side operator comparisons
**Tablet Component Structure**:
```blade
<div class="operators-tablet-layout flex h-screen">
<!-- Industry Filter Sidebar -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
<div class="p-6">
<livewire:operators-industry-search :advanced="true" />
<div class="mt-6">
<livewire:operators-role-filter :expanded="true" />
</div>
<div class="mt-6">
<livewire:operators-industry-filters :show-financial="true" />
</div>
<div class="mt-6">
<livewire:operators-industry-stats :detailed="true" />
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="flex-1 flex flex-col">
<!-- Industry Header -->
<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">{{ $operators->total() }} Industry Leaders</h2>
<livewire:operators-market-overview />
</div>
<div class="flex items-center space-x-2">
<livewire:operators-sort-selector />
<livewire:operators-view-toggle />
</div>
</div>
</div>
<!-- Content Grid -->
<div class="flex-1 overflow-y-auto p-6">
@if($view === 'grid')
<div class="grid grid-cols-2 gap-6">
@foreach($operators as $operator)
<livewire:operator-tablet-card :operator="$operator" :detailed="true" :key="$operator->id" />
@endforeach
</div>
@elseif($view === 'portfolio')
<div class="space-y-6">
@foreach($operators as $operator)
<livewire:operator-portfolio-showcase :operator="$operator" :key="$operator->id" />
@endforeach
</div>
@else
<livewire:operators-market-analysis :operators="$operators" />
@endif
<div class="mt-6">
{{ $operators->links() }}
</div>
</div>
</div>
</div>
```
#### Desktop Layout (1024px - 1919px)
- **Three-Pane Layout**: Filters + main content + industry insights
- **Advanced Analytics**: Market share analysis and industry trends
- **Corporate Hierarchies**: Visual representation of corporate structures
- **Portfolio Deep Dives**: Comprehensive portfolio analysis
**Desktop Component Structure**:
```blade
<div class="operators-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:operators-industry-search :advanced="true" :autocomplete="true" />
<div class="mt-6">
<livewire:operators-role-filter :advanced="true" :show-statistics="true" />
</div>
<div class="mt-6">
<livewire:operators-industry-filters :advanced="true" :show-financial="true" />
</div>
</div>
</div>
<!-- Main Content -->
<div class="flex-1 flex flex-col">
<!-- Industry Dashboard 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">{{ $operators->total() }} Industry Operators</h1>
<livewire:operators-market-summary />
</div>
<div class="flex items-center space-x-4">
<livewire:operators-sort-selector :advanced="true" />
<livewire:operators-view-selector />
<livewire:operators-export-options />
</div>
</div>
<livewire:operators-advanced-search />
</div>
<!-- Content Area -->
<div class="flex-1 overflow-y-auto">
@if($view === 'grid')
<div class="p-6">
<div class="grid grid-cols-3 xl:grid-cols-4 gap-6">
@foreach($operators as $operator)
<livewire:operator-desktop-card :operator="$operator" :comprehensive="true" :key="$operator->id" />
@endforeach
</div>
<div class="mt-8">
{{ $operators->links('pagination.desktop') }}
</div>
</div>
@elseif($view === 'portfolio')
<div class="p-6 space-y-8">
@foreach($operators as $operator)
<livewire:operator-portfolio-detailed :operator="$operator" :key="$operator->id" />
@endforeach
</div>
@elseif($view === 'hierarchy')
<div class="p-6">
<livewire:operators-hierarchy-visualization :operators="$operators" />
</div>
@else
<div class="p-6">
<livewire:operators-market-dashboard :operators="$operators" />
</div>
@endif
</div>
</div>
<!-- Industry Insights Panel -->
<div class="w-80 bg-gray-50 dark:bg-gray-800 overflow-y-auto">
<div class="p-6">
<livewire:operators-industry-insights />
<div class="mt-6">
<livewire:operators-market-trends />
</div>
<div class="mt-6">
<livewire:operators-recent-activity />
</div>
</div>
</div>
</div>
```
#### Large Screen Layout (1920px+)
- **Dashboard-Style Interface**: Comprehensive industry analytics
- **Multi-Panel Views**: Simultaneous portfolio and market analysis
- **Advanced Visualizations**: Corporate network maps and market dynamics
- **Real-Time Market Data**: Live industry statistics and trends
### Performance Optimization Strategy
#### Industry-Specific Caching
```php
public function mount()
{
$this->industryStats = Cache::remember(
'operators.industry.stats',
now()->addHours(6),
fn() => $this->calculateIndustryStatistics()
);
$this->marketData = Cache::remember(
'operators.market.data',
now()->addHours(12),
fn() => $this->loadMarketAnalysis()
);
}
public function getOperatorsProperty()
{
$cacheKey = "operators.listing." . md5(serialize([
'search' => $this->search,
'filters' => $this->filters,
'role_filter' => $this->roleFilter,
'sort' => $this->sort,
'page' => $this->page
]));
return Cache::remember($cacheKey, now()->addMinutes(30), function() {
return $this->dualRoleSearch($this->search, $this->roleFilter)
->applyIndustryFilters($this->filters)
->orderBy($this->sort['column'], $this->sort['direction'])
->paginate(20);
});
}
```
#### Financial Data Optimization
```php
// Optimized query for financial and portfolio data
public function optimizedFinancialQuery()
{
return Operator::select([
'operators.*',
DB::raw('COALESCE(parks_count.count, 0) as parks_count'),
DB::raw('COALESCE(rides_count.count, 0) as manufactured_rides_count'),
DB::raw('COALESCE(designed_rides_count.count, 0) as designed_rides_count'),
DB::raw('CASE
WHEN annual_revenue > 10000000000 THEN "enterprise"
WHEN annual_revenue > 1000000000 THEN "large"
WHEN annual_revenue > 100000000 THEN "medium"
ELSE "small"
END as company_size_category')
])
->leftJoin(DB::raw('(SELECT operator_id, COUNT(*) as count FROM parks GROUP BY operator_id) as parks_count'),
'operators.id', '=', 'parks_count.operator_id')
->leftJoin(DB::raw('(SELECT manufacturer_id, COUNT(*) as count FROM rides GROUP BY manufacturer_id) as rides_count'),
'operators.id', '=', 'rides_count.manufacturer_id')
->leftJoin(DB::raw('(SELECT designer_id, COUNT(*) as count FROM rides GROUP BY designer_id) as designed_rides_count'),
'operators.id', '=', 'designed_rides_count.designer_id')
->with([
'location:id,city,state,country',
'parks:id,operator_id,name,opening_date',
'manufactured_rides:id,manufacturer_id,name,ride_type',
'designed_rides:id,designer_id,name,ride_type'
]);
}
```
### Component Reuse Strategy
#### Shared Components
- **`OperatorsRoleFilter`**: Multi-role filtering with statistics
- **`OperatorPortfolioCard`**: Comprehensive portfolio display
- **`OperatorsIndustryStats`**: Real-time industry analytics
- **`OperatorFinancialMetrics`**: Financial performance indicators
#### Context Variations
- **`ParkOperatorsListing`**: Park operators only with park portfolios
- **`ManufacturersListing`**: Ride manufacturers with product catalogs
- **`DesignersListing`**: Ride designers with design portfolios
- **`CorporateGroupsListing`**: Corporate hierarchies and subsidiaries
### Testing Requirements
#### Feature Tests
```php
/** @test */
public function can_filter_operators_by_dual_roles()
{
$pureOperator = Operator::factory()->create(['name' => 'Disney Parks']);
$pureOperator->parks()->create(['name' => 'Magic Kingdom']);
$pureManufacturer = Operator::factory()->create(['name' => 'Intamin']);
$pureManufacturer->manufactured_rides()->create(['name' => 'Millennium Force']);
$mixedOperator = Operator::factory()->create(['name' => 'Universal']);
$mixedOperator->parks()->create(['name' => 'Universal Studios']);
$mixedOperator->manufactured_rides()->create(['name' => 'Custom Ride']);
Livewire::test(OperatorsListing::class)
->set('roleFilter', ['park_operator'])
->assertSee($pureOperator->name)
->assertSee($mixedOperator->name)
->assertDontSee($pureManufacturer->name);
}
/** @test */
public function calculates_industry_statistics_correctly()
{
Operator::factory()->count(10)->create(['industry_sector' => 'entertainment']);
Operator::factory()->count(5)->create(['industry_sector' => 'manufacturing']);
$component = Livewire::test(OperatorsListing::class);
$stats = $component->get('industryStats');
$this->assertEquals(15, $stats['total_operators']);
$this->assertEquals(10, $stats['entertainment_operators']);
$this->assertEquals(5, $stats['manufacturing_operators']);
}
/** @test */
public function maintains_django_parity_performance_with_portfolio_data()
{
Operator::factory()->count(50)->create();
$start = microtime(true);
Livewire::test(OperatorsListing::class);
$end = microtime(true);
$this->assertLessThan(0.5, $end - $start); // < 500ms with portfolio data
}
```
#### Financial Data Tests
```php
/** @test */
public function categorizes_company_size_correctly()
{
$enterprise = Operator::factory()->create(['annual_revenue' => 15000000000]);
$large = Operator::factory()->create(['annual_revenue' => 5000000000]);
$medium = Operator::factory()->create(['annual_revenue' => 500000000]);
$small = Operator::factory()->create(['annual_revenue' => 50000000]);
Livewire::test(OperatorsListing::class)
->set('filters.company_size', 'enterprise')
->assertSee($enterprise->name)
->assertDontSee($large->name);
}
/** @test */
public function handles_portfolio_metrics_calculation()
{
$operator = Operator::factory()->create();
$operator->parks()->createMany(3, ['name' => 'Test Park']);
$operator->manufactured_rides()->createMany(5, ['name' => 'Test Ride']);
$component = Livewire::test(OperatorsListing::class);
$portfolioData = $component->get('operators')->first();
$this->assertEquals(3, $portfolioData->parks_count);
$this->assertEquals(5, $portfolioData->manufactured_rides_count);
}
```
### Performance Targets
#### Universal Performance Standards with Financial Data
- **Initial Load**: < 500ms (including industry statistics)
- **Portfolio Calculation**: < 200ms for 100 operators
- **Financial Filtering**: < 150ms with complex criteria
- **Market Analysis**: < 1 second for trend calculations
- **Industry Statistics**: < 100ms (cached)
#### Industry-Specific Caching Strategy
- **Market Data Cache**: 12 hours (financial markets change)
- **Industry Statistics**: 6 hours (relatively stable)
- **Portfolio Metrics**: 1 hour (operational data)
- **Company Profiles**: 24 hours (corporate data stable)
### Success Criteria Checklist
#### Django Parity Verification
- [ ] Dual-role filtering matches Django behavior exactly
- [ ] Industry statistics calculated identically to Django
- [ ] Portfolio metrics match Django calculations
- [ ] Financial filtering provides same results as Django
- [ ] Corporate hierarchy display matches Django structure
#### Screen-Agnostic Compliance
- [ ] Mobile layout optimized for corporate data consumption
- [ ] Tablet layout provides effective portfolio comparisons
- [ ] Desktop layout maximizes industry analytics
- [ ] Large screen layout provides comprehensive market view
- [ ] All layouts handle complex financial data gracefully
#### Performance Benchmarks
- [ ] Initial load under 500ms including portfolio data
- [ ] Financial calculations under 200ms
- [ ] Industry statistics under 100ms (cached)
- [ ] Market analysis under 1 second
- [ ] Portfolio caching reduces server load by 60%
#### Industry Feature Completeness
- [ ] Dual-role filtering works across all operator types
- [ ] Financial metrics display accurately
- [ ] Portfolio showcases provide comprehensive overviews
- [ ] Market analysis provides meaningful insights
- [ ] Corporate hierarchies visualize relationships correctly
This prompt ensures complete Django parity while providing comprehensive industry analysis capabilities that leverage modern data visualization and maintain ThrillWiki's screen-agnostic design principles.