Files
thrillwiki_laravel/app/Livewire/OperatorsListingUniversal.php
pacnpal 97a7682eb7 Add Livewire components for parks, rides, and manufacturers
- Implemented ParksLocationSearch component with loading state and refresh functionality.
- Created ParksMapView component with similar structure and functionality.
- Added RegionalParksListing component for displaying regional parks.
- Developed RidesListingUniversal component for universal listing integration.
- Established ManufacturersListing view with navigation and Livewire integration.
- Added feature tests for various Livewire components including OperatorHierarchyView, OperatorParksListing, OperatorPortfolioCard, OperatorsListing, OperatorsRoleFilter, ParksListing, ParksLocationSearch, ParksMapView, and RegionalParksListing to ensure proper rendering and adherence to patterns.
2025-06-23 21:31:05 -04:00

479 lines
15 KiB
PHP

<?php
namespace App\Livewire;
use App\Models\Operator;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class OperatorsListingUniversal extends Component
{
use WithPagination;
// Universal Listing System Integration
public string $entityType = 'operators';
#[Url(as: 'q')]
public string $search = '';
#[Url(as: 'roles')]
public array $roleFilter = [];
#[Url(as: 'sector')]
public string $industrySector = '';
#[Url(as: 'size')]
public string $companySize = '';
#[Url(as: 'founded_from')]
public string $foundedYearFrom = '';
#[Url(as: 'founded_to')]
public string $foundedYearTo = '';
#[Url(as: 'presence')]
public string $geographicPresence = '';
#[Url(as: 'min_revenue')]
public string $minRevenue = '';
#[Url(as: 'max_revenue')]
public string $maxRevenue = '';
#[Url(as: 'sort')]
public string $sortBy = 'name';
#[Url(as: 'dir')]
public string $sortDirection = 'asc';
#[Url(as: 'view')]
public string $viewMode = 'grid';
public int $perPage = 20;
public array $industryStats = [];
public array $marketData = [];
protected $queryString = [
'search' => ['except' => ''],
'roleFilter' => ['except' => []],
'industrySector' => ['except' => ''],
'companySize' => ['except' => ''],
'foundedYearFrom' => ['except' => ''],
'foundedYearTo' => ['except' => ''],
'geographicPresence' => ['except' => ''],
'minRevenue' => ['except' => ''],
'maxRevenue' => ['except' => ''],
'sortBy' => ['except' => 'name'],
'sortDirection' => ['except' => 'asc'],
'viewMode' => ['except' => 'grid'],
'page' => ['except' => 1],
];
/**
* Component initialization
*/
public function mount(): void
{
$this->loadIndustryStatistics();
$this->loadMarketData();
}
/**
* Load industry statistics with caching
*/
protected function loadIndustryStatistics(): void
{
$this->industryStats = Cache::remember(
'operators.industry.stats',
now()->addHours(6),
fn() => $this->calculateIndustryStatistics()
);
}
/**
* Load market analysis data with caching
*/
protected function loadMarketData(): void
{
$this->marketData = Cache::remember(
'operators.market.data',
now()->addHours(12),
fn() => $this->loadMarketAnalysis()
);
}
/**
* Calculate comprehensive industry statistics
*/
protected function calculateIndustryStatistics(): array
{
return [
'total_operators' => Operator::active()->count(),
'park_operators' => Operator::active()->parkOperators()->count(),
'manufacturers' => Operator::active()->manufacturers()->count(),
'designers' => Operator::active()->designers()->count(),
'mixed_role' => Operator::active()
->whereHas('parks')
->whereHas('manufactured_rides')
->count(),
'sectors' => Operator::active()
->select('industry_sector', DB::raw('count(*) as count'))
->whereNotNull('industry_sector')
->groupBy('industry_sector')
->orderByDesc('count')
->get()
->pluck('count', 'industry_sector')
->toArray(),
'company_sizes' => [
'small' => Operator::active()->companySize('small')->count(),
'medium' => Operator::active()->companySize('medium')->count(),
'large' => Operator::active()->companySize('large')->count(),
'enterprise' => Operator::active()->companySize('enterprise')->count(),
],
'geographic_distribution' => Operator::active()
->whereHas('parks.location')
->with('parks.location')
->get()
->flatMap(fn($op) => $op->parks->pluck('location.country'))
->countBy()
->sortDesc()
->take(10)
->toArray(),
];
}
/**
* Load market analysis data
*/
protected function loadMarketAnalysis(): array
{
return [
'total_market_cap' => Operator::active()
->whereNotNull('market_cap')
->sum('market_cap'),
'total_revenue' => Operator::active()
->whereNotNull('annual_revenue')
->sum('annual_revenue'),
'average_parks_per_operator' => Operator::active()
->parkOperators()
->avg('total_parks'),
'top_operators_by_parks' => Operator::active()
->parkOperators()
->orderByDesc('total_parks')
->take(5)
->get(['name', 'total_parks'])
->toArray(),
'top_manufacturers_by_rides' => Operator::active()
->manufacturers()
->orderByDesc('total_rides_manufactured')
->take(5)
->get(['name', 'total_rides_manufactured'])
->toArray(),
];
}
/**
* Django parity dual-role search functionality
*/
public function dualRoleSearch($query, $roles = [])
{
return Operator::query()
->when($query, function ($q) use ($query) {
$terms = explode(' ', trim($query));
foreach ($terms as $term) {
if (strlen($term) >= 2) {
$q->where(function ($subQuery) use ($term) {
$subQuery->where('name', 'ilike', "%{$term}%")
->orWhere('description', 'ilike', "%{$term}%")
->orWhere('industry_sector', 'ilike', "%{$term}%")
->orWhere('headquarters_location', '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->whereHas('parks');
}
if (in_array('ride_manufacturer', $roles)) {
$roleQuery->orWhereHas('manufactured_rides');
}
if (in_array('ride_designer', $roles)) {
$roleQuery->orWhereHas('designed_rides');
}
});
})
->active()
->with(['location', 'parks:id,operator_id,name', 'manufactured_rides:id,manufacturer_id,name', 'designed_rides:id,designer_id,name'])
->withCount(['parks', 'manufactured_rides', 'designed_rides']);
}
/**
* Apply advanced industry filters
*/
public function applyIndustryFilters($query)
{
return $query
->when($this->industrySector, fn($q, $sector) =>
$q->where('industry_sector', $sector))
->when($this->companySize, fn($q, $size) =>
$q->companySize($size))
->when($this->foundedYearFrom, fn($q, $year) =>
$q->where('founded_year', '>=', $year))
->when($this->foundedYearTo, fn($q, $year) =>
$q->where('founded_year', '<=', $year))
->when($this->geographicPresence, 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($this->minRevenue, fn($q, $revenue) =>
$q->where('annual_revenue', '>=', $revenue))
->when($this->maxRevenue, fn($q, $revenue) =>
$q->where('annual_revenue', '<=', $revenue));
}
/**
* Get operators with optimized caching
*/
public function getOperatorsProperty()
{
$cacheKey = "operators.listing." . md5(serialize([
'search' => $this->search,
'roleFilter' => $this->roleFilter,
'industrySector' => $this->industrySector,
'companySize' => $this->companySize,
'foundedYearFrom' => $this->foundedYearFrom,
'foundedYearTo' => $this->foundedYearTo,
'geographicPresence' => $this->geographicPresence,
'minRevenue' => $this->minRevenue,
'maxRevenue' => $this->maxRevenue,
'sortBy' => $this->sortBy,
'sortDirection' => $this->sortDirection,
'page' => $this->getPage(),
'perPage' => $this->perPage,
]));
return Cache::remember($cacheKey, now()->addMinutes(30), function() {
$query = $this->dualRoleSearch($this->search, $this->roleFilter);
$query = $this->applyIndustryFilters($query);
// Apply sorting
switch ($this->sortBy) {
case 'name':
$query->orderBy('name', $this->sortDirection);
break;
case 'founded_year':
$query->orderBy('founded_year', $this->sortDirection);
break;
case 'parks_count':
$query->orderBy('total_parks', $this->sortDirection);
break;
case 'rides_count':
$query->orderBy('total_rides_manufactured', $this->sortDirection);
break;
case 'revenue':
$query->orderBy('annual_revenue', $this->sortDirection);
break;
case 'market_influence':
$query->orderByRaw('(total_parks * 10 + total_rides_manufactured * 2) ' . $this->sortDirection);
break;
default:
$query->orderBy('name', 'asc');
}
return $query->paginate($this->perPage);
});
}
/**
* Update search and reset pagination
*/
public function updatedSearch(): void
{
$this->resetPage();
$this->invalidateCache();
}
/**
* Update role filter and reset pagination
*/
public function updatedRoleFilter(): void
{
$this->resetPage();
$this->invalidateCache();
}
/**
* Update any filter and reset pagination
*/
public function updatedIndustrySector(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedCompanySize(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedFoundedYearFrom(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedFoundedYearTo(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedGeographicPresence(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedMinRevenue(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedMaxRevenue(): void
{
$this->resetPage();
$this->invalidateCache();
}
/**
* Sort by specific column
*/
public function sortBy(string $column): void
{
if ($this->sortBy === $column) {
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $column;
$this->sortDirection = 'asc';
}
$this->resetPage();
$this->invalidateCache();
}
/**
* Change view mode
*/
public function setViewMode(string $mode): void
{
$this->viewMode = $mode;
}
/**
* Clear all filters
*/
public function clearFilters(): void
{
$this->search = '';
$this->roleFilter = [];
$this->industrySector = '';
$this->companySize = '';
$this->foundedYearFrom = '';
$this->foundedYearTo = '';
$this->geographicPresence = '';
$this->minRevenue = '';
$this->maxRevenue = '';
$this->sortBy = 'name';
$this->sortDirection = 'asc';
$this->resetPage();
$this->invalidateCache();
}
/**
* Toggle role filter
*/
public function toggleRoleFilter(string $role): void
{
if (in_array($role, $this->roleFilter)) {
$this->roleFilter = array_values(array_diff($this->roleFilter, [$role]));
} else {
$this->roleFilter[] = $role;
}
$this->resetPage();
$this->invalidateCache();
}
/**
* Invalidate component cache
*/
protected function invalidateCache(): void
{
Cache::forget('operators.industry.stats');
Cache::forget('operators.market.data');
// Clear listing cache pattern - simplified approach
$cacheKeys = [
'operators.listing.*'
];
foreach ($cacheKeys as $pattern) {
Cache::forget($pattern);
}
}
/**
* Get cache key for this component
*/
protected function getCacheKey(string $suffix = ''): string
{
return 'thrillwiki.' . class_basename(static::class) . '.' . $suffix;
}
/**
* Remember data with caching
*/
protected function remember(string $key, $callback, int $ttl = 3600)
{
return Cache::remember($this->getCacheKey($key), $ttl, $callback);
}
/**
* Render the component
*/
public function render()
{
return view('livewire.operators-listing-universal', [
'operators' => $this->operators,
'industryStats' => $this->industryStats,
'marketData' => $this->marketData,
]);
}
}