23 KiB
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:
# 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:
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:
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:
<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:
<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:
<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
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
// 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 statisticsOperatorPortfolioCard: Comprehensive portfolio displayOperatorsIndustryStats: Real-time industry analyticsOperatorFinancialMetrics: Financial performance indicators
Context Variations
ParkOperatorsListing: Park operators only with park portfoliosManufacturersListing: Ride manufacturers with product catalogsDesignersListing: Ride designers with design portfoliosCorporateGroupsListing: Corporate hierarchies and subsidiaries
Testing Requirements
Feature Tests
/** @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
/** @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.