mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 06:31:10 -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.
221 lines
5.8 KiB
PHP
221 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use App\Models\Ride;
|
|
use App\Models\Park;
|
|
use App\Models\Operator;
|
|
use Livewire\Component;
|
|
use Livewire\Attributes\On;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class RidesSearchSuggestions extends Component
|
|
{
|
|
public string $query = '';
|
|
public bool $showSuggestions = false;
|
|
public int $maxSuggestions = 8;
|
|
public array $suggestions = [];
|
|
|
|
/**
|
|
* Component initialization
|
|
*/
|
|
public function mount(string $query = ''): void
|
|
{
|
|
$this->query = $query;
|
|
if (!empty($query)) {
|
|
$this->updateSuggestions();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listen for search query updates from parent components
|
|
*/
|
|
#[On('search-query-updated')]
|
|
public function handleSearchUpdate(string $query): void
|
|
{
|
|
$this->query = $query;
|
|
$this->updateSuggestions();
|
|
}
|
|
|
|
/**
|
|
* Update search suggestions based on current query
|
|
*/
|
|
public function updateSuggestions(): void
|
|
{
|
|
if (strlen($this->query) < 2) {
|
|
$this->suggestions = [];
|
|
$this->showSuggestions = false;
|
|
return;
|
|
}
|
|
|
|
$this->suggestions = $this->remember(
|
|
'suggestions.' . md5(strtolower($this->query)),
|
|
fn() => $this->buildSuggestions(),
|
|
300 // 5-minute cache for suggestions
|
|
);
|
|
|
|
$this->showSuggestions = !empty($this->suggestions);
|
|
}
|
|
|
|
/**
|
|
* Build search suggestions from multiple sources
|
|
*/
|
|
protected function buildSuggestions(): array
|
|
{
|
|
$query = strtolower(trim($this->query));
|
|
$suggestions = collect();
|
|
|
|
// Ride name suggestions
|
|
$rideSuggestions = Ride::select('name', 'slug', 'id')
|
|
->with(['park:id,name,slug'])
|
|
->where('name', 'ilike', "%{$query}%")
|
|
->limit(4)
|
|
->get()
|
|
->map(function ($ride) {
|
|
return [
|
|
'type' => 'ride',
|
|
'title' => $ride->name,
|
|
'subtitle' => $ride->park->name ?? 'Unknown Park',
|
|
'url' => route('rides.show', $ride->slug),
|
|
'icon' => 'ride',
|
|
'category' => 'Rides'
|
|
];
|
|
});
|
|
|
|
// Park name suggestions
|
|
$parkSuggestions = Park::select('name', 'slug', 'id')
|
|
->where('name', 'ilike', "%{$query}%")
|
|
->limit(3)
|
|
->get()
|
|
->map(function ($park) {
|
|
return [
|
|
'type' => 'park',
|
|
'title' => $park->name,
|
|
'subtitle' => 'Theme Park',
|
|
'url' => route('parks.show', $park->slug),
|
|
'icon' => 'park',
|
|
'category' => 'Parks'
|
|
];
|
|
});
|
|
|
|
// Manufacturer/Designer suggestions
|
|
$operatorSuggestions = Operator::select('name', 'slug', 'id')
|
|
->where('name', 'ilike', "%{$query}%")
|
|
->limit(2)
|
|
->get()
|
|
->map(function ($operator) {
|
|
return [
|
|
'type' => 'operator',
|
|
'title' => $operator->name,
|
|
'subtitle' => 'Manufacturer/Designer',
|
|
'url' => route('operators.show', $operator->slug),
|
|
'icon' => 'operator',
|
|
'category' => 'Companies'
|
|
];
|
|
});
|
|
|
|
// Combine and prioritize suggestions
|
|
$suggestions = $suggestions
|
|
->concat($rideSuggestions)
|
|
->concat($parkSuggestions)
|
|
->concat($operatorSuggestions)
|
|
->take($this->maxSuggestions);
|
|
|
|
return $suggestions->toArray();
|
|
}
|
|
|
|
/**
|
|
* Handle suggestion selection
|
|
*/
|
|
public function selectSuggestion(array $suggestion): void
|
|
{
|
|
$this->dispatch('suggestion-selected', $suggestion);
|
|
$this->hideSuggestions();
|
|
}
|
|
|
|
/**
|
|
* Hide suggestions dropdown
|
|
*/
|
|
public function hideSuggestions(): void
|
|
{
|
|
$this->showSuggestions = false;
|
|
}
|
|
|
|
/**
|
|
* Show suggestions dropdown
|
|
*/
|
|
public function showSuggestionsDropdown(): void
|
|
{
|
|
if (!empty($this->suggestions)) {
|
|
$this->showSuggestions = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle input focus
|
|
*/
|
|
public function onFocus(): void
|
|
{
|
|
$this->showSuggestionsDropdown();
|
|
}
|
|
|
|
/**
|
|
* Handle input blur with delay to allow clicks
|
|
*/
|
|
public function onBlur(): void
|
|
{
|
|
// Delay hiding to allow suggestion clicks
|
|
$this->dispatch('delayed-hide-suggestions');
|
|
}
|
|
|
|
/**
|
|
* Get icon class for suggestion type
|
|
*/
|
|
public function getIconClass(string $type): string
|
|
{
|
|
return match($type) {
|
|
'ride' => 'fas fa-roller-coaster',
|
|
'park' => 'fas fa-map-marker-alt',
|
|
'operator' => 'fas fa-industry',
|
|
default => 'fas fa-search'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Render the component
|
|
*/
|
|
public function render()
|
|
{
|
|
return view('livewire.rides-search-suggestions');
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
} |