Add search suggestions feature with category filtering and display; enhance ride list view with improved search functionality

This commit is contained in:
pacnpal
2025-02-11 13:41:05 -05:00
parent 2e0d32819a
commit 59efc39143
7 changed files with 987 additions and 220 deletions

View File

@@ -0,0 +1,26 @@
{% if suggestions %}
<div id="search-suggestions" class="search-suggestions">
{% for suggestion in suggestions %}
<div class="suggestion"
data-type="{{ suggestion.type }}"
data-suggestion="{{ suggestion.text|escape }}"
role="option">
{% if suggestion.type == 'ride' %}
<span class="icon">🎢</span>
<span class="text">{{ suggestion.text }}</span>
<span class="count">({{ suggestion.count }} rides)</span>
{% elif suggestion.type == 'park' %}
<span class="icon">🎪</span>
<span class="text">{{ suggestion.text }}</span>
{% if suggestion.location %}
<span class="location">{{ suggestion.location }}</span>
{% endif %}
{% elif suggestion.type == 'category' %}
<span class="icon">📂</span>
<span class="text">{{ suggestion.text }}</span>
<span class="count">({{ suggestion.count }} rides)</span>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}

View File

@@ -0,0 +1,214 @@
{% extends "base/base.html" %}
{% load static %}
{% load ride_tags %}
{% block title %}
{% if park %}
Rides at {{ park.name }} - ThrillWiki
{% else %}
All Rides - ThrillWiki
{% endif %}
{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold mb-4">
{% if park %}
Rides at {{ park.name }}
{% else %}
All Rides
{% endif %}
</h1>
{# Search Section #}
<div class="relative">
<div class="flex items-center">
<input type="text"
id="ride-search"
name="q"
class="w-full p-4 border rounded-lg shadow-sm"
placeholder="Search rides by name, park, or category..."
hx-get="."
hx-trigger="keyup changed delay:500ms, search"
hx-target="#ride-list-results"
hx-push-url="true"
hx-indicator="#search-loading"
autocomplete="off">
<div id="search-loading" class="loading-indicator htmx-indicator">
<i class="ml-3 fa fa-spinner fa-spin text-gray-400"></i>
</div>
</div>
{# Search Suggestions #}
<div id="search-suggestions-wrapper"
hx-get="{% url 'rides:search_suggestions' %}"
hx-trigger="keyup from:#ride-search delay:200ms"
class="absolute w-full bg-white border rounded-lg shadow-lg mt-1 z-50">
</div>
</div>
{# Quick Filter Buttons #}
<div class="flex flex-wrap gap-2 mt-4">
<button class="filter-btn active"
hx-get="."
hx-target="#ride-list-results"
hx-push-url="true"
hx-include="[name='q']">
All Rides
</button>
<button class="filter-btn"
hx-get="."
hx-target="#ride-list-results"
hx-push-url="true"
hx-include="[name='q']"
hx-vals='{"operating": "true"}'>
Operating
</button>
{% for code, name in category_choices %}
<button class="filter-btn"
hx-get="."
hx-target="#ride-list-results"
hx-push-url="true"
hx-include="[name='q']"
hx-vals='{"category": "{{ code }}"}'>
{{ name }}
</button>
{% endfor %}
</div>
{# Active Filter Tags #}
<div id="active-filters" class="flex flex-wrap gap-2 mt-4">
{% if request.GET.q %}
<span class="filter-tag">
Search: {{ request.GET.q }}
<button class="ml-2"
hx-get="."
hx-target="#ride-list-results"
hx-push-url="true"
title="Clear search">&times;</button>
</span>
{% endif %}
{% if request.GET.category %}
<span class="filter-tag">
Category: {{ request.GET.category|get_category_display }}
<button class="ml-2"
hx-get="."
hx-target="#ride-list-results"
hx-push-url="true"
hx-include="[name='q']"
title="Clear category">&times;</button>
</span>
{% endif %}
{% if request.GET.operating %}
<span class="filter-tag">
Operating Only
<button class="ml-2"
hx-get="."
hx-target="#ride-list-results"
hx-push-url="true"
hx-include="[name='q']"
title="Clear operating filter">&times;</button>
</span>
{% endif %}
</div>
</div>
{# Results Section #}
<div id="ride-list-results" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% include "rides/partials/ride_list_results.html" %}
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle suggestion selection
document.addEventListener('click', function(e) {
const suggestion = e.target.closest('.suggestion');
if (suggestion) {
const searchInput = document.getElementById('ride-search');
searchInput.value = suggestion.dataset.suggestion;
searchInput.dispatchEvent(new Event('keyup')); // Trigger HTMX search
document.getElementById('search-suggestions-wrapper').innerHTML = '';
}
});
// Handle filter button UI
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
});
});
// Initialize active state based on URL params
const urlParams = new URLSearchParams(window.location.search);
const category = urlParams.get('category');
const operating = urlParams.get('operating');
document.querySelectorAll('.filter-btn').forEach(btn => {
const btnVals = btn.getAttribute('hx-vals');
if (
(category && btnVals && btnVals.includes(category)) ||
(operating && btnVals && btnVals.includes('operating')) ||
(!category && !operating && btn.textContent.trim() === 'All Rides')
) {
btn.classList.add('active');
}
});
});
</script>
<style>
.filter-tag {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.75rem;
background-color: #e5e7eb;
color: #374151;
border-radius: 9999px;
font-size: 0.875rem;
line-height: 1.25rem;
}
.filter-tag button {
color: #6b7280;
font-weight: bold;
cursor: pointer;
}
.filter-tag button:hover {
color: #374151;
}
.loading-indicator {
display: none;
}
.htmx-request .loading-indicator {
display: block;
}
.filter-btn {
padding: 0.5rem 1rem;
border-radius: 0.375rem;
background-color: #f3f4f6;
color: #374151;
font-size: 0.875rem;
line-height: 1.25rem;
transition: all 0.2s;
}
.filter-btn:hover {
background-color: #e5e7eb;
}
.filter-btn.active {
background-color: #3b82f6;
color: white;
}
</style>
{% endblock %}