Files
thrillwiki_laravel/app/Livewire/RidesListing.php
pacnpal 5caa148a89 feat: Complete generation and implementation of Rides Listing components
- Marked Rides Listing Components Generation as completed with detailed results.
- Implemented search/filter logic in RidesListing component for Django parity.
- Created ParkRidesListing, RidesFilters, and RidesSearchSuggestions components with caching and pagination.
- Developed corresponding Blade views for each component.
- Added comprehensive tests for ParkRidesListing, RidesListing, and RidesSearchSuggestions components to ensure functionality and adherence to patterns.
2025-06-23 11:34:13 -04:00

283 lines
7.9 KiB
PHP

<?php
namespace App\Livewire;
use App\Models\Ride;
use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Support\Facades\Cache;
use Livewire\Attributes\Url;
class RidesListing extends Component
{
use WithPagination;
// Search and filter properties with URL binding for deep linking
#[Url(as: 'q')]
public string $search = '';
#[Url(as: 'category')]
public string $category = '';
#[Url(as: 'status')]
public string $status = '';
#[Url(as: 'manufacturer')]
public string $manufacturerId = '';
#[Url(as: 'year_from')]
public string $openingYearFrom = '';
#[Url(as: 'year_to')]
public string $openingYearTo = '';
#[Url(as: 'min_height')]
public string $minHeight = '';
#[Url(as: 'max_height')]
public string $maxHeight = '';
#[Url(as: 'park')]
public string $parkId = '';
// Performance optimization
protected $queryString = [
'search' => ['except' => ''],
'category' => ['except' => ''],
'status' => ['except' => ''],
'manufacturerId' => ['except' => ''],
'openingYearFrom' => ['except' => ''],
'openingYearTo' => ['except' => ''],
'minHeight' => ['except' => ''],
'maxHeight' => ['except' => ''],
'parkId' => ['except' => ''],
'page' => ['except' => 1],
];
/**
* Component initialization
*/
public function mount(): void
{
// Initialize component state
}
/**
* Reset pagination when search/filters change
*/
public function updatedSearch(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedCategory(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedStatus(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedManufacturerId(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedOpeningYearFrom(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedOpeningYearTo(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedMinHeight(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedMaxHeight(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
public function updatedParkId(): void
{
$this->resetPage();
$this->invalidateCache('rides');
}
/**
* Clear all filters
*/
public function clearFilters(): void
{
$this->reset([
'search',
'category',
'status',
'manufacturerId',
'openingYearFrom',
'openingYearTo',
'minHeight',
'maxHeight',
'parkId'
]);
$this->resetPage();
$this->invalidateCache('rides');
}
/**
* Get rides with Django parity search and filtering
*/
public function getRidesProperty()
{
$cacheKey = $this->getCacheKey('rides.' . md5(serialize([
'search' => $this->search,
'category' => $this->category,
'status' => $this->status,
'manufacturerId' => $this->manufacturerId,
'openingYearFrom' => $this->openingYearFrom,
'openingYearTo' => $this->openingYearTo,
'minHeight' => $this->minHeight,
'maxHeight' => $this->maxHeight,
'parkId' => $this->parkId,
'page' => $this->getPage(),
])));
return $this->remember($cacheKey, function () {
return $this->buildQuery()->paginate(12);
}, 300); // 5 minute cache
}
/**
* Build the query with Django parity search and filters
*/
protected function buildQuery()
{
$query = Ride::query()
->with(['park', 'manufacturer', 'designer', 'photos' => function ($q) {
$q->where('is_featured', true)->limit(1);
}]);
// Django parity multi-term search
if (!empty($this->search)) {
$terms = explode(' ', trim($this->search));
foreach ($terms as $term) {
$query->where(function ($subQuery) use ($term) {
$subQuery->where('name', 'ilike', "%{$term}%")
->orWhere('description', 'ilike', "%{$term}%")
->orWhereHas('park', fn($q) => $q->where('name', 'ilike', "%{$term}%"))
->orWhereHas('manufacturer', fn($q) => $q->where('name', 'ilike', "%{$term}%"))
->orWhereHas('designer', fn($q) => $q->where('name', 'ilike', "%{$term}%"));
});
}
}
// Apply filters with Django parity
$query = $this->applyFilters($query);
return $query->orderBy('name');
}
/**
* Apply filters with Django parity
*/
protected function applyFilters($query)
{
return $query
->when($this->category, fn($q, $category) =>
$q->where('ride_type', $category))
->when($this->status, fn($q, $status) =>
$q->where('status', $status))
->when($this->manufacturerId, fn($q, $manufacturerId) =>
$q->where('manufacturer_id', $manufacturerId))
->when($this->openingYearFrom, fn($q, $year) =>
$q->where('opening_date', '>=', "{$year}-01-01"))
->when($this->openingYearTo, fn($q, $year) =>
$q->where('opening_date', '<=', "{$year}-12-31"))
->when($this->minHeight, fn($q, $height) =>
$q->where('height_requirement', '>=', $height))
->when($this->maxHeight, fn($q, $height) =>
$q->where('height_requirement', '<=', $height))
->when($this->parkId, fn($q, $parkId) =>
$q->where('park_id', $parkId));
}
/**
* Get available filter options for dropdowns
*/
public function getFilterOptionsProperty()
{
return $this->remember('filter_options', function () {
return [
'categories' => Ride::select('ride_type')
->distinct()
->whereNotNull('ride_type')
->orderBy('ride_type')
->pluck('ride_type', 'ride_type'),
'statuses' => Ride::select('status')
->distinct()
->whereNotNull('status')
->orderBy('status')
->pluck('status', 'status'),
'manufacturers' => \App\Models\Manufacturer::orderBy('name')
->pluck('name', 'id'),
'parks' => \App\Models\Park::orderBy('name')
->pluck('name', 'id'),
];
}, 3600); // 1 hour cache for filter options
}
/**
* Render the component
*/
public function render()
{
return view('livewire.rides-listing', [
'rides' => $this->rides,
'filterOptions' => $this->filterOptions,
]);
}
/**
* 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);
}
/**
* Invalidate component cache
*/
protected function invalidateCache(string $key = null): void
{
if ($key) {
Cache::forget($this->getCacheKey($key));
} else {
// Clear all cache for this component
Cache::flush();
}
}
}