mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 11:11:10 -05:00
add category views for each type of ride, add ride designers
This commit is contained in:
Binary file not shown.
@@ -5,6 +5,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.db.models import Count, Sum
|
||||||
from .models import Company, Manufacturer
|
from .models import Company, Manufacturer
|
||||||
from .forms import CompanyForm, ManufacturerForm
|
from .forms import CompanyForm, ManufacturerForm
|
||||||
from rides.models import Ride
|
from rides.models import Ride
|
||||||
@@ -173,9 +174,13 @@ class CompanyDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmissionM
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['parks'] = Park.objects.filter(
|
parks = Park.objects.filter(
|
||||||
owner=self.object
|
owner=self.object
|
||||||
).select_related('owner')
|
).select_related('owner')
|
||||||
|
|
||||||
|
context['parks'] = parks
|
||||||
|
context['total_rides'] = Ride.objects.filter(park__in=parks).count()
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_redirect_url_pattern(self):
|
def get_redirect_url_pattern(self):
|
||||||
@@ -195,9 +200,14 @@ class ManufacturerDetailView(SlugRedirectMixin, EditSubmissionMixin, PhotoSubmis
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['rides'] = Ride.objects.filter(
|
rides = Ride.objects.filter(
|
||||||
manufacturer=self.object
|
manufacturer=self.object
|
||||||
).select_related('park', 'coaster_stats')
|
).select_related('park', 'coaster_stats')
|
||||||
|
|
||||||
|
context['rides'] = rides
|
||||||
|
context['coaster_count'] = rides.filter(category='ROLLER_COASTER').count()
|
||||||
|
context['parks_count'] = rides.values('park').distinct().count()
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_redirect_url_pattern(self):
|
def get_redirect_url_pattern(self):
|
||||||
|
|||||||
0
designers/__init__.py
Normal file
0
designers/__init__.py
Normal file
10
designers/admin.py
Normal file
10
designers/admin.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from simple_history.admin import SimpleHistoryAdmin
|
||||||
|
from .models import Designer
|
||||||
|
|
||||||
|
@admin.register(Designer)
|
||||||
|
class DesignerAdmin(SimpleHistoryAdmin):
|
||||||
|
list_display = ('name', 'headquarters', 'founded_date', 'website')
|
||||||
|
search_fields = ('name', 'headquarters')
|
||||||
|
list_filter = ('founded_date',)
|
||||||
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
6
designers/apps.py
Normal file
6
designers/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DesignersConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "designers"
|
||||||
88
designers/migrations/0001_initial.py
Normal file
88
designers/migrations/0001_initial.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-04 00:28
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import simple_history.models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Designer",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255)),
|
||||||
|
("slug", models.SlugField(max_length=255, unique=True)),
|
||||||
|
("description", models.TextField(blank=True)),
|
||||||
|
("website", models.URLField(blank=True)),
|
||||||
|
("founded_date", models.DateField(blank=True, null=True)),
|
||||||
|
("headquarters", models.CharField(blank=True, max_length=255)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"ordering": ["name"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="HistoricalDesigner",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigIntegerField(
|
||||||
|
auto_created=True, blank=True, db_index=True, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255)),
|
||||||
|
("slug", models.SlugField(max_length=255)),
|
||||||
|
("description", models.TextField(blank=True)),
|
||||||
|
("website", models.URLField(blank=True)),
|
||||||
|
("founded_date", models.DateField(blank=True, null=True)),
|
||||||
|
("headquarters", models.CharField(blank=True, max_length=255)),
|
||||||
|
("created_at", models.DateTimeField(blank=True, editable=False)),
|
||||||
|
("updated_at", models.DateTimeField(blank=True, editable=False)),
|
||||||
|
("history_id", models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
("history_date", models.DateTimeField(db_index=True)),
|
||||||
|
("history_change_reason", models.CharField(max_length=100, null=True)),
|
||||||
|
(
|
||||||
|
"history_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
|
||||||
|
max_length=1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"history_user",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "historical designer",
|
||||||
|
"verbose_name_plural": "historical designers",
|
||||||
|
"ordering": ("-history_date", "-history_id"),
|
||||||
|
"get_latest_by": ("history_date", "history_id"),
|
||||||
|
},
|
||||||
|
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
designers/migrations/__init__.py
Normal file
0
designers/migrations/__init__.py
Normal file
37
designers/models.py
Normal file
37
designers/models.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.text import slugify
|
||||||
|
from simple_history.models import HistoricalRecords
|
||||||
|
|
||||||
|
class Designer(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
slug = models.SlugField(max_length=255, unique=True)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
website = models.URLField(blank=True)
|
||||||
|
founded_date = models.DateField(null=True, blank=True)
|
||||||
|
headquarters = models.CharField(max_length=255, blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
history = HistoricalRecords()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.slug:
|
||||||
|
self.slug = slugify(self.name)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_slug(cls, slug):
|
||||||
|
"""Get designer by current or historical slug"""
|
||||||
|
try:
|
||||||
|
return cls.objects.get(slug=slug), False
|
||||||
|
except cls.DoesNotExist:
|
||||||
|
# Check historical slugs
|
||||||
|
history = cls.history.filter(slug=slug).order_by('-history_date').first()
|
||||||
|
if history:
|
||||||
|
return cls.objects.get(id=history.id), True
|
||||||
|
raise cls.DoesNotExist("No designer found with this slug")
|
||||||
3
designers/tests.py
Normal file
3
designers/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
8
designers/urls.py
Normal file
8
designers/urls.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'designers'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('<slug:slug>/', views.DesignerDetailView.as_view(), name='designer_detail'),
|
||||||
|
]
|
||||||
29
designers/views.py
Normal file
29
designers/views.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from django.views.generic import DetailView
|
||||||
|
from .models import Designer
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
class DesignerDetailView(DetailView):
|
||||||
|
model = Designer
|
||||||
|
template_name = 'designers/designer_detail.html'
|
||||||
|
context_object_name = 'designer'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
# Get all rides by this designer
|
||||||
|
context['rides'] = self.object.rides.select_related(
|
||||||
|
'park',
|
||||||
|
'manufacturer',
|
||||||
|
'coaster_stats'
|
||||||
|
).order_by('-opening_date')
|
||||||
|
|
||||||
|
# Get stats
|
||||||
|
context['stats'] = {
|
||||||
|
'total_rides': self.object.rides.count(),
|
||||||
|
'total_parks': self.object.rides.values('park').distinct().count(),
|
||||||
|
'total_coasters': self.object.rides.filter(category='RC').count(),
|
||||||
|
'total_countries': self.object.rides.values(
|
||||||
|
'park__location__country'
|
||||||
|
).distinct().count(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-04 00:17
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("history_tracking", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="historicalpark",
|
||||||
|
name="history_user",
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="Park",
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="HistoricalPark",
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@@ -1,5 +1,6 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from . import views
|
from . import views
|
||||||
|
from rides.views import ParkSingleCategoryListView
|
||||||
|
|
||||||
app_name = "parks"
|
app_name = "parks"
|
||||||
|
|
||||||
@@ -17,6 +18,14 @@ urlpatterns = [
|
|||||||
# Area views
|
# Area views
|
||||||
path("<slug:park_slug>/areas/<slug:area_slug>/", views.ParkAreaDetailView.as_view(), name="area_detail"),
|
path("<slug:park_slug>/areas/<slug:area_slug>/", views.ParkAreaDetailView.as_view(), name="area_detail"),
|
||||||
|
|
||||||
|
# Park-specific category URLs
|
||||||
|
path("<slug:park_slug>/roller_coasters/", ParkSingleCategoryListView.as_view(), {'category': 'RC'}, name="park_roller_coasters"),
|
||||||
|
path("<slug:park_slug>/dark_rides/", ParkSingleCategoryListView.as_view(), {'category': 'DR'}, name="park_dark_rides"),
|
||||||
|
path("<slug:park_slug>/flat_rides/", ParkSingleCategoryListView.as_view(), {'category': 'FR'}, name="park_flat_rides"),
|
||||||
|
path("<slug:park_slug>/water_rides/", ParkSingleCategoryListView.as_view(), {'category': 'WR'}, name="park_water_rides"),
|
||||||
|
path("<slug:park_slug>/transports/", ParkSingleCategoryListView.as_view(), {'category': 'TR'}, name="park_transports"),
|
||||||
|
path("<slug:park_slug>/others/", ParkSingleCategoryListView.as_view(), {'category': 'OT'}, name="park_others"),
|
||||||
|
|
||||||
# Include rides URLs
|
# Include rides URLs
|
||||||
path("<slug:park_slug>/rides/", include("rides.urls", namespace="rides")),
|
path("<slug:park_slug>/rides/", include("rides.urls", namespace="rides")),
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@ from .models import Ride
|
|||||||
class RideForm(forms.ModelForm):
|
class RideForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ride
|
model = Ride
|
||||||
fields = ['name', 'park_area', 'category', 'manufacturer', 'model_name', 'status',
|
fields = ['name', 'park_area', 'category', 'manufacturer', 'designer', 'model_name', 'status',
|
||||||
'opening_date', 'closing_date', 'status_since', 'min_height_in', 'max_height_in',
|
'opening_date', 'closing_date', 'status_since', 'min_height_in', 'max_height_in',
|
||||||
'accessibility_options', 'capacity_per_hour', 'ride_duration_seconds', 'description']
|
'accessibility_options', 'capacity_per_hour', 'ride_duration_seconds', 'description']
|
||||||
widgets = {
|
widgets = {
|
||||||
@@ -20,6 +20,9 @@ class RideForm(forms.ModelForm):
|
|||||||
'manufacturer': forms.Select(attrs={
|
'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'
|
'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={
|
'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'
|
'class': 'w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white'
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-04 00:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("rides", "0002_alter_ride_manufacturer"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalrollercoasterstats",
|
||||||
|
name="max_drop_height_ft",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
help_text="Maximum vertical drop height in feet",
|
||||||
|
max_digits=6,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalrollercoasterstats",
|
||||||
|
name="track_material",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("STEEL", "Steel"),
|
||||||
|
("WOOD", "Wood"),
|
||||||
|
("HYBRID", "Hybrid"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
],
|
||||||
|
default="STEEL",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="rollercoasterstats",
|
||||||
|
name="max_drop_height_ft",
|
||||||
|
field=models.DecimalField(
|
||||||
|
blank=True,
|
||||||
|
decimal_places=2,
|
||||||
|
help_text="Maximum vertical drop height in feet",
|
||||||
|
max_digits=6,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="rollercoasterstats",
|
||||||
|
name="track_material",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("STEEL", "Steel"),
|
||||||
|
("WOOD", "Wood"),
|
||||||
|
("HYBRID", "Hybrid"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
],
|
||||||
|
default="STEEL",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-04 00:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("rides", "0003_historicalrollercoasterstats_max_drop_height_ft_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalrollercoasterstats",
|
||||||
|
name="roller_coaster_type",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("SITDOWN", "Sit-Down"),
|
||||||
|
("INVERTED", "Inverted"),
|
||||||
|
("FLYING", "Flying"),
|
||||||
|
("STANDUP", "Stand-Up"),
|
||||||
|
("WING", "Wing"),
|
||||||
|
("SUSPENDED", "Suspended"),
|
||||||
|
("BOBSLED", "Bobsled"),
|
||||||
|
("PIPELINE", "Pipeline"),
|
||||||
|
("MOTORBIKE", "Motorbike"),
|
||||||
|
("FLOORLESS", "Floorless"),
|
||||||
|
("DIVE", "Dive"),
|
||||||
|
("FAMILY", "Family"),
|
||||||
|
("WILD_MOUSE", "Wild Mouse"),
|
||||||
|
("SPINNING", "Spinning"),
|
||||||
|
("FOURTH_DIMENSION", "4th Dimension"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
],
|
||||||
|
default="SITDOWN",
|
||||||
|
help_text="The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="rollercoasterstats",
|
||||||
|
name="roller_coaster_type",
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
choices=[
|
||||||
|
("SITDOWN", "Sit-Down"),
|
||||||
|
("INVERTED", "Inverted"),
|
||||||
|
("FLYING", "Flying"),
|
||||||
|
("STANDUP", "Stand-Up"),
|
||||||
|
("WING", "Wing"),
|
||||||
|
("SUSPENDED", "Suspended"),
|
||||||
|
("BOBSLED", "Bobsled"),
|
||||||
|
("PIPELINE", "Pipeline"),
|
||||||
|
("MOTORBIKE", "Motorbike"),
|
||||||
|
("FLOORLESS", "Floorless"),
|
||||||
|
("DIVE", "Dive"),
|
||||||
|
("FAMILY", "Family"),
|
||||||
|
("WILD_MOUSE", "Wild Mouse"),
|
||||||
|
("SPINNING", "Spinning"),
|
||||||
|
("FOURTH_DIMENSION", "4th Dimension"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
],
|
||||||
|
default="SITDOWN",
|
||||||
|
help_text="The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2024-11-04 00:28
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("designers", "0001_initial"),
|
||||||
|
("rides", "0004_historicalrollercoasterstats_roller_coaster_type_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="historicalride",
|
||||||
|
name="designer",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
db_constraint=False,
|
||||||
|
help_text="The designer/engineering firm responsible for the ride",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
related_name="+",
|
||||||
|
to="designers.designer",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="ride",
|
||||||
|
name="designer",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="The designer/engineering firm responsible for the ride",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="rides",
|
||||||
|
to="designers.designer",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -43,10 +43,19 @@ class Ride(models.Model):
|
|||||||
default='OT'
|
default='OT'
|
||||||
)
|
)
|
||||||
manufacturer = models.ForeignKey(
|
manufacturer = models.ForeignKey(
|
||||||
'companies.manufacturer', on_delete=models.CASCADE, null=False, blank=False
|
'companies.manufacturer',
|
||||||
)
|
on_delete=models.CASCADE,
|
||||||
# other fields...
|
null=False,
|
||||||
|
blank=False
|
||||||
|
)
|
||||||
|
designer = models.ForeignKey(
|
||||||
|
'designers.Designer',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='rides',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text='The designer/engineering firm responsible for the ride'
|
||||||
|
)
|
||||||
model_name = models.CharField(max_length=255, blank=True)
|
model_name = models.CharField(max_length=255, blank=True)
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
@@ -108,6 +117,32 @@ class RollerCoasterStats(models.Model):
|
|||||||
('OTHER', 'Other'),
|
('OTHER', 'Other'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
TRACK_MATERIAL_CHOICES = [
|
||||||
|
('STEEL', 'Steel'),
|
||||||
|
('WOOD', 'Wood'),
|
||||||
|
('HYBRID', 'Hybrid'),
|
||||||
|
('OTHER', 'Other'),
|
||||||
|
]
|
||||||
|
|
||||||
|
COASTER_TYPE_CHOICES = [
|
||||||
|
('SITDOWN', 'Sit-Down'),
|
||||||
|
('INVERTED', 'Inverted'),
|
||||||
|
('FLYING', 'Flying'),
|
||||||
|
('STANDUP', 'Stand-Up'),
|
||||||
|
('WING', 'Wing'),
|
||||||
|
('SUSPENDED', 'Suspended'),
|
||||||
|
('BOBSLED', 'Bobsled'),
|
||||||
|
('PIPELINE', 'Pipeline'),
|
||||||
|
('MOTORBIKE', 'Motorbike'),
|
||||||
|
('FLOORLESS', 'Floorless'),
|
||||||
|
('DIVE', 'Dive'),
|
||||||
|
('FAMILY', 'Family'),
|
||||||
|
('WILD_MOUSE', 'Wild Mouse'),
|
||||||
|
('SPINNING', 'Spinning'),
|
||||||
|
('FOURTH_DIMENSION', '4th Dimension'),
|
||||||
|
('OTHER', 'Other'),
|
||||||
|
]
|
||||||
|
|
||||||
ride = models.OneToOneField(
|
ride = models.OneToOneField(
|
||||||
Ride,
|
Ride,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@@ -134,6 +169,26 @@ class RollerCoasterStats(models.Model):
|
|||||||
inversions = models.PositiveIntegerField(default=0)
|
inversions = models.PositiveIntegerField(default=0)
|
||||||
ride_time_seconds = models.PositiveIntegerField(null=True, blank=True)
|
ride_time_seconds = models.PositiveIntegerField(null=True, blank=True)
|
||||||
track_type = models.CharField(max_length=255, blank=True)
|
track_type = models.CharField(max_length=255, blank=True)
|
||||||
|
track_material = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=TRACK_MATERIAL_CHOICES,
|
||||||
|
default='STEEL',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
roller_coaster_type = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=COASTER_TYPE_CHOICES,
|
||||||
|
default='SITDOWN',
|
||||||
|
blank=True,
|
||||||
|
help_text='The type/style of roller coaster (e.g. Sit-Down, Inverted, Flying)'
|
||||||
|
)
|
||||||
|
max_drop_height_ft = models.DecimalField(
|
||||||
|
max_digits=6,
|
||||||
|
decimal_places=2,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text='Maximum vertical drop height in feet'
|
||||||
|
)
|
||||||
launch_type = models.CharField(
|
launch_type = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=LAUNCH_CHOICES,
|
choices=LAUNCH_CHOICES,
|
||||||
|
|||||||
@@ -4,8 +4,17 @@ from . import views
|
|||||||
app_name = 'rides' # Add namespace
|
app_name = 'rides' # Add namespace
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('all/', views.RideListView.as_view(), name='all_rides'), # New pattern for all rides
|
# Global category URLs
|
||||||
path('', views.RideListView.as_view(), name='ride_list'),
|
path('', views.RideListView.as_view(), name='ride_list'),
|
||||||
|
path('all/', views.RideListView.as_view(), name='all_rides'),
|
||||||
|
path('roller_coasters/', views.SingleCategoryListView.as_view(), {'category': 'RC'}, name='roller_coasters'),
|
||||||
|
path('dark_rides/', views.SingleCategoryListView.as_view(), {'category': 'DR'}, name='dark_rides'),
|
||||||
|
path('flat_rides/', views.SingleCategoryListView.as_view(), {'category': 'FR'}, name='flat_rides'),
|
||||||
|
path('water_rides/', views.SingleCategoryListView.as_view(), {'category': 'WR'}, name='water_rides'),
|
||||||
|
path('transports/', views.SingleCategoryListView.as_view(), {'category': 'TR'}, name='transports'),
|
||||||
|
path('others/', views.SingleCategoryListView.as_view(), {'category': 'OT'}, name='others'),
|
||||||
|
|
||||||
|
# Basic ride URLs
|
||||||
path('create/', views.RideCreateView.as_view(), name='ride_create'),
|
path('create/', views.RideCreateView.as_view(), name='ride_create'),
|
||||||
path('<slug:ride_slug>/edit/', views.RideUpdateView.as_view(), name='ride_edit'),
|
path('<slug:ride_slug>/edit/', views.RideUpdateView.as_view(), name='ride_edit'),
|
||||||
path('<slug:ride_slug>/', views.RideDetailView.as_view(), name='ride_detail'),
|
path('<slug:ride_slug>/', views.RideDetailView.as_view(), name='ride_detail'),
|
||||||
|
|||||||
@@ -6,15 +6,79 @@ from django.db.models import Q
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import JsonResponse, HttpResponseRedirect
|
from django.http import JsonResponse, HttpResponseRedirect, Http404
|
||||||
|
from django.db.models import Count
|
||||||
from .models import Ride, RollerCoasterStats
|
from .models import Ride, RollerCoasterStats
|
||||||
from .forms import RideForm
|
from .forms import RideForm
|
||||||
from parks.models import Park
|
from parks.models import Park
|
||||||
from core.views import SlugRedirectMixin
|
from core.views import SlugRedirectMixin
|
||||||
from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin
|
from moderation.mixins import EditSubmissionMixin, PhotoSubmissionMixin, HistoryMixin
|
||||||
from moderation.models import EditSubmission
|
|
||||||
from media.models import Photo
|
from media.models import Photo
|
||||||
|
|
||||||
|
class SingleCategoryListView(ListView):
|
||||||
|
model = Ride
|
||||||
|
template_name = 'rides/ride_category_list.html'
|
||||||
|
context_object_name = 'categories'
|
||||||
|
|
||||||
|
def get_category_code(self):
|
||||||
|
category = self.kwargs.get('category')
|
||||||
|
if not category:
|
||||||
|
raise Http404("Category not found")
|
||||||
|
return category
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
category_code = self.get_category_code()
|
||||||
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
||||||
|
|
||||||
|
rides = Ride.objects.filter(category=category_code).select_related(
|
||||||
|
'park', 'manufacturer'
|
||||||
|
).order_by('name')
|
||||||
|
|
||||||
|
return {category_name: rides} if rides.exists() else {}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
category_code = self.get_category_code()
|
||||||
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
||||||
|
context['title'] = f'All {category_name}s'
|
||||||
|
context['category_code'] = category_code
|
||||||
|
return context
|
||||||
|
|
||||||
|
class ParkSingleCategoryListView(ListView):
|
||||||
|
model = Ride
|
||||||
|
template_name = 'rides/ride_category_list.html'
|
||||||
|
context_object_name = 'categories'
|
||||||
|
|
||||||
|
def setup(self, request, *args, **kwargs):
|
||||||
|
super().setup(request, *args, **kwargs)
|
||||||
|
self.park = get_object_or_404(Park, slug=self.kwargs['park_slug'])
|
||||||
|
|
||||||
|
def get_category_code(self):
|
||||||
|
category = self.kwargs.get('category')
|
||||||
|
if not category:
|
||||||
|
raise Http404("Category not found")
|
||||||
|
return category
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
category_code = self.get_category_code()
|
||||||
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
||||||
|
|
||||||
|
rides = Ride.objects.filter(
|
||||||
|
park=self.park,
|
||||||
|
category=category_code
|
||||||
|
).select_related('manufacturer').order_by('name')
|
||||||
|
|
||||||
|
return {category_name: rides} if rides.exists() else {}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['park'] = self.park
|
||||||
|
category_code = self.get_category_code()
|
||||||
|
category_name = dict(Ride.CATEGORY_CHOICES)[category_code]
|
||||||
|
context['title'] = f'{category_name}s at {self.park.name}'
|
||||||
|
context['category_code'] = category_code
|
||||||
|
return context
|
||||||
|
|
||||||
class RideCreateView(LoginRequiredMixin, CreateView):
|
class RideCreateView(LoginRequiredMixin, CreateView):
|
||||||
model = Ride
|
model = Ride
|
||||||
form_class = RideForm
|
form_class = RideForm
|
||||||
|
|||||||
@@ -2253,6 +2253,10 @@ select {
|
|||||||
grid-column: span 1 / span 1;
|
grid-column: span 1 / span 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.col-span-12 {
|
||||||
|
grid-column: span 12 / span 12;
|
||||||
|
}
|
||||||
|
|
||||||
.mx-1 {
|
.mx-1 {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
@@ -2361,6 +2365,30 @@ select {
|
|||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-1\.5 {
|
||||||
|
margin-right: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-0\.5 {
|
||||||
|
margin-bottom: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-0\.5 {
|
||||||
|
margin-left: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-0\.5 {
|
||||||
|
margin-right: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-1\.5 {
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-10 {
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -2433,6 +2461,10 @@ select {
|
|||||||
height: 340px;
|
height: 340px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-auto {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.max-h-60 {
|
.max-h-60 {
|
||||||
max-height: 15rem;
|
max-height: 15rem;
|
||||||
}
|
}
|
||||||
@@ -2441,6 +2473,10 @@ select {
|
|||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-h-\[340px\] {
|
||||||
|
max-height: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
.min-h-\[calc\(100vh-16rem\)\] {
|
.min-h-\[calc\(100vh-16rem\)\] {
|
||||||
min-height: calc(100vh - 16rem);
|
min-height: calc(100vh - 16rem);
|
||||||
}
|
}
|
||||||
@@ -2453,6 +2489,10 @@ select {
|
|||||||
min-height: 0px;
|
min-height: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.min-h-\[200px\] {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
.w-16 {
|
.w-16 {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
}
|
}
|
||||||
@@ -2526,6 +2566,10 @@ select {
|
|||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-shrink-0 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-grow {
|
.flex-grow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
@@ -2659,6 +2703,14 @@ select {
|
|||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-1\.5 {
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-x-8 {
|
.gap-x-8 {
|
||||||
-moz-column-gap: 2rem;
|
-moz-column-gap: 2rem;
|
||||||
column-gap: 2rem;
|
column-gap: 2rem;
|
||||||
@@ -2732,6 +2784,10 @@ select {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overflow-y-auto {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded {
|
.rounded {
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -2935,6 +2991,11 @@ select {
|
|||||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-gray-100 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-opacity-50 {
|
.bg-opacity-50 {
|
||||||
--tw-bg-opacity: 0.5;
|
--tw-bg-opacity: 0.5;
|
||||||
}
|
}
|
||||||
@@ -3006,6 +3067,18 @@ select {
|
|||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-2\.5 {
|
||||||
|
padding: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-0\.5 {
|
||||||
|
padding: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-1\.5 {
|
||||||
|
padding: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.px-2 {
|
.px-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
@@ -3071,6 +3144,11 @@ select {
|
|||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.py-0\.5 {
|
||||||
|
padding-top: 0.125rem;
|
||||||
|
padding-bottom: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pb-4 {
|
.pb-4 {
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@@ -3079,6 +3157,10 @@ select {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.align-middle {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.text-2xl {
|
.text-2xl {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
@@ -3114,6 +3196,11 @@ select {
|
|||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-base {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.font-bold {
|
.font-bold {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -3126,6 +3213,10 @@ select {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leading-tight {
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
.text-blue-500 {
|
.text-blue-500 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
color: rgb(59 130 246 / var(--tw-text-opacity));
|
||||||
@@ -3240,6 +3331,16 @@ select {
|
|||||||
color: rgb(22 163 74 / var(--tw-text-opacity));
|
color: rgb(22 163 74 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-sky-400 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(56 189 248 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sky-900 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(12 74 110 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -3335,6 +3436,12 @@ select {
|
|||||||
transition-duration: 150ms;
|
transition-duration: 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.transition-shadow {
|
||||||
|
transition-property: box-shadow;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
.duration-100 {
|
.duration-100 {
|
||||||
transition-duration: 100ms;
|
transition-duration: 100ms;
|
||||||
}
|
}
|
||||||
@@ -3455,6 +3562,11 @@ select {
|
|||||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-gray-200:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:text-blue-500:hover {
|
.hover\:text-blue-500:hover {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
color: rgb(59 130 246 / var(--tw-text-opacity));
|
||||||
@@ -3494,10 +3606,41 @@ select {
|
|||||||
color: rgb(79 70 229 / 0.8);
|
color: rgb(79 70 229 / 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:text-sky-300:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(125 211 252 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:text-sky-900:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(12 74 110 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:text-sky-950:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(8 47 73 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:text-sky-800:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(7 89 133 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:text-blue-800:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(30 64 175 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:underline:hover {
|
.hover\:underline:hover {
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:shadow-md:hover {
|
||||||
|
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||||
|
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
.focus\:border-blue-500:focus {
|
.focus\:border-blue-500:focus {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(59 130 246 / var(--tw-border-opacity));
|
border-color: rgb(59 130 246 / var(--tw-border-opacity));
|
||||||
@@ -3765,6 +3908,11 @@ select {
|
|||||||
color: rgb(74 222 128 / var(--tw-text-opacity));
|
color: rgb(74 222 128 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:text-sky-400:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(56 189 248 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:ring-1:is(.dark *) {
|
.dark\:ring-1:is(.dark *) {
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
@@ -3838,6 +3986,26 @@ select {
|
|||||||
color: rgb(79 70 229 / var(--tw-text-opacity));
|
color: rgb(79 70 229 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:hover\:text-sky-400:hover:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(56 189 248 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:hover\:text-sky-600:hover:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(2 132 199 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:hover\:text-sky-200:hover:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(186 230 253 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:hover\:text-sky-300:hover:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(125 211 252 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
.sm\:col-span-2 {
|
.sm\:col-span-2 {
|
||||||
grid-column: span 2 / span 2;
|
grid-column: span 2 / span 2;
|
||||||
@@ -3847,6 +4015,54 @@ select {
|
|||||||
grid-column: span 4 / span 4;
|
grid-column: span 4 / span 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm\:col-span-3 {
|
||||||
|
grid-column: span 3 / span 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:col-span-8 {
|
||||||
|
grid-column: span 8 / span 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:col-span-9 {
|
||||||
|
grid-column: span 9 / span 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:mb-8 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:mb-16 {
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:h-\[340px\] {
|
||||||
|
height: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:h-\[300px\] {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:h-\[200px\] {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:h-\[160px\] {
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:h-\[140px\] {
|
||||||
|
height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:h-auto {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.sm\:grid-cols-2 {
|
.sm\:grid-cols-2 {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -3855,6 +4071,26 @@ select {
|
|||||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm\:grid-cols-12 {
|
||||||
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:grid-cols-4 {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:grid-cols-1 {
|
||||||
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:gap-4 {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.sm\:space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
.sm\:space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||||
@@ -3866,6 +4102,41 @@ select {
|
|||||||
margin-right: calc(1.5rem * var(--tw-space-x-reverse));
|
margin-right: calc(1.5rem * var(--tw-space-x-reverse));
|
||||||
margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
|
margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm\:text-3xl {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:text-base {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:text-4xl {
|
||||||
|
font-size: 2.25rem;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:text-lg {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:text-xs {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm\:text-2xl {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@@ -3893,6 +4164,14 @@ select {
|
|||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md\:mb-8 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md\:h-\[140px\] {
|
||||||
|
height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
.md\:grid-cols-2 {
|
.md\:grid-cols-2 {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,75 +5,100 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container px-4 mx-auto">
|
<div class="container px-4 mx-auto">
|
||||||
<!-- Company Header -->
|
<!-- Action Buttons - Above header -->
|
||||||
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="flex justify-end gap-2 mb-2">
|
||||||
<div class="flex flex-col items-start justify-between md:flex-row md:items-center">
|
{% if company.website %}
|
||||||
<div>
|
<a href="{{ company.website }}" target="_blank" rel="noopener noreferrer"
|
||||||
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{{ company.name }}</h1>
|
class="transition-transform btn-secondary hover:scale-105">
|
||||||
{% if company.headquarters %}
|
<i class="mr-1 fas fa-external-link-alt"></i>Visit Website
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
</a>
|
||||||
<i class="mr-2 fas fa-map-marker-alt"></i>{{ company.headquarters }}
|
{% endif %}
|
||||||
</p>
|
{% if user.is_authenticated %}
|
||||||
{% endif %}
|
<a href="{% url 'companies:company_edit' slug=company.slug %}"
|
||||||
</div>
|
class="transition-transform btn-secondary hover:scale-105">
|
||||||
<div class="flex gap-2 mt-4 md:mt-0">
|
<i class="mr-1 fas fa-edit"></i>Edit
|
||||||
{% if company.website %}
|
</a>
|
||||||
<a href="{{ company.website }}" target="_blank" rel="noopener noreferrer"
|
|
||||||
class="btn-secondary">
|
|
||||||
<i class="mr-2 fas fa-external-link-alt"></i>Visit Website
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<a href="{% url 'companies:company_edit' slug=company.slug %}" class="btn-secondary">
|
|
||||||
<i class="mr-2 fas fa-edit"></i>Edit
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if company.description %}
|
|
||||||
<div class="mt-6 prose dark:prose-invert max-w-none">
|
|
||||||
{{ company.description|linebreaks }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Company Stats -->
|
<!-- Header Grid -->
|
||||||
<div class="grid grid-cols-1 gap-6 mb-6 md:grid-cols-3">
|
<div class="grid gap-2 mb-12 sm:mb-16 md:mb-8 grid-cols-1 sm:grid-cols-12 h-auto md:h-[140px]">
|
||||||
<div class="p-6 text-center bg-white rounded-lg shadow dark:bg-gray-800">
|
<!-- Company Info Card -->
|
||||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="flex flex-col items-center justify-center h-full col-span-1 p-2 text-center bg-white rounded-lg shadow-lg sm:col-span-3 dark:bg-gray-800">
|
||||||
{{ parks.count }}
|
<h1 class="text-2xl font-bold leading-tight text-gray-900 sm:text-3xl dark:text-white">{{ company.name }}</h1>
|
||||||
|
|
||||||
|
{% if company.headquarters %}
|
||||||
|
<div class="flex items-center justify-center mt-0.5 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<i class="mr-1 fas fa-map-marker-alt"></i>
|
||||||
|
<p>{{ company.headquarters }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-gray-600 dark:text-gray-400">Theme Parks</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6 text-center bg-white rounded-lg shadow dark:bg-gray-800">
|
<!-- Stats and Quick Facts -->
|
||||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="grid h-full grid-cols-12 col-span-1 gap-2 sm:col-span-9">
|
||||||
{{ parks|length }}
|
<!-- Stats Column -->
|
||||||
|
<div class="grid grid-cols-2 col-span-12 gap-2 sm:col-span-4">
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Total Parks</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ parks.count }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Active Parks</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ parks|length }}</dd>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-gray-600 dark:text-gray-400">Active Parks</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-6 text-center bg-white rounded-lg shadow dark:bg-gray-800">
|
<!-- Quick Facts Grid -->
|
||||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="grid h-full grid-cols-3 col-span-12 gap-1 p-1.5 bg-white rounded-lg shadow-lg sm:col-span-8 dark:bg-gray-800">
|
||||||
{% with total_rides=0 %}
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
{% for park in parks %}
|
<i class="text-sm text-blue-600 sm:text-base fas fa-ticket-alt dark:text-blue-400"></i>
|
||||||
{% with total_rides=total_rides|add:park.rides.count %}{% endwith %}
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Total Attractions</dt>
|
||||||
{% endfor %}
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ total_rides }}</dd>
|
||||||
{{ total_rides }}
|
</div>
|
||||||
{% endwith %}
|
|
||||||
|
{% if company.founded_date %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-calendar-alt dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Founded</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ company.founded_date }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if company.website %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-globe dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Website</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="{{ company.website }}"
|
||||||
|
class="text-blue-600 text-2xs sm:text-xs hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
|
target="_blank" rel="noopener noreferrer">
|
||||||
|
Visit
|
||||||
|
<i class="ml-0.5 text-2xs fas fa-external-link-alt"></i>
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-gray-600 dark:text-gray-400">Total Attractions</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if company.description %}
|
||||||
|
<div class="p-6 mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">About</h2>
|
||||||
|
<div class="prose dark:prose-invert max-w-none">
|
||||||
|
{{ company.description|linebreaks }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Parks List -->
|
<!-- Parks List -->
|
||||||
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
<h2 class="mb-6 text-2xl font-bold text-gray-900 dark:text-white">Theme Parks</h2>
|
<h2 class="mb-6 text-xl font-semibold text-gray-900 dark:text-white">Theme Parks</h2>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{% for park in parks %}
|
{% for park in parks %}
|
||||||
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700">
|
<div class="overflow-hidden transition-transform rounded-lg hover:scale-[1.02] bg-gray-50 dark:bg-gray-700">
|
||||||
{% if park.photos.exists %}
|
{% if park.photos.exists %}
|
||||||
<img src="{{ park.photos.first.image.url }}"
|
<img src="{{ park.photos.first.image.url }}"
|
||||||
alt="{{ park.name }}"
|
alt="{{ park.name }}"
|
||||||
@@ -87,7 +112,7 @@
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h3 class="mb-2 text-lg font-semibold">
|
<h3 class="mb-2 text-lg font-semibold">
|
||||||
<a href="{% url 'parks:park_detail' park.slug %}"
|
<a href="{% url 'parks:park_detail' park.slug %}"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline">
|
class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300">
|
||||||
{{ park.name }}
|
{{ park.name }}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -5,70 +5,100 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container px-4 mx-auto">
|
<div class="container px-4 mx-auto">
|
||||||
<!-- Manufacturer Header -->
|
<!-- Action Buttons - Above header -->
|
||||||
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="flex justify-end gap-2 mb-2">
|
||||||
<div class="flex flex-col items-start justify-between md:flex-row md:items-center">
|
{% if manufacturer.website %}
|
||||||
<div>
|
<a href="{{ manufacturer.website }}" target="_blank" rel="noopener noreferrer"
|
||||||
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{{ manufacturer.name }}</h1>
|
class="transition-transform btn-secondary hover:scale-105">
|
||||||
{% if manufacturer.headquarters %}
|
<i class="mr-1 fas fa-external-link-alt"></i>Visit Website
|
||||||
<p class="text-gray-600 dark:text-gray-400">
|
</a>
|
||||||
<i class="mr-2 fas fa-map-marker-alt"></i>{{ manufacturer.headquarters }}
|
{% endif %}
|
||||||
</p>
|
{% if user.is_authenticated %}
|
||||||
{% endif %}
|
<a href="{% url 'companies:manufacturer_edit' slug=manufacturer.slug %}"
|
||||||
</div>
|
class="transition-transform btn-secondary hover:scale-105">
|
||||||
<div class="flex gap-2 mt-4 md:mt-0">
|
<i class="mr-1 fas fa-edit"></i>Edit
|
||||||
{% if manufacturer.website %}
|
</a>
|
||||||
<a href="{{ manufacturer.website }}" target="_blank" rel="noopener noreferrer"
|
|
||||||
class="btn-secondary">
|
|
||||||
<i class="mr-2 fas fa-external-link-alt"></i>Visit Website
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<a href="{% url 'companies:manufacturer_edit' slug=manufacturer.slug %}" class="btn-secondary">
|
|
||||||
<i class="mr-2 fas fa-edit"></i>Edit
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if manufacturer.description %}
|
|
||||||
<div class="mt-6 prose dark:prose-invert max-w-none">
|
|
||||||
{{ manufacturer.description|linebreaks }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Manufacturer Stats -->
|
<!-- Header Grid -->
|
||||||
<div class="grid grid-cols-1 gap-6 mb-6 md:grid-cols-3">
|
<div class="grid gap-2 mb-12 sm:mb-16 md:mb-8 grid-cols-1 sm:grid-cols-12 h-auto md:h-[140px]">
|
||||||
<div class="p-6 text-center bg-white rounded-lg shadow dark:bg-gray-800">
|
<!-- Manufacturer Info Card -->
|
||||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="flex flex-col items-center justify-center h-full col-span-1 p-2 text-center bg-white rounded-lg shadow-lg sm:col-span-3 dark:bg-gray-800">
|
||||||
{{ rides.count }}
|
<h1 class="text-2xl font-bold leading-tight text-gray-900 sm:text-3xl dark:text-white">{{ manufacturer.name }}</h1>
|
||||||
|
|
||||||
|
{% if manufacturer.headquarters %}
|
||||||
|
<div class="flex items-center justify-center mt-0.5 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<i class="mr-1 fas fa-map-marker-alt"></i>
|
||||||
|
<p>{{ manufacturer.headquarters }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-gray-600 dark:text-gray-400">Total Rides</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6 text-center bg-white rounded-lg shadow dark:bg-gray-800">
|
<!-- Stats and Quick Facts -->
|
||||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="grid h-full grid-cols-12 col-span-1 gap-2 sm:col-span-9">
|
||||||
{{ rides|filter:"type='ROLLER_COASTER'"|length }}
|
<!-- Stats Column -->
|
||||||
|
<div class="grid grid-cols-2 col-span-12 gap-2 sm:col-span-4">
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Total Rides</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ rides.count }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Coasters</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ coaster_count }}</dd>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-gray-600 dark:text-gray-400">Roller Coasters</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-6 text-center bg-white rounded-lg shadow dark:bg-gray-800">
|
<!-- Quick Facts Grid -->
|
||||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
<div class="grid h-full grid-cols-3 col-span-12 gap-1 p-1.5 bg-white rounded-lg shadow-lg sm:col-span-8 dark:bg-gray-800">
|
||||||
{{ rides|regroup:"park"|length }}
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-map dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Parks Served</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ parks_count }}</dd>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if manufacturer.founded_date %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-calendar-alt dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Founded</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ manufacturer.founded_date }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if manufacturer.website %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-globe dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Website</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="{{ manufacturer.website }}"
|
||||||
|
class="text-blue-600 text-2xs sm:text-xs hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
|
target="_blank" rel="noopener noreferrer">
|
||||||
|
Visit
|
||||||
|
<i class="ml-0.5 text-2xs fas fa-external-link-alt"></i>
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-gray-600 dark:text-gray-400">Parks with Rides</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if manufacturer.description %}
|
||||||
|
<div class="p-6 mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">About</h2>
|
||||||
|
<div class="prose dark:prose-invert max-w-none">
|
||||||
|
{{ manufacturer.description|linebreaks }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Rides List -->
|
<!-- Rides List -->
|
||||||
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
<h2 class="mb-6 text-2xl font-bold text-gray-900 dark:text-white">Rides</h2>
|
<h2 class="mb-6 text-xl font-semibold text-gray-900 dark:text-white">Rides</h2>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{% for ride in rides %}
|
{% for ride in rides %}
|
||||||
<div class="overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-700">
|
<div class="overflow-hidden transition-transform rounded-lg hover:scale-[1.02] bg-gray-50 dark:bg-gray-700">
|
||||||
{% if ride.photos.exists %}
|
{% if ride.photos.exists %}
|
||||||
<img src="{{ ride.photos.first.image.url }}"
|
<img src="{{ ride.photos.first.image.url }}"
|
||||||
alt="{{ ride.name }}"
|
alt="{{ ride.name }}"
|
||||||
@@ -82,7 +112,7 @@
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h3 class="mb-2 text-lg font-semibold">
|
<h3 class="mb-2 text-lg font-semibold">
|
||||||
<a href="{% url 'parks:rides:ride_detail' ride.park.slug ride.slug %}"
|
<a href="{% url 'parks:rides:ride_detail' ride.park.slug ride.slug %}"
|
||||||
class="text-blue-600 dark:text-blue-400 hover:underline">
|
class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300">
|
||||||
{{ ride.name }}
|
{{ ride.name }}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
121
templates/designers/designer_detail.html
Normal file
121
templates/designers/designer_detail.html
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
{% extends 'base/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ designer.name }} - ThrillWiki{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container px-4 mx-auto">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="grid grid-cols-1 gap-6 mb-8 lg:grid-cols-3">
|
||||||
|
<!-- Designer Info -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
|
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{{ designer.name }}</h1>
|
||||||
|
{% if designer.description %}
|
||||||
|
<div class="mt-4 prose dark:prose-invert max-w-none">
|
||||||
|
{{ designer.description|linebreaks }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Card -->
|
||||||
|
<div class="lg:col-span-1">
|
||||||
|
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Quick Stats</h2>
|
||||||
|
<dl class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Rides</dt>
|
||||||
|
<dd class="mt-1 text-3xl font-semibold text-blue-600 dark:text-blue-400">{{ stats.total_rides }}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Roller Coasters</dt>
|
||||||
|
<dd class="mt-1 text-3xl font-semibold text-blue-600 dark:text-blue-400">{{ stats.total_coasters }}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Parks</dt>
|
||||||
|
<dd class="mt-1 text-3xl font-semibold text-blue-600 dark:text-blue-400">{{ stats.total_parks }}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Countries</dt>
|
||||||
|
<dd class="mt-1 text-3xl font-semibold text-blue-600 dark:text-blue-400">{{ stats.total_countries }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
{% if designer.website %}
|
||||||
|
<div class="mt-6">
|
||||||
|
<a href="{{ designer.website }}" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
|
<i class="mr-2 fas fa-external-link-alt"></i>
|
||||||
|
Visit Website
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rides List -->
|
||||||
|
<div class="p-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
|
<h2 class="mb-6 text-2xl font-semibold text-gray-900 dark:text-white">Designed Rides</h2>
|
||||||
|
|
||||||
|
{% if rides %}
|
||||||
|
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{% for ride in rides %}
|
||||||
|
<div class="p-4 transition-shadow rounded-lg bg-gray-50 hover:shadow-md dark:bg-gray-700/50">
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
|
<a href="{% url 'parks:rides:ride_detail' park_slug=ride.park.slug ride_slug=ride.slug %}"
|
||||||
|
class="hover:text-blue-600 dark:hover:text-blue-400">
|
||||||
|
{{ ride.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
at <a href="{% url 'parks:park_detail' ride.park.slug %}"
|
||||||
|
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
|
{{ ride.park.name }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span class="px-2 py-1 text-xs font-medium text-blue-800 bg-blue-100 rounded-full dark:bg-blue-700 dark:text-blue-50">
|
||||||
|
{{ ride.get_category_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4 mt-4">
|
||||||
|
{% if ride.opening_date %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Opened</dt>
|
||||||
|
<dd class="text-gray-900 dark:text-white">{{ ride.opening_date }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if ride.manufacturer %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Manufacturer</dt>
|
||||||
|
<dd class="text-gray-900 dark:text-white">{{ ride.manufacturer.name }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if ride.category == 'RC' and ride.coaster_stats %}
|
||||||
|
{% if ride.coaster_stats.height_ft %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Height</dt>
|
||||||
|
<dd class="text-gray-900 dark:text-white">{{ ride.coaster_stats.height_ft }} ft</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if ride.coaster_stats.speed_mph %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Speed</dt>
|
||||||
|
<dd class="text-gray-900 dark:text-white">{{ ride.coaster_stats.speed_mph }} mph</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">No rides found.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -22,35 +22,35 @@
|
|||||||
<div class="container px-4 mx-auto">
|
<div class="container px-4 mx-auto">
|
||||||
<!-- Action Buttons - Above header -->
|
<!-- Action Buttons - Above header -->
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<div class="flex justify-end gap-3 mb-4">
|
<div class="flex justify-end gap-2 mb-2">
|
||||||
<a href="{% url 'parks:park_update' park.slug %}"
|
<a href="{% url 'parks:park_update' park.slug %}"
|
||||||
class="transition-transform btn-secondary hover:scale-105">
|
class="transition-transform btn-secondary hover:scale-105">
|
||||||
<i class="mr-2 fas fa-pencil-alt"></i>Edit
|
<i class="mr-1 fas fa-pencil-alt"></i>Edit
|
||||||
</a>
|
</a>
|
||||||
{% if perms.media.add_photo %}
|
{% if perms.media.add_photo %}
|
||||||
<button class="transition-transform btn-secondary hover:scale-105"
|
<button class="transition-transform btn-secondary hover:scale-105"
|
||||||
@click="$dispatch('show-photo-upload')">
|
@click="$dispatch('show-photo-upload')">
|
||||||
<i class="mr-2 fas fa-camera"></i>Upload Photo
|
<i class="mr-1 fas fa-camera"></i>Upload Photo
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Header Grid -->
|
<!-- Header Grid -->
|
||||||
<div class="grid h-[340px] gap-4 mb-6 grid-cols-1 sm:grid-cols-6 md:grid-cols-12">
|
<div class="grid gap-2 mb-12 sm:mb-16 md:mb-8 grid-cols-1 sm:grid-cols-12 h-auto md:h-[140px]">
|
||||||
<!-- Park Info Card -->
|
<!-- Park Info Card -->
|
||||||
<div class="flex flex-col h-full col-span-1 p-4 overflow-auto bg-white rounded-lg shadow-lg sm:col-span-2 md:col-span-3 dark:bg-gray-800">
|
<div class="flex flex-col items-center justify-center h-full col-span-1 p-2 text-center bg-white rounded-lg shadow-lg sm:col-span-3 dark:bg-gray-800">
|
||||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">{{ park.name }}</h1>
|
<h1 class="text-2xl font-bold leading-tight text-gray-900 sm:text-3xl dark:text-white">{{ park.name }}</h1>
|
||||||
|
|
||||||
{% if park.formatted_location %}
|
{% if park.formatted_location %}
|
||||||
<div class="flex items-center mt-2 text-gray-600 dark:text-gray-400">
|
<div class="flex items-center justify-center mt-0.5 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<i class="mr-2 fas fa-map-marker-alt"></i>
|
<i class="mr-1 fas fa-map-marker-alt"></i>
|
||||||
<p>{{ park.formatted_location }}</p>
|
<p>{{ park.formatted_location }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-2 mt-3">
|
<div class="flex flex-wrap items-center justify-center gap-1 mt-1">
|
||||||
<span class="status-badge text-sm font-medium {% if park.status == 'OPERATING' %}status-operating
|
<span class="status-badge text-xs sm:text-sm font-medium py-0.5 {% if park.status == 'OPERATING' %}status-operating
|
||||||
{% elif park.status == 'CLOSED_TEMP' or park.status == 'CLOSED_PERM' %}status-closed
|
{% elif park.status == 'CLOSED_TEMP' or park.status == 'CLOSED_PERM' %}status-closed
|
||||||
{% elif park.status == 'UNDER_CONSTRUCTION' %}status-construction
|
{% elif park.status == 'UNDER_CONSTRUCTION' %}status-construction
|
||||||
{% elif park.status == 'DEMOLISHED' %}status-demolished
|
{% elif park.status == 'DEMOLISHED' %}status-demolished
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
{{ park.get_status_display }}
|
{{ park.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
{% if park.average_rating %}
|
{% if park.average_rating %}
|
||||||
<span class="flex items-center text-sm font-medium text-yellow-800 bg-yellow-100 status-badge dark:bg-yellow-600 dark:text-yellow-50">
|
<span class="flex items-center text-xs font-medium text-yellow-800 bg-yellow-100 sm:text-sm status-badge py-0.5 dark:bg-yellow-600 dark:text-yellow-50">
|
||||||
<span class="mr-1 text-yellow-500 dark:text-yellow-200">★</span>
|
<span class="mr-0.5 text-yellow-500 dark:text-yellow-200">★</span>
|
||||||
{{ park.average_rating|floatformat:1 }}/10
|
{{ park.average_rating|floatformat:1 }}/10
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -67,36 +67,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats and Quick Facts -->
|
<!-- Stats and Quick Facts -->
|
||||||
<div class="grid h-full grid-cols-1 col-span-1 gap-4 sm:grid-cols-6 sm:col-span-4 md:grid-cols-12 md:col-span-9">
|
<div class="grid h-full grid-cols-12 col-span-1 gap-2 sm:col-span-9">
|
||||||
<!-- Stats Column -->
|
<!-- Stats Column -->
|
||||||
<div class="flex flex-col col-span-1 gap-4 sm:col-span-2 md:col-span-4">
|
<div class="grid-cols-2 col-span-12 gap-2 text-sky-400grid sm:grid-cols-1 md:grid-cols-2 sm:col-span-4">
|
||||||
<!-- Total Rides Card -->
|
<!-- Total Rides Card -->
|
||||||
{% if park.total_rides %}
|
{% if park.total_rides %}
|
||||||
<a href="{% url 'parks:rides:ride_list' park.slug %}"
|
<a href="{% url 'parks:rides:ride_list' park.slug %}"
|
||||||
class="flex flex-col flex-1 p-4 transition-transform bg-white rounded-lg shadow-lg hover:scale-[1.02] dark:bg-gray-800">
|
class="flex flex-col items-center justify-center p-2 text-center transition-transform bg-white rounded-lg shadow-lg hover:scale-[1.02] dark:bg-gray-800">
|
||||||
<dt class="text-lg font-semibold text-gray-900 dark:text-white">Total Rides</dt>
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Total Rides</dt>
|
||||||
<dd class="mt-2 text-4xl font-bold text-gray-900 dark:text-white">{{ park.total_rides }}</dd>
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 hover:text-sky-800 sm:text-2xl dark:text-sky-400 dark:hover:text-sky-300">{{ park.total_rides }}</dd>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Total Roller Coasters Card -->
|
<!-- Total Roller Coasters Card -->
|
||||||
{% if park.total_roller_coasters %}
|
{% if park.total_roller_coasters %}
|
||||||
<div class="flex flex-col flex-1 p-4 bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
<dt class="text-lg font-semibold text-gray-900 dark:text-white">Total Roller Coasters</dt>
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Roller Coasters</dt>
|
||||||
<dd class="mt-2 text-4xl font-bold text-gray-900 dark:text-white">{{ park.total_roller_coasters }}</dd>
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 hover:text-sky-800 sm:text-2xl dark:text-sky-400 dark:hover:text-sky-300">{{ park.total_roller_coasters }}</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Facts Grid -->
|
<!-- Quick Facts Grid -->
|
||||||
<div class="grid h-full grid-cols-2 col-span-1 gap-4 p-4 bg-white rounded-lg shadow-lg sm:grid-cols-2 sm:col-span-4 md:grid-cols-4 md:col-span-8 lg:grid-cols-6 dark:bg-gray-800">
|
<div class="grid h-full grid-cols-3 col-span-12 gap-1 p-1.5 bg-white rounded-lg shadow-lg sm:col-span-8 dark:bg-gray-800">
|
||||||
{% if park.owner %}
|
{% if park.owner %}
|
||||||
<div class="flex flex-col items-center justify-center text-center lg:col-span-2">
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
<i class="mb-1 text-lg text-blue-600 fas fa-building dark:text-blue-400"></i>
|
<i class="text-sm text-blue-600 sm:text-base fas fa-building dark:text-blue-400"></i>
|
||||||
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Owner/Operator</dt>
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Owner</dt>
|
||||||
<dd class="mt-0.5">
|
<dd>
|
||||||
<a href="{% url 'companies:company_detail' park.owner.slug %}"
|
<a href="{% url 'companies:company_detail' park.owner.slug %}"
|
||||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
class="text-blue-600 text-2xs sm:text-xs hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
{{ park.owner.name }}
|
{{ park.owner.name }}
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
@@ -104,39 +104,23 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if park.opening_date %}
|
{% if park.opening_date %}
|
||||||
<div class="flex flex-col items-center justify-center text-center">
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
<i class="mb-1 text-lg text-blue-600 fas fa-calendar-alt dark:text-blue-400"></i>
|
<i class="text-sm text-blue-600 sm:text-base fas fa-calendar-alt dark:text-blue-400"></i>
|
||||||
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Opening Date</dt>
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Opened</dt>
|
||||||
<dd class="mt-0.5 text-sm text-gray-900 dark:text-white">{{ park.opening_date }}</dd>
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ park.opening_date }}</dd>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if park.operating_season %}
|
|
||||||
<div class="flex flex-col items-center justify-center text-center">
|
|
||||||
<i class="mb-1 text-lg text-blue-600 fas fa-clock dark:text-blue-400"></i>
|
|
||||||
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Operating Season</dt>
|
|
||||||
<dd class="mt-0.5 text-sm text-gray-900 dark:text-white">{{ park.operating_season }}</dd>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if park.size_acres %}
|
|
||||||
<div class="flex flex-col items-center justify-center text-center">
|
|
||||||
<i class="mb-1 text-lg text-blue-600 fas fa-ruler-combined dark:text-blue-400"></i>
|
|
||||||
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Size</dt>
|
|
||||||
<dd class="mt-0.5 text-sm text-gray-900 dark:text-white">{{ park.size_acres }} acres</dd>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if park.website %}
|
{% if park.website %}
|
||||||
<div class="flex flex-col items-center justify-center text-center lg:col-span-2">
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
<i class="mb-1 text-lg text-blue-600 fas fa-globe dark:text-blue-400"></i>
|
<i class="text-sm text-blue-600 sm:text-base fas fa-globe dark:text-blue-400"></i>
|
||||||
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400">Website</dt>
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Website</dt>
|
||||||
<dd class="mt-0.5">
|
<dd>
|
||||||
<a href="{{ park.website }}"
|
<a href="{{ park.website }}"
|
||||||
class="inline-flex items-center text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="inline-flex items-center text-blue-600 text-2xs sm:text-xs hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
target="_blank" rel="noopener noreferrer">
|
target="_blank" rel="noopener noreferrer">
|
||||||
Official Website
|
Visit
|
||||||
<i class="ml-1 fas fa-external-link-alt"></i>
|
<i class="ml-0.5 text-2xs fas fa-external-link-alt"></i>
|
||||||
</a>
|
</a>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,7 +128,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Photos -->
|
|
||||||
|
<!-- Photos Section -->
|
||||||
{% if park.photos.exists %}
|
{% if park.photos.exists %}
|
||||||
<div class="p-6 mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="p-6 mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Photos</h2>
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Photos</h2>
|
||||||
|
|||||||
154
templates/rides/ride_category_list.html
Normal file
154
templates/rides/ride_category_list.html
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
{% extends "base/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load ride_tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container px-4 mx-auto">
|
||||||
|
<div class="flex flex-col items-start justify-between gap-4 mb-6 md:flex-row md:items-center">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">{{ title }}</h1>
|
||||||
|
{% if park %}
|
||||||
|
<a href="{% url 'parks:park_detail' park.slug %}" class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
|
Back to {{ park.name }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category Filters -->
|
||||||
|
<div class="flex flex-wrap gap-4 mb-8">
|
||||||
|
{% if park %}
|
||||||
|
<a href="{% url 'parks:park_roller_coasters' park.slug %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'RC' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Roller Coasters
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'parks:park_dark_rides' park.slug %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'DR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Dark Rides
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'parks:park_flat_rides' park.slug %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'FR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Flat Rides
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'parks:park_water_rides' park.slug %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'WR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Water Rides
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'parks:park_transports' park.slug %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'TR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Transports
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'parks:park_others' park.slug %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'OT' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Others
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'rides:roller_coasters' %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'RC' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Roller Coasters
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'rides:dark_rides' %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'DR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Dark Rides
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'rides:flat_rides' %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'FR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Flat Rides
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'rides:water_rides' %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'WR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Water Rides
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'rides:transports' %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'TR' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Transports
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'rides:others' %}"
|
||||||
|
class="px-4 py-2 rounded-lg transition-colors {% if category_code == 'OT' %}bg-blue-600 text-white dark:bg-blue-500{% else %}bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600{% endif %}">
|
||||||
|
Others
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not categories %}
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">No rides found.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for category_name, rides in categories.items %}
|
||||||
|
<div class="mb-10">
|
||||||
|
<h2 class="mb-4 text-2xl font-semibold text-gray-900 dark:text-white">{{ category_name }}s</h2>
|
||||||
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{% for ride in rides %}
|
||||||
|
<div class="overflow-hidden transition-transform transform bg-white rounded-lg shadow-lg dark:bg-gray-800 hover:-translate-y-1">
|
||||||
|
<div class="aspect-w-16 aspect-h-9">
|
||||||
|
{% if ride.photos.exists %}
|
||||||
|
<img src="{{ ride.photos.first.image.url }}"
|
||||||
|
alt="{{ ride.name }}"
|
||||||
|
class="object-cover w-full">
|
||||||
|
{% else %}
|
||||||
|
<img src="{% get_ride_placeholder_image ride.category %}"
|
||||||
|
alt="{{ ride.name }}"
|
||||||
|
class="object-cover w-full">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<h3 class="mb-2 text-xl font-bold">
|
||||||
|
<a href="{% url 'parks:rides:ride_detail' ride.park.slug ride.slug %}"
|
||||||
|
class="text-gray-900 hover:text-blue-600 dark:text-white dark:hover:text-blue-400">
|
||||||
|
{{ ride.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
{% if not park %}
|
||||||
|
<p class="mb-3 text-gray-600 dark:text-gray-400">
|
||||||
|
at <a href="{% url 'parks:park_detail' ride.park.slug %}"
|
||||||
|
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
|
{{ ride.park.name }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if ride.manufacturer %}
|
||||||
|
<p class="mb-3 text-gray-600 dark:text-gray-400">{{ ride.manufacturer.name }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<span class="text-blue-800 bg-blue-100 status-badge dark:bg-blue-400/30 dark:text-blue-200 dark:ring-1 dark:ring-blue-400/30">
|
||||||
|
{{ ride.get_category_display }}
|
||||||
|
</span>
|
||||||
|
<span class="status-badge {% if ride.status == 'OPERATING' %}status-operating
|
||||||
|
{% elif ride.status == 'CLOSED_TEMP' or ride.status == 'CLOSED_PERM' %}status-closed
|
||||||
|
{% elif ride.status == 'UNDER_CONSTRUCTION' %}status-construction
|
||||||
|
{% elif ride.status == 'DEMOLISHED' %}status-demolished
|
||||||
|
{% elif ride.status == 'RELOCATED' %}status-relocated{% endif %}">
|
||||||
|
{{ ride.get_status_display }}
|
||||||
|
</span>
|
||||||
|
{% if ride.average_rating %}
|
||||||
|
<span class="text-yellow-800 bg-yellow-100 status-badge dark:bg-yellow-400/30 dark:text-yellow-200 dark:ring-1 dark:ring-yellow-400/30">
|
||||||
|
<i class="mr-1 text-yellow-500 fas fa-star dark:text-yellow-300"></i>
|
||||||
|
{{ ride.average_rating|floatformat:1 }}/10
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if ride.coaster_stats %}
|
||||||
|
<div class="grid grid-cols-2 gap-2 mt-4">
|
||||||
|
{% if ride.coaster_stats.height_ft %}
|
||||||
|
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
Height: {{ ride.coaster_stats.height_ft }}ft
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if ride.coaster_stats.speed_mph %}
|
||||||
|
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
Speed: {{ ride.coaster_stats.speed_mph }}mph
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -5,54 +5,161 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container px-4 mx-auto">
|
<div class="container px-4 mx-auto">
|
||||||
<!-- Header -->
|
<!-- Action Buttons - Above header -->
|
||||||
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
{% if user.is_authenticated %}
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex justify-end gap-2 mb-2">
|
||||||
<div>
|
<a href="{% url 'parks:rides:ride_edit' park_slug=ride.park.slug ride_slug=ride.slug %}"
|
||||||
<h1 class="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{{ ride.name }}</h1>
|
class="transition-transform btn-secondary hover:scale-105">
|
||||||
<p class="mb-2 text-gray-600 dark:text-gray-400">
|
<i class="mr-1 fas fa-pencil-alt"></i>Edit
|
||||||
at <a href="{% url 'parks:park_detail' ride.park.slug %}" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
|
</a>
|
||||||
{{ ride.park.name }}
|
{% if perms.media.add_photo %}
|
||||||
</a>
|
<button class="transition-transform btn-secondary hover:scale-105"
|
||||||
{% if ride.park_area %}
|
@click="$dispatch('show-photo-upload')">
|
||||||
- {{ ride.park_area.name }}
|
<i class="mr-1 fas fa-camera"></i>Upload Photo
|
||||||
{% endif %}
|
</button>
|
||||||
</p>
|
{% endif %}
|
||||||
<div class="flex flex-wrap gap-2 mt-3">
|
</div>
|
||||||
<span class="status-badge {% if ride.status == 'OPERATING' %}status-operating
|
{% endif %}
|
||||||
{% elif ride.status == 'CLOSED_TEMP' or ride.status == 'CLOSED_PERM' %}status-closed
|
|
||||||
{% elif ride.status == 'UNDER_CONSTRUCTION' %}status-construction
|
<!-- Header Grid -->
|
||||||
{% elif ride.status == 'DEMOLISHED' %}status-demolished
|
<div class="grid grid-cols-1 gap-2 mb-8 sm:grid-cols-12">
|
||||||
{% elif ride.status == 'RELOCATED' %}status-relocated{% endif %}">
|
<!-- Ride Info Card -->
|
||||||
{{ ride.get_status_display }}
|
<div class="flex flex-col items-center justify-center h-full col-span-1 p-2 text-center bg-white rounded-lg shadow-lg sm:col-span-3 dark:bg-gray-800">
|
||||||
</span>
|
<h1 class="text-2xl font-bold leading-tight text-gray-900 sm:text-3xl dark:text-white">{{ ride.name }}</h1>
|
||||||
<span class="text-blue-800 bg-blue-100 status-badge dark:bg-blue-700 dark:text-blue-50">
|
|
||||||
{{ ride.get_category_display }}
|
<div class="flex items-center justify-center mt-0.5 text-sm text-gray-600 dark:text-gray-400">
|
||||||
</span>
|
at <a href="{% url 'parks:park_detail' ride.park.slug %}" class="ml-1 text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
{% if ride.average_rating %}
|
{{ ride.park.name }}
|
||||||
<span class="flex items-center text-yellow-800 bg-yellow-100 status-badge dark:bg-yellow-600 dark:text-yellow-50">
|
</a>
|
||||||
<span class="mr-1 text-yellow-500 dark:text-yellow-200">★</span>
|
{% if ride.park_area %}
|
||||||
{{ ride.average_rating|floatformat:1 }}/10
|
- {{ ride.park_area.name }}
|
||||||
</span>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-wrap items-center justify-center gap-1 mt-1">
|
||||||
<a href="{% url 'parks:rides:ride_edit' park_slug=ride.park.slug ride_slug=ride.slug %}" class="btn-secondary">
|
<span class="status-badge text-xs sm:text-sm font-medium py-0.5 {% if ride.status == 'OPERATING' %}status-operating
|
||||||
<i class="mr-2 fas fa-pencil-alt"></i>Edit
|
{% elif ride.status == 'CLOSED_TEMP' or ride.status == 'CLOSED_PERM' %}status-closed
|
||||||
</a>
|
{% elif ride.status == 'UNDER_CONSTRUCTION' %}status-construction
|
||||||
{% if perms.media.add_photo %}
|
{% elif ride.status == 'DEMOLISHED' %}status-demolished
|
||||||
<button class="btn-secondary" @click="$dispatch('show-photo-upload')">
|
{% elif ride.status == 'RELOCATED' %}status-relocated{% endif %}">
|
||||||
<i class="mr-2 fas fa-camera"></i>Upload Photo
|
{{ ride.get_status_display }}
|
||||||
</button>
|
</span>
|
||||||
|
<span class="text-blue-800 bg-blue-100 status-badge text-xs sm:text-sm font-medium py-0.5 dark:bg-blue-700 dark:text-blue-50">
|
||||||
|
{{ ride.get_category_display }}
|
||||||
|
</span>
|
||||||
|
{% if ride.average_rating %}
|
||||||
|
<span class="flex items-center text-xs font-medium text-yellow-800 bg-yellow-100 sm:text-sm status-badge py-0.5 dark:bg-yellow-600 dark:text-yellow-50">
|
||||||
|
<span class="mr-0.5 text-yellow-500 dark:text-yellow-200">★</span>
|
||||||
|
{{ ride.average_rating|floatformat:1 }}/10
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats and Quick Facts -->
|
||||||
|
<div class="grid h-full grid-cols-12 col-span-1 gap-2 sm:col-span-9">
|
||||||
|
<!-- Stats Column -->
|
||||||
|
<div class="grid grid-cols-2 col-span-12 gap-2 sm:col-span-4">
|
||||||
|
{% if coaster_stats %}
|
||||||
|
{% if coaster_stats.height_ft %}
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Height</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ coaster_stats.height_ft }} ft</dd>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if coaster_stats.speed_mph %}
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Speed</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ coaster_stats.speed_mph }} mph</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if coaster_stats.inversions %}
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Inversions</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ coaster_stats.inversions }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if coaster_stats.length_ft %}
|
||||||
|
<div class="flex flex-col items-center justify-center p-2 text-center bg-white rounded-lg shadow-lg dark:bg-gray-800">
|
||||||
|
<dt class="text-sm font-semibold text-gray-900 sm:text-base dark:text-white">Length</dt>
|
||||||
|
<dd class="mt-0.5 text-xl font-bold text-sky-900 sm:text-2xl dark:text-sky-400">{{ coaster_stats.length_ft }} ft</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Facts Grid -->
|
||||||
|
<div class="grid h-full grid-cols-3 col-span-12 gap-1 p-1.5 bg-white rounded-lg shadow-lg sm:col-span-8 dark:bg-gray-800">
|
||||||
|
{% if ride.manufacturer %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-industry dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Manufacturer</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="{% url 'companies:manufacturer_detail' ride.manufacturer.slug %}"
|
||||||
|
class="text-blue-600 text-2xs sm:text-xs hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
|
{{ ride.manufacturer.name }}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ride.designer %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-drafting-compass dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Designer</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="{% url 'designers:designer_detail' ride.designer.slug %}"
|
||||||
|
class="text-blue-600 text-2xs sm:text-xs hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
|
{{ ride.designer.name }}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if coaster_stats.roller_coaster_type %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-train dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Coaster Type</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ coaster_stats.get_roller_coaster_type_display }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if coaster_stats.track_material %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-layer-group dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Track Material</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ coaster_stats.get_track_material_display }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ride.opening_date %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-calendar-alt dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Opened</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ ride.opening_date }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if ride.capacity_per_hour %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-users dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Capacity</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ ride.capacity_per_hour }}/hr</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if coaster_stats.launch_type %}
|
||||||
|
<div class="flex flex-col items-center justify-center text-center p-0.5">
|
||||||
|
<i class="text-sm text-blue-600 sm:text-base fas fa-rocket dark:text-blue-400"></i>
|
||||||
|
<dt class="font-medium text-gray-500 text-2xs dark:text-gray-400">Launch Type</dt>
|
||||||
|
<dd class="text-gray-900 text-2xs sm:text-xs dark:text-white">{{ coaster_stats.get_launch_type_display }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Photos -->
|
<!-- Photos Section -->
|
||||||
{% if ride.photos.exists %}
|
{% if ride.photos.exists %}
|
||||||
<div class="p-6 mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="p-6 mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Photos</h2>
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Photos</h2>
|
||||||
@@ -60,13 +167,50 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Reviews Section -->
|
||||||
|
<div class="p-6 mb-8 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Reviews</h2>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<button class="btn-primary">
|
||||||
|
<i class="mr-2 fas fa-star"></i>
|
||||||
|
Write a Review
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if ride.reviews.exists %}
|
||||||
|
<div class="space-y-4">
|
||||||
|
{% for review in ride.reviews.all %}
|
||||||
|
<div class="pb-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-semibold text-gray-900 dark:text-white">{{ review.title }}</h3>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
by {{ review.user.username }} on {{ review.created_at|date }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-1 text-yellow-400">★</span>
|
||||||
|
<span class="text-gray-900 dark:text-white">{{ review.rating }}/10</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-gray-700 dark:text-gray-300">{{ review.content }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">No reviews yet. Be the first to review this ride!</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Main Content Grid -->
|
<!-- Main Content Grid -->
|
||||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
<!-- Left Column - Description and Details -->
|
<!-- Left Column - Description and Details -->
|
||||||
<div class="lg:col-span-2">
|
<div class="lg:col-span-2">
|
||||||
{% if ride.description %}
|
{% if ride.description %}
|
||||||
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">About</h2>
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Trivia</h2>
|
||||||
<div class="prose dark:prose-invert max-w-none">
|
<div class="prose dark:prose-invert max-w-none">
|
||||||
{{ ride.description|linebreaks }}
|
{{ ride.description|linebreaks }}
|
||||||
</div>
|
</div>
|
||||||
@@ -79,8 +223,8 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
{% for name_history in ride.previous_names %}
|
{% for name_history in ride.previous_names %}
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span>{{ name_history.name }}</span>
|
<span class="text-gray-900 dark:text-white">{{ name_history.name }}</span>
|
||||||
<span class="text-gray-500">{{ name_history.period }}</span>
|
<span class="text-gray-500 dark:text-gray-400">{{ name_history.period }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -91,44 +235,129 @@
|
|||||||
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
<div class="p-6 mb-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||||
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Roller Coaster Statistics</h2>
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Roller Coaster Statistics</h2>
|
||||||
<div class="grid grid-cols-2 gap-4 md:grid-cols-3">
|
<div class="grid grid-cols-2 gap-4 md:grid-cols-3">
|
||||||
|
<!-- Coaster Type -->
|
||||||
|
{% if coaster_stats.roller_coaster_type %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Coaster Type</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.get_roller_coaster_type_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Height Stats -->
|
||||||
{% if coaster_stats.height_ft %}
|
{% if coaster_stats.height_ft %}
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-gray-500">Height</span>
|
<span class="block text-gray-500 dark:text-gray-400">Maximum Height</span>
|
||||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
{{ coaster_stats.height_ft }} ft
|
{{ coaster_stats.height_ft }} ft
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if coaster_stats.max_drop_height_ft %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Drop Height</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.max_drop_height_ft }} ft
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Track Stats -->
|
||||||
{% if coaster_stats.length_ft %}
|
{% if coaster_stats.length_ft %}
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-gray-500">Length</span>
|
<span class="block text-gray-500 dark:text-gray-400">Track Length</span>
|
||||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
{{ coaster_stats.length_ft }} ft
|
{{ coaster_stats.length_ft }} ft
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if coaster_stats.track_type %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Track Layout</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.track_type }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if coaster_stats.track_material %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Track Material</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.get_track_material_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Speed and Time -->
|
||||||
{% if coaster_stats.speed_mph %}
|
{% if coaster_stats.speed_mph %}
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-gray-500">Speed</span>
|
<span class="block text-gray-500 dark:text-gray-400">Maximum Speed</span>
|
||||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
{{ coaster_stats.speed_mph }} mph
|
{{ coaster_stats.speed_mph }} mph
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
|
||||||
<span class="block text-gray-500">Inversions</span>
|
|
||||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
|
||||||
{{ coaster_stats.inversions }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% if coaster_stats.ride_time_seconds %}
|
{% if coaster_stats.ride_time_seconds %}
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-gray-500">Ride Duration</span>
|
<span class="block text-gray-500 dark:text-gray-400">Ride Duration</span>
|
||||||
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
{{ coaster_stats.ride_time_seconds }} sec
|
{{ coaster_stats.ride_time_seconds }} sec
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Train Details -->
|
||||||
|
{% if coaster_stats.train_style %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Train Style</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.train_style }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if coaster_stats.trains_count %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Number of Trains</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.trains_count }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if coaster_stats.cars_per_train %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Cars per Train</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.cars_per_train }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if coaster_stats.seats_per_car %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Seats per Car</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.seats_per_car }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Other Stats -->
|
||||||
|
{% if coaster_stats.inversions %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Inversions</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.inversions }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if coaster_stats.launch_type %}
|
||||||
|
<div>
|
||||||
|
<span class="block text-gray-500 dark:text-gray-400">Launch Type</span>
|
||||||
|
<span class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
|
{{ coaster_stats.get_launch_type_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -140,18 +369,29 @@
|
|||||||
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Quick Facts</h2>
|
<h2 class="mb-4 text-xl font-semibold text-gray-900 dark:text-white">Quick Facts</h2>
|
||||||
<dl class="space-y-4">
|
<dl class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500">Manufacturer</dt>
|
<dt class="text-gray-500 dark:text-gray-400">Manufacturer</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">{{ ride.manufacturer }}</dd>
|
<dd class="font-medium text-gray-900 dark:text-white">{{ ride.manufacturer }}</dd>
|
||||||
</div>
|
</div>
|
||||||
|
{% if ride.designer %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-500 dark:text-gray-400">Designer</dt>
|
||||||
|
<dd class="font-medium text-gray-900 dark:text-white">
|
||||||
|
<a href="{% url 'designers:designer_detail' ride.designer.slug %}"
|
||||||
|
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
||||||
|
{{ ride.designer.name }}
|
||||||
|
</a>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if ride.model_name %}
|
{% if ride.model_name %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500">Model</dt>
|
<dt class="text-gray-500 dark:text-gray-400">Model</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">{{ ride.model_name }}</dd>
|
<dd class="font-medium text-gray-900 dark:text-white">{{ ride.model_name }}</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ride.opening_date %}
|
{% if ride.opening_date %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500">Opening Date</dt>
|
<dt class="text-gray-500 dark:text-gray-400">Opening Date</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-gray-900 dark:text-white">
|
||||||
{{ ride.opening_date }}
|
{{ ride.opening_date }}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -159,7 +399,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ride.status_since %}
|
{% if ride.status_since %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500">Status Since</dt>
|
<dt class="text-gray-500 dark:text-gray-400">Status Since</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-gray-900 dark:text-white">
|
||||||
{{ ride.status_since }}
|
{{ ride.status_since }}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -167,7 +407,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ride.closing_date %}
|
{% if ride.closing_date %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500">Closing Date</dt>
|
<dt class="text-gray-500 dark:text-gray-400">Closing Date</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-gray-900 dark:text-white">
|
||||||
{{ ride.closing_date }}
|
{{ ride.closing_date }}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -175,7 +415,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ride.capacity_per_hour %}
|
{% if ride.capacity_per_hour %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500">Capacity</dt>
|
<dt class="text-gray-500 dark:text-gray-400">Capacity</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-gray-900 dark:text-white">
|
||||||
{{ ride.capacity_per_hour }} riders/hour
|
{{ ride.capacity_per_hour }} riders/hour
|
||||||
</dd>
|
</dd>
|
||||||
@@ -183,7 +423,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ride.min_height_in %}
|
{% if ride.min_height_in %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-500">Minimum Height</dt>
|
<dt class="text-gray-500 dark:text-gray-400">Minimum Height</dt>
|
||||||
<dd class="font-medium text-gray-900 dark:text-white">
|
<dd class="font-medium text-gray-900 dark:text-white">
|
||||||
{{ ride.min_height_in }} inches
|
{{ ride.min_height_in }} inches
|
||||||
</dd>
|
</dd>
|
||||||
@@ -206,65 +446,33 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
{% for field, changes in record.diff_against_previous.items %}
|
{% for field, changes in record.diff_against_previous.items %}
|
||||||
<div class="text-sm">
|
{% if field != "updated_at" %}
|
||||||
<span class="font-medium">{{ field }}:</span>
|
<div class="text-sm">
|
||||||
{{ changes.old }} → {{ changes.new }}
|
<span class="font-medium">{{ field|title }}:</span>
|
||||||
</div>
|
<span class="text-red-600 dark:text-red-400">{{ changes.old }}</span>
|
||||||
|
<span class="mx-1">→</span>
|
||||||
|
<span class="text-green-600 dark:text-green-400">{{ changes.new }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p class="text-gray-500">No history available.</p>
|
<p class="text-gray-500 dark:text-gray-400">No history available.</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Reviews Section -->
|
|
||||||
<div class="p-6 mt-6 bg-white rounded-lg shadow dark:bg-gray-800">
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Reviews</h2>
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<button class="btn-primary">
|
|
||||||
<i class="mr-2 fas fa-star"></i>
|
|
||||||
Write a Review
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if ride.reviews.exists %}
|
|
||||||
<div class="space-y-4">
|
|
||||||
{% for review in ride.reviews.all %}
|
|
||||||
<div class="pb-4 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="font-semibold text-gray-900 dark:text-white">{{ review.title }}</h3>
|
|
||||||
<p class="text-sm text-gray-500">
|
|
||||||
by {{ review.user.username }} on {{ review.created_at|date }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="mr-1 text-yellow-400">★</span>
|
|
||||||
<span>{{ review.rating }}/10</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="mt-2 text-gray-700 dark:text-gray-300">{{ review.content }}</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="text-gray-500">No reviews yet. Be the first to review this ride!</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Photo Upload Modal -->
|
<!-- Photo Upload Modal -->
|
||||||
{% if perms.media.add_photo %}
|
{% if perms.media.add_photo %}
|
||||||
<div x-data="{ show: false }"
|
<div x-cloak
|
||||||
|
x-data="{ show: false }"
|
||||||
@show-photo-upload.window="show = true"
|
@show-photo-upload.window="show = true"
|
||||||
x-show="show"
|
x-show="show"
|
||||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
|
class="fixed inset-0 z-[60] flex items-center justify-center bg-black bg-opacity-50"
|
||||||
@click.self="show = false">
|
@click.self="show = false">
|
||||||
<div class="w-full max-w-2xl p-6 mx-4 bg-white rounded-lg shadow-xl dark:bg-gray-800">
|
<div class="w-full max-w-2xl p-6 mx-4 bg-white rounded-lg shadow-xl dark:bg-gray-800">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -45,6 +45,7 @@ INSTALLED_APPS = [
|
|||||||
"media.apps.MediaConfig",
|
"media.apps.MediaConfig",
|
||||||
"moderation",
|
"moderation",
|
||||||
"history_tracking",
|
"history_tracking",
|
||||||
|
"designers",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ urlpatterns = [
|
|||||||
# Other URLs
|
# Other URLs
|
||||||
path("reviews/", include("reviews.urls")),
|
path("reviews/", include("reviews.urls")),
|
||||||
path("companies/", include("companies.urls")),
|
path("companies/", include("companies.urls")),
|
||||||
|
path("designers/", include("designers.urls", namespace="designers")),
|
||||||
path("photos/", include("media.urls", namespace="photos")), # Add photos URLs
|
path("photos/", include("media.urls", namespace="photos")), # Add photos URLs
|
||||||
path("search/", SearchView.as_view(), name="search"),
|
path("search/", SearchView.as_view(), name="search"),
|
||||||
path(
|
path(
|
||||||
|
|||||||
Reference in New Issue
Block a user