mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 06:31:10 -05:00
- 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.
283 lines
7.9 KiB
PHP
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();
|
|
}
|
|
}
|
|
} |