mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 00:31:09 -05:00
here we go
This commit is contained in:
@@ -3,19 +3,13 @@
|
||||
|
||||
{% block title %}{{ park.name }} - ThrillWiki{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/inline-edit.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-4 mx-auto">
|
||||
<!-- Park Header -->
|
||||
<div class="p-6 mb-6 bg-white border border-gray-200 rounded-lg shadow-lg dark:bg-gray-800 dark:border-gray-700 editable-container">
|
||||
<div class="p-6 mb-6 bg-white border border-gray-200 rounded-lg shadow-lg dark:bg-gray-800 dark:border-gray-700">
|
||||
<div class="flex flex-col items-start justify-between md:flex-row md:items-center">
|
||||
<div>
|
||||
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white"
|
||||
data-editable data-content-id="{{ park.id }}"
|
||||
data-field-name="name">{{ park.name }}</h1>
|
||||
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{{ park.name }}</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300">
|
||||
<i class="mr-2 fas fa-map-marker-alt"></i>
|
||||
<span>{{ park.get_formatted_location }}</span>
|
||||
@@ -29,12 +23,9 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<button class="btn-secondary" data-edit-button
|
||||
data-content-id="{{ park.id }}"
|
||||
data-content-type="park"
|
||||
{% if not can_auto_approve %}data-require-reason="true"{% endif %}>
|
||||
<a href="{% url 'parks:park_edit' slug=park.slug %}" class="btn-secondary">
|
||||
<i class="mr-2 fas fa-edit"></i>Edit
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,17 +35,7 @@
|
||||
{% elif park.status == 'CLOSED_TEMP' or park.status == 'CLOSED_PERM' %}status-closed
|
||||
{% elif park.status == 'UNDER_CONSTRUCTION' %}status-construction
|
||||
{% elif park.status == 'DEMOLISHED' %}status-demolished
|
||||
{% elif park.status == 'RELOCATED' %}status-relocated{% endif %}"
|
||||
data-editable data-content-id="{{ park.id }}"
|
||||
data-field-name="status" data-field-type="select"
|
||||
data-options='[
|
||||
{"value": "OPERATING", "label": "Operating"},
|
||||
{"value": "CLOSED_TEMP", "label": "Temporarily Closed"},
|
||||
{"value": "CLOSED_PERM", "label": "Permanently Closed"},
|
||||
{"value": "UNDER_CONSTRUCTION", "label": "Under Construction"},
|
||||
{"value": "DEMOLISHED", "label": "Demolished"},
|
||||
{"value": "RELOCATED", "label": "Relocated"}
|
||||
]'>
|
||||
{% elif park.status == 'RELOCATED' %}status-relocated{% endif %}">
|
||||
{{ park.get_status_display }}
|
||||
</span>
|
||||
{% if park.average_rating %}
|
||||
@@ -97,11 +78,9 @@
|
||||
<!-- Left Column - Description and Areas -->
|
||||
<div class="lg:col-span-2">
|
||||
{% if park.description %}
|
||||
<div class="p-6 mb-6 bg-white rounded-lg shadow-lg dark:bg-gray-800 editable-container">
|
||||
<div class="p-6 mb-6 bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-2xl font-bold text-gray-900 dark:text-white">About</h2>
|
||||
<div class="text-gray-700 dark:text-gray-300"
|
||||
data-editable data-content-id="{{ park.id }}"
|
||||
data-field-name="description" data-field-type="textarea">
|
||||
<div class="text-gray-700 dark:text-gray-300">
|
||||
{{ park.description|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,7 +159,7 @@
|
||||
|
||||
<!-- Right Column - Quick Facts and History -->
|
||||
<div>
|
||||
<div class="p-6 mb-6 bg-white rounded-lg shadow-lg dark:bg-gray-800 editable-container">
|
||||
<div class="p-6 mb-6 bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||
<h2 class="mb-4 text-2xl font-bold text-gray-900 dark:text-white">Quick Facts</h2>
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-1">
|
||||
{% if park.owner %}
|
||||
@@ -212,9 +191,7 @@
|
||||
<i class="w-5 text-blue-500 fas fa-calendar-alt dark:text-blue-400"></i>
|
||||
<span class="ml-2">Opening Date</span>
|
||||
</dt>
|
||||
<dd class="font-medium text-gray-900 dark:text-white"
|
||||
data-editable data-content-id="{{ park.id }}"
|
||||
data-field-name="opening_date" data-field-type="date">
|
||||
<dd class="font-medium text-gray-900 dark:text-white">
|
||||
{{ park.opening_date }}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -225,9 +202,7 @@
|
||||
<i class="w-5 text-blue-500 fas fa-calendar-times dark:text-blue-400"></i>
|
||||
<span class="ml-2">Closing Date</span>
|
||||
</dt>
|
||||
<dd class="font-medium text-gray-900 dark:text-white"
|
||||
data-editable data-content-id="{{ park.id }}"
|
||||
data-field-name="closing_date" data-field-type="date">
|
||||
<dd class="font-medium text-gray-900 dark:text-white">
|
||||
{{ park.closing_date }}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -238,9 +213,7 @@
|
||||
<i class="w-5 text-blue-500 fas fa-clock dark:text-blue-400"></i>
|
||||
<span class="ml-2">Operating Season</span>
|
||||
</dt>
|
||||
<dd class="font-medium text-gray-900 dark:text-white"
|
||||
data-editable data-content-id="{{ park.id }}"
|
||||
data-field-name="operating_season">
|
||||
<dd class="font-medium text-gray-900 dark:text-white">
|
||||
{{ park.operating_season }}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -251,9 +224,7 @@
|
||||
<i class="w-5 text-blue-500 fas fa-ruler-combined dark:text-blue-400"></i>
|
||||
<span class="ml-2">Size</span>
|
||||
</dt>
|
||||
<dd class="font-medium text-gray-900 dark:text-white"
|
||||
data-editable data-content-id="{{ park.id }}"
|
||||
data-field-name="size_acres" data-field-type="number">
|
||||
<dd class="font-medium text-gray-900 dark:text-white">
|
||||
{{ park.size_acres }} acres
|
||||
</dd>
|
||||
</div>
|
||||
@@ -307,7 +278,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="{% static 'js/inline-edit.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
{% extends 'base/base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Add Park - ThrillWiki{% endblock %}
|
||||
{% block title %}{% if is_edit %}Edit{% else %}Add{% endif %} Park - ThrillWiki{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container px-4 mx-auto">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<h1 class="mb-6 text-3xl font-bold text-gray-900 dark:text-white">Add Park</h1>
|
||||
<h1 class="mb-6 text-3xl font-bold text-gray-900 dark:text-white">{% if is_edit %}Edit{% else %}Add{% endif %} Park</h1>
|
||||
|
||||
<form method="post" class="space-y-6">
|
||||
{% if form.errors %}
|
||||
<div class="p-4 mb-6 text-red-700 bg-red-100 border border-red-400 rounded-lg dark:bg-red-900 dark:text-red-100 dark:border-red-700">
|
||||
<p class="font-medium">Please correct the following errors:</p>
|
||||
<ul class="ml-4 list-disc">
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<li>{{ field.label }}: {{ error }}</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for error in form.non_field_errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="space-y-6" id="park-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Hidden fields -->
|
||||
@@ -20,7 +36,7 @@
|
||||
<!-- Name field -->
|
||||
<div>
|
||||
<label for="{{ form.name.id_for_label }}" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Name
|
||||
Name *
|
||||
</label>
|
||||
<div>
|
||||
{{ form.name }}
|
||||
@@ -33,46 +49,88 @@
|
||||
</div>
|
||||
|
||||
<!-- Location fields -->
|
||||
<div>
|
||||
<div x-data="locationAutocomplete('country', false)" class="relative">
|
||||
<label for="{{ form.country_name.id_for_label }}" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Country
|
||||
Country *
|
||||
</label>
|
||||
<div>
|
||||
{{ form.country_name }}
|
||||
</div>
|
||||
{% if form.country_name.errors %}
|
||||
<div class="mt-1 text-sm text-red-600 dark:text-red-400">
|
||||
{{ form.country_name.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="text"
|
||||
id="id_country_name"
|
||||
name="country_name"
|
||||
x-model="query"
|
||||
@input.debounce.300ms="fetchSuggestions()"
|
||||
@focus="fetchSuggestions()"
|
||||
@click.away="suggestions = []"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select country..."
|
||||
value="{{ form.country_name.value|default:'' }}"
|
||||
autocomplete="off">
|
||||
<!-- Suggestions Dropdown -->
|
||||
<ul x-show="suggestions.length > 0"
|
||||
x-cloak
|
||||
class="absolute z-50 w-full py-1 mt-1 overflow-auto bg-white rounded-md shadow-lg dark:bg-gray-700 max-h-60">
|
||||
<template x-for="suggestion in suggestions" :key="suggestion.id">
|
||||
<li @click="selectSuggestion(suggestion)"
|
||||
x-text="suggestion.name"
|
||||
class="px-4 py-2 text-gray-700 cursor-pointer dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600">
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div x-data="locationAutocomplete('region', false)" class="relative">
|
||||
<label for="{{ form.region_name.id_for_label }}" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Region/State
|
||||
</label>
|
||||
<div>
|
||||
{{ form.region_name }}
|
||||
</div>
|
||||
{% if form.region_name.errors %}
|
||||
<div class="mt-1 text-sm text-red-600 dark:text-red-400">
|
||||
{{ form.region_name.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="text"
|
||||
id="id_region_name"
|
||||
name="region_name"
|
||||
x-model="query"
|
||||
@input.debounce.300ms="fetchSuggestions()"
|
||||
@focus="fetchSuggestions()"
|
||||
@click.away="suggestions = []"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select region/state..."
|
||||
value="{{ form.region_name.value|default:'' }}"
|
||||
autocomplete="off">
|
||||
<!-- Suggestions Dropdown -->
|
||||
<ul x-show="suggestions.length > 0"
|
||||
x-cloak
|
||||
class="absolute z-50 w-full py-1 mt-1 overflow-auto bg-white rounded-md shadow-lg dark:bg-gray-700 max-h-60">
|
||||
<template x-for="suggestion in suggestions" :key="suggestion.id">
|
||||
<li @click="selectSuggestion(suggestion)"
|
||||
x-text="suggestion.name"
|
||||
class="px-4 py-2 text-gray-700 cursor-pointer dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600">
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div x-data="locationAutocomplete('city', false)" class="relative">
|
||||
<label for="{{ form.city_name.id_for_label }}" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
City
|
||||
</label>
|
||||
<div>
|
||||
{{ form.city_name }}
|
||||
</div>
|
||||
{% if form.city_name.errors %}
|
||||
<div class="mt-1 text-sm text-red-600 dark:text-red-400">
|
||||
{{ form.city_name.errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="text"
|
||||
id="id_city_name"
|
||||
name="city_name"
|
||||
x-model="query"
|
||||
@input.debounce.300ms="fetchSuggestions()"
|
||||
@focus="fetchSuggestions()"
|
||||
@click.away="suggestions = []"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select city..."
|
||||
value="{{ form.city_name.value|default:'' }}"
|
||||
autocomplete="off">
|
||||
<!-- Suggestions Dropdown -->
|
||||
<ul x-show="suggestions.length > 0"
|
||||
x-cloak
|
||||
class="absolute z-50 w-full py-1 mt-1 overflow-auto bg-white rounded-md shadow-lg dark:bg-gray-700 max-h-60">
|
||||
<template x-for="suggestion in suggestions" :key="suggestion.id">
|
||||
<li @click="selectSuggestion(suggestion)"
|
||||
x-text="suggestion.name"
|
||||
class="px-4 py-2 text-gray-700 cursor-pointer dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600">
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Other fields -->
|
||||
@@ -80,7 +138,7 @@
|
||||
{% if field.name not in 'name,country,region,city,country_name,region_name,city_name' %}
|
||||
<div>
|
||||
<label for="{{ field.id_for_label }}" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ field.label }}
|
||||
{{ field.label }}{% if field.field.required %} *{% endif %}
|
||||
</label>
|
||||
<div>
|
||||
{{ field }}
|
||||
@@ -98,17 +156,20 @@
|
||||
{% endfor %}
|
||||
|
||||
{% if not user.role == 'MODERATOR' and not user.role == 'ADMIN' and not user.role == 'SUPERUSER' %}
|
||||
<div class="p-4 mb-4 text-blue-700 bg-blue-100 border border-blue-400 rounded-lg dark:bg-blue-900 dark:text-blue-100 dark:border-blue-700">
|
||||
<p>Your submission will be reviewed by a moderator before being published.</p>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="reason" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Reason for Addition
|
||||
Reason for {% if is_edit %}Edit{% else %}Addition{% endif %} *
|
||||
</label>
|
||||
<textarea name="reason"
|
||||
id="reason"
|
||||
class="w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
rows="3"
|
||||
required
|
||||
placeholder="Please explain why you're adding this park and provide any relevant details."></textarea>
|
||||
placeholder="Please explain why you're {% if is_edit %}editing{% else %}adding{% endif %} this park and provide any relevant details."></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="source" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
@@ -124,11 +185,12 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="flex justify-end space-x-4">
|
||||
<a href="{% url 'parks:park_list' %}" class="px-4 py-2 text-gray-700 bg-gray-200 rounded-lg hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500">
|
||||
<a href="{% if is_edit %}{% url 'parks:park_detail' slug=object.slug %}{% else %}{% url 'parks:park_list' %}{% endif %}"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-200 rounded-lg hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-200 dark:hover:bg-gray-500">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">
|
||||
Submit
|
||||
{% if is_edit %}Save Changes{% else %}Submit{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -136,100 +198,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.css" />
|
||||
<style>
|
||||
.awesomplete {
|
||||
width: 100%;
|
||||
}
|
||||
.awesomplete > ul {
|
||||
@apply bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg;
|
||||
}
|
||||
.awesomplete > ul > li {
|
||||
@apply px-4 py-2 cursor-pointer text-gray-700 dark:text-gray-300;
|
||||
}
|
||||
.awesomplete > ul > li:hover,
|
||||
.awesomplete > ul > li[aria-selected="true"] {
|
||||
@apply bg-gray-100 dark:bg-gray-600;
|
||||
}
|
||||
.awesomplete mark {
|
||||
@apply bg-blue-100 dark:bg-blue-900;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Helper function to initialize Awesomplete
|
||||
function initAwesomplete(input, url, params = {}) {
|
||||
if (!input) return null;
|
||||
|
||||
var awesomplete = new Awesomplete(input, {
|
||||
minChars: 1,
|
||||
maxItems: 10,
|
||||
autoFirst: true
|
||||
});
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
// Build query parameters
|
||||
const queryParams = new URLSearchParams({
|
||||
q: this.value,
|
||||
...Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => [key, typeof value === 'function' ? value() : value])
|
||||
)
|
||||
});
|
||||
|
||||
fetch(`${url}?${queryParams}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
awesomplete.list = data;
|
||||
});
|
||||
});
|
||||
|
||||
return awesomplete;
|
||||
}
|
||||
|
||||
// Initialize Awesomplete for each location field
|
||||
var countryInput = document.getElementById('id_country_name');
|
||||
var regionInput = document.getElementById('id_region_name');
|
||||
var cityInput = document.getElementById('id_city_name');
|
||||
var countryHidden = document.getElementById('id_country');
|
||||
var regionHidden = document.getElementById('id_region');
|
||||
var cityHidden = document.getElementById('id_city');
|
||||
|
||||
var countryAwesomplete = initAwesomplete(countryInput, '/parks/ajax/countries/');
|
||||
|
||||
if (regionInput) {
|
||||
var regionAwesomplete = initAwesomplete(regionInput, '/parks/ajax/regions/', {
|
||||
country: () => countryInput ? countryInput.value : ''
|
||||
});
|
||||
|
||||
// Clear dependent fields when country changes
|
||||
countryInput.addEventListener('awesomplete-select', function(event) {
|
||||
regionInput.value = '';
|
||||
regionHidden.value = '';
|
||||
if (cityInput) {
|
||||
cityInput.value = '';
|
||||
cityHidden.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (cityInput) {
|
||||
var cityAwesomplete = initAwesomplete(cityInput, '/parks/ajax/cities/', {
|
||||
country: () => countryInput ? countryInput.value : '',
|
||||
region: () => regionInput ? regionInput.value : ''
|
||||
});
|
||||
|
||||
// Clear city when region changes
|
||||
regionInput.addEventListener('awesomplete-select', function(event) {
|
||||
cityInput.value = '';
|
||||
cityHidden.value = '';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="p-4 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<form id="park-filters" class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-5"
|
||||
<form id="park-filters" class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4"
|
||||
hx-get="{% url 'parks:park_list' %}"
|
||||
hx-trigger="change from:select, input from:input[type='text'] delay:500ms"
|
||||
hx-trigger="change from:select, input from:input[type='text'] delay:500ms, click from:.status-filter"
|
||||
hx-target="#parks-grid"
|
||||
hx-push-url="true">
|
||||
<div>
|
||||
@@ -28,41 +28,134 @@
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Search parks...">
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<!-- Country Field -->
|
||||
<div x-data="locationAutocomplete('country', true)" class="relative">
|
||||
<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"
|
||||
<input type="text"
|
||||
name="country"
|
||||
id="country"
|
||||
x-model="query"
|
||||
@input.debounce.300ms="fetchSuggestions()"
|
||||
@focus="fetchSuggestions()"
|
||||
@click.away="suggestions = []"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select country..."
|
||||
value="{{ current_filters.country }}"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select country...">
|
||||
autocomplete="off">
|
||||
<!-- Suggestions Dropdown -->
|
||||
<ul x-show="suggestions.length > 0"
|
||||
x-cloak
|
||||
class="absolute z-50 w-full py-1 mt-1 overflow-auto bg-white rounded-md shadow-lg dark:bg-gray-700 max-h-60">
|
||||
<template x-for="suggestion in suggestions" :key="suggestion.id">
|
||||
<li @click="selectSuggestion(suggestion)"
|
||||
x-text="suggestion.name"
|
||||
class="px-4 py-2 text-gray-700 cursor-pointer dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600">
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<!-- Region Field -->
|
||||
<div x-data="locationAutocomplete('region', true)" class="relative">
|
||||
<label for="region" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">State/Region</label>
|
||||
<input type="text" name="region" id="region"
|
||||
<input type="text"
|
||||
name="region"
|
||||
id="region"
|
||||
x-model="query"
|
||||
@input.debounce.300ms="fetchSuggestions()"
|
||||
@focus="fetchSuggestions()"
|
||||
@click.away="suggestions = []"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select state/region..."
|
||||
value="{{ current_filters.region }}"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select state/region...">
|
||||
autocomplete="off">
|
||||
<!-- Suggestions Dropdown -->
|
||||
<ul x-show="suggestions.length > 0"
|
||||
x-cloak
|
||||
class="absolute z-50 w-full py-1 mt-1 overflow-auto bg-white rounded-md shadow-lg dark:bg-gray-700 max-h-60">
|
||||
<template x-for="suggestion in suggestions" :key="suggestion.id">
|
||||
<li @click="selectSuggestion(suggestion)"
|
||||
x-text="suggestion.name"
|
||||
class="px-4 py-2 text-gray-700 cursor-pointer dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600">
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<!-- City Field -->
|
||||
<div x-data="locationAutocomplete('city', true)" class="relative">
|
||||
<label for="city" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">City</label>
|
||||
<input type="text" name="city" id="city"
|
||||
value="{{ current_filters.city }}"
|
||||
<input type="text"
|
||||
name="city"
|
||||
id="city"
|
||||
x-model="query"
|
||||
@input.debounce.300ms="fetchSuggestions()"
|
||||
@focus="fetchSuggestions()"
|
||||
@click.away="suggestions = []"
|
||||
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
placeholder="Select city...">
|
||||
</div>
|
||||
<div>
|
||||
<label for="status" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Status</label>
|
||||
<select name="status" id="status"
|
||||
class="w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="OPERATING" {% if current_filters.status == 'OPERATING' %}selected{% endif %}>Operating</option>
|
||||
<option value="CLOSED_TEMP" {% if current_filters.status == 'CLOSED_TEMP' %}selected{% endif %}>Temporarily Closed</option>
|
||||
<option value="CLOSED_PERM" {% if current_filters.status == 'CLOSED_PERM' %}selected{% endif %}>Permanently Closed</option>
|
||||
<option value="UNDER_CONSTRUCTION" {% if current_filters.status == 'UNDER_CONSTRUCTION' %}selected{% endif %}>Under Construction</option>
|
||||
<option value="DEMOLISHED" {% if current_filters.status == 'DEMOLISHED' %}selected{% endif %}>Demolished</option>
|
||||
<option value="RELOCATED" {% if current_filters.status == 'RELOCATED' %}selected{% endif %}>Relocated</option>
|
||||
</select>
|
||||
placeholder="Select city..."
|
||||
value="{{ current_filters.city }}"
|
||||
autocomplete="off">
|
||||
<!-- Suggestions Dropdown -->
|
||||
<ul x-show="suggestions.length > 0"
|
||||
x-cloak
|
||||
class="absolute z-50 w-full py-1 mt-1 overflow-auto bg-white rounded-md shadow-lg dark:bg-gray-700 max-h-60">
|
||||
<template x-for="suggestion in suggestions" :key="suggestion.id">
|
||||
<li @click="selectSuggestion(suggestion)"
|
||||
x-text="suggestion.name"
|
||||
class="px-4 py-2 text-gray-700 cursor-pointer dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-600">
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Hidden inputs for selected statuses -->
|
||||
{% for status in current_filters.statuses %}
|
||||
<input type="hidden" name="status" value="{{ status }}">
|
||||
{% endfor %}
|
||||
</form>
|
||||
|
||||
<!-- Status Filter Icons -->
|
||||
<div class="flex flex-wrap gap-2 mt-4">
|
||||
<label class="block w-full mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">Status Filter</label>
|
||||
<button type="button"
|
||||
class="status-filter status-badge status-operating {% if 'OPERATING' in current_filters.statuses %}ring-2 ring-blue-500{% endif %}"
|
||||
data-status="OPERATING"
|
||||
onclick="toggleStatus(this, 'OPERATING')">
|
||||
<i class="mr-1 fas fa-check-circle"></i>Operating
|
||||
</button>
|
||||
<button type="button"
|
||||
class="status-filter status-badge status-closed {% if 'CLOSED_TEMP' in current_filters.statuses %}ring-2 ring-blue-500{% endif %}"
|
||||
data-status="CLOSED_TEMP"
|
||||
onclick="toggleStatus(this, 'CLOSED_TEMP')">
|
||||
<i class="mr-1 fas fa-clock"></i>Temporarily Closed
|
||||
</button>
|
||||
<button type="button"
|
||||
class="status-filter status-badge status-closed {% if 'CLOSED_PERM' in current_filters.statuses %}ring-2 ring-blue-500{% endif %}"
|
||||
data-status="CLOSED_PERM"
|
||||
onclick="toggleStatus(this, 'CLOSED_PERM')">
|
||||
<i class="mr-1 fas fa-times-circle"></i>Permanently Closed
|
||||
</button>
|
||||
<button type="button"
|
||||
class="status-filter status-badge status-construction {% if 'UNDER_CONSTRUCTION' in current_filters.statuses %}ring-2 ring-blue-500{% endif %}"
|
||||
data-status="UNDER_CONSTRUCTION"
|
||||
onclick="toggleStatus(this, 'UNDER_CONSTRUCTION')">
|
||||
<i class="mr-1 fas fa-hard-hat"></i>Under Construction
|
||||
</button>
|
||||
<button type="button"
|
||||
class="status-filter status-badge status-demolished {% if 'DEMOLISHED' in current_filters.statuses %}ring-2 ring-blue-500{% endif %}"
|
||||
data-status="DEMOLISHED"
|
||||
onclick="toggleStatus(this, 'DEMOLISHED')">
|
||||
<i class="mr-1 fas fa-ban"></i>Demolished
|
||||
</button>
|
||||
<button type="button"
|
||||
class="status-filter status-badge status-relocated {% if 'RELOCATED' in current_filters.statuses %}ring-2 ring-blue-500{% endif %}"
|
||||
data-status="RELOCATED"
|
||||
onclick="toggleStatus(this, 'RELOCATED')">
|
||||
<i class="mr-1 fas fa-truck-moving"></i>Relocated
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Parks Grid -->
|
||||
@@ -70,107 +163,28 @@
|
||||
{% include "parks/partials/park_list.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.css" />
|
||||
<style>
|
||||
.awesomplete {
|
||||
width: 100%;
|
||||
}
|
||||
.awesomplete > ul {
|
||||
@apply bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg;
|
||||
}
|
||||
.awesomplete > ul > li {
|
||||
@apply px-4 py-2 cursor-pointer text-gray-700 dark:text-gray-300;
|
||||
}
|
||||
.awesomplete > ul > li:hover,
|
||||
.awesomplete > ul > li[aria-selected="true"] {
|
||||
@apply bg-gray-100 dark:bg-gray-600;
|
||||
}
|
||||
.awesomplete mark {
|
||||
@apply bg-blue-100 dark:bg-blue-900;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const countryInput = document.getElementById('country');
|
||||
const regionInput = document.getElementById('region');
|
||||
const cityInput = document.getElementById('city');
|
||||
|
||||
// Initialize Awesomplete for country
|
||||
if (countryInput) {
|
||||
const countryList = new Awesomplete(countryInput, {
|
||||
minChars: 1,
|
||||
maxItems: 10,
|
||||
autoFirst: true
|
||||
});
|
||||
|
||||
countryInput.addEventListener('input', function() {
|
||||
fetch(`/parks/ajax/countries/?q=${encodeURIComponent(this.value)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
countryList.list = data;
|
||||
});
|
||||
});
|
||||
function toggleStatus(button, status) {
|
||||
const form = document.getElementById('park-filters');
|
||||
const existingInputs = form.querySelectorAll(`input[name="status"][value="${status}"]`);
|
||||
|
||||
if (existingInputs.length > 0) {
|
||||
// Status is already selected, remove it
|
||||
existingInputs.forEach(input => input.remove());
|
||||
button.classList.remove('ring-2', 'ring-blue-500');
|
||||
} else {
|
||||
// Status is not selected, add it
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'status';
|
||||
input.value = status;
|
||||
form.appendChild(input);
|
||||
button.classList.add('ring-2', 'ring-blue-500');
|
||||
}
|
||||
|
||||
// Initialize Awesomplete for region
|
||||
if (regionInput) {
|
||||
const regionList = new Awesomplete(regionInput, {
|
||||
minChars: 1,
|
||||
maxItems: 10,
|
||||
autoFirst: true
|
||||
});
|
||||
|
||||
regionInput.addEventListener('input', function() {
|
||||
const country = countryInput.value;
|
||||
fetch(`/parks/ajax/regions/?q=${encodeURIComponent(this.value)}&country=${encodeURIComponent(country)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
regionList.list = data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize Awesomplete for city
|
||||
if (cityInput) {
|
||||
const cityList = new Awesomplete(cityInput, {
|
||||
minChars: 1,
|
||||
maxItems: 10,
|
||||
autoFirst: true
|
||||
});
|
||||
|
||||
cityInput.addEventListener('input', function() {
|
||||
const country = countryInput.value;
|
||||
const region = regionInput.value;
|
||||
fetch(`/parks/ajax/cities/?q=${encodeURIComponent(this.value)}&country=${encodeURIComponent(country)}®ion=${encodeURIComponent(region)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
cityList.list = data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle location link clicks
|
||||
document.querySelectorAll('.location-link').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const params = new URLSearchParams(this.getAttribute('href').split('?')[1]);
|
||||
|
||||
// Update form inputs
|
||||
countryInput.value = params.get('country') || '';
|
||||
regionInput.value = params.get('region') || '';
|
||||
cityInput.value = params.get('city') || '';
|
||||
|
||||
// Trigger form submission
|
||||
htmx.trigger('#park-filters', 'change');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger form submission
|
||||
form.dispatchEvent(new Event('change'));
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user