here we go

This commit is contained in:
pacnpal
2024-10-31 22:32:01 +00:00
parent 71272e36a6
commit 3cbda93094
68 changed files with 3114 additions and 1485 deletions

View File

@@ -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)}&region=${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 %}