Files
thrillwiki_django_no_react/templates/maps/location_list.html
2025-09-21 20:19:12 -04:00

577 lines
29 KiB
HTML

{% extends 'base/base.html' %}
{% load static %}
{% block title %}Location Search Results - ThrillWiki{% endblock %}
{% block extra_head %}
<style>
.location-card {
@apply bg-white dark:bg-gray-800 rounded-lg p-4 shadow-sm hover:shadow-md transition-all cursor-pointer border border-gray-200 dark:border-gray-700;
}
.location-card:hover {
@apply border-blue-300 dark:border-blue-600 shadow-lg;
}
.location-type-badge {
@apply inline-flex items-center px-2 py-1 rounded-full text-xs font-medium;
}
.location-type-park {
@apply bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100;
}
.location-type-ride {
@apply bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100;
}
.location-type-company {
@apply bg-purple-100 text-purple-800 dark:bg-purple-800 dark:text-purple-100;
}
.status-badge {
@apply inline-flex items-center px-2 py-1 rounded-full text-xs font-medium;
}
.status-operating {
@apply bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100;
}
.status-closed {
@apply bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100;
}
.status-construction {
@apply bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100;
}
.status-demolished {
@apply bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-100;
}
.filter-chip {
@apply inline-flex items-center px-3 py-1 rounded-full text-sm font-medium cursor-pointer transition-all;
}
.filter-chip.active {
@apply bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-100;
}
.filter-chip.inactive {
@apply bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-600;
}
.pagination-container {
@apply flex items-center justify-between mt-8;
}
.pagination-info {
@apply text-sm text-gray-700 dark:text-gray-300;
}
.pagination-controls {
@apply flex items-center space-x-2;
}
.pagination-btn {
@apply px-3 py-2 text-sm font-medium rounded-lg border transition-colors;
}
.pagination-btn.active {
@apply bg-blue-600 text-white border-blue-600;
}
.pagination-btn.inactive {
@apply bg-white text-gray-700 border-gray-300 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700;
}
.pagination-btn:disabled {
@apply opacity-50 cursor-not-allowed;
}
.search-summary {
@apply bg-blue-50 dark:bg-blue-900 dark:bg-opacity-30 rounded-lg p-4 mb-6;
}
.no-results {
@apply text-center py-12;
}
.loading-skeleton {
@apply animate-pulse;
}
.loading-skeleton .skeleton-text {
@apply bg-gray-200 dark:bg-gray-700 rounded h-4;
}
.loading-skeleton .skeleton-text.w-3-4 {
width: 75%;
}
.loading-skeleton .skeleton-text.w-1-2 {
width: 50%;
}
.loading-skeleton .skeleton-text.w-1-4 {
width: 25%;
}
</style>
{% endblock %}
{% block content %}
<div class="container px-4 mx-auto">
<!-- Header -->
<div class="flex flex-col items-start justify-between gap-4 mb-6 md:flex-row md:items-center">
<div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Search Results</h1>
<p class="mt-2 text-gray-600 dark:text-gray-400">
{% if query %}
Search results for "{{ query }}"
{% else %}
Browse all locations in ThrillWiki
{% endif %}
</p>
</div>
<div class="flex gap-3">
<a href="{% url 'maps:universal_map' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors">
<i class="mr-2 fas fa-map"></i>View on Map
</a>
<a href="{% url 'maps:nearby_locations' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 transition-colors">
<i class="mr-2 fas fa-search-location"></i>Find Nearby
</a>
</div>
</div>
<!-- Search and Filters -->
<div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
<form id="search-form" method="get" class="space-y-4">
<!-- Search Input -->
<div>
<label for="search" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">Search</label>
<div class="relative">
<input type="text" name="q" id="search"
class="w-full pl-10 pr-4 py-2 border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Search by name, location, or keyword..."
value="{{ request.GET.q|default:'' }}"
hx-get="{% url 'maps:location_list' %}"
hx-trigger="input changed delay:500ms"
hx-target="#results-container"
hx-include="#search-form"
hx-indicator="#search-loading">
<i class="absolute left-3 top-1/2 transform -translate-y-1/2 fas fa-search text-gray-400"></i>
<div id="search-loading" class="htmx-indicator absolute right-3 top-1/2 transform -translate-y-1/2">
<div class="w-4 h-4 border-2 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
</div>
</div>
</div>
<!-- Filter Chips -->
<div class="space-y-3">
<!-- Location Types -->
<div>
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">Location Types</label>
<div class="flex flex-wrap gap-2">
<label class="filter-chip {% if 'park' in location_types %}active{% else %}inactive{% endif %}">
<input type="checkbox" name="types" value="park" class="hidden"
{% if 'park' in location_types %}checked{% endif %}>
<i class="mr-2 fas fa-tree"></i>Parks
</label>
<label class="filter-chip {% if 'ride' in location_types %}active{% else %}inactive{% endif %}">
<input type="checkbox" name="types" value="ride" class="hidden"
{% if 'ride' in location_types %}checked{% endif %}>
<i class="mr-2 fas fa-rocket"></i>Rides
</label>
<label class="filter-chip {% if 'company' in location_types %}active{% else %}inactive{% endif %}">
<input type="checkbox" name="types" value="company" class="hidden"
{% if 'company' in location_types %}checked{% endif %}>
<i class="mr-2 fas fa-building"></i>Companies
</label>
</div>
</div>
<!-- Additional Filters (for parks) -->
{% if 'park' in location_types %}
<div>
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">Park Status</label>
<div class="flex flex-wrap gap-2">
<label class="filter-chip {% if 'OPERATING' in park_statuses %}active{% else %}inactive{% endif %}">
<input type="checkbox" name="park_status" value="OPERATING" class="hidden"
{% if 'OPERATING' in park_statuses %}checked{% endif %}>
<i class="mr-2 fas fa-check-circle"></i>Operating
</label>
<label class="filter-chip {% if 'CLOSED_TEMP' in park_statuses %}active{% else %}inactive{% endif %}">
<input type="checkbox" name="park_status" value="CLOSED_TEMP" class="hidden"
{% if 'CLOSED_TEMP' in park_statuses %}checked{% endif %}>
<i class="mr-2 fas fa-clock"></i>Temporarily Closed
</label>
<label class="filter-chip {% if 'CLOSED_PERM' in park_statuses %}active{% else %}inactive{% endif %}">
<input type="checkbox" name="park_status" value="CLOSED_PERM" class="hidden"
{% if 'CLOSED_PERM' in park_statuses %}checked{% endif %}>
<i class="mr-2 fas fa-times-circle"></i>Permanently Closed
</label>
<label class="filter-chip {% if 'UNDER_CONSTRUCTION' in park_statuses %}active{% else %}inactive{% endif %}">
<input type="checkbox" name="park_status" value="UNDER_CONSTRUCTION" class="hidden"
{% if 'UNDER_CONSTRUCTION' in park_statuses %}checked{% endif %}>
<i class="mr-2 fas fa-hard-hat"></i>Under Construction
</label>
</div>
</div>
{% endif %}
<!-- Location Filters -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div>
<label for="country" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Country</label>
<input type="text" name="country" id="country"
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Filter by country..."
value="{{ request.GET.country|default:'' }}">
</div>
<div>
<label for="state" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">State/Region</label>
<input type="text" name="state" id="state"
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
placeholder="Filter by state..."
value="{{ request.GET.state|default:'' }}">
</div>
<div>
<label for="sort" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Sort By</label>
<select name="sort" id="sort"
class="w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="name" {% if request.GET.sort == 'name' %}selected{% endif %}>Name (A-Z)</option>
<option value="-name" {% if request.GET.sort == '-name' %}selected{% endif %}>Name (Z-A)</option>
<option value="location" {% if request.GET.sort == 'location' %}selected{% endif %}>Location</option>
<option value="-created_at" {% if request.GET.sort == '-created_at' %}selected{% endif %}>Recently Added</option>
{% if 'park' in location_types %}
<option value="-ride_count" {% if request.GET.sort == '-ride_count' %}selected{% endif %}>Most Rides</option>
{% endif %}
</select>
</div>
</div>
</div>
<div class="flex gap-3">
<button type="submit"
class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors">
<i class="mr-2 fas fa-search"></i>Apply Filters
</button>
<a href="{% url 'maps:location_list' %}"
class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 transition-colors">
<i class="mr-2 fas fa-times"></i>Clear All
</a>
</div>
</form>
</div>
<!-- Search Summary -->
{% if locations %}
<div class="search-summary">
<div class="flex items-center justify-between">
<div>
<h3 class="text-lg font-semibold text-blue-900 dark:text-blue-100">
{{ paginator.count }} location{{ paginator.count|pluralize }} found
</h3>
<p class="text-sm text-blue-700 dark:text-blue-200 mt-1">
{% if query %}
Showing results for "{{ query }}"
{% endif %}
{% if location_types %}
• Types: {{ location_types|join:", "|title }}
{% endif %}
{% if request.GET.country %}
• Country: {{ request.GET.country }}
{% endif %}
{% if request.GET.state %}
• State: {{ request.GET.state }}
{% endif %}
</p>
</div>
<div class="text-sm text-blue-700 dark:text-blue-200">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</div>
</div>
</div>
{% endif %}
<!-- Results Container -->
<div id="results-container">
{% if locations %}
<!-- Location Cards -->
<div class="grid grid-cols-1 gap-4 mb-8 md:grid-cols-2 lg:grid-cols-3">
{% for location in locations %}
<div class="location-card"
onclick="window.location.href='{{ location.get_absolute_url }}'">
<div class="flex items-start justify-between mb-3">
<div class="flex-1 min-w-0">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white truncate">
{{ location.name }}
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 truncate">
{{ location.formatted_location|default:"Location not specified" }}
</p>
</div>
<div class="flex-shrink-0 ml-3">
<span class="location-type-badge location-type-{{ location.type }}">
{% if location.type == 'park' %}
<i class="mr-1 fas fa-tree"></i>Park
{% elif location.type == 'ride' %}
<i class="mr-1 fas fa-rocket"></i>Ride
{% elif location.type == 'company' %}
<i class="mr-1 fas fa-building"></i>Company
{% endif %}
</span>
</div>
</div>
<!-- Type-specific information -->
{% if location.type == 'park' %}
<div class="space-y-2">
{% if location.status %}
<div class="flex items-center">
<span class="status-badge {% if location.status == 'OPERATING' %}status-operating{% elif location.status == 'CLOSED_TEMP' or location.status == 'CLOSED_PERM' %}status-closed{% elif location.status == 'UNDER_CONSTRUCTION' %}status-construction{% else %}status-demolished{% endif %}">
{% if location.status == 'OPERATING' %}
<i class="mr-1 fas fa-check-circle"></i>Operating
{% elif location.status == 'CLOSED_TEMP' %}
<i class="mr-1 fas fa-clock"></i>Temporarily Closed
{% elif location.status == 'CLOSED_PERM' %}
<i class="mr-1 fas fa-times-circle"></i>Permanently Closed
{% elif location.status == 'UNDER_CONSTRUCTION' %}
<i class="mr-1 fas fa-hard-hat"></i>Under Construction
{% elif location.status == 'DEMOLISHED' %}
<i class="mr-1 fas fa-ban"></i>Demolished
{% endif %}
</span>
</div>
{% endif %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
{% if location.operator %}
<i class="mr-2 fas fa-building"></i>
<span>{{ location.operator }}</span>
{% endif %}
</div>
{% if location.ride_count %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<i class="mr-2 fas fa-rocket"></i>
<span>{{ location.ride_count }} ride{{ location.ride_count|pluralize }}</span>
</div>
{% endif %}
{% if location.average_rating %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<i class="mr-2 fas fa-star text-yellow-500"></i>
<span>{{ location.average_rating|floatformat:1 }}/10</span>
</div>
{% endif %}
</div>
{% elif location.type == 'ride' %}
<div class="space-y-2">
{% if location.park_name %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<i class="mr-2 fas fa-tree"></i>
<span>{{ location.park_name }}</span>
</div>
{% endif %}
{% if location.manufacturer %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<i class="mr-2 fas fa-industry"></i>
<span>{{ location.manufacturer }}</span>
</div>
{% endif %}
{% if location.opening_date %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<i class="mr-2 fas fa-calendar"></i>
<span>Opened {{ location.opening_date.year }}</span>
</div>
{% endif %}
</div>
{% elif location.type == 'company' %}
<div class="space-y-2">
{% if location.company_type %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<i class="mr-2 fas fa-tag"></i>
<span>{{ location.get_company_type_display }}</span>
</div>
{% endif %}
{% if location.founded_year %}
<div class="flex items-center text-sm text-gray-600 dark:text-gray-400">
<i class="mr-2 fas fa-calendar"></i>
<span>Founded {{ location.founded_year }}</span>
</div>
{% endif %}
</div>
{% endif %}
<!-- Action buttons -->
<div class="flex gap-2 mt-4">
<a href="{{ location.get_absolute_url }}"
class="flex-1 px-3 py-2 text-sm text-center text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors">
View Details
</a>
{% if location.latitude and location.longitude %}
<a href="{% url 'maps:nearby_locations' %}?lat={{ location.latitude }}&lng={{ location.longitude }}&radius=25"
class="px-3 py-2 text-sm text-blue-600 border border-blue-600 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-900 transition-colors">
<i class="fas fa-search-location"></i>
</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<div class="pagination-container">
<div class="pagination-info">
Showing {{ page_obj.start_index }}-{{ page_obj.end_index }} of {{ paginator.count }} results
</div>
<div class="pagination-controls">
{% if page_obj.has_previous %}
<a href="?{% if request.GET.urlencode %}{{ request.GET.urlencode }}&{% endif %}page=1"
class="pagination-btn inactive">
<i class="fas fa-angle-double-left"></i>
</a>
<a href="?{% if request.GET.urlencode %}{{ request.GET.urlencode }}&{% endif %}page={{ page_obj.previous_page_number }}"
class="pagination-btn inactive">
<i class="fas fa-angle-left"></i>
</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<span class="pagination-btn active">{{ num }}</span>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a href="?{% if request.GET.urlencode %}{{ request.GET.urlencode }}&{% endif %}page={{ num }}"
class="pagination-btn inactive">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?{% if request.GET.urlencode %}{{ request.GET.urlencode }}&{% endif %}page={{ page_obj.next_page_number }}"
class="pagination-btn inactive">
<i class="fas fa-angle-right"></i>
</a>
<a href="?{% if request.GET.urlencode %}{{ request.GET.urlencode }}&{% endif %}page={{ paginator.num_pages }}"
class="pagination-btn inactive">
<i class="fas fa-angle-double-right"></i>
</a>
{% endif %}
</div>
</div>
{% endif %}
{% else %}
<!-- No Results -->
<div class="no-results">
<i class="fas fa-search text-6xl text-gray-400 mb-6"></i>
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">No locations found</h3>
<p class="text-gray-600 dark:text-gray-400 mb-6">
{% if query %}
No results found for "{{ query }}". Try adjusting your search or filters.
{% else %}
No locations match your current filters. Try adjusting your search criteria.
{% endif %}
</p>
<div class="flex justify-center gap-3">
<a href="{% url 'maps:location_list' %}"
class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors">
<i class="mr-2 fas fa-refresh"></i>Clear Filters
</a>
<a href="{% url 'maps:universal_map' %}"
class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 transition-colors">
<i class="mr-2 fas fa-map"></i>Browse Map
</a>
</div>
</div>
{% endif %}
</div>
<!-- Loading Template for HTMX -->
<template id="loading-template">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{% for i in "123456789" %}
<div class="location-card loading-skeleton">
<div class="flex items-start justify-between mb-3">
<div class="flex-1">
<div class="skeleton-text w-3-4 h-6 mb-2"></div>
<div class="skeleton-text w-1-2 h-4"></div>
</div>
<div class="skeleton-text w-1-4 h-6"></div>
</div>
<div class="space-y-2">
<div class="skeleton-text w-1-2 h-4"></div>
<div class="skeleton-text w-3-4 h-4"></div>
</div>
<div class="flex gap-2 mt-4">
<div class="skeleton-text flex-1 h-8"></div>
<div class="skeleton-text w-10 h-8"></div>
</div>
</div>
{% endfor %}
</div>
</template>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle filter chip toggles
document.querySelectorAll('.filter-chip').forEach(chip => {
const checkbox = chip.querySelector('input[type="checkbox"]');
chip.addEventListener('click', (e) => {
e.preventDefault();
if (checkbox) {
checkbox.checked = !checkbox.checked;
chip.classList.toggle('active', checkbox.checked);
chip.classList.toggle('inactive', !checkbox.checked);
// Auto-submit form on filter change
document.getElementById('search-form').dispatchEvent(new Event('submit'));
}
});
});
// Handle form changes
document.getElementById('search-form').addEventListener('change', function(e) {
if (e.target.name !== 'q') { // Don't auto-submit on search input changes
this.submit();
}
});
// Show loading state during HTMX requests
document.addEventListener('htmx:beforeRequest', function(event) {
if (event.target.id === 'results-container') {
const template = document.getElementById('loading-template');
event.target.innerHTML = template.innerHTML;
}
});
// Handle HTMX errors
document.addEventListener('htmx:responseError', function(event) {
console.error('Search request failed:', event.detail);
event.target.innerHTML = `
<div class="text-center py-12">
<i class="fas fa-exclamation-triangle text-6xl text-red-400 mb-6"></i>
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">Search Error</h3>
<p class="text-gray-600 dark:text-gray-400 mb-6">There was an error performing your search. Please try again.</p>
<button onclick="location.reload()"
class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors">
<i class="mr-2 fas fa-refresh"></i>Retry
</button>
</div>
`;
});
});
</script>
{% endblock %}