mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 10:31:11 -05:00
- 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.
563 lines
19 KiB
PHP
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,
|
|
]);
|
|
}
|
|
} |