mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-29 14:47:00 -05:00
Compare commits
7 Commits
757ad1be89
...
5b7b203619
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b7b203619 | ||
|
|
47c435d2f5 | ||
|
|
ce382a4361 | ||
|
|
07ab9f28f2 | ||
|
|
40e5cf3162 | ||
|
|
b9377ead37 | ||
|
|
851709058f |
@@ -199,24 +199,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('moderationDashboard', () => ({
|
||||||
|
init() {
|
||||||
|
// Handle HTMX events using AlpineJS approach
|
||||||
|
document.body.addEventListener('htmx:afterRequest', (evt) => {
|
||||||
if (evt.detail.successful) {
|
if (evt.detail.successful) {
|
||||||
const path = evt.detail.requestConfig.path;
|
const path = evt.detail.requestConfig.path;
|
||||||
let event;
|
let eventName;
|
||||||
|
|
||||||
if (path.includes('approve')) {
|
if (path.includes('approve')) {
|
||||||
event = new CustomEvent('submission-approved');
|
eventName = 'submission-approved';
|
||||||
} else if (path.includes('reject')) {
|
} else if (path.includes('reject')) {
|
||||||
event = new CustomEvent('submission-rejected');
|
eventName = 'submission-rejected';
|
||||||
} else if (path.includes('escalate')) {
|
} else if (path.includes('escalate')) {
|
||||||
event = new CustomEvent('submission-escalated');
|
eventName = 'submission-escalated';
|
||||||
} else if (path.includes('edit')) {
|
} else if (path.includes('edit')) {
|
||||||
event = new CustomEvent('submission-updated');
|
eventName = 'submission-updated';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event) {
|
if (eventName) {
|
||||||
window.dispatchEvent(event);
|
this.$dispatch(eventName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
style="max-height: 240px; overflow-y: auto;"
|
||||||
|
x-data="designerSearchResults('{{ submission_id }}')"
|
||||||
|
@click.outside="clearResults()">
|
||||||
{% if designers %}
|
{% if designers %}
|
||||||
{% for designer in designers %}
|
{% for designer in designers %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
onclick="selectDesignerForSubmission('{{ designer.id }}', '{{ designer.name|escapejs }}', '{{ submission_id }}')">
|
@click="selectDesigner('{{ designer.id }}', '{{ designer.name|escapejs }}')">
|
||||||
{{ designer.name }}
|
{{ designer.name }}
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -19,14 +22,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function selectDesignerForSubmission(id, name, submissionId) {
|
document.addEventListener('alpine:init', () => {
|
||||||
// Debug logging
|
Alpine.data('designerSearchResults', (submissionId) => ({
|
||||||
console.log('Selecting designer:', {id, name, submissionId});
|
submissionId: submissionId,
|
||||||
|
|
||||||
// Find elements
|
selectDesigner(id, name) {
|
||||||
const designerInput = document.querySelector(`#designer-input-${submissionId}`);
|
// Debug logging
|
||||||
const searchInput = document.querySelector(`#designer-search-${submissionId}`);
|
console.log('Selecting designer:', {id, name, submissionId: this.submissionId});
|
||||||
const resultsDiv = document.querySelector(`#designer-search-results-${submissionId}`);
|
|
||||||
|
// Find elements using AlpineJS approach
|
||||||
|
const designerInput = document.querySelector(`#designer-input-${this.submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#designer-search-${this.submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#designer-search-results-${this.submissionId}`);
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
console.log('Found elements:', {
|
console.log('Found elements:', {
|
||||||
@@ -48,20 +55,16 @@ function selectDesignerForSubmission(id, name, submissionId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear results
|
// Clear results
|
||||||
|
this.clearResults();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearResults() {
|
||||||
|
const resultsDiv = document.querySelector(`#designer-search-results-${this.submissionId}`);
|
||||||
if (resultsDiv) {
|
if (resultsDiv) {
|
||||||
resultsDiv.innerHTML = '';
|
resultsDiv.innerHTML = '';
|
||||||
console.log('Cleared results div');
|
console.log('Cleared results div');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Close search results when clicking outside
|
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
const searchResults = document.querySelectorAll('[id^="designer-search-results-"]');
|
|
||||||
searchResults.forEach(function(resultsDiv) {
|
|
||||||
const searchInput = document.querySelector(`#designer-search-${resultsDiv.id.split('-').pop()}`);
|
|
||||||
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
|
||||||
resultsDiv.innerHTML = '';
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -19,30 +19,60 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="p-4 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
<div class="p-4 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50"
|
||||||
|
x-data="locationWidget({
|
||||||
|
submissionId: '{{ submission.id }}',
|
||||||
|
initialData: {
|
||||||
|
city: '{{ submission.changes.city|default:"" }}',
|
||||||
|
state: '{{ submission.changes.state|default:"" }}',
|
||||||
|
country: '{{ submission.changes.country|default:"" }}',
|
||||||
|
postal_code: '{{ submission.changes.postal_code|default:"" }}',
|
||||||
|
street_address: '{{ submission.changes.street_address|default:"" }}',
|
||||||
|
latitude: '{{ submission.changes.latitude|default:"" }}',
|
||||||
|
longitude: '{{ submission.changes.longitude|default:"" }}'
|
||||||
|
}
|
||||||
|
})"
|
||||||
|
x-init="init()">
|
||||||
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-300">Location</h3>
|
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-300">Location</h3>
|
||||||
|
|
||||||
<div class="location-widget" id="locationWidget-{{ submission.id }}">
|
<div class="location-widget">
|
||||||
{# Search Form #}
|
{# Search Form #}
|
||||||
<div class="relative mb-4">
|
<div class="relative mb-4">
|
||||||
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Search Location
|
Search Location
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="locationSearch-{{ submission.id }}"
|
x-model="searchQuery"
|
||||||
|
@input.debounce.300ms="handleSearch()"
|
||||||
|
@click.outside="showSearchResults = false"
|
||||||
class="relative w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
class="relative w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
||||||
placeholder="Search for a location..."
|
placeholder="Search for a location..."
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
style="z-index: 10;">
|
style="z-index: 10;">
|
||||||
<div id="searchResults-{{ submission.id }}"
|
<div x-show="showSearchResults"
|
||||||
|
x-transition
|
||||||
style="position: absolute; top: 100%; left: 0; right: 0; z-index: 1000;"
|
style="position: absolute; top: 100%; left: 0; right: 0; z-index: 1000;"
|
||||||
class="hidden w-full mt-1 overflow-auto bg-white border rounded-md shadow-lg max-h-60 dark:bg-gray-700 dark:border-gray-600">
|
class="w-full mt-1 overflow-auto bg-white border rounded-md shadow-lg max-h-60 dark:bg-gray-700 dark:border-gray-600">
|
||||||
|
<template x-for="(result, index) in searchResults" :key="index">
|
||||||
|
<div class="p-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600"
|
||||||
|
@click="selectLocation(result)">
|
||||||
|
<div class="font-medium text-gray-900 dark:text-white" x-text="result.display_name || result.name || ''"></div>
|
||||||
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
<span x-text="result.address?.city ? result.address.city + ', ' : ''"></span>
|
||||||
|
<span x-text="result.address?.country || ''"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div x-show="searchResults.length === 0 && searchQuery.length > 0"
|
||||||
|
class="p-2 text-gray-500 dark:text-gray-400">
|
||||||
|
No results found
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Map Container #}
|
{# Map Container #}
|
||||||
<div class="relative mb-4" style="z-index: 1;">
|
<div class="relative mb-4" style="z-index: 1;">
|
||||||
<div id="locationMap-{{ submission.id }}"
|
<div x-ref="mapContainer"
|
||||||
class="h-[300px] w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
class="h-[300px] w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -54,9 +84,8 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="street_address"
|
name="street_address"
|
||||||
id="streetAddress-{{ submission.id }}"
|
x-model="formData.street_address"
|
||||||
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input">
|
||||||
value="{{ submission.changes.street_address }}">
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
@@ -64,9 +93,8 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="city"
|
name="city"
|
||||||
id="city-{{ submission.id }}"
|
x-model="formData.city"
|
||||||
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input">
|
||||||
value="{{ submission.changes.city }}">
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
@@ -74,9 +102,8 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="state"
|
name="state"
|
||||||
id="state-{{ submission.id }}"
|
x-model="formData.state"
|
||||||
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input">
|
||||||
value="{{ submission.changes.state }}">
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
@@ -84,9 +111,8 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="country"
|
name="country"
|
||||||
id="country-{{ submission.id }}"
|
x-model="formData.country"
|
||||||
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input">
|
||||||
value="{{ submission.changes.country }}">
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
@@ -94,58 +120,49 @@
|
|||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="postal_code"
|
name="postal_code"
|
||||||
id="postalCode-{{ submission.id }}"
|
x-model="formData.postal_code"
|
||||||
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input"
|
class="w-full px-4 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-700 dark:border-gray-600 form-input">
|
||||||
value="{{ submission.changes.postal_code }}">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Hidden Coordinate Fields #}
|
{# Hidden Coordinate Fields #}
|
||||||
<div class="hidden">
|
<div class="hidden">
|
||||||
<input type="hidden" name="latitude" id="latitude-{{ submission.id }}" value="{{ submission.changes.latitude }}">
|
<input type="hidden" name="latitude" x-model="formData.latitude">
|
||||||
<input type="hidden" name="longitude" id="longitude-{{ submission.id }}" value="{{ submission.changes.longitude }}">
|
<input type="hidden" name="longitude" x-model="formData.longitude">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('alpine:init', () => {
|
||||||
let maps = {};
|
Alpine.data('locationWidget', (config) => ({
|
||||||
let markers = {};
|
submissionId: config.submissionId,
|
||||||
const searchInput = document.getElementById('locationSearch-{{ submission.id }}');
|
formData: { ...config.initialData },
|
||||||
const searchResults = document.getElementById('searchResults-{{ submission.id }}');
|
searchQuery: '',
|
||||||
let searchTimeout;
|
searchResults: [],
|
||||||
|
showSearchResults: false,
|
||||||
|
map: null,
|
||||||
|
marker: null,
|
||||||
|
|
||||||
// Initialize form fields with existing values
|
init() {
|
||||||
const fields = {
|
// Set initial search query if location exists
|
||||||
city: '{{ submission.changes.city|default:"" }}',
|
if (this.formData.street_address || this.formData.city) {
|
||||||
state: '{{ submission.changes.state|default:"" }}',
|
|
||||||
country: '{{ submission.changes.country|default:"" }}',
|
|
||||||
postal_code: '{{ submission.changes.postal_code|default:"" }}',
|
|
||||||
street_address: '{{ submission.changes.street_address|default:"" }}',
|
|
||||||
latitude: '{{ submission.changes.latitude|default:"" }}',
|
|
||||||
longitude: '{{ submission.changes.longitude|default:"" }}'
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.entries(fields).forEach(([field, value]) => {
|
|
||||||
const element = document.getElementById(`${field}-{{ submission.id }}`);
|
|
||||||
if (element) {
|
|
||||||
element.value = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set initial search input value if location exists
|
|
||||||
if (fields.street_address || fields.city) {
|
|
||||||
const parts = [
|
const parts = [
|
||||||
fields.street_address,
|
this.formData.street_address,
|
||||||
fields.city,
|
this.formData.city,
|
||||||
fields.state,
|
this.formData.state,
|
||||||
fields.country
|
this.formData.country
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
searchInput.value = parts.join(', ');
|
this.searchQuery = parts.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeCoordinate(value, maxDigits, decimalPlaces) {
|
// Initialize map when component is ready
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initMap();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizeCoordinate(value, maxDigits, decimalPlaces) {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
try {
|
try {
|
||||||
const rounded = Number(value).toFixed(decimalPlaces);
|
const rounded = Number(value).toFixed(decimalPlaces);
|
||||||
@@ -161,11 +178,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.error('Coordinate normalization failed:', error);
|
console.error('Coordinate normalization failed:', error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
function validateCoordinates(lat, lng) {
|
validateCoordinates(lat, lng) {
|
||||||
const normalizedLat = normalizeCoordinate(lat, 9, 6);
|
const normalizedLat = this.normalizeCoordinate(lat, 9, 6);
|
||||||
const normalizedLng = normalizeCoordinate(lng, 10, 6);
|
const normalizedLng = this.normalizeCoordinate(lng, 10, 6);
|
||||||
|
|
||||||
if (normalizedLat === null || normalizedLng === null) {
|
if (normalizedLat === null || normalizedLng === null) {
|
||||||
throw new Error('Invalid coordinate format');
|
throw new Error('Invalid coordinate format');
|
||||||
@@ -182,55 +199,61 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { lat: normalizedLat, lng: normalizedLng };
|
return { lat: normalizedLat, lng: normalizedLng };
|
||||||
}
|
},
|
||||||
|
|
||||||
function initMap() {
|
initMap() {
|
||||||
const submissionId = '{{ submission.id }}';
|
if (!this.$refs.mapContainer) {
|
||||||
const mapId = `locationMap-${submissionId}`;
|
console.error('Map container not found');
|
||||||
const mapContainer = document.getElementById(mapId);
|
|
||||||
|
|
||||||
if (!mapContainer) {
|
|
||||||
console.error(`Map container ${mapId} not found`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If map already exists, remove it
|
// If map already exists, remove it
|
||||||
if (maps[submissionId]) {
|
if (this.map) {
|
||||||
maps[submissionId].remove();
|
this.map.remove();
|
||||||
delete maps[submissionId];
|
this.map = null;
|
||||||
delete markers[submissionId];
|
this.marker = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new map
|
// Create new map
|
||||||
maps[submissionId] = L.map(mapId);
|
this.map = L.map(this.$refs.mapContainer);
|
||||||
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
attribution: '© OpenStreetMap contributors'
|
attribution: '© OpenStreetMap contributors'
|
||||||
}).addTo(maps[submissionId]);
|
}).addTo(this.map);
|
||||||
|
|
||||||
// Initialize with existing coordinates if available
|
// Initialize with existing coordinates if available
|
||||||
const initialLat = fields.latitude;
|
if (this.formData.latitude && this.formData.longitude) {
|
||||||
const initialLng = fields.longitude;
|
|
||||||
|
|
||||||
if (initialLat && initialLng) {
|
|
||||||
try {
|
try {
|
||||||
const normalized = validateCoordinates(initialLat, initialLng);
|
const normalized = this.validateCoordinates(this.formData.latitude, this.formData.longitude);
|
||||||
maps[submissionId].setView([normalized.lat, normalized.lng], 13);
|
this.map.setView([normalized.lat, normalized.lng], 13);
|
||||||
addMarker(normalized.lat, normalized.lng);
|
this.addMarker(normalized.lat, normalized.lng);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Invalid initial coordinates:', error);
|
console.error('Invalid initial coordinates:', error);
|
||||||
maps[submissionId].setView([0, 0], 2);
|
this.map.setView([0, 0], 2);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
maps[submissionId].setView([0, 0], 2);
|
this.map.setView([0, 0], 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle map clicks - HTMX version
|
// Handle map clicks
|
||||||
maps[submissionId].on('click', function(e) {
|
this.map.on('click', (e) => {
|
||||||
try {
|
this.handleMapClick(e.latlng.lat, e.latlng.lng);
|
||||||
const normalized = validateCoordinates(e.latlng.lat, e.latlng.lng);
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Create a temporary form for HTMX request
|
addMarker(lat, lng) {
|
||||||
|
if (this.marker) {
|
||||||
|
this.marker.remove();
|
||||||
|
}
|
||||||
|
this.marker = L.marker([lat, lng]).addTo(this.map);
|
||||||
|
this.map.setView([lat, lng], 13);
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleMapClick(lat, lng) {
|
||||||
|
try {
|
||||||
|
const normalized = this.validateCoordinates(lat, lng);
|
||||||
|
|
||||||
|
// Use HTMX for reverse geocoding
|
||||||
const tempForm = document.createElement('form');
|
const tempForm = document.createElement('form');
|
||||||
tempForm.style.display = 'none';
|
tempForm.style.display = 'none';
|
||||||
tempForm.setAttribute('hx-get', '/parks/search/reverse-geocode/');
|
tempForm.setAttribute('hx-get', '/parks/search/reverse-geocode/');
|
||||||
@@ -241,15 +264,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
tempForm.setAttribute('hx-trigger', 'submit');
|
tempForm.setAttribute('hx-trigger', 'submit');
|
||||||
tempForm.setAttribute('hx-swap', 'none');
|
tempForm.setAttribute('hx-swap', 'none');
|
||||||
|
|
||||||
// Add event listener for HTMX response
|
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||||
tempForm.addEventListener('htmx:afterRequest', function(event) {
|
|
||||||
if (event.detail.successful) {
|
if (event.detail.successful) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.detail.xhr.responseText);
|
const data = JSON.parse(event.detail.xhr.responseText);
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
throw new Error(data.error);
|
throw new Error(data.error);
|
||||||
}
|
}
|
||||||
updateLocation(normalized.lat, normalized.lng, data);
|
this.updateLocation(normalized.lat, normalized.lng, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Location update failed:', error);
|
console.error('Location update failed:', error);
|
||||||
alert(error.message || 'Failed to update location. Please try again.');
|
alert(error.message || 'Failed to update location. Please try again.');
|
||||||
@@ -258,7 +280,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.error('Geocoding request failed');
|
console.error('Geocoding request failed');
|
||||||
alert('Failed to update location. Please try again.');
|
alert('Failed to update location. Please try again.');
|
||||||
}
|
}
|
||||||
// Clean up temporary form
|
|
||||||
document.body.removeChild(tempForm);
|
document.body.removeChild(tempForm);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -269,56 +290,89 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.error('Location update failed:', error);
|
console.error('Location update failed:', error);
|
||||||
alert(error.message || 'Failed to update location. Please try again.');
|
alert(error.message || 'Failed to update location. Please try again.');
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
}
|
|
||||||
|
|
||||||
function addMarker(lat, lng) {
|
updateLocation(lat, lng, data) {
|
||||||
const submissionId = '{{ submission.id }}';
|
|
||||||
if (markers[submissionId]) {
|
|
||||||
markers[submissionId].remove();
|
|
||||||
}
|
|
||||||
markers[submissionId] = L.marker([lat, lng]).addTo(maps[submissionId]);
|
|
||||||
maps[submissionId].setView([lat, lng], 13);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLocation(lat, lng, data) {
|
|
||||||
try {
|
try {
|
||||||
const normalized = validateCoordinates(lat, lng);
|
const normalized = this.validateCoordinates(lat, lng);
|
||||||
const submissionId = '{{ submission.id }}';
|
|
||||||
|
|
||||||
// Update coordinates
|
// Update coordinates
|
||||||
document.getElementById(`latitude-${submissionId}`).value = normalized.lat;
|
this.formData.latitude = normalized.lat;
|
||||||
document.getElementById(`longitude-${submissionId}`).value = normalized.lng;
|
this.formData.longitude = normalized.lng;
|
||||||
|
|
||||||
// Update marker
|
// Update marker
|
||||||
addMarker(normalized.lat, normalized.lng);
|
this.addMarker(normalized.lat, normalized.lng);
|
||||||
|
|
||||||
// Update form fields with English names where available
|
// Update form fields with English names where available
|
||||||
const address = data.address || {};
|
const address = data.address || {};
|
||||||
document.getElementById(`streetAddress-${submissionId}`).value =
|
this.formData.street_address = `${address.house_number || ''} ${address.road || address.street || ''}`.trim() || '';
|
||||||
`${address.house_number || ''} ${address.road || address.street || ''}`.trim() || '';
|
this.formData.city = address.city || address.town || address.village || '';
|
||||||
document.getElementById(`city-${submissionId}`).value =
|
this.formData.state = address.state || address.region || '';
|
||||||
address.city || address.town || address.village || '';
|
this.formData.country = address.country || '';
|
||||||
document.getElementById(`state-${submissionId}`).value =
|
this.formData.postal_code = address.postcode || '';
|
||||||
address.state || address.region || '';
|
|
||||||
document.getElementById(`country-${submissionId}`).value = address.country || '';
|
|
||||||
document.getElementById(`postalCode-${submissionId}`).value = address.postcode || '';
|
|
||||||
|
|
||||||
// Update search input
|
// Update search input
|
||||||
const locationString-3 = [
|
const locationParts = [
|
||||||
document.getElementById(`streetAddress-${submissionId}`).value,
|
this.formData.street_address,
|
||||||
document.getElementById(`city-${submissionId}`).value,
|
this.formData.city,
|
||||||
document.getElementById(`state-${submissionId}`).value,
|
this.formData.state,
|
||||||
document.getElementById(`country-${submissionId}`).value
|
this.formData.country
|
||||||
].filter(Boolean).join(', ');
|
].filter(Boolean);
|
||||||
searchInput.value = locationString;
|
this.searchQuery = locationParts.join(', ');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Location update failed:', error);
|
console.error('Location update failed:', error);
|
||||||
alert(error.message || 'Failed to update location. Please try again.');
|
alert(error.message || 'Failed to update location. Please try again.');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSearch() {
|
||||||
|
const query = this.searchQuery.trim();
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
this.showSearchResults = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectLocation(result) {
|
// Use HTMX for location search
|
||||||
|
const tempForm = document.createElement('form');
|
||||||
|
tempForm.style.display = 'none';
|
||||||
|
tempForm.setAttribute('hx-get', '/parks/search/location/');
|
||||||
|
tempForm.setAttribute('hx-vals', JSON.stringify({
|
||||||
|
q: query
|
||||||
|
}));
|
||||||
|
tempForm.setAttribute('hx-trigger', 'submit');
|
||||||
|
tempForm.setAttribute('hx-swap', 'none');
|
||||||
|
|
||||||
|
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||||
|
if (event.detail.successful) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.detail.xhr.responseText);
|
||||||
|
|
||||||
|
if (data.results && data.results.length > 0) {
|
||||||
|
this.searchResults = data.results;
|
||||||
|
this.showSearchResults = true;
|
||||||
|
} else {
|
||||||
|
this.searchResults = [];
|
||||||
|
this.showSearchResults = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Search failed:', error);
|
||||||
|
this.searchResults = [];
|
||||||
|
this.showSearchResults = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Search request failed');
|
||||||
|
this.searchResults = [];
|
||||||
|
this.showSearchResults = false;
|
||||||
|
}
|
||||||
|
document.body.removeChild(tempForm);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(tempForm);
|
||||||
|
htmx.trigger(tempForm, 'submit');
|
||||||
|
},
|
||||||
|
|
||||||
|
selectLocation(result) {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -329,7 +383,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
throw new Error('Invalid coordinates in search result');
|
throw new Error('Invalid coordinates in search result');
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = validateCoordinates(lat, lon);
|
const normalized = this.validateCoordinates(lat, lon);
|
||||||
|
|
||||||
// Create a normalized address object
|
// Create a normalized address object
|
||||||
const address = {
|
const address = {
|
||||||
@@ -344,118 +398,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateLocation(normalized.lat, normalized.lng, address);
|
this.updateLocation(normalized.lat, normalized.lng, address);
|
||||||
searchResults.classList.add('hidden');
|
this.showSearchResults = false;
|
||||||
searchInput.value = address.name;
|
this.searchQuery = address.name;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Location selection failed:', error);
|
console.error('Location selection failed:', error);
|
||||||
alert(error.message || 'Failed to select location. Please try again.');
|
alert(error.message || 'Failed to select location. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle location search - HTMX version
|
|
||||||
searchInput.addEventListener('input', function() {
|
|
||||||
clearTimeout(searchTimeout);
|
|
||||||
const query = this.value.trim();
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
searchResults.classList.add('hidden');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchTimeout = setTimeout(function() {
|
|
||||||
// Create a temporary form for HTMX request
|
|
||||||
const tempForm = document.createElement('form');
|
|
||||||
tempForm.style.display = 'none';
|
|
||||||
tempForm.setAttribute('hx-get', '/parks/search/location/');
|
|
||||||
tempForm.setAttribute('hx-vals', JSON.stringify({
|
|
||||||
q: query
|
|
||||||
}));
|
}));
|
||||||
tempForm.setAttribute('hx-trigger', 'submit');
|
|
||||||
tempForm.setAttribute('hx-swap', 'none');
|
|
||||||
|
|
||||||
// Add event listener for HTMX response
|
|
||||||
tempForm.addEventListener('htmx:afterRequest', function(event) {
|
|
||||||
if (event.detail.successful) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(event.detail.xhr.responseText);
|
|
||||||
|
|
||||||
if (data.results && data.results.length > 0) {
|
|
||||||
const resultsHtml = data.results.map((result, index) => `
|
|
||||||
<div class="p-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600"
|
|
||||||
data-result-index="${index}">
|
|
||||||
<div class="font-medium text-gray-900 dark:text-white">${result.display_name || result.name || ''}</div>
|
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
${(result.address && result.address.city) ? result.address.city + ', ' : ''}${(result.address && result.address.country) || ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
|
|
||||||
searchResults.innerHTML = resultsHtml;
|
|
||||||
searchResults.classList.remove('hidden');
|
|
||||||
|
|
||||||
// Store results data
|
|
||||||
searchResults.dataset.results = JSON.stringify(data.results);
|
|
||||||
|
|
||||||
// Add click handlers
|
|
||||||
searchResults.querySelectorAll('[data-result-index]').forEach(el => {
|
|
||||||
el.addEventListener('click', function() {
|
|
||||||
const results = JSON.parse(searchResults.dataset.results);
|
|
||||||
const result = results[this.dataset.resultIndex];
|
|
||||||
selectLocation(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
searchResults.innerHTML = '<div class="p-2 text-gray-500 dark:text-gray-400">No results found</div>';
|
|
||||||
searchResults.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Search failed:', error);
|
|
||||||
searchResults.innerHTML = '<div class="p-2 text-red-500 dark:text-red-400">Search failed. Please try again.</div>';
|
|
||||||
searchResults.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Search request failed');
|
|
||||||
searchResults.innerHTML = '<div class="p-2 text-red-500 dark:text-red-400">Search failed. Please try again.</div>';
|
|
||||||
searchResults.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
// Clean up temporary form
|
|
||||||
document.body.removeChild(tempForm);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.appendChild(tempForm);
|
|
||||||
htmx.trigger(tempForm, 'submit');
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hide search results when clicking outside
|
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
if (!searchResults.contains(e.target) && e.target !== searchInput) {
|
|
||||||
searchResults.classList.add('hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize map when the element becomes visible
|
|
||||||
const observer = new MutationObserver(function(mutations) {
|
|
||||||
mutations.forEach(function(mutation) {
|
|
||||||
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
|
|
||||||
const mapContainer = document.getElementById(`locationMap-{{ submission.id }}`);
|
|
||||||
if (mapContainer && window.getComputedStyle(mapContainer).display !== 'none') {
|
|
||||||
initMap();
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapContainer = document.getElementById(`locationMap-{{ submission.id }}`);
|
|
||||||
if (mapContainer) {
|
|
||||||
observer.observe(mapContainer.parentElement.parentElement, { attributes: true });
|
|
||||||
|
|
||||||
// Also initialize immediately if the container is already visible
|
|
||||||
if (window.getComputedStyle(mapContainer).display !== 'none') {
|
|
||||||
initMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
style="max-height: 240px; overflow-y: auto;"
|
||||||
|
x-data="manufacturerSearchResults('{{ submission_id }}')"
|
||||||
|
@click.outside="clearResults()">
|
||||||
{% if manufacturers %}
|
{% if manufacturers %}
|
||||||
{% for manufacturer in manufacturers %}
|
{% for manufacturer in manufacturers %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
onclick="selectManufacturerForSubmission('{{ manufacturer.id }}', '{{ manufacturer.name|escapejs }}', '{{ submission_id }}')">
|
@click="selectManufacturer('{{ manufacturer.id }}', '{{ manufacturer.name|escapejs }}')">
|
||||||
{{ manufacturer.name }}
|
{{ manufacturer.name }}
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -19,14 +22,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function selectManufacturerForSubmission(id, name, submissionId) {
|
document.addEventListener('alpine:init', () => {
|
||||||
// Debug logging
|
Alpine.data('manufacturerSearchResults', (submissionId) => ({
|
||||||
console.log('Selecting manufacturer:', {id, name, submissionId});
|
submissionId: submissionId,
|
||||||
|
|
||||||
// Find elements
|
selectManufacturer(id, name) {
|
||||||
const manufacturerInput = document.querySelector(`#manufacturer-input-${submissionId}`);
|
// Debug logging
|
||||||
const searchInput = document.querySelector(`#manufacturer-search-${submissionId}`);
|
console.log('Selecting manufacturer:', {id, name, submissionId: this.submissionId});
|
||||||
const resultsDiv = document.querySelector(`#manufacturer-search-results-${submissionId}`);
|
|
||||||
|
// Find elements using AlpineJS approach
|
||||||
|
const manufacturerInput = document.querySelector(`#manufacturer-input-${this.submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#manufacturer-search-${this.submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#manufacturer-search-results-${this.submissionId}`);
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
console.log('Found elements:', {
|
console.log('Found elements:', {
|
||||||
@@ -48,20 +55,16 @@ function selectManufacturerForSubmission(id, name, submissionId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear results
|
// Clear results
|
||||||
|
this.clearResults();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearResults() {
|
||||||
|
const resultsDiv = document.querySelector(`#manufacturer-search-results-${this.submissionId}`);
|
||||||
if (resultsDiv) {
|
if (resultsDiv) {
|
||||||
resultsDiv.innerHTML = '';
|
resultsDiv.innerHTML = '';
|
||||||
console.log('Cleared results div');
|
console.log('Cleared results div');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Close search results when clicking outside
|
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
const searchResults = document.querySelectorAll('[id^="manufacturer-search-results-"]');
|
|
||||||
searchResults.forEach(function(resultsDiv) {
|
|
||||||
const searchInput = document.querySelector(`#manufacturer-search-${resultsDiv.id.split('-').pop()}`);
|
|
||||||
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
|
||||||
resultsDiv.innerHTML = '';
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
style="max-height: 240px; overflow-y: auto;"
|
||||||
|
x-data="parkSearchResults('{{ submission_id }}')"
|
||||||
|
@click.outside="clearResults()">
|
||||||
{% if parks %}
|
{% if parks %}
|
||||||
{% for park in parks %}
|
{% for park in parks %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
onclick="selectParkForSubmission('{{ park.id }}', '{{ park.name|escapejs }}', '{{ submission_id }}')">
|
@click="selectPark('{{ park.id }}', '{{ park.name|escapejs }}')">
|
||||||
{{ park.name }}
|
{{ park.name }}
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -19,14 +22,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function selectParkForSubmission(id, name, submissionId) {
|
document.addEventListener('alpine:init', () => {
|
||||||
// Debug logging
|
Alpine.data('parkSearchResults', (submissionId) => ({
|
||||||
console.log('Selecting park:', {id, name, submissionId});
|
submissionId: submissionId,
|
||||||
|
|
||||||
// Find elements
|
selectPark(id, name) {
|
||||||
const parkInput = document.querySelector(`#park-input-${submissionId}`);
|
// Debug logging
|
||||||
const searchInput = document.querySelector(`#park-search-${submissionId}`);
|
console.log('Selecting park:', {id, name, submissionId: this.submissionId});
|
||||||
const resultsDiv = document.querySelector(`#park-search-results-${submissionId}`);
|
|
||||||
|
// Find elements using AlpineJS approach
|
||||||
|
const parkInput = document.querySelector(`#park-input-${this.submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#park-search-${this.submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#park-search-results-${this.submissionId}`);
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
console.log('Found elements:', {
|
console.log('Found elements:', {
|
||||||
@@ -48,26 +55,22 @@ function selectParkForSubmission(id, name, submissionId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear results
|
// Clear results
|
||||||
if (resultsDiv) {
|
this.clearResults();
|
||||||
resultsDiv.innerHTML = '';
|
|
||||||
console.log('Cleared results div');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger park areas update
|
// Trigger park areas update
|
||||||
if (parkInput) {
|
if (parkInput) {
|
||||||
htmx.trigger(parkInput, 'change');
|
htmx.trigger(parkInput, 'change');
|
||||||
console.log('Triggered change event');
|
console.log('Triggered change event');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
// Close search results when clicking outside
|
clearResults() {
|
||||||
document.addEventListener('click', function(e) {
|
const resultsDiv = document.querySelector(`#park-search-results-${this.submissionId}`);
|
||||||
const searchResults = document.querySelectorAll('[id^="park-search-results-"]');
|
if (resultsDiv) {
|
||||||
searchResults.forEach(function(resultsDiv) {
|
|
||||||
const searchInput = document.querySelector(`#park-search-${resultsDiv.id.split('-').pop()}`);
|
|
||||||
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
|
||||||
resultsDiv.innerHTML = '';
|
resultsDiv.innerHTML = '';
|
||||||
|
console.log('Cleared results div');
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
style="max-height: 240px; overflow-y: auto;"
|
||||||
|
x-data="rideModelSearchResults('{{ submission_id }}')"
|
||||||
|
@click.outside="clearResults()">
|
||||||
{% if ride_models %}
|
{% if ride_models %}
|
||||||
{% for model in ride_models %}
|
{% for model in ride_models %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
onclick="selectRideModelForSubmission('{{ model.id }}', '{{ model.name|escapejs }}', '{{ submission_id }}')">
|
@click="selectRideModel('{{ model.id }}', '{{ model.name|escapejs }}')">
|
||||||
{{ model.name }}
|
{{ model.name }}
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -19,14 +22,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function selectRideModelForSubmission(id, name, submissionId) {
|
document.addEventListener('alpine:init', () => {
|
||||||
// Debug logging
|
Alpine.data('rideModelSearchResults', (submissionId) => ({
|
||||||
console.log('Selecting ride model:', {id, name, submissionId});
|
submissionId: submissionId,
|
||||||
|
|
||||||
// Find elements
|
selectRideModel(id, name) {
|
||||||
const modelInput = document.querySelector(`#ride-model-input-${submissionId}`);
|
// Debug logging
|
||||||
const searchInput = document.querySelector(`#ride-model-search-${submissionId}`);
|
console.log('Selecting ride model:', {id, name, submissionId: this.submissionId});
|
||||||
const resultsDiv = document.querySelector(`#ride-model-search-results-${submissionId}`);
|
|
||||||
|
// Find elements using AlpineJS approach
|
||||||
|
const modelInput = document.querySelector(`#ride-model-input-${this.submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#ride-model-search-${this.submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#ride-model-search-results-${this.submissionId}`);
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
console.log('Found elements:', {
|
console.log('Found elements:', {
|
||||||
@@ -48,20 +55,16 @@ function selectRideModelForSubmission(id, name, submissionId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear results
|
// Clear results
|
||||||
|
this.clearResults();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearResults() {
|
||||||
|
const resultsDiv = document.querySelector(`#ride-model-search-results-${this.submissionId}`);
|
||||||
if (resultsDiv) {
|
if (resultsDiv) {
|
||||||
resultsDiv.innerHTML = '';
|
resultsDiv.innerHTML = '';
|
||||||
console.log('Cleared results div');
|
console.log('Cleared results div');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Close search results when clicking outside
|
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
const searchResults = document.querySelectorAll('[id^="ride-model-search-results-"]');
|
|
||||||
searchResults.forEach(function(resultsDiv) {
|
|
||||||
const searchInput = document.querySelector(`#ride-model-search-${resultsDiv.id.split('-').pop()}`);
|
|
||||||
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
|
||||||
resultsDiv.innerHTML = '';
|
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,15 +1,63 @@
|
|||||||
<!-- Add Ride Modal -->
|
<script>
|
||||||
<div id="add-ride-modal" class="fixed inset-0 z-50 hidden overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('addRideModal', () => ({
|
||||||
|
isOpen: false,
|
||||||
|
|
||||||
|
openModal() {
|
||||||
|
this.isOpen = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
this.isOpen = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleBackdropClick(event) {
|
||||||
|
if (event.target === event.currentTarget) {
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div x-data="addRideModal()">
|
||||||
|
<!-- Add Ride Modal -->
|
||||||
|
<div x-show="isOpen"
|
||||||
|
x-transition:enter="transition ease-out duration-300"
|
||||||
|
x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100"
|
||||||
|
x-transition:leave="transition ease-in duration-200"
|
||||||
|
x-transition:leave-start="opacity-100"
|
||||||
|
x-transition:leave-end="opacity-0"
|
||||||
|
@click="handleBackdropClick($event)"
|
||||||
|
@keydown.escape.window="closeModal()"
|
||||||
|
class="fixed inset-0 z-50 overflow-y-auto"
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
style="display: none;">
|
||||||
<div class="flex items-center justify-center min-h-screen p-4">
|
<div class="flex items-center justify-center min-h-screen p-4">
|
||||||
<!-- Background overlay -->
|
<!-- Background overlay -->
|
||||||
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" aria-hidden="true"></div>
|
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" aria-hidden="true"></div>
|
||||||
|
|
||||||
<!-- Modal panel -->
|
<!-- Modal panel -->
|
||||||
<div class="relative w-full max-w-3xl p-6 mx-auto bg-white rounded-lg shadow-xl dark:bg-gray-800">
|
<div class="relative w-full max-w-3xl p-6 mx-auto bg-white rounded-lg shadow-xl dark:bg-gray-800"
|
||||||
|
x-transition:enter="transition ease-out duration-300 transform"
|
||||||
|
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-200 transform"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
Add Ride at {{ park.name }}
|
Add Ride at {{ park.name }}
|
||||||
</h2>
|
</h2>
|
||||||
|
<button @click="closeModal()"
|
||||||
|
class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="modal-content">
|
<div id="modal-content">
|
||||||
@@ -17,31 +65,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal Toggle Button -->
|
<!-- Modal Toggle Button -->
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onclick="openModal('add-ride-modal')"
|
@click="openModal()"
|
||||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600">
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Add Ride
|
Add Ride
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<script>
|
|
||||||
function openModal(modalId) {
|
|
||||||
document.getElementById(modalId).classList.remove('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
document.getElementById('add-ride-modal').classList.add('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close modal when clicking outside
|
|
||||||
document.getElementById('add-ride-modal').addEventListener('click', function(event) {
|
|
||||||
if (event.target === this) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,9 +1,32 @@
|
|||||||
<div class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
<script>
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('designerSearchResults', () => ({
|
||||||
|
selectDesigner(id, name) {
|
||||||
|
// Update designer fields using AlpineJS reactive approach
|
||||||
|
const designerInput = this.$el.closest('form').querySelector('#id_designer');
|
||||||
|
const searchInput = this.$el.closest('form').querySelector('#id_designer_search');
|
||||||
|
const resultsDiv = this.$el.closest('form').querySelector('#designer-search-results');
|
||||||
|
|
||||||
|
if (designerInput) designerInput.value = id;
|
||||||
|
if (searchInput) searchInput.value = name;
|
||||||
|
if (resultsDiv) resultsDiv.innerHTML = '';
|
||||||
|
|
||||||
|
// Dispatch custom event for parent component
|
||||||
|
this.$dispatch('designer-selected', { id, name });
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div x-data="designerSearchResults()"
|
||||||
|
@click.outside="$el.innerHTML = ''"
|
||||||
|
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
style="max-height: 240px; overflow-y: auto;">
|
||||||
{% if designers %}
|
{% if designers %}
|
||||||
{% for designer in designers %}
|
{% for designer in designers %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
onclick="selectDesigner('{{ designer.id }}', '{{ designer.name|escapejs }}')">
|
@click="selectDesigner('{{ designer.id }}', '{{ designer.name|escapejs }}')">
|
||||||
{{ designer.name }}
|
{{ designer.name }}
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -17,11 +40,3 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function selectDesigner(id, name) {
|
|
||||||
document.getElementById('id_designer').value = id;
|
|
||||||
document.getElementById('id_designer_search').value = name;
|
|
||||||
document.getElementById('designer-search-results').innerHTML = '';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,9 +1,38 @@
|
|||||||
<div class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
<script>
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('manufacturerSearchResults', () => ({
|
||||||
|
selectManufacturer(id, name) {
|
||||||
|
// Update manufacturer fields using AlpineJS reactive approach
|
||||||
|
const manufacturerInput = this.$el.closest('form').querySelector('#id_manufacturer');
|
||||||
|
const searchInput = this.$el.closest('form').querySelector('#id_manufacturer_search');
|
||||||
|
const resultsDiv = this.$el.closest('form').querySelector('#manufacturer-search-results');
|
||||||
|
|
||||||
|
if (manufacturerInput) manufacturerInput.value = id;
|
||||||
|
if (searchInput) searchInput.value = name;
|
||||||
|
if (resultsDiv) resultsDiv.innerHTML = '';
|
||||||
|
|
||||||
|
// Update ride model search to include manufacturer using HTMX
|
||||||
|
const rideModelSearch = this.$el.closest('form').querySelector('#id_ride_model_search');
|
||||||
|
if (rideModelSearch) {
|
||||||
|
rideModelSearch.setAttribute('hx-include', '[name="manufacturer"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch custom event for parent component
|
||||||
|
this.$dispatch('manufacturer-selected', { id, name });
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div x-data="manufacturerSearchResults()"
|
||||||
|
@click.outside="$el.innerHTML = ''"
|
||||||
|
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
style="max-height: 240px; overflow-y: auto;">
|
||||||
{% if manufacturers %}
|
{% if manufacturers %}
|
||||||
{% for manufacturer in manufacturers %}
|
{% for manufacturer in manufacturers %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
onclick="selectManufacturer('{{ manufacturer.id }}', '{{ manufacturer.name|escapejs }}')">
|
@click="selectManufacturer('{{ manufacturer.id }}', '{{ manufacturer.name|escapejs }}')">
|
||||||
{{ manufacturer.name }}
|
{{ manufacturer.name }}
|
||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -17,17 +46,3 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function selectManufacturer(id, name) {
|
|
||||||
document.getElementById('id_manufacturer').value = id;
|
|
||||||
document.getElementById('id_manufacturer_search').value = name;
|
|
||||||
document.getElementById('manufacturer-search-results').innerHTML = '';
|
|
||||||
|
|
||||||
// Update ride model search to include manufacturer
|
|
||||||
const rideModelSearch = document.getElementById('id_ride_model_search');
|
|
||||||
if (rideModelSearch) {
|
|
||||||
rideModelSearch.setAttribute('hx-include', '[name="manufacturer"]');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,59 +1,85 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function selectManufacturer(id, name) {
|
document.addEventListener('alpine:init', () => {
|
||||||
document.getElementById('id_manufacturer').value = id;
|
Alpine.data('rideForm', () => ({
|
||||||
document.getElementById('id_manufacturer_search').value = name;
|
init() {
|
||||||
document.getElementById('manufacturer-search-results').innerHTML = '';
|
// Handle form submission cleanup
|
||||||
|
this.$el.addEventListener('submit', () => {
|
||||||
|
this.clearAllSearchResults();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
selectManufacturer(id, name) {
|
||||||
|
const manufacturerInput = document.getElementById('id_manufacturer');
|
||||||
|
const manufacturerSearch = document.getElementById('id_manufacturer_search');
|
||||||
|
const manufacturerResults = document.getElementById('manufacturer-search-results');
|
||||||
|
|
||||||
|
if (manufacturerInput) manufacturerInput.value = id;
|
||||||
|
if (manufacturerSearch) manufacturerSearch.value = name;
|
||||||
|
if (manufacturerResults) manufacturerResults.innerHTML = '';
|
||||||
|
|
||||||
// Update ride model search to include manufacturer
|
// Update ride model search to include manufacturer
|
||||||
const rideModelSearch = document.getElementById('id_ride_model_search');
|
const rideModelSearch = document.getElementById('id_ride_model_search');
|
||||||
if (rideModelSearch) {
|
if (rideModelSearch) {
|
||||||
rideModelSearch.setAttribute('hx-include', '[name="manufacturer"]');
|
rideModelSearch.setAttribute('hx-include', '[name="manufacturer"]');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
function selectDesigner(id, name) {
|
selectDesigner(id, name) {
|
||||||
document.getElementById('id_designer').value = id;
|
const designerInput = document.getElementById('id_designer');
|
||||||
document.getElementById('id_designer_search').value = name;
|
const designerSearch = document.getElementById('id_designer_search');
|
||||||
document.getElementById('designer-search-results').innerHTML = '';
|
const designerResults = document.getElementById('designer-search-results');
|
||||||
}
|
|
||||||
|
|
||||||
function selectRideModel(id, name) {
|
if (designerInput) designerInput.value = id;
|
||||||
document.getElementById('id_ride_model').value = id;
|
if (designerSearch) designerSearch.value = name;
|
||||||
document.getElementById('id_ride_model_search').value = name;
|
if (designerResults) designerResults.innerHTML = '';
|
||||||
document.getElementById('ride-model-search-results').innerHTML = '';
|
},
|
||||||
}
|
|
||||||
|
|
||||||
// Handle form submission
|
selectRideModel(id, name) {
|
||||||
document.addEventListener('submit', function(e) {
|
const rideModelInput = document.getElementById('id_ride_model');
|
||||||
if (e.target.id === 'ride-form') {
|
const rideModelSearch = document.getElementById('id_ride_model_search');
|
||||||
// Clear search results
|
const rideModelResults = document.getElementById('ride-model-search-results');
|
||||||
document.getElementById('manufacturer-search-results').innerHTML = '';
|
|
||||||
document.getElementById('designer-search-results').innerHTML = '';
|
|
||||||
document.getElementById('ride-model-search-results').innerHTML = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle clicks outside search results
|
if (rideModelInput) rideModelInput.value = id;
|
||||||
document.addEventListener('click', function(e) {
|
if (rideModelSearch) rideModelSearch.value = name;
|
||||||
|
if (rideModelResults) rideModelResults.innerHTML = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
clearAllSearchResults() {
|
||||||
const manufacturerResults = document.getElementById('manufacturer-search-results');
|
const manufacturerResults = document.getElementById('manufacturer-search-results');
|
||||||
const designerResults = document.getElementById('designer-search-results');
|
const designerResults = document.getElementById('designer-search-results');
|
||||||
const rideModelResults = document.getElementById('ride-model-search-results');
|
const rideModelResults = document.getElementById('ride-model-search-results');
|
||||||
|
|
||||||
if (!e.target.closest('#manufacturer-search-container')) {
|
if (manufacturerResults) manufacturerResults.innerHTML = '';
|
||||||
manufacturerResults.innerHTML = '';
|
if (designerResults) designerResults.innerHTML = '';
|
||||||
}
|
if (rideModelResults) rideModelResults.innerHTML = '';
|
||||||
if (!e.target.closest('#designer-search-container')) {
|
},
|
||||||
designerResults.innerHTML = '';
|
|
||||||
}
|
clearManufacturerResults() {
|
||||||
if (!e.target.closest('#ride-model-search-container')) {
|
const manufacturerResults = document.getElementById('manufacturer-search-results');
|
||||||
rideModelResults.innerHTML = '';
|
if (manufacturerResults) manufacturerResults.innerHTML = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
clearDesignerResults() {
|
||||||
|
const designerResults = document.getElementById('designer-search-results');
|
||||||
|
if (designerResults) designerResults.innerHTML = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
clearRideModelResults() {
|
||||||
|
const rideModelResults = document.getElementById('ride-model-search-results');
|
||||||
|
if (rideModelResults) rideModelResults.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form method="post" id="ride-form" class="space-y-6" enctype="multipart/form-data">
|
<form method="post"
|
||||||
|
id="ride-form"
|
||||||
|
class="space-y-6"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
x-data="rideForm"
|
||||||
|
x-init="init()">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Park Area -->
|
<!-- Park Area -->
|
||||||
@@ -86,7 +112,9 @@ document.addEventListener('click', function(e) {
|
|||||||
|
|
||||||
<!-- Manufacturer -->
|
<!-- Manufacturer -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div id="manufacturer-search-container" class="relative">
|
<div id="manufacturer-search-container"
|
||||||
|
class="relative"
|
||||||
|
@click.outside="clearManufacturerResults()">
|
||||||
<label for="{{ form.manufacturer_search.id_for_label }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label for="{{ form.manufacturer_search.id_for_label }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Manufacturer
|
Manufacturer
|
||||||
</label>
|
</label>
|
||||||
@@ -103,7 +131,9 @@ document.addEventListener('click', function(e) {
|
|||||||
|
|
||||||
<!-- Designer -->
|
<!-- Designer -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div id="designer-search-container" class="relative">
|
<div id="designer-search-container"
|
||||||
|
class="relative"
|
||||||
|
@click.outside="clearDesignerResults()">
|
||||||
<label for="{{ form.designer_search.id_for_label }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label for="{{ form.designer_search.id_for_label }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Designer
|
Designer
|
||||||
</label>
|
</label>
|
||||||
@@ -120,7 +150,9 @@ document.addEventListener('click', function(e) {
|
|||||||
|
|
||||||
<!-- Ride Model -->
|
<!-- Ride Model -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div id="ride-model-search-container" class="relative">
|
<div id="ride-model-search-container"
|
||||||
|
class="relative"
|
||||||
|
@click.outside="clearRideModelResults()">
|
||||||
<label for="{{ form.ride_model_search.id_for_label }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
<label for="{{ form.ride_model_search.id_for_label }}" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
Ride Model
|
Ride Model
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -1,4 +1,27 @@
|
|||||||
<div class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
<script>
|
||||||
|
document.addEventListener('alpine:init', () => {
|
||||||
|
Alpine.data('rideModelSearchResults', () => ({
|
||||||
|
selectRideModel(id, name) {
|
||||||
|
// Update ride model fields using AlpineJS reactive approach
|
||||||
|
const rideModelInput = this.$el.closest('form').querySelector('#id_ride_model');
|
||||||
|
const searchInput = this.$el.closest('form').querySelector('#id_ride_model_search');
|
||||||
|
const resultsDiv = this.$el.closest('form').querySelector('#ride-model-search-results');
|
||||||
|
|
||||||
|
if (rideModelInput) rideModelInput.value = id;
|
||||||
|
if (searchInput) searchInput.value = name;
|
||||||
|
if (resultsDiv) resultsDiv.innerHTML = '';
|
||||||
|
|
||||||
|
// Dispatch custom event for parent component
|
||||||
|
this.$dispatch('ride-model-selected', { id, name });
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div x-data="rideModelSearchResults()"
|
||||||
|
@click.outside="$el.innerHTML = ''"
|
||||||
|
class="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
style="max-height: 240px; overflow-y: auto;">
|
||||||
{% if not manufacturer_id %}
|
{% if not manufacturer_id %}
|
||||||
<div class="px-4 py-2 text-gray-700 dark:text-gray-300">
|
<div class="px-4 py-2 text-gray-700 dark:text-gray-300">
|
||||||
Please select a manufacturer first
|
Please select a manufacturer first
|
||||||
@@ -8,7 +31,7 @@
|
|||||||
{% for ride_model in ride_models %}
|
{% for ride_model in ride_models %}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
onclick="selectRideModel('{{ ride_model.id }}', '{{ ride_model.name|escapejs }}')">
|
@click="selectRideModel('{{ ride_model.id }}', '{{ ride_model.name|escapejs }}')">
|
||||||
{{ ride_model.name }}
|
{{ ride_model.name }}
|
||||||
{% if ride_model.manufacturer %}
|
{% if ride_model.manufacturer %}
|
||||||
<div class="text-sm text-gray-700 dark:text-gray-300">
|
<div class="text-sm text-gray-700 dark:text-gray-300">
|
||||||
@@ -28,11 +51,3 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function selectRideModel(id, name) {
|
|
||||||
document.getElementById('id_ride_model').value = id;
|
|
||||||
document.getElementById('id_ride_model_search').value = name;
|
|
||||||
document.getElementById('ride-model-search-results').innerHTML = '';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user