Refactor search functionality: remove obsolete JavaScript and HTML templates; enhance error handling and response rendering for park search results

This commit is contained in:
pacnpal
2025-02-13 10:29:57 -05:00
parent c197051b25
commit bba707fa98
7 changed files with 164 additions and 173 deletions

View File

@@ -38,19 +38,54 @@ Browse and filter amusement parks, theme parks, and water parks from around the
<div class="mb-6">
{# Quick Search #}
<div class="mb-8">
<div class="max-w-3xl mx-auto">
<div class="max-w-3xl mx-auto"
x-data="{
selectedIndex: -1,
results: [],
get hasResults() { return this.results.length > 0 },
init() {
this.$watch('results', () => this.selectedIndex = -1)
},
onKeyDown(e) {
if (!this.hasResults) return
if (e.key === 'ArrowDown') {
e.preventDefault()
this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1)
}
else if (e.key === 'ArrowUp') {
e.preventDefault()
this.selectedIndex = Math.max(this.selectedIndex - 1, 0)
}
else if (e.key === 'Enter' && this.selectedIndex >= 0) {
e.preventDefault()
this.$refs[`result-${this.selectedIndex}`]?.click()
}
else if (e.key === 'Escape') {
this.results = []
this.$refs.input.value = ''
this.$refs.input.blur()
}
}
}"
@click.away="results = []">
<label for="search" class="sr-only">Search parks</label>
<div class="relative">
<input type="search"
name="search"
<input type="search"
name="search"
id="search"
x-ref="input"
class="block w-full rounded-md border-gray-300 bg-white py-3 pl-4 pr-10 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 sm:text-sm"
placeholder="Search parks by name or location..."
hx-get="{% url 'parks:search_parks' %}"
hx-trigger="keyup changed delay:300ms, search"
hx-target="#search-results"
hx-indicator="#search-indicator"
autocomplete="off">
@keydown="onKeyDown($event)"
autocomplete="off"
role="combobox"
aria-expanded="false"
aria-controls="search-results"
aria-autocomplete="list">
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<div id="search-indicator" class="htmx-indicator">
<svg class="h-5 w-5 text-gray-400 animate-spin" viewBox="0 0 24 24">
@@ -60,7 +95,10 @@ Browse and filter amusement parks, theme parks, and water parks from around the
</div>
</div>
</div>
<div id="search-results" class="mt-2"></div>
<div id="search-results"
class="mt-2"
role="listbox"
x-init="$watch('$el.children', value => results = Array.from(value))"></div>
</div>
</div>
@@ -87,7 +125,7 @@ Browse and filter amusement parks, theme parks, and water parks from around the
{% block results_section %}
<div class="space-y-6">
{% for park in parks %}
{% include "search/partials/park_results.html" with park=park %}
{% include "parks/partials/park_list_item.html" with park=park %}
{% endfor %}
</div>
{% endblock %}

View File

@@ -0,0 +1,78 @@
{% if error %}
<div class="p-4">
<div class="inline-flex items-center px-4 py-2 rounded-md bg-red-50 text-red-700">
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
</svg>
{{ error }}
</div>
</div>
{% else %}
<div class="divide-y">
{% for park in object_list|default:parks %}
<div role="option"
id="result-{{ forloop.counter0 }}"
:class="{ 'bg-gray-50 dark:bg-gray-800': selectedIndex === {{ forloop.counter0 }} }"
class="p-4 flex items-start space-x-4">
{% if park.photos.exists %}
<img src="{{ park.photos.first.image.url }}"
alt="{{ park.name }}"
class="w-24 h-24 object-cover rounded-lg">
{% else %}
<div class="w-24 h-24 bg-gray-100 rounded-lg flex items-center justify-center">
<span class="text-2xl font-medium text-gray-400">{{ park.name|first|upper }}</span>
</div>
{% endif %}
<div class="flex-1 min-w-0">
<h3 class="text-lg font-semibold truncate">
<a href="{% url 'parks:park_detail' park.slug %}"
x-ref="result-{{ forloop.counter0 }}"
:class="{ 'bg-gray-50 dark:bg-gray-800': selectedIndex === {{ forloop.counter0 }} }"
class="hover:text-blue-600 block w-full py-1 px-2 -mx-2 rounded">
{{ park.name }}
</a>
</h3>
<div class="mt-1 text-sm text-gray-500 truncate">
{% with location=park.location.first %}
{% if location %}
{{ location.city }}{% if location.state %}, {{ location.state }}{% endif %}{% if location.country %}, {{ location.country }}{% endif %}
{% else %}
Location unknown
{% endif %}
{% endwith %}
</div>
<div class="mt-2 flex flex-wrap gap-2">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ park.get_status_color }}">
{{ park.get_status_display }}
</span>
{% if park.opening_date %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
Opened {{ park.opening_date|date:"Y" }}
</span>
{% endif %}
{% if park.ride_count %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{{ park.ride_count }} rides
</span>
{% endif %}
{% if park.coaster_count %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
{{ park.coaster_count }} coasters
</span>
{% endif %}
</div>
</div>
</div>
{% empty %}
<div class="p-4 text-sm text-gray-500 text-center">
No parks found matching your search.
</div>
{% endfor %}
</div>
{% endif %}

View File

@@ -1,58 +0,0 @@
{% if error %}
<div class="text-red-600 bg-red-50 p-4 rounded-md" role="alert">
{{ error }}
</div>
{% else %}
<div class="space-y-4">
{% for park in parks %}
<div class="bg-white shadow sm:rounded-lg">
<a href="{% url 'parks:park_detail' park.slug %}" class="block px-4 py-4 hover:bg-gray-50">
<div class="flex items-start space-x-4">
{% if park.photos.exists %}
<img src="{{ park.photos.first.image.url }}"
alt="{{ park.name }}"
class="h-20 w-20 object-cover rounded-lg">
{% else %}
<div class="h-20 w-20 bg-gray-100 rounded-lg flex items-center justify-center">
<span class="text-2xl font-medium text-gray-400">{{ park.name|first|upper }}</span>
</div>
{% endif %}
<div class="flex-1 min-w-0">
<div class="flex justify-between">
<h3 class="font-medium text-gray-900">{{ park.name }}</h3>
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium {{ park.get_status_color }}">
{{ park.get_status_display }}
</span>
</div>
{% with location=park.location.first %}
{% if location %}
<p class="mt-1 text-sm text-gray-500">
{{ location.city }}{% if location.state %}, {{ location.state }}{% endif %}
{% if location.country %}, {{ location.country }}{% endif %}
</p>
{% endif %}
{% endwith %}
<div class="mt-2 flex items-center space-x-2 text-sm text-gray-500">
{% if park.ride_count %}
<span>{{ park.ride_count }} rides</span>
{% endif %}
{% if park.coaster_count %}
<span></span>
<span>{{ park.coaster_count }} coasters</span>
{% endif %}
</div>
</div>
</div>
</a>
</div>
{% empty %}
<div class="text-center py-8 bg-white shadow sm:rounded-lg">
<p class="text-gray-500">No parks found matching your search.</p>
</div>
{% endfor %}
</div>
{% endif %}