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:
pacnpal
2025-08-15 20:53:00 -04:00
parent da7c7e3381
commit b5bae44cb8
99 changed files with 18697 additions and 4010 deletions

View 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 %}