mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 15:31:08 -05:00
Enhance park search functionality: update view mode handling and improve park list item layout
This commit is contained in:
@@ -52,7 +52,7 @@
|
|||||||
id="search"
|
id="search"
|
||||||
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"
|
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..."
|
placeholder="Search parks by name or location..."
|
||||||
hx-get="{% url 'parks:search_parks' %}"
|
hx-get="{% url 'parks:search_parks' %}?view_mode={{ view_mode|default:'grid' }}"
|
||||||
hx-trigger="input delay:300ms, search"
|
hx-trigger="input delay:300ms, search"
|
||||||
hx-target="#park-results"
|
hx-target="#park-results"
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
|
|||||||
@@ -11,80 +11,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="{% if view_mode == 'grid' %}grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 p-4{% else %}flex flex-col gap-4 p-4{% endif %}"
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
data-testid="park-list"
|
|
||||||
data-view-mode="{{ view_mode|default:'grid' }}">
|
|
||||||
{% for park in object_list|default:parks %}
|
{% for park in object_list|default:parks %}
|
||||||
<article class="park-card group relative bg-white border rounded-lg transition-all duration-200 ease-in-out hover:shadow-lg {% if view_mode == 'list' %}flex gap-4 p-4{% endif %}"
|
<div class="overflow-hidden transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1">
|
||||||
data-testid="park-card"
|
<div class="p-4">
|
||||||
data-park-id="{{ park.id }}"
|
<h2 class="mb-2 text-xl font-bold">
|
||||||
data-view-mode="{{ view_mode|default:'grid' }}">
|
<a href="{% url 'parks:park_detail' park.slug %}" class="text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400">
|
||||||
|
{{ park.name }}
|
||||||
<a href="{% url 'parks:park_detail' park.slug %}"
|
</a>
|
||||||
class="absolute inset-0 z-0"
|
</h2>
|
||||||
aria-label="View details for {{ park.name }}"></a>
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
<div class="relative z-10 {% if view_mode == 'grid' %}aspect-video{% endif %}">
|
<span class="status-badge status-{{ park.status|lower }}">
|
||||||
{% if park.photos.exists %}
|
{{ park.get_status_display }}
|
||||||
<img src="{{ park.photos.first.image.url }}"
|
</span>
|
||||||
alt="Photo of {{ park.name }}"
|
</div>
|
||||||
class="{% if view_mode == 'grid' %}w-full h-full object-cover rounded-t-lg{% else %}w-24 h-24 object-cover rounded-lg flex-shrink-0{% endif %}"
|
|
||||||
loading="lazy">
|
{% if park.owner %}
|
||||||
{% else %}
|
<div class="mt-4 text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
<div class="{% if view_mode == 'grid' %}w-full h-full bg-gray-100 rounded-t-lg flex items-center justify-center{% else %}w-24 h-24 bg-gray-100 rounded-lg flex-shrink-0 flex items-center justify-center{% endif %}"
|
<a href="{% url 'companies:company_detail' park.owner.slug %}">
|
||||||
role="img"
|
{{ park.owner.name }}
|
||||||
aria-label="Park initial letter">
|
</a>
|
||||||
<span class="text-2xl font-medium text-gray-400">{{ park.name|first|upper }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="{% if view_mode == 'grid' %}p-4{% else %}flex-1 min-w-0{% endif %}">
|
|
||||||
<h3 class="text-lg font-semibold text-gray-900 truncate group-hover:text-blue-600">
|
|
||||||
{{ park.name }}
|
|
||||||
</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 }} status-badge"
|
|
||||||
data-testid="park-status">
|
|
||||||
{{ 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"
|
|
||||||
data-testid="park-opening-date">
|
|
||||||
Opened {{ park.opening_date|date:"Y" }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if park.current_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"
|
|
||||||
data-testid="park-ride-count">
|
|
||||||
{{ park.current_ride_count }} ride{{ park.current_ride_count|pluralize }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if park.current_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"
|
|
||||||
data-testid="park-coaster-count">
|
|
||||||
{{ park.current_coaster_count }} coaster{{ park.current_coaster_count|pluralize }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<div class="{% if view_mode == 'grid' %}col-span-full{% endif %} p-4 text-sm text-gray-500 text-center" data-testid="no-parks-found">
|
<div class="col-span-full p-4 text-sm text-gray-500 text-center" data-testid="no-parks-found">
|
||||||
{% if search_query %}
|
{% if search_query %}
|
||||||
No parks found matching "{{ search_query }}". Try adjusting your search terms.
|
No parks found matching "{{ search_query }}". Try adjusting your search terms.
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import requests
|
||||||
from decimal import Decimal, ROUND_DOWN
|
from decimal import Decimal, ROUND_DOWN
|
||||||
from typing import Any, Optional, cast, Literal
|
from typing import Any, Optional, cast, Literal
|
||||||
from django.views.generic import DetailView, ListView, CreateView, UpdateView
|
from django.views.generic import DetailView, ListView, CreateView, UpdateView
|
||||||
|
from decimal import InvalidOperation
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.db.models import Q, Count, QuerySet
|
from django.db.models import Q, Count, QuerySet
|
||||||
@@ -23,6 +25,70 @@ from search.mixins import HTMXFilterableMixin
|
|||||||
ViewMode = Literal["grid", "list"]
|
ViewMode = Literal["grid", "list"]
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_osm_result(result: dict) -> dict:
|
||||||
|
"""Normalize OpenStreetMap result to a consistent format with enhanced address details"""
|
||||||
|
from .location_utils import get_english_name, normalize_coordinate
|
||||||
|
|
||||||
|
# Get address details
|
||||||
|
address = result.get('address', {})
|
||||||
|
|
||||||
|
# Normalize coordinates
|
||||||
|
lat = normalize_coordinate(float(result.get('lat')), 9, 6)
|
||||||
|
lon = normalize_coordinate(float(result.get('lon')), 10, 6)
|
||||||
|
|
||||||
|
# Get English names where possible
|
||||||
|
name = ''
|
||||||
|
if 'namedetails' in result:
|
||||||
|
name = get_english_name(result['namedetails'])
|
||||||
|
|
||||||
|
# Build street address from available components
|
||||||
|
street_parts = []
|
||||||
|
if address.get('house_number'):
|
||||||
|
street_parts.append(address['house_number'])
|
||||||
|
if address.get('road') or address.get('street'):
|
||||||
|
street_parts.append(address.get('road') or address.get('street'))
|
||||||
|
elif address.get('pedestrian'):
|
||||||
|
street_parts.append(address['pedestrian'])
|
||||||
|
elif address.get('footway'):
|
||||||
|
street_parts.append(address['footway'])
|
||||||
|
|
||||||
|
# Handle additional address components
|
||||||
|
suburb = address.get('suburb', '')
|
||||||
|
district = address.get('district', '')
|
||||||
|
neighborhood = address.get('neighbourhood', '')
|
||||||
|
|
||||||
|
# Build city from available components
|
||||||
|
city = (address.get('city') or
|
||||||
|
address.get('town') or
|
||||||
|
address.get('village') or
|
||||||
|
address.get('municipality') or
|
||||||
|
'')
|
||||||
|
|
||||||
|
# Get detailed state/region information
|
||||||
|
state = (address.get('state') or
|
||||||
|
address.get('province') or
|
||||||
|
address.get('region') or
|
||||||
|
'')
|
||||||
|
|
||||||
|
# Get postal code with fallbacks
|
||||||
|
postal_code = (address.get('postcode') or
|
||||||
|
address.get('postal_code') or
|
||||||
|
'')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'display_name': name or result.get('display_name', ''),
|
||||||
|
'lat': lat,
|
||||||
|
'lon': lon,
|
||||||
|
'street': ' '.join(street_parts).strip(),
|
||||||
|
'suburb': suburb,
|
||||||
|
'district': district,
|
||||||
|
'neighborhood': neighborhood,
|
||||||
|
'city': city,
|
||||||
|
'state': state,
|
||||||
|
'country': address.get('country', ''),
|
||||||
|
'postal_code': postal_code,
|
||||||
|
}
|
||||||
|
|
||||||
def get_view_mode(request: HttpRequest) -> ViewMode:
|
def get_view_mode(request: HttpRequest) -> ViewMode:
|
||||||
"""Get the current view mode from request, defaulting to grid"""
|
"""Get the current view mode from request, defaulting to grid"""
|
||||||
view_mode = request.GET.get('view_mode', 'grid')
|
view_mode = request.GET.get('view_mode', 'grid')
|
||||||
@@ -202,6 +268,8 @@ def search_parks(request: HttpRequest) -> HttpResponse:
|
|||||||
if not search_query:
|
if not search_query:
|
||||||
return HttpResponse('')
|
return HttpResponse('')
|
||||||
|
|
||||||
|
# Get current view mode from request
|
||||||
|
current_view_mode = request.GET.get('view_mode', 'grid')
|
||||||
park_filter = ParkFilter({
|
park_filter = ParkFilter({
|
||||||
'search': search_query
|
'search': search_query
|
||||||
}, queryset=get_base_park_queryset())
|
}, queryset=get_base_park_queryset())
|
||||||
@@ -215,7 +283,7 @@ def search_parks(request: HttpRequest) -> HttpResponse:
|
|||||||
"parks/partials/park_list_item.html",
|
"parks/partials/park_list_item.html",
|
||||||
{
|
{
|
||||||
"parks": parks,
|
"parks": parks,
|
||||||
"view_mode": get_view_mode(request),
|
"view_mode": current_view_mode,
|
||||||
"search_query": search_query,
|
"search_query": search_query,
|
||||||
"is_search": True
|
"is_search": True
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user