mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 18:31:09 -05:00
Enhance moderation dashboard UI and UX:
- Add HTMX-powered filtering with instant updates - Add smooth transitions and loading states - Improve visual hierarchy and styling - Add review notes functionality - Add confirmation dialogs for actions - Make navigation sticky - Add hover effects and visual feedback - Improve dark mode support
This commit is contained in:
335
rides/forms.py
335
rides/forms.py
@@ -1,74 +1,283 @@
|
||||
from django import forms
|
||||
from .models import Ride
|
||||
from django.forms import ModelChoiceField
|
||||
from django.urls import reverse_lazy
|
||||
from .models import Ride, RideModel
|
||||
from parks.models import Park, ParkArea
|
||||
from companies.models import Manufacturer, Designer
|
||||
|
||||
|
||||
class RideForm(forms.ModelForm):
|
||||
park_search = forms.CharField(
|
||||
label="Park *",
|
||||
required=True,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Search for a park...",
|
||||
"hx-get": "/parks/search/",
|
||||
"hx-trigger": "click, input delay:200ms",
|
||||
"hx-target": "#park-search-results",
|
||||
"name": "q",
|
||||
"autocomplete": "off",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
manufacturer_search = forms.CharField(
|
||||
label="Manufacturer",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Search for a manufacturer...",
|
||||
"hx-get": reverse_lazy("rides:search_manufacturers"),
|
||||
"hx-trigger": "click, input delay:200ms",
|
||||
"hx-target": "#manufacturer-search-results",
|
||||
"name": "q",
|
||||
"autocomplete": "off",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
designer_search = forms.CharField(
|
||||
label="Designer",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Search for a designer...",
|
||||
"hx-get": reverse_lazy("rides:search_designers"),
|
||||
"hx-trigger": "click, input delay:200ms",
|
||||
"hx-target": "#designer-search-results",
|
||||
"name": "q",
|
||||
"autocomplete": "off",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
ride_model_search = forms.CharField(
|
||||
label="Ride Model",
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Search for a ride model...",
|
||||
"hx-get": reverse_lazy("rides:search_ride_models"),
|
||||
"hx-trigger": "click, input delay:200ms",
|
||||
"hx-target": "#ride-model-search-results",
|
||||
"hx-include": "[name='manufacturer']",
|
||||
"name": "q",
|
||||
"autocomplete": "off",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
park = forms.ModelChoiceField(
|
||||
queryset=Park.objects.all(),
|
||||
required=True,
|
||||
label="",
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
|
||||
manufacturer = forms.ModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False,
|
||||
label="",
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
|
||||
designer = forms.ModelChoiceField(
|
||||
queryset=Designer.objects.all(),
|
||||
required=False,
|
||||
label="",
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
|
||||
ride_model = forms.ModelChoiceField(
|
||||
queryset=RideModel.objects.all(),
|
||||
required=False,
|
||||
label="",
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
|
||||
park_area = ModelChoiceField(
|
||||
queryset=ParkArea.objects.none(),
|
||||
required=False,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Select an area within the park..."
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Ride
|
||||
fields = ['name', 'park_area', 'category', 'manufacturer', 'designer', 'model_name', 'status',
|
||||
'opening_date', 'closing_date', 'status_since', 'min_height_in', 'max_height_in',
|
||||
'accessibility_options', 'capacity_per_hour', 'ride_duration_seconds', 'description']
|
||||
fields = [
|
||||
"name",
|
||||
"category",
|
||||
"manufacturer",
|
||||
"designer",
|
||||
"ride_model",
|
||||
"status",
|
||||
"post_closing_status",
|
||||
"opening_date",
|
||||
"closing_date",
|
||||
"status_since",
|
||||
"min_height_in",
|
||||
"max_height_in",
|
||||
"capacity_per_hour",
|
||||
"ride_duration_seconds",
|
||||
"description",
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'park_area': forms.Select(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'category': forms.Select(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'manufacturer': forms.Select(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'designer': forms.Select(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'model_name': forms.TextInput(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'status': forms.Select(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'opening_date': forms.DateInput(attrs={
|
||||
'type': 'date',
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'closing_date': forms.DateInput(attrs={
|
||||
'type': 'date',
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'status_since': forms.DateInput(attrs={
|
||||
'type': 'date',
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'min_height_in': forms.NumberInput(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
||||
'min': '0'
|
||||
}),
|
||||
'max_height_in': forms.NumberInput(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
||||
'min': '0'
|
||||
}),
|
||||
'accessibility_options': forms.TextInput(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
'capacity_per_hour': forms.NumberInput(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
||||
'min': '0'
|
||||
}),
|
||||
'ride_duration_seconds': forms.NumberInput(attrs={
|
||||
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white',
|
||||
'min': '0'
|
||||
}),
|
||||
'description': forms.Textarea(attrs={
|
||||
'rows': 4,
|
||||
'class': 'w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||
}),
|
||||
"name": forms.TextInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Official name of the ride"
|
||||
}
|
||||
),
|
||||
"category": forms.Select(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"hx-get": reverse_lazy("rides:coaster_fields"),
|
||||
"hx-target": "#coaster-fields",
|
||||
"hx-trigger": "change",
|
||||
"hx-include": "this",
|
||||
"hx-swap": "innerHTML"
|
||||
}
|
||||
),
|
||||
"status": forms.Select(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Current operational status",
|
||||
"x-model": "status",
|
||||
"@change": "handleStatusChange"
|
||||
}
|
||||
),
|
||||
"post_closing_status": forms.Select(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Status after closing",
|
||||
"x-show": "status === 'CLOSING'"
|
||||
}
|
||||
),
|
||||
"opening_date": forms.DateInput(
|
||||
attrs={
|
||||
"type": "date",
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Date when ride first opened"
|
||||
}
|
||||
),
|
||||
"closing_date": forms.DateInput(
|
||||
attrs={
|
||||
"type": "date",
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Date when ride will close",
|
||||
"x-show": "['CLOSING', 'SBNO', 'CLOSED_PERM', 'DEMOLISHED', 'RELOCATED'].includes(status)",
|
||||
":required": "status === 'CLOSING'"
|
||||
}
|
||||
),
|
||||
"status_since": forms.DateInput(
|
||||
attrs={
|
||||
"type": "date",
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Date when current status took effect"
|
||||
}
|
||||
),
|
||||
"min_height_in": forms.NumberInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"min": "0",
|
||||
"placeholder": "Minimum height requirement in inches"
|
||||
}
|
||||
),
|
||||
"max_height_in": forms.NumberInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"min": "0",
|
||||
"placeholder": "Maximum height limit in inches (if applicable)"
|
||||
}
|
||||
),
|
||||
"capacity_per_hour": forms.NumberInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"min": "0",
|
||||
"placeholder": "Theoretical hourly ride capacity"
|
||||
}
|
||||
),
|
||||
"ride_duration_seconds": forms.NumberInput(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"min": "0",
|
||||
"placeholder": "Total duration of one ride cycle in seconds"
|
||||
}
|
||||
),
|
||||
"description": forms.Textarea(
|
||||
attrs={
|
||||
"rows": 4,
|
||||
"class": "w-full border-gray-300 rounded-lg form-textarea dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "General description and notable features of the ride"
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
park = kwargs.pop('park', None)
|
||||
park = kwargs.pop("park", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Make category required
|
||||
self.fields['category'].required = True
|
||||
|
||||
# Clear any default values for date fields
|
||||
self.fields["opening_date"].initial = None
|
||||
self.fields["closing_date"].initial = None
|
||||
self.fields["status_since"].initial = None
|
||||
|
||||
# Move fields to the beginning in desired order
|
||||
field_order = [
|
||||
"park_search", "park", "park_area",
|
||||
"name", "manufacturer_search", "manufacturer",
|
||||
"designer_search", "designer", "ride_model_search",
|
||||
"ride_model", "category", "status",
|
||||
"post_closing_status", "opening_date", "closing_date", "status_since",
|
||||
"min_height_in", "max_height_in", "capacity_per_hour",
|
||||
"ride_duration_seconds", "description"
|
||||
]
|
||||
self.order_fields(field_order)
|
||||
|
||||
if park:
|
||||
# Filter park_area choices to only show areas from the current park
|
||||
self.fields['park_area'].queryset = park.areas.all()
|
||||
# If park is provided, set it as the initial value
|
||||
self.fields["park"].initial = park
|
||||
# Hide the park search field since we know the park
|
||||
del self.fields["park_search"]
|
||||
# Create new park_area field with park's areas
|
||||
self.fields["park_area"] = forms.ModelChoiceField(
|
||||
queryset=park.areas.all(),
|
||||
required=False,
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
"class": "w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
"placeholder": "Select an area within the park..."
|
||||
}
|
||||
),
|
||||
)
|
||||
else:
|
||||
# If no park provided, show park search and disable park_area until park is selected
|
||||
self.fields["park_area"].widget.attrs["disabled"] = True
|
||||
# Initialize park search with current park name if editing
|
||||
if self.instance and self.instance.pk and self.instance.park:
|
||||
self.fields["park_search"].initial = self.instance.park.name
|
||||
self.fields["park"].initial = self.instance.park
|
||||
|
||||
# Initialize manufacturer, designer, and ride model search fields if editing
|
||||
if self.instance and self.instance.pk:
|
||||
if self.instance.manufacturer:
|
||||
self.fields["manufacturer_search"].initial = self.instance.manufacturer.name
|
||||
self.fields["manufacturer"].initial = self.instance.manufacturer
|
||||
if self.instance.designer:
|
||||
self.fields["designer_search"].initial = self.instance.designer.name
|
||||
self.fields["designer"].initial = self.instance.designer
|
||||
if self.instance.ride_model:
|
||||
self.fields["ride_model_search"].initial = self.instance.ride_model.name
|
||||
self.fields["ride_model"].initial = self.instance.ride_model
|
||||
|
||||
Reference in New Issue
Block a user