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(); } } }