mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:31:08 -05:00
Add Road Trip Planner template with interactive map and trip management features
- Implemented a new HTML template for the Road Trip Planner. - Integrated Leaflet.js for interactive mapping and routing. - Added functionality for searching and selecting parks to include in a trip. - Enabled drag-and-drop reordering of selected parks. - Included trip optimization and route calculation features. - Created a summary display for trip statistics. - Added functionality to save trips and manage saved trips. - Enhanced UI with responsive design and dark mode support.
This commit is contained in:
332
templates/core/search/location_results.html
Normal file
332
templates/core/search/location_results.html
Normal file
@@ -0,0 +1,332 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Location Search - ThrillWiki{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.search-result-card {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.search-result-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.distance-badge {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
.content-type-badge {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Search Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||
Location Search Results
|
||||
</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300">
|
||||
Found {{ total_results }} result{{ total_results|pluralize }} across parks, rides, and companies
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
<!-- Enhanced Search Filters -->
|
||||
<div class="lg:w-1/4">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 sticky top-4">
|
||||
<h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">Search Filters</h2>
|
||||
|
||||
<form hx-get="{% url 'search:location_search' %}"
|
||||
hx-target="#search-results"
|
||||
hx-swap="outerHTML"
|
||||
hx-indicator="#search-loading"
|
||||
class="space-y-4">
|
||||
|
||||
<!-- Text Search -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
{{ search_form.q.label }}
|
||||
</label>
|
||||
{{ search_form.q }}
|
||||
</div>
|
||||
|
||||
<!-- Location Search -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
{{ search_form.location.label }}
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
{{ search_form.location }}
|
||||
{{ search_form.lat }}
|
||||
{{ search_form.lng }}
|
||||
<div class="flex gap-2">
|
||||
<button type="button"
|
||||
id="use-my-location"
|
||||
class="flex-1 px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-300">
|
||||
📍 Use My Location
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Radius -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
{{ search_form.radius_km.label }}
|
||||
</label>
|
||||
{{ search_form.radius_km }}
|
||||
</div>
|
||||
|
||||
<!-- Content Types -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Search In
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center">
|
||||
{{ search_form.search_parks }}
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ search_form.search_parks.label }}</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
{{ search_form.search_rides }}
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ search_form.search_rides.label }}</span>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
{{ search_form.search_companies }}
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ search_form.search_companies.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Geographic Filters -->
|
||||
<div class="border-t pt-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Geographic Filters</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
{{ search_form.country }}
|
||||
{{ search_form.state }}
|
||||
{{ search_form.city }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
|
||||
🔍 Search
|
||||
</button>
|
||||
|
||||
{% if request.GET %}
|
||||
<a href="{% url 'search:location_search' %}"
|
||||
class="block w-full text-center bg-gray-100 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-200 transition-colors dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600">
|
||||
Clear Filters
|
||||
</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Section -->
|
||||
<div class="lg:w-3/4" id="search-results">
|
||||
<!-- Loading indicator -->
|
||||
<div id="search-loading" class="hidden">
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if results %}
|
||||
<!-- Results Summary -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow mb-6 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ total_results }} Result{{ total_results|pluralize }} Found
|
||||
</h2>
|
||||
{% if has_location_filter %}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Sorted by distance from your location
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats -->
|
||||
<div class="flex gap-4 text-sm">
|
||||
{% if grouped_results.parks %}
|
||||
<span class="bg-green-100 text-green-800 px-2 py-1 rounded dark:bg-green-900 dark:text-green-300">
|
||||
{{ grouped_results.parks|length }} Park{{ grouped_results.parks|length|pluralize }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if grouped_results.rides %}
|
||||
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded dark:bg-blue-900 dark:text-blue-300">
|
||||
{{ grouped_results.rides|length }} Ride{{ grouped_results.rides|length|pluralize }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if grouped_results.companies %}
|
||||
<span class="bg-purple-100 text-purple-800 px-2 py-1 rounded dark:bg-purple-900 dark:text-purple-300">
|
||||
{{ grouped_results.companies|length }} Compan{{ grouped_results.companies|length|pluralize:"y,ies" }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Results -->
|
||||
<div class="space-y-4">
|
||||
{% for result in results %}
|
||||
<div class="search-result-card bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<!-- Header with type badge -->
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="content-type-badge px-2 py-1 rounded-full text-xs
|
||||
{% if result.content_type == 'park' %}bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300
|
||||
{% elif result.content_type == 'ride' %}bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300
|
||||
{% else %}bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300{% endif %}">
|
||||
{{ result.content_type|title }}
|
||||
</span>
|
||||
|
||||
{% if result.distance_km %}
|
||||
<span class="distance-badge text-white px-2 py-1 rounded-full text-xs">
|
||||
{{ result.distance_km|floatformat:1 }} km away
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
{% if result.url %}
|
||||
<a href="{{ result.url }}" class="hover:text-blue-600 dark:hover:text-blue-400">
|
||||
{{ result.name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ result.name }}
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
<!-- Description -->
|
||||
{% if result.description %}
|
||||
<p class="text-gray-600 dark:text-gray-300 mb-3 line-clamp-2">
|
||||
{{ result.description }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Location Info -->
|
||||
{% if result.city or result.address %}
|
||||
<div class="flex items-center text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
{% if result.address %}
|
||||
{{ result.address }}
|
||||
{% else %}
|
||||
{{ result.city }}{% if result.state %}, {{ result.state }}{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Tags and Status -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% if result.status %}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300">
|
||||
{{ result.status }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if result.rating %}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300">
|
||||
★ {{ result.rating|floatformat:1 }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% for tag in result.tags %}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300">
|
||||
{{ tag|title }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map link -->
|
||||
{% if result.latitude and result.longitude %}
|
||||
<div class="ml-4">
|
||||
<a href="{% url 'maps:universal_map' %}?lat={{ result.latitude }}&lng={{ result.longitude }}&zoom=15"
|
||||
class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-600">
|
||||
🗺️ View on Map
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<!-- No Results -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-8 text-center">
|
||||
<div class="text-gray-400 mb-4">
|
||||
<svg class="mx-auto h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">No results found</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||
Try adjusting your search criteria or expanding your search radius.
|
||||
</p>
|
||||
<div class="space-y-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<p>• Try broader search terms</p>
|
||||
<p>• Increase the search radius</p>
|
||||
<p>• Check spelling and try different keywords</p>
|
||||
<p>• Remove some filters to see more results</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Geolocation support
|
||||
const useLocationBtn = document.getElementById('use-my-location');
|
||||
const latInput = document.getElementById('lat-input');
|
||||
const lngInput = document.getElementById('lng-input');
|
||||
const locationInput = document.getElementById('location-input');
|
||||
|
||||
if (useLocationBtn && 'geolocation' in navigator) {
|
||||
useLocationBtn.addEventListener('click', function() {
|
||||
this.textContent = '📍 Getting location...';
|
||||
this.disabled = true;
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function(position) {
|
||||
latInput.value = position.coords.latitude;
|
||||
lngInput.value = position.coords.longitude;
|
||||
locationInput.value = `${position.coords.latitude}, ${position.coords.longitude}`;
|
||||
useLocationBtn.textContent = '✅ Location set';
|
||||
setTimeout(() => {
|
||||
useLocationBtn.textContent = '📍 Use My Location';
|
||||
useLocationBtn.disabled = false;
|
||||
}, 2000);
|
||||
},
|
||||
function(error) {
|
||||
useLocationBtn.textContent = '❌ Location failed';
|
||||
console.error('Geolocation error:', error);
|
||||
setTimeout(() => {
|
||||
useLocationBtn.textContent = '📍 Use My Location';
|
||||
useLocationBtn.disabled = false;
|
||||
}, 2000);
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (useLocationBtn) {
|
||||
useLocationBtn.style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="{% static 'js/location-search.js' %}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user