Files
thrillwiki_laravel/app/Livewire/DesignersListingUniversal.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

563 lines
19 KiB
PHP

<?php
namespace App\Livewire;
use App\Models\Designer;
use Livewire\Component;
use Livewire\WithPagination;
use Livewire\Attributes\Url;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class DesignersListingUniversal extends Component
{
use WithPagination;
// Universal Listing System Integration
public string $entityType = 'designers';
#[Url(as: 'q')]
public string $search = '';
#[Url(as: 'specialties')]
public array $specialties = [];
#[Url(as: 'style')]
public string $designStyle = '';
#[Url(as: 'founded_from')]
public string $foundedYearFrom = '';
#[Url(as: 'founded_to')]
public string $foundedYearTo = '';
#[Url(as: 'innovation_min')]
public string $minInnovationScore = '';
#[Url(as: 'innovation_max')]
public string $maxInnovationScore = '';
#[Url(as: 'active_years_min')]
public string $minActiveYears = '';
#[Url(as: 'active_years_max')]
public string $maxActiveYears = '';
#[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 $portfolioStats = [];
public array $innovationTimeline = [];
public array $collaborationNetworks = [];
protected $queryString = [
'search' => ['except' => ''],
'specialties' => ['except' => []],
'designStyle' => ['except' => ''],
'foundedYearFrom' => ['except' => ''],
'foundedYearTo' => ['except' => ''],
'minInnovationScore' => ['except' => ''],
'maxInnovationScore' => ['except' => ''],
'minActiveYears' => ['except' => ''],
'maxActiveYears' => ['except' => ''],
'sortBy' => ['except' => 'name'],
'sortDirection' => ['except' => 'asc'],
'viewMode' => ['except' => 'grid'],
'page' => ['except' => 1],
];
/**
* Component initialization
*/
public function mount(): void
{
$this->loadPortfolioStatistics();
$this->loadInnovationTimeline();
$this->loadCollaborationNetworks();
}
/**
* Load creative portfolio statistics with caching
*/
protected function loadPortfolioStatistics(): void
{
$this->portfolioStats = Cache::remember(
'designers.portfolio.stats',
now()->addHours(6),
fn() => $this->calculatePortfolioStatistics()
);
}
/**
* Load innovation timeline data with caching
*/
protected function loadInnovationTimeline(): void
{
$this->innovationTimeline = Cache::remember(
'designers.innovation.timeline',
now()->addHours(12),
fn() => $this->calculateInnovationTimeline()
);
}
/**
* Load collaboration networks with caching
*/
protected function loadCollaborationNetworks(): void
{
$this->collaborationNetworks = Cache::remember(
'designers.collaboration.networks',
now()->addHours(6),
fn() => $this->calculateCollaborationNetworks()
);
}
/**
* Calculate comprehensive portfolio statistics
*/
protected function calculatePortfolioStatistics(): array
{
return [
'total_designers' => Designer::active()->count(),
'coaster_designers' => Designer::active()->specialty('roller_coaster')->count(),
'dark_ride_designers' => Designer::active()->specialty('dark_ride')->count(),
'themed_experience_designers' => Designer::active()->specialty('themed_experience')->count(),
'water_attraction_designers' => Designer::active()->specialty('water_attraction')->count(),
'specialties_distribution' => Designer::active()
->select('specialty', DB::raw('count(*) as count'))
->whereNotNull('specialty')
->groupBy('specialty')
->orderByDesc('count')
->get()
->pluck('count', 'specialty')
->toArray(),
'average_innovation_score' => Designer::active()
->whereNotNull('innovation_score')
->avg('innovation_score'),
'total_designs' => Designer::active()
->withCount('rides')
->get()
->sum('rides_count'),
'top_innovators' => Designer::active()
->orderByDesc('innovation_score')
->take(5)
->get(['name', 'innovation_score', 'specialty'])
->toArray(),
'design_styles' => Designer::active()
->select('design_style', DB::raw('count(*) as count'))
->whereNotNull('design_style')
->groupBy('design_style')
->orderByDesc('count')
->get()
->pluck('count', 'design_style')
->toArray(),
];
}
/**
* Calculate innovation timeline data
*/
protected function calculateInnovationTimeline(): array
{
$timelineData = Designer::active()
->whereNotNull('founded_year')
->whereNotNull('innovation_score')
->select('founded_year', 'innovation_score', 'name', 'specialty')
->orderBy('founded_year')
->get()
->groupBy(function($designer) {
return floor($designer->founded_year / 10) * 10; // Group by decade
})
->map(function($decade) {
return [
'count' => $decade->count(),
'avg_innovation' => $decade->avg('innovation_score'),
'top_designer' => $decade->sortByDesc('innovation_score')->first(),
'specialties' => $decade->countBy('specialty')->toArray()
];
});
return [
'timeline' => $timelineData->toArray(),
'innovation_milestones' => Designer::active()
->where('innovation_score', '>=', 8.5)
->orderByDesc('innovation_score')
->take(10)
->get(['name', 'founded_year', 'innovation_score', 'specialty'])
->toArray(),
'breakthrough_years' => Designer::active()
->whereNotNull('founded_year')
->select('founded_year', DB::raw('count(*) as new_designers'), DB::raw('avg(innovation_score) as avg_innovation'))
->groupBy('founded_year')
->having('new_designers', '>=', 2)
->orderByDesc('avg_innovation')
->take(5)
->get()
->toArray(),
];
}
/**
* Calculate collaboration networks
*/
protected function calculateCollaborationNetworks(): array
{
return [
'collaboration_pairs' => Designer::active()
->whereHas('rides', function($query) {
$query->whereHas('park', function($parkQuery) {
$parkQuery->whereHas('rides', function($rideQuery) {
$rideQuery->whereNotNull('designer_id');
});
});
})
->with(['rides.park.rides.designer'])
->get()
->flatMap(function($designer) {
return $designer->rides->flatMap(function($ride) use ($designer) {
return $ride->park->rides
->where('designer_id', '!=', $designer->id)
->whereNotNull('designer_id')
->pluck('designer.name')
->map(function($collaborator) use ($designer, $ride) {
return [
'designer' => $designer->name,
'collaborator' => $collaborator,
'park' => $ride->park->name
];
});
});
})
->groupBy(function($item) {
$names = [$item['designer'], $item['collaborator']];
sort($names);
return implode(' + ', $names);
})
->map(function($collaborations) {
return [
'count' => $collaborations->count(),
'parks' => $collaborations->pluck('park')->unique()->values()->toArray()
];
})
->sortByDesc('count')
->take(10)
->toArray(),
'network_hubs' => Designer::active()
->withCount('rides')
->having('rides_count', '>=', 3)
->orderByDesc('rides_count')
->take(10)
->get(['name', 'specialty', 'rides_count'])
->toArray(),
'cross_specialty_projects' => Designer::active()
->whereHas('rides', function($query) {
$query->whereHas('park', function($parkQuery) {
$parkQuery->whereHas('rides', function($rideQuery) {
$rideQuery->whereHas('designer', function($designerQuery) {
$designerQuery->whereColumn('specialty', '!=', 'designers.specialty');
});
});
});
})
->with(['rides.park'])
->get()
->flatMap(function($designer) {
return $designer->rides->map(function($ride) use ($designer) {
return [
'designer' => $designer->name,
'specialty' => $designer->specialty,
'park' => $ride->park->name,
'ride' => $ride->name
];
});
})
->take(15)
->toArray(),
];
}
/**
* Django parity creative portfolio search functionality
*/
public function creativePortfolioSearch($query, $specialties = [])
{
return Designer::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('specialty', 'ilike', "%{$term}%")
->orWhere('design_style', 'ilike', "%{$term}%")
->orWhere('headquarters', 'ilike', "%{$term}%")
->orWhereHas('rides', function($rideQuery) use ($term) {
$rideQuery->where('name', 'ilike', "%{$term}%")
->orWhere('category', 'ilike', "%{$term}%");
})
->orWhereHas('rides.park', function($parkQuery) use ($term) {
$parkQuery->where('name', 'ilike', "%{$term}%");
});
});
}
}
})
->when($specialties, function ($q) use ($specialties) {
$q->whereIn('specialty', $specialties);
})
->active()
->with(['rides:id,designer_id,name,category,park_id', 'rides.park:id,name'])
->withCount(['rides']);
}
/**
* Apply creative and innovation filters
*/
public function applyCreativeFilters($query)
{
return $query
->when($this->designStyle, fn($q, $style) =>
$q->where('design_style', $style))
->when($this->foundedYearFrom, fn($q, $year) =>
$q->where('founded_year', '>=', $year))
->when($this->foundedYearTo, fn($q, $year) =>
$q->where('founded_year', '<=', $year))
->when($this->minInnovationScore, fn($q, $score) =>
$q->where('innovation_score', '>=', $score))
->when($this->maxInnovationScore, fn($q, $score) =>
$q->where('innovation_score', '<=', $score))
->when($this->minActiveYears, fn($q, $years) =>
$q->where('active_years', '>=', $years))
->when($this->maxActiveYears, fn($q, $years) =>
$q->where('active_years', '<=', $years));
}
/**
* Get designers with optimized caching
*/
public function getDesignersProperty()
{
$cacheKey = "designers.listing." . md5(serialize([
'search' => $this->search,
'specialties' => $this->specialties,
'designStyle' => $this->designStyle,
'foundedYearFrom' => $this->foundedYearFrom,
'foundedYearTo' => $this->foundedYearTo,
'minInnovationScore' => $this->minInnovationScore,
'maxInnovationScore' => $this->maxInnovationScore,
'minActiveYears' => $this->minActiveYears,
'maxActiveYears' => $this->maxActiveYears,
'sortBy' => $this->sortBy,
'sortDirection' => $this->sortDirection,
'page' => $this->getPage(),
'perPage' => $this->perPage,
]));
return Cache::remember($cacheKey, now()->addMinutes(30), function() {
$query = $this->creativePortfolioSearch($this->search, $this->specialties);
$query = $this->applyCreativeFilters($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 'innovation_score':
$query->orderBy('innovation_score', $this->sortDirection);
break;
case 'designed_rides_count':
$query->orderBy('rides_count', $this->sortDirection);
break;
case 'active_years':
$query->orderBy('active_years', $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 specialties filter and reset pagination
*/
public function updatedSpecialties(): void
{
$this->resetPage();
$this->invalidateCache();
}
/**
* Update any filter and reset pagination
*/
public function updatedDesignStyle(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedFoundedYearFrom(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedFoundedYearTo(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedMinInnovationScore(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedMaxInnovationScore(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedMinActiveYears(): void
{
$this->resetPage();
$this->invalidateCache();
}
public function updatedMaxActiveYears(): 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->specialties = [];
$this->designStyle = '';
$this->foundedYearFrom = '';
$this->foundedYearTo = '';
$this->minInnovationScore = '';
$this->maxInnovationScore = '';
$this->minActiveYears = '';
$this->maxActiveYears = '';
$this->sortBy = 'name';
$this->sortDirection = 'asc';
$this->resetPage();
$this->invalidateCache();
}
/**
* Toggle specialty filter
*/
public function toggleSpecialtyFilter(string $specialty): void
{
if (in_array($specialty, $this->specialties)) {
$this->specialties = array_values(array_diff($this->specialties, [$specialty]));
} else {
$this->specialties[] = $specialty;
}
$this->resetPage();
$this->invalidateCache();
}
/**
* Invalidate component cache
*/
protected function invalidateCache(): void
{
Cache::forget('designers.portfolio.stats');
Cache::forget('designers.innovation.timeline');
Cache::forget('designers.collaboration.networks');
// Clear listing cache pattern - simplified approach
$cacheKeys = [
'designers.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.designers-listing-universal', [
'designers' => $this->designers,
'portfolioStats' => $this->portfolioStats,
'innovationTimeline' => $this->innovationTimeline,
'collaborationNetworks' => $this->collaborationNetworks,
]);
}
}