mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 09:31:09 -05:00
code commit
This commit is contained in:
BIN
media/submissions/photos/coaster_track.gif
Normal file
BIN
media/submissions/photos/coaster_track.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 B |
BIN
media/submissions/photos/park_entrance.gif
Normal file
BIN
media/submissions/photos/park_entrance.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 B |
BIN
media/submissions/photos/test_image.gif
Normal file
BIN
media/submissions/photos/test_image.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 B |
BIN
media/submissions/photos/test_image2.gif
Normal file
BIN
media/submissions/photos/test_image2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 B |
BIN
media/submissions/photos/test_image2_ruT57k4.gif
Normal file
BIN
media/submissions/photos/test_image2_ruT57k4.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 B |
BIN
media/submissions/photos/test_image_iI0mcgf.gif
Normal file
BIN
media/submissions/photos/test_image_iI0mcgf.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 B |
0
moderation/management/__init__.py
Normal file
0
moderation/management/__init__.py
Normal file
0
moderation/management/commands/__init__.py
Normal file
0
moderation/management/commands/__init__.py
Normal file
228
moderation/management/commands/seed_submissions.py
Normal file
228
moderation/management/commands/seed_submissions.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
|
from django.utils import timezone
|
||||||
|
from moderation.models import EditSubmission, PhotoSubmission
|
||||||
|
from parks.models import Park
|
||||||
|
from rides.models import Ride
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Seeds test submissions for moderation dashboard'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
# Ensure we have a test user
|
||||||
|
user, created = User.objects.get_or_create(
|
||||||
|
username='test_user',
|
||||||
|
email='test@example.com'
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
user.set_password('testpass123')
|
||||||
|
user.save()
|
||||||
|
self.stdout.write(self.style.SUCCESS('Created test user'))
|
||||||
|
|
||||||
|
# Get content types
|
||||||
|
park_ct = ContentType.objects.get_for_model(Park)
|
||||||
|
ride_ct = ContentType.objects.get_for_model(Ride)
|
||||||
|
|
||||||
|
# Create test park for edit submissions
|
||||||
|
test_park, created = Park.objects.get_or_create(
|
||||||
|
name='Test Park',
|
||||||
|
defaults={
|
||||||
|
'description': 'A test theme park located in Orlando, Florida',
|
||||||
|
'status': 'OPERATING',
|
||||||
|
'operating_season': 'Year-round',
|
||||||
|
'size_acres': 100.50,
|
||||||
|
'website': 'https://testpark.example.com'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create test ride for edit submissions
|
||||||
|
test_ride, created = Ride.objects.get_or_create(
|
||||||
|
name='Test Coaster',
|
||||||
|
park=test_park,
|
||||||
|
defaults={
|
||||||
|
'description': 'A thrilling steel roller coaster with multiple inversions',
|
||||||
|
'status': 'OPERATING',
|
||||||
|
'category': 'RC',
|
||||||
|
'capacity_per_hour': 1200,
|
||||||
|
'ride_duration_seconds': 180,
|
||||||
|
'min_height_in': 48,
|
||||||
|
'opening_date': date(2020, 6, 15)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create EditSubmissions
|
||||||
|
|
||||||
|
# New park creation with detailed information
|
||||||
|
EditSubmission.objects.create(
|
||||||
|
user=user,
|
||||||
|
content_type=park_ct,
|
||||||
|
submission_type='CREATE',
|
||||||
|
changes={
|
||||||
|
'name': 'Adventure World Orlando',
|
||||||
|
'description': ('A brand new theme park coming to Orlando featuring five uniquely themed lands: '
|
||||||
|
'Future Frontier, Ancient Mysteries, Ocean Depths, Sky Kingdom, and Fantasy Forest. '
|
||||||
|
'The park will feature state-of-the-art attractions including 3 roller coasters, '
|
||||||
|
'4 dark rides, and multiple family attractions in each themed area.'),
|
||||||
|
'status': 'UNDER_CONSTRUCTION',
|
||||||
|
'opening_date': '2024-06-01',
|
||||||
|
'operating_season': 'Year-round with extended hours during summer and holidays',
|
||||||
|
'size_acres': 250.75,
|
||||||
|
'website': 'https://adventureworld.example.com',
|
||||||
|
'location': {
|
||||||
|
'street_address': '1234 Theme Park Way',
|
||||||
|
'city': 'Orlando',
|
||||||
|
'state': 'Florida',
|
||||||
|
'country': 'United States',
|
||||||
|
'postal_code': '32819',
|
||||||
|
'latitude': '28.538336',
|
||||||
|
'longitude': '-81.379234'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reason=('Submitting new theme park details based on official press release and construction permits. '
|
||||||
|
'The park has begun vertical construction and has announced its opening date.'),
|
||||||
|
source=('Official press release: https://adventureworld.example.com/press/announcement\n'
|
||||||
|
'Construction permits: Orange County Building Department #2023-12345'),
|
||||||
|
status='PENDING'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Existing park edit with comprehensive updates
|
||||||
|
EditSubmission.objects.create(
|
||||||
|
user=user,
|
||||||
|
content_type=park_ct,
|
||||||
|
object_id=test_park.id,
|
||||||
|
submission_type='EDIT',
|
||||||
|
changes={
|
||||||
|
'description': ('A world-class theme park featuring 12 uniquely themed areas and over 50 attractions. '
|
||||||
|
'Recent expansion added the new "Cosmic Adventures" area with 2 roller coasters and '
|
||||||
|
'3 family attractions. The park now offers enhanced dining options and night-time '
|
||||||
|
'spectacular "Starlight Dreams".'),
|
||||||
|
'status': 'OPERATING',
|
||||||
|
'website': 'https://testpark.example.com',
|
||||||
|
'size_acres': 120.25,
|
||||||
|
'operating_season': ('Year-round with extended hours (9AM-11PM) during summer. '
|
||||||
|
'Special events during Halloween and Christmas seasons.'),
|
||||||
|
'location': {
|
||||||
|
'street_address': '5678 Park Boulevard',
|
||||||
|
'city': 'Orlando',
|
||||||
|
'state': 'Florida',
|
||||||
|
'country': 'United States',
|
||||||
|
'postal_code': '32830',
|
||||||
|
'latitude': '28.538336',
|
||||||
|
'longitude': '-81.379234'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reason=('Updating park information to reflect recent expansion and operational changes. '
|
||||||
|
'The new Cosmic Adventures area opened last month and operating hours have been extended.'),
|
||||||
|
source=('Park press release: https://testpark.example.com/news/expansion\n'
|
||||||
|
'Official park map: https://testpark.example.com/map\n'
|
||||||
|
'Personal visit and photos from opening day of new area'),
|
||||||
|
status='PENDING'
|
||||||
|
)
|
||||||
|
|
||||||
|
# New ride creation with detailed specifications
|
||||||
|
EditSubmission.objects.create(
|
||||||
|
user=user,
|
||||||
|
content_type=ride_ct,
|
||||||
|
submission_type='CREATE',
|
||||||
|
changes={
|
||||||
|
'name': 'Thunderbolt: The Ultimate Launch Coaster',
|
||||||
|
'park': test_park.id,
|
||||||
|
'description': ('A cutting-edge steel launch coaster featuring the world\'s tallest inversion (160 ft) '
|
||||||
|
'and fastest launch acceleration (0-80 mph in 2 seconds). The ride features a unique '
|
||||||
|
'triple launch system, 5 inversions including a zero-g roll and cobra roll, and a '
|
||||||
|
'first-of-its-kind vertical helix element. Total track length is 4,500 feet with a '
|
||||||
|
'maximum height of 375 feet.'),
|
||||||
|
'status': 'UNDER_CONSTRUCTION',
|
||||||
|
'category': 'RC',
|
||||||
|
'opening_date': '2024-07-01',
|
||||||
|
'capacity_per_hour': 1400,
|
||||||
|
'ride_duration_seconds': 210,
|
||||||
|
'min_height_in': 52,
|
||||||
|
'manufacturer': 1, # Assuming manufacturer ID
|
||||||
|
'park_area': 1, # Assuming park area ID
|
||||||
|
'stats': {
|
||||||
|
'height_ft': 375,
|
||||||
|
'length_ft': 4500,
|
||||||
|
'speed_mph': 80,
|
||||||
|
'inversions': 5,
|
||||||
|
'launch_type': 'LSM',
|
||||||
|
'track_material': 'STEEL',
|
||||||
|
'roller_coaster_type': 'SITDOWN',
|
||||||
|
'trains_count': 3,
|
||||||
|
'cars_per_train': 6,
|
||||||
|
'seats_per_car': 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reason=('Submitting details for the new flagship roller coaster announced by the park. '
|
||||||
|
'Construction has begun and track pieces are arriving on site.'),
|
||||||
|
source=('Official announcement: https://testpark.example.com/thunderbolt\n'
|
||||||
|
'Construction photos: https://coasterfan.com/thunderbolt-construction\n'
|
||||||
|
'Manufacturer specifications sheet'),
|
||||||
|
status='PENDING'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Existing ride edit with technical updates
|
||||||
|
EditSubmission.objects.create(
|
||||||
|
user=user,
|
||||||
|
content_type=ride_ct,
|
||||||
|
object_id=test_ride.id,
|
||||||
|
submission_type='EDIT',
|
||||||
|
changes={
|
||||||
|
'description': ('A high-speed steel roller coaster featuring 4 inversions and a unique '
|
||||||
|
'dual-loading station system. Recent upgrades include new magnetic braking '
|
||||||
|
'system and enhanced on-board audio experience.'),
|
||||||
|
'status': 'OPERATING',
|
||||||
|
'capacity_per_hour': 1500, # Increased after station upgrades
|
||||||
|
'ride_duration_seconds': 185,
|
||||||
|
'min_height_in': 48,
|
||||||
|
'max_height_in': 80,
|
||||||
|
'stats': {
|
||||||
|
'trains_count': 3,
|
||||||
|
'cars_per_train': 8,
|
||||||
|
'seats_per_car': 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reason=('Updating ride information to reflect recent upgrades including new braking system, '
|
||||||
|
'audio system, and increased capacity due to improved loading efficiency.'),
|
||||||
|
source=('Park operations manual\n'
|
||||||
|
'Maintenance records\n'
|
||||||
|
'Personal observation and timing of new ride cycle'),
|
||||||
|
status='PENDING'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create PhotoSubmissions with detailed captions
|
||||||
|
|
||||||
|
# Park photo submission
|
||||||
|
image_data = b'GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;'
|
||||||
|
dummy_image = SimpleUploadedFile('park_entrance.gif', image_data, content_type='image/gif')
|
||||||
|
|
||||||
|
PhotoSubmission.objects.create(
|
||||||
|
user=user,
|
||||||
|
content_type=park_ct,
|
||||||
|
object_id=test_park.id,
|
||||||
|
photo=dummy_image,
|
||||||
|
caption=('Main entrance plaza of Test Park showing the newly installed digital display board '
|
||||||
|
'and renovated ticketing area. Photo taken during morning park opening.'),
|
||||||
|
date_taken=date(2024, 1, 15),
|
||||||
|
status='PENDING'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ride photo submission
|
||||||
|
dummy_image2 = SimpleUploadedFile('coaster_track.gif', image_data, content_type='image/gif')
|
||||||
|
PhotoSubmission.objects.create(
|
||||||
|
user=user,
|
||||||
|
content_type=ride_ct,
|
||||||
|
object_id=test_ride.id,
|
||||||
|
photo=dummy_image2,
|
||||||
|
caption=('Test Coaster\'s first drop and loop element showing the new paint scheme. '
|
||||||
|
'Photo taken from the guest pathway near Station Alpha.'),
|
||||||
|
date_taken=date(2024, 1, 20),
|
||||||
|
status='PENDING'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS('Successfully seeded test submissions'))
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2024-11-13 19:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("moderation", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="editsubmission",
|
||||||
|
name="status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("PENDING", "Pending"),
|
||||||
|
("APPROVED", "Approved"),
|
||||||
|
("REJECTED", "Rejected"),
|
||||||
|
("ESCALATED", "Escalated"),
|
||||||
|
],
|
||||||
|
default="PENDING",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="photosubmission",
|
||||||
|
name="status",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("PENDING", "Pending"),
|
||||||
|
("APPROVED", "Approved"),
|
||||||
|
("REJECTED", "Rejected"),
|
||||||
|
("ESCALATED", "Escalated"),
|
||||||
|
],
|
||||||
|
default="PENDING",
|
||||||
|
max_length=20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
32
moderation/migrations/0003_update_existing_statuses.py
Normal file
32
moderation/migrations/0003_update_existing_statuses.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def update_statuses(apps, schema_editor):
|
||||||
|
EditSubmission = apps.get_model('moderation', 'EditSubmission')
|
||||||
|
PhotoSubmission = apps.get_model('moderation', 'PhotoSubmission')
|
||||||
|
|
||||||
|
# Update EditSubmissions
|
||||||
|
EditSubmission.objects.filter(status='NEW').update(status='PENDING')
|
||||||
|
|
||||||
|
# Update PhotoSubmissions
|
||||||
|
PhotoSubmission.objects.filter(status='NEW').update(status='PENDING')
|
||||||
|
PhotoSubmission.objects.filter(status='AUTO_APPROVED').update(status='APPROVED')
|
||||||
|
|
||||||
|
def reverse_statuses(apps, schema_editor):
|
||||||
|
EditSubmission = apps.get_model('moderation', 'EditSubmission')
|
||||||
|
PhotoSubmission = apps.get_model('moderation', 'PhotoSubmission')
|
||||||
|
|
||||||
|
# Reverse EditSubmissions
|
||||||
|
EditSubmission.objects.filter(status='PENDING').update(status='NEW')
|
||||||
|
|
||||||
|
# Reverse PhotoSubmissions
|
||||||
|
PhotoSubmission.objects.filter(status='PENDING').update(status='NEW')
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('moderation', '0002_alter_editsubmission_status_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(update_statuses, reverse_statuses),
|
||||||
|
]
|
||||||
22
moderation/migrations/0004_add_moderator_changes.py
Normal file
22
moderation/migrations/0004_add_moderator_changes.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.1.3 on 2024-11-13 20:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("moderation", "0003_update_existing_statuses"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="editsubmission",
|
||||||
|
name="moderator_changes",
|
||||||
|
field=models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Moderator's edited version of the changes before approval",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -15,7 +15,7 @@ UserType = Union[AbstractBaseUser, AnonymousUser]
|
|||||||
|
|
||||||
class EditSubmission(models.Model):
|
class EditSubmission(models.Model):
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
("NEW", "New"),
|
("PENDING", "Pending"),
|
||||||
("APPROVED", "Approved"),
|
("APPROVED", "Approved"),
|
||||||
("REJECTED", "Rejected"),
|
("REJECTED", "Rejected"),
|
||||||
("ESCALATED", "Escalated"),
|
("ESCALATED", "Escalated"),
|
||||||
@@ -49,13 +49,20 @@ class EditSubmission(models.Model):
|
|||||||
changes = models.JSONField(
|
changes = models.JSONField(
|
||||||
help_text="JSON representation of the changes or new object data"
|
help_text="JSON representation of the changes or new object data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Moderator's edited version of changes before approval
|
||||||
|
moderator_changes = models.JSONField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Moderator's edited version of the changes before approval"
|
||||||
|
)
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
reason = models.TextField(help_text="Why this edit/addition is needed")
|
reason = models.TextField(help_text="Why this edit/addition is needed")
|
||||||
source = models.TextField(
|
source = models.TextField(
|
||||||
blank=True, help_text="Source of information (if applicable)"
|
blank=True, help_text="Source of information (if applicable)"
|
||||||
)
|
)
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="NEW")
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING")
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
# Review details
|
# Review details
|
||||||
@@ -133,7 +140,10 @@ class EditSubmission(models.Model):
|
|||||||
raise ValueError("Could not resolve model class")
|
raise ValueError("Could not resolve model class")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resolved_data = self._resolve_foreign_keys(self.changes)
|
# Use moderator_changes if available, otherwise use original changes
|
||||||
|
changes_to_apply = self.moderator_changes if self.moderator_changes is not None else self.changes
|
||||||
|
|
||||||
|
resolved_data = self._resolve_foreign_keys(changes_to_apply)
|
||||||
prepared_data = self._prepare_model_data(resolved_data, model_class)
|
prepared_data = self._prepare_model_data(resolved_data, model_class)
|
||||||
|
|
||||||
# For CREATE submissions, check for duplicates by name
|
# For CREATE submissions, check for duplicates by name
|
||||||
@@ -168,7 +178,7 @@ class EditSubmission(models.Model):
|
|||||||
return obj
|
return obj
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.status != "REJECTED": # Don't override if already rejected due to duplicate
|
if self.status != "REJECTED": # Don't override if already rejected due to duplicate
|
||||||
self.status = "NEW" # Reset status if approval failed
|
self.status = "PENDING" # Reset status if approval failed
|
||||||
self.save()
|
self.save()
|
||||||
raise ValueError(f"Error approving submission: {str(e)}") from e
|
raise ValueError(f"Error approving submission: {str(e)}") from e
|
||||||
|
|
||||||
@@ -189,10 +199,10 @@ class EditSubmission(models.Model):
|
|||||||
|
|
||||||
class PhotoSubmission(models.Model):
|
class PhotoSubmission(models.Model):
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
("NEW", "New"),
|
("PENDING", "Pending"),
|
||||||
("APPROVED", "Approved"),
|
("APPROVED", "Approved"),
|
||||||
("REJECTED", "Rejected"),
|
("REJECTED", "Rejected"),
|
||||||
("AUTO_APPROVED", "Auto Approved"),
|
("ESCALATED", "Escalated"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Who submitted the photo
|
# Who submitted the photo
|
||||||
@@ -213,7 +223,7 @@ class PhotoSubmission(models.Model):
|
|||||||
date_taken = models.DateField(null=True, blank=True)
|
date_taken = models.DateField(null=True, blank=True)
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="NEW")
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING")
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
# Review details
|
# Review details
|
||||||
@@ -268,22 +278,10 @@ class PhotoSubmission(models.Model):
|
|||||||
self.notes = notes
|
self.notes = notes
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def auto_approve(self) -> None:
|
def escalate(self, moderator: UserType, notes: str = "") -> None:
|
||||||
"""Auto-approve the photo submission (for moderators/admins)"""
|
"""Escalate the photo submission to admin"""
|
||||||
from media.models import Photo
|
self.status = "ESCALATED"
|
||||||
|
self.handled_by = moderator # type: ignore
|
||||||
self.status = "AUTO_APPROVED"
|
|
||||||
self.handled_by = self.user
|
|
||||||
self.handled_at = timezone.now()
|
self.handled_at = timezone.now()
|
||||||
|
self.notes = notes
|
||||||
# Create the approved photo
|
|
||||||
Photo.objects.create(
|
|
||||||
uploaded_by=self.user,
|
|
||||||
content_type=self.content_type,
|
|
||||||
object_id=self.object_id,
|
|
||||||
image=self.photo,
|
|
||||||
caption=self.caption,
|
|
||||||
is_approved=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|||||||
54
moderation/templatetags/moderation_tags.py
Normal file
54
moderation/templatetags/moderation_tags.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from django import template
|
||||||
|
from django.apps import apps
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from rides.models import CATEGORY_CHOICES
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_object(value, model_path):
|
||||||
|
"""
|
||||||
|
Template filter to get an object instance from its ID and model path.
|
||||||
|
Usage: {{ value|get_object:'app_label.ModelName' }}
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
app_label, model_name = model_path.split('.')
|
||||||
|
model = apps.get_model(app_label, model_name)
|
||||||
|
return model.objects.get(id=value)
|
||||||
|
except (ValueError, LookupError, ObjectDoesNotExist):
|
||||||
|
return None
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_category_display(value):
|
||||||
|
"""
|
||||||
|
Template filter to get the display name for a ride category.
|
||||||
|
Usage: {{ value|get_category_display }}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return dict(CATEGORY_CHOICES).get(value, value)
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_object_name(value, model_path):
|
||||||
|
"""
|
||||||
|
Template filter to get an object's name from its ID and model path.
|
||||||
|
Usage: {{ value|get_object_name:'app_label.ModelName' }}
|
||||||
|
"""
|
||||||
|
obj = get_object(value, model_path)
|
||||||
|
return obj.name if obj else None
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_park_area_name(value, park_id):
|
||||||
|
"""
|
||||||
|
Template filter to get a park area's name from its ID and park ID.
|
||||||
|
Usage: {{ value|get_park_area_name:park_id }}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ParkArea = apps.get_model('parks', 'ParkArea')
|
||||||
|
area = ParkArea.objects.get(id=value, park_id=park_id)
|
||||||
|
return area.name
|
||||||
|
except (ValueError, LookupError, ObjectDoesNotExist):
|
||||||
|
return None
|
||||||
@@ -16,7 +16,14 @@ urlpatterns = [
|
|||||||
path('dashboard/', views.DashboardView.as_view(), name='dashboard'),
|
path('dashboard/', views.DashboardView.as_view(), name='dashboard'),
|
||||||
path('submissions/', views.submission_list, name='submission_list'),
|
path('submissions/', views.submission_list, name='submission_list'),
|
||||||
|
|
||||||
|
# Search endpoints
|
||||||
|
path('search/parks/', views.search_parks, name='search_parks'),
|
||||||
|
path('search/manufacturers/', views.search_manufacturers, name='search_manufacturers'),
|
||||||
|
path('search/designers/', views.search_designers, name='search_designers'),
|
||||||
|
path('search/ride-models/', views.search_ride_models, name='search_ride_models'),
|
||||||
|
|
||||||
# Submission Actions
|
# Submission Actions
|
||||||
|
path('submissions/<int:submission_id>/edit/', views.edit_submission, name='edit_submission'),
|
||||||
path('submissions/<int:submission_id>/approve/', views.approve_submission, name='approve_submission'),
|
path('submissions/<int:submission_id>/approve/', views.approve_submission, name='approve_submission'),
|
||||||
path('submissions/<int:submission_id>/reject/', views.reject_submission, name='reject_submission'),
|
path('submissions/<int:submission_id>/reject/', views.reject_submission, name='reject_submission'),
|
||||||
path('submissions/<int:submission_id>/escalate/', views.escalate_submission, name='escalate_submission'),
|
path('submissions/<int:submission_id>/escalate/', views.escalate_submission, name='escalate_submission'),
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ from typing import Optional, Any, cast
|
|||||||
from accounts.models import User
|
from accounts.models import User
|
||||||
|
|
||||||
from .models import EditSubmission, PhotoSubmission
|
from .models import EditSubmission, PhotoSubmission
|
||||||
|
from parks.models import Park, ParkArea
|
||||||
|
from designers.models import Designer
|
||||||
|
from companies.models import Manufacturer
|
||||||
|
from rides.models import RideModel
|
||||||
|
|
||||||
MODERATOR_ROLES = ['MODERATOR', 'ADMIN', 'SUPERUSER']
|
MODERATOR_ROLES = ['MODERATOR', 'ADMIN', 'SUPERUSER']
|
||||||
|
|
||||||
@@ -29,6 +33,108 @@ class ModeratorRequiredMixin(UserPassesTestMixin):
|
|||||||
return super().handle_no_permission()
|
return super().handle_no_permission()
|
||||||
raise PermissionDenied("You do not have moderator permissions.")
|
raise PermissionDenied("You do not have moderator permissions.")
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def search_parks(request: HttpRequest) -> HttpResponse:
|
||||||
|
"""HTMX endpoint for searching parks in moderation dashboard"""
|
||||||
|
user = cast(User, request.user)
|
||||||
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
||||||
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
|
query = request.GET.get('q', '').strip()
|
||||||
|
submission_id = request.GET.get('submission_id')
|
||||||
|
|
||||||
|
# If no query, show first 10 parks
|
||||||
|
if not query:
|
||||||
|
parks = Park.objects.all().order_by('name')[:10]
|
||||||
|
else:
|
||||||
|
parks = Park.objects.filter(name__icontains=query).order_by('name')[:10]
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'parks': parks,
|
||||||
|
'search_term': query,
|
||||||
|
'submission_id': submission_id
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'moderation/partials/park_search_results.html', context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def search_manufacturers(request: HttpRequest) -> HttpResponse:
|
||||||
|
"""HTMX endpoint for searching manufacturers in moderation dashboard"""
|
||||||
|
user = cast(User, request.user)
|
||||||
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
||||||
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
|
query = request.GET.get('q', '').strip()
|
||||||
|
submission_id = request.GET.get('submission_id')
|
||||||
|
|
||||||
|
# If no query, show first 10 manufacturers
|
||||||
|
if not query:
|
||||||
|
manufacturers = Manufacturer.objects.all().order_by('name')[:10]
|
||||||
|
else:
|
||||||
|
manufacturers = Manufacturer.objects.filter(name__icontains=query).order_by('name')[:10]
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'manufacturers': manufacturers,
|
||||||
|
'search_term': query,
|
||||||
|
'submission_id': submission_id
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'moderation/partials/manufacturer_search_results.html', context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def search_designers(request: HttpRequest) -> HttpResponse:
|
||||||
|
"""HTMX endpoint for searching designers in moderation dashboard"""
|
||||||
|
user = cast(User, request.user)
|
||||||
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
||||||
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
|
query = request.GET.get('q', '').strip()
|
||||||
|
submission_id = request.GET.get('submission_id')
|
||||||
|
|
||||||
|
# If no query, show first 10 designers
|
||||||
|
if not query:
|
||||||
|
designers = Designer.objects.all().order_by('name')[:10]
|
||||||
|
else:
|
||||||
|
designers = Designer.objects.filter(name__icontains=query).order_by('name')[:10]
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'designers': designers,
|
||||||
|
'search_term': query,
|
||||||
|
'submission_id': submission_id
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'moderation/partials/designer_search_results.html', context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def search_ride_models(request: HttpRequest) -> HttpResponse:
|
||||||
|
"""HTMX endpoint for searching ride models in moderation dashboard"""
|
||||||
|
user = cast(User, request.user)
|
||||||
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
||||||
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
|
query = request.GET.get('q', '').strip()
|
||||||
|
submission_id = request.GET.get('submission_id')
|
||||||
|
manufacturer_id = request.GET.get('manufacturer')
|
||||||
|
|
||||||
|
queryset = RideModel.objects.all()
|
||||||
|
|
||||||
|
if manufacturer_id:
|
||||||
|
queryset = queryset.filter(manufacturer_id=manufacturer_id)
|
||||||
|
|
||||||
|
# If no query, show first 10 models
|
||||||
|
if not query:
|
||||||
|
ride_models = queryset.order_by('name')[:10]
|
||||||
|
else:
|
||||||
|
ride_models = queryset.filter(name__icontains=query).order_by('name')[:10]
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'ride_models': ride_models,
|
||||||
|
'search_term': query,
|
||||||
|
'submission_id': submission_id
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'moderation/partials/ride_model_search_results.html', context)
|
||||||
|
|
||||||
class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
||||||
template_name = 'moderation/dashboard.html'
|
template_name = 'moderation/dashboard.html'
|
||||||
context_object_name = 'submissions'
|
context_object_name = 'submissions'
|
||||||
@@ -40,7 +146,7 @@ class DashboardView(LoginRequiredMixin, ModeratorRequiredMixin, ListView):
|
|||||||
return [self.template_name]
|
return [self.template_name]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
status = self.request.GET.get('status', 'NEW')
|
status = self.request.GET.get('status', 'PENDING')
|
||||||
submission_type = self.request.GET.get('submission_type', '')
|
submission_type = self.request.GET.get('submission_type', '')
|
||||||
|
|
||||||
if submission_type == 'photo':
|
if submission_type == 'photo':
|
||||||
@@ -63,7 +169,7 @@ def submission_list(request: HttpRequest) -> HttpResponse:
|
|||||||
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
||||||
return HttpResponse(status=403)
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
status = request.GET.get('status', 'NEW')
|
status = request.GET.get('status', 'PENDING')
|
||||||
submission_type = request.GET.get('submission_type', '')
|
submission_type = request.GET.get('submission_type', '')
|
||||||
|
|
||||||
if submission_type == 'photo':
|
if submission_type == 'photo':
|
||||||
@@ -80,6 +186,11 @@ def submission_list(request: HttpRequest) -> HttpResponse:
|
|||||||
context = {
|
context = {
|
||||||
'submissions': queryset,
|
'submissions': queryset,
|
||||||
'user': request.user,
|
'user': request.user,
|
||||||
|
'parks': [(p.id, str(p)) for p in Park.objects.all()],
|
||||||
|
'designers': [(d.id, str(d)) for d in Designer.objects.all()],
|
||||||
|
'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()],
|
||||||
|
'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()],
|
||||||
|
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])]
|
||||||
}
|
}
|
||||||
|
|
||||||
# If it's an HTMX request, return just the content
|
# If it's an HTMX request, return just the content
|
||||||
@@ -89,6 +200,64 @@ def submission_list(request: HttpRequest) -> HttpResponse:
|
|||||||
# For direct URL access, return the full dashboard template
|
# For direct URL access, return the full dashboard template
|
||||||
return render(request, 'moderation/dashboard.html', context)
|
return render(request, 'moderation/dashboard.html', context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||||
|
"""HTMX endpoint for editing a submission"""
|
||||||
|
user = cast(User, request.user)
|
||||||
|
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
||||||
|
return HttpResponse(status=403)
|
||||||
|
|
||||||
|
submission = get_object_or_404(EditSubmission, id=submission_id)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Handle the edit submission
|
||||||
|
notes = request.POST.get('notes')
|
||||||
|
if not notes:
|
||||||
|
return HttpResponse("Notes are required when editing a submission", status=400)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Update the moderator_changes with the edited values
|
||||||
|
edited_changes = submission.changes.copy()
|
||||||
|
for field in submission.changes.keys():
|
||||||
|
if field == 'stats':
|
||||||
|
edited_stats = {}
|
||||||
|
for key in submission.changes['stats'].keys():
|
||||||
|
if new_value := request.POST.get(f'stats.{key}'):
|
||||||
|
edited_stats[key] = new_value
|
||||||
|
edited_changes['stats'] = edited_stats
|
||||||
|
else:
|
||||||
|
if new_value := request.POST.get(field):
|
||||||
|
# Handle special field types
|
||||||
|
if field in ['latitude', 'longitude', 'size_acres']:
|
||||||
|
try:
|
||||||
|
edited_changes[field] = float(new_value)
|
||||||
|
except ValueError:
|
||||||
|
return HttpResponse(f"Invalid value for {field}", status=400)
|
||||||
|
else:
|
||||||
|
edited_changes[field] = new_value
|
||||||
|
|
||||||
|
submission.moderator_changes = edited_changes
|
||||||
|
submission.notes = notes
|
||||||
|
submission.save()
|
||||||
|
|
||||||
|
# Return the updated submission
|
||||||
|
context = {
|
||||||
|
'submission': submission,
|
||||||
|
'user': request.user,
|
||||||
|
'parks': [(p.id, str(p)) for p in Park.objects.all()],
|
||||||
|
'designers': [(d.id, str(d)) for d in Designer.objects.all()],
|
||||||
|
'manufacturers': [(m.id, str(m)) for m in Manufacturer.objects.all()],
|
||||||
|
'ride_models': [(m.id, str(m)) for m in RideModel.objects.all()],
|
||||||
|
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])],
|
||||||
|
'park_areas': [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=edited_changes.get('park'))] if edited_changes.get('park') else []
|
||||||
|
}
|
||||||
|
return render(request, 'moderation/partials/submission_list.html', context)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return HttpResponse(str(e), status=400)
|
||||||
|
|
||||||
|
return HttpResponse("Invalid request method", status=405)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||||
"""HTMX endpoint for approving a submission"""
|
"""HTMX endpoint for approving a submission"""
|
||||||
@@ -105,7 +274,7 @@ def approve_submission(request: HttpRequest, submission_id: int) -> HttpResponse
|
|||||||
_update_submission_notes(submission, request.POST.get('notes'))
|
_update_submission_notes(submission, request.POST.get('notes'))
|
||||||
|
|
||||||
# Get updated queryset with filters
|
# Get updated queryset with filters
|
||||||
status = request.GET.get('status', 'NEW')
|
status = request.GET.get('status', 'PENDING')
|
||||||
submission_type = request.GET.get('submission_type', '')
|
submission_type = request.GET.get('submission_type', '')
|
||||||
|
|
||||||
if submission_type == 'photo':
|
if submission_type == 'photo':
|
||||||
@@ -143,7 +312,7 @@ def reject_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
|||||||
_update_submission_notes(submission, request.POST.get('notes'))
|
_update_submission_notes(submission, request.POST.get('notes'))
|
||||||
|
|
||||||
# Get updated queryset with filters
|
# Get updated queryset with filters
|
||||||
status = request.GET.get('status', 'NEW')
|
status = request.GET.get('status', 'PENDING')
|
||||||
submission_type = request.GET.get('submission_type', '')
|
submission_type = request.GET.get('submission_type', '')
|
||||||
|
|
||||||
if submission_type == 'photo':
|
if submission_type == 'photo':
|
||||||
@@ -179,7 +348,7 @@ def escalate_submission(request: HttpRequest, submission_id: int) -> HttpRespons
|
|||||||
_update_submission_notes(submission, request.POST.get('notes'))
|
_update_submission_notes(submission, request.POST.get('notes'))
|
||||||
|
|
||||||
# Get updated queryset with filters
|
# Get updated queryset with filters
|
||||||
status = request.GET.get('status', 'NEW')
|
status = request.GET.get('status', 'PENDING')
|
||||||
submission_type = request.GET.get('submission_type', '')
|
submission_type = request.GET.get('submission_type', '')
|
||||||
|
|
||||||
if submission_type == 'photo':
|
if submission_type == 'photo':
|
||||||
|
|||||||
@@ -2249,10 +2249,6 @@ select {
|
|||||||
z-index: 60;
|
z-index: 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
.z-10 {
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-span-1 {
|
.col-span-1 {
|
||||||
grid-column: span 1 / span 1;
|
grid-column: span 1 / span 1;
|
||||||
}
|
}
|
||||||
@@ -2273,6 +2269,16 @@ select {
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.-mx-1\.5 {
|
||||||
|
margin-left: -0.375rem;
|
||||||
|
margin-right: -0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.-my-1\.5 {
|
||||||
|
margin-top: -0.375rem;
|
||||||
|
margin-bottom: -0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mx-1 {
|
.mx-1 {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
@@ -2298,21 +2304,6 @@ select {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx-2\.5 {
|
|
||||||
margin-left: 0.625rem;
|
|
||||||
margin-right: 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.-mx-1\.5 {
|
|
||||||
margin-left: -0.375rem;
|
|
||||||
margin-right: -0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.-my-1\.5 {
|
|
||||||
margin-top: -0.375rem;
|
|
||||||
margin-bottom: -0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.-mb-px {
|
.-mb-px {
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
}
|
}
|
||||||
@@ -2357,22 +2348,42 @@ select {
|
|||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ml-1\.5 {
|
||||||
|
margin-left: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-2 {
|
.ml-2 {
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ml-3 {
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-6 {
|
.ml-6 {
|
||||||
margin-left: 1.5rem;
|
margin-left: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ml-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.mr-1 {
|
.mr-1 {
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-1\.5 {
|
||||||
|
margin-right: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mr-2 {
|
.mr-2 {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-2\.5 {
|
||||||
|
margin-right: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mr-3 {
|
.mr-3 {
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -2385,6 +2396,10 @@ select {
|
|||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-1\.5 {
|
||||||
|
margin-top: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-2 {
|
.mt-2 {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -2405,30 +2420,6 @@ select {
|
|||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mr-2\.5 {
|
|
||||||
margin-right: 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-1\.5 {
|
|
||||||
margin-left: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-1\.5 {
|
|
||||||
margin-right: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-1\.5 {
|
|
||||||
margin-top: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-3 {
|
|
||||||
margin-left: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-auto {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -2562,10 +2553,6 @@ select {
|
|||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.min-w-\[120px\] {
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-2xl {
|
.max-w-2xl {
|
||||||
max-width: 42rem;
|
max-width: 42rem;
|
||||||
}
|
}
|
||||||
@@ -2578,6 +2565,10 @@ select {
|
|||||||
max-width: 56rem;
|
max-width: 56rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-w-6xl {
|
||||||
|
max-width: 72rem;
|
||||||
|
}
|
||||||
|
|
||||||
.max-w-7xl {
|
.max-w-7xl {
|
||||||
max-width: 80rem;
|
max-width: 80rem;
|
||||||
}
|
}
|
||||||
@@ -2602,10 +2593,6 @@ select {
|
|||||||
max-width: 20rem;
|
max-width: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.max-w-6xl {
|
|
||||||
max-width: 72rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
@@ -2638,16 +2625,6 @@ select {
|
|||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
.translate-x-0 {
|
|
||||||
--tw-translate-x: 0px;
|
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.translate-x-4 {
|
|
||||||
--tw-translate-x: 1rem;
|
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.scale-100 {
|
.scale-100 {
|
||||||
--tw-scale-x: 1;
|
--tw-scale-x: 1;
|
||||||
--tw-scale-y: 1;
|
--tw-scale-y: 1;
|
||||||
@@ -2660,12 +2637,6 @@ select {
|
|||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
.scale-\[1\.01\] {
|
|
||||||
--tw-scale-x: 1.01;
|
|
||||||
--tw-scale-y: 1.01;
|
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.transform {
|
.transform {
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
@@ -2814,12 +2785,6 @@ select {
|
|||||||
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-x-6 > :not([hidden]) ~ :not([hidden]) {
|
|
||||||
--tw-space-x-reverse: 0;
|
|
||||||
margin-right: calc(1.5rem * var(--tw-space-x-reverse));
|
|
||||||
margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-auto {
|
.overflow-auto {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
@@ -2906,6 +2871,10 @@ select {
|
|||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-blue-200\/50 {
|
||||||
|
border-color: rgb(191 219 254 / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.border-blue-500 {
|
.border-blue-500 {
|
||||||
--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));
|
||||||
@@ -2930,6 +2899,11 @@ select {
|
|||||||
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-gray-700 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(55 65 81 / var(--tw-border-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.border-green-500 {
|
.border-green-500 {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(34 197 94 / var(--tw-border-opacity));
|
border-color: rgb(34 197 94 / var(--tw-border-opacity));
|
||||||
@@ -2954,16 +2928,6 @@ select {
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-gray-800 {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(31 41 55 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-700 {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(55 65 81 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-t-transparent {
|
.border-t-transparent {
|
||||||
border-top-color: transparent;
|
border-top-color: transparent;
|
||||||
}
|
}
|
||||||
@@ -2973,10 +2937,6 @@ select {
|
|||||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-black\/20 {
|
|
||||||
background-color: rgb(0 0 0 / 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-black\/50 {
|
.bg-black\/50 {
|
||||||
background-color: rgb(0 0 0 / 0.5);
|
background-color: rgb(0 0 0 / 0.5);
|
||||||
}
|
}
|
||||||
@@ -3001,6 +2961,10 @@ select {
|
|||||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
|
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-blue-900\/40 {
|
||||||
|
background-color: rgb(30 58 138 / 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-gray-100 {
|
.bg-gray-100 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
||||||
@@ -3021,6 +2985,20 @@ select {
|
|||||||
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-gray-800 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-900 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-900\/80 {
|
||||||
|
background-color: rgb(17 24 39 / 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-green-100 {
|
.bg-green-100 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
|
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
|
||||||
@@ -3036,9 +3014,8 @@ select {
|
|||||||
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-orange-100 {
|
.bg-green-900\/40 {
|
||||||
--tw-bg-opacity: 1;
|
background-color: rgb(20 83 45 / 0.4);
|
||||||
background-color: rgb(255 237 213 / var(--tw-bg-opacity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-red-100 {
|
.bg-red-100 {
|
||||||
@@ -3065,6 +3042,10 @@ select {
|
|||||||
background-color: rgb(255 255 255 / 0.1);
|
background-color: rgb(255 255 255 / 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-white\/80 {
|
||||||
|
background-color: rgb(255 255 255 / 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-white\/90 {
|
.bg-white\/90 {
|
||||||
background-color: rgb(255 255 255 / 0.9);
|
background-color: rgb(255 255 255 / 0.9);
|
||||||
}
|
}
|
||||||
@@ -3084,32 +3065,6 @@ select {
|
|||||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-gray-900 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-blue-900\/40 {
|
|
||||||
background-color: rgb(30 58 138 / 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-800 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-green-900\/40 {
|
|
||||||
background-color: rgb(20 83 45 / 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-blue-900\/30 {
|
|
||||||
background-color: rgb(30 58 138 / 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-900\/80 {
|
|
||||||
background-color: rgb(17 24 39 / 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-opacity-50 {
|
.bg-opacity-50 {
|
||||||
--tw-bg-opacity: 0.5;
|
--tw-bg-opacity: 0.5;
|
||||||
}
|
}
|
||||||
@@ -3247,6 +3202,11 @@ select {
|
|||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.py-2\.5 {
|
||||||
|
padding-top: 0.625rem;
|
||||||
|
padding-bottom: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
.py-3 {
|
.py-3 {
|
||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
padding-bottom: 0.75rem;
|
padding-bottom: 0.75rem;
|
||||||
@@ -3267,16 +3227,6 @@ select {
|
|||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.py-2\.5 {
|
|
||||||
padding-top: 0.625rem;
|
|
||||||
padding-bottom: 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-5 {
|
|
||||||
padding-left: 1.25rem;
|
|
||||||
padding-right: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pb-4 {
|
.pb-4 {
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@@ -3304,6 +3254,11 @@ select {
|
|||||||
line-height: 2.5rem;
|
line-height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-5xl {
|
||||||
|
font-size: 3rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.text-lg {
|
.text-lg {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
@@ -3324,11 +3279,6 @@ select {
|
|||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-5xl {
|
|
||||||
font-size: 3rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-bold {
|
.font-bold {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -3357,6 +3307,11 @@ select {
|
|||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-blue-400 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(96 165 250 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.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));
|
||||||
@@ -3367,16 +3322,16 @@ select {
|
|||||||
color: rgb(37 99 235 / var(--tw-text-opacity));
|
color: rgb(37 99 235 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-blue-700 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(29 78 216 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-800 {
|
.text-blue-800 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(30 64 175 / var(--tw-text-opacity));
|
color: rgb(30 64 175 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-blue-900 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(30 58 138 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-gray-200 {
|
.text-gray-200 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(229 231 235 / var(--tw-text-opacity));
|
color: rgb(229 231 235 / var(--tw-text-opacity));
|
||||||
@@ -3412,9 +3367,9 @@ select {
|
|||||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-green-500 {
|
.text-green-400 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(34 197 94 / var(--tw-text-opacity));
|
color: rgb(74 222 128 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-green-600 {
|
.text-green-600 {
|
||||||
@@ -3432,11 +3387,6 @@ select {
|
|||||||
color: rgb(22 101 52 / var(--tw-text-opacity));
|
color: rgb(22 101 52 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-orange-800 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(154 52 18 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(79 70 229 / var(--tw-text-opacity));
|
color: rgb(79 70 229 / var(--tw-text-opacity));
|
||||||
@@ -3501,26 +3451,6 @@ select {
|
|||||||
color: rgb(133 77 14 / var(--tw-text-opacity));
|
color: rgb(133 77 14 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-blue-400 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(96 165 250 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-green-400 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(74 222 128 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-200 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(191 219 254 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-300 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(147 197 253 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@@ -3694,21 +3624,21 @@ select {
|
|||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:scale-\[1\.01\]:hover {
|
|
||||||
--tw-scale-x: 1.01;
|
|
||||||
--tw-scale-y: 1.01;
|
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:transform:hover {
|
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:border-gray-300:hover {
|
.hover\:border-gray-300:hover {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-blue-100:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:bg-blue-500:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-blue-700:hover {
|
.hover\:bg-blue-700:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||||
@@ -3734,9 +3664,9 @@ select {
|
|||||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:bg-green-700:hover {
|
.hover\:bg-green-500:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:bg-red-50:hover {
|
.hover\:bg-red-50:hover {
|
||||||
@@ -3744,6 +3674,11 @@ select {
|
|||||||
background-color: rgb(254 242 242 / var(--tw-bg-opacity));
|
background-color: rgb(254 242 242 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-red-500:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-red-700:hover {
|
.hover\:bg-red-700:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
|
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
|
||||||
@@ -3753,16 +3688,16 @@ select {
|
|||||||
background-color: rgb(255 255 255 / 0.2);
|
background-color: rgb(255 255 255 / 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-yellow-500:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-yellow-600:hover {
|
.hover\:bg-yellow-600:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:bg-yellow-700:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(161 98 7 / 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));
|
||||||
@@ -3783,6 +3718,11 @@ select {
|
|||||||
color: rgb(30 64 175 / var(--tw-text-opacity));
|
color: rgb(30 64 175 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:text-blue-900:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(30 58 138 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:text-gray-300:hover {
|
.hover\:text-gray-300:hover {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(209 213 219 / var(--tw-text-opacity));
|
color: rgb(209 213 219 / var(--tw-text-opacity));
|
||||||
@@ -3798,6 +3738,11 @@ select {
|
|||||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:text-gray-900:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:text-primary:hover {
|
.hover\:text-primary:hover {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(79 70 229 / var(--tw-text-opacity));
|
color: rgb(79 70 229 / var(--tw-text-opacity));
|
||||||
@@ -3812,16 +3757,6 @@ select {
|
|||||||
color: rgb(7 89 133 / var(--tw-text-opacity));
|
color: rgb(7 89 133 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:text-gray-900:hover {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:text-blue-300:hover {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(147 197 253 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:underline:hover {
|
.hover\:underline:hover {
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
@@ -3867,11 +3802,6 @@ select {
|
|||||||
--tw-ring-color: rgb(79 70 229 / 0.5);
|
--tw-ring-color: rgb(79 70 229 / 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus\:ring-gray-300:focus {
|
|
||||||
--tw-ring-opacity: 1;
|
|
||||||
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.focus\:ring-offset-2:focus {
|
.focus\:ring-offset-2:focus {
|
||||||
--tw-ring-offset-width: 2px;
|
--tw-ring-offset-width: 2px;
|
||||||
}
|
}
|
||||||
@@ -3890,6 +3820,10 @@ select {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:border-blue-700\/50:is(.dark *) {
|
||||||
|
border-color: rgb(29 78 216 / 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:border-gray-600:is(.dark *) {
|
.dark\:border-gray-600:is(.dark *) {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(75 85 99 / var(--tw-border-opacity));
|
border-color: rgb(75 85 99 / var(--tw-border-opacity));
|
||||||
@@ -3904,10 +3838,6 @@ select {
|
|||||||
border-color: rgb(55 65 81 / 0.5);
|
border-color: rgb(55 65 81 / 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-black\/40:is(.dark *) {
|
|
||||||
background-color: rgb(0 0 0 / 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-blue-400\/30:is(.dark *) {
|
.dark\:bg-blue-400\/30:is(.dark *) {
|
||||||
background-color: rgb(96 165 250 / 0.3);
|
background-color: rgb(96 165 250 / 0.3);
|
||||||
}
|
}
|
||||||
@@ -3922,8 +3852,12 @@ select {
|
|||||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-blue-900\/50:is(.dark *) {
|
.dark\:bg-blue-900\/30:is(.dark *) {
|
||||||
background-color: rgb(30 58 138 / 0.5);
|
background-color: rgb(30 58 138 / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:bg-blue-900\/40:is(.dark *) {
|
||||||
|
background-color: rgb(30 58 138 / 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-gray-600:is(.dark *) {
|
.dark\:bg-gray-600:is(.dark *) {
|
||||||
@@ -3949,41 +3883,33 @@ select {
|
|||||||
background-color: rgb(31 41 55 / 0.9);
|
background-color: rgb(31 41 55 / 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:bg-gray-900:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark\:bg-gray-900\/80:is(.dark *) {
|
||||||
|
background-color: rgb(17 24 39 / 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:bg-green-200:is(.dark *) {
|
.dark\:bg-green-200:is(.dark *) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(187 247 208 / var(--tw-bg-opacity));
|
background-color: rgb(187 247 208 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-green-500:is(.dark *) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-green-700:is(.dark *) {
|
.dark\:bg-green-700:is(.dark *) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-green-900\/50:is(.dark *) {
|
|
||||||
background-color: rgb(20 83 45 / 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-orange-900\/50:is(.dark *) {
|
|
||||||
background-color: rgb(124 45 18 / 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-red-200:is(.dark *) {
|
.dark\:bg-red-200:is(.dark *) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(254 202 202 / var(--tw-bg-opacity));
|
background-color: rgb(254 202 202 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-red-500:is(.dark *) {
|
.dark\:bg-red-700:is(.dark *) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-red-900\/50:is(.dark *) {
|
|
||||||
background-color: rgb(127 29 29 / 0.5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-yellow-200:is(.dark *) {
|
.dark\:bg-yellow-200:is(.dark *) {
|
||||||
@@ -3995,38 +3921,20 @@ select {
|
|||||||
background-color: rgb(250 204 21 / 0.3);
|
background-color: rgb(250 204 21 / 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-yellow-500:is(.dark *) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-yellow-600:is(.dark *) {
|
.dark\:bg-yellow-600:is(.dark *) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:bg-yellow-700:is(.dark *) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(161 98 7 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:bg-yellow-900\/50:is(.dark *) {
|
.dark\:bg-yellow-900\/50:is(.dark *) {
|
||||||
background-color: rgb(113 63 18 / 0.5);
|
background-color: rgb(113 63 18 / 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:bg-blue-900\/60:is(.dark *) {
|
|
||||||
background-color: rgb(30 58 138 / 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-green-800:is(.dark *) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(22 101 52 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-green-900:is(.dark *) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(20 83 45 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:bg-blue-900\/30:is(.dark *) {
|
|
||||||
background-color: rgb(30 58 138 / 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:from-gray-950:is(.dark *) {
|
.dark\:from-gray-950:is(.dark *) {
|
||||||
--tw-gradient-from: #030712 var(--tw-gradient-from-position);
|
--tw-gradient-from: #030712 var(--tw-gradient-from-position);
|
||||||
--tw-gradient-to: rgb(3 7 18 / 0) var(--tw-gradient-to-position);
|
--tw-gradient-to: rgb(3 7 18 / 0) var(--tw-gradient-to-position);
|
||||||
@@ -4047,6 +3955,11 @@ select {
|
|||||||
color: rgb(191 219 254 / var(--tw-text-opacity));
|
color: rgb(191 219 254 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:text-blue-300:is(.dark *) {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(147 197 253 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:text-blue-400:is(.dark *) {
|
.dark\:text-blue-400:is(.dark *) {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(96 165 250 / var(--tw-text-opacity));
|
color: rgb(96 165 250 / var(--tw-text-opacity));
|
||||||
@@ -4157,16 +4070,6 @@ select {
|
|||||||
color: rgb(133 77 14 / var(--tw-text-opacity));
|
color: rgb(133 77 14 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:text-blue-300:is(.dark *) {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(147 197 253 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:text-green-200:is(.dark *) {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(187 247 208 / 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);
|
||||||
@@ -4196,6 +4099,10 @@ select {
|
|||||||
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark\:hover\:bg-blue-900\/40:hover:is(.dark *) {
|
||||||
|
background-color: rgb(30 58 138 / 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:hover\:bg-gray-500:hover:is(.dark *) {
|
.dark\:hover\:bg-gray-500:hover:is(.dark *) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
||||||
@@ -4364,6 +4271,14 @@ select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
.md\:col-span-1 {
|
||||||
|
grid-column: span 1 / span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md\:col-span-2 {
|
||||||
|
grid-column: span 2 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
.md\:mb-8 {
|
.md\:mb-8 {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
150
templates/moderation/partials/coaster_fields.html
Normal file
150
templates/moderation/partials/coaster_fields.html
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
|
<!-- Left Column -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label for="id_height_ft" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Height (ft)
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
name="stats.height_ft"
|
||||||
|
id="id_height_ft"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="Total height of the coaster in feet"
|
||||||
|
min="0"
|
||||||
|
value="{{ stats.height_ft }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_length_ft" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Length (ft)
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
name="stats.length_ft"
|
||||||
|
id="id_length_ft"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="Total track length in feet"
|
||||||
|
min="0"
|
||||||
|
value="{{ stats.length_ft }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_speed_mph" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Speed (mph)
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
name="stats.speed_mph"
|
||||||
|
id="id_speed_mph"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="Maximum speed in miles per hour"
|
||||||
|
min="0"
|
||||||
|
value="{{ stats.speed_mph }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_inversions" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Inversions
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
name="stats.inversions"
|
||||||
|
id="id_inversions"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="Number of inversions"
|
||||||
|
min="0"
|
||||||
|
value="{{ stats.inversions }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_trains_count" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Number of Trains
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
name="stats.trains_count"
|
||||||
|
id="id_trains_count"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="Number of trains"
|
||||||
|
min="0"
|
||||||
|
value="{{ stats.trains_count }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_cars_per_train" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Cars per Train
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
name="stats.cars_per_train"
|
||||||
|
id="id_cars_per_train"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="Number of cars per train"
|
||||||
|
min="0"
|
||||||
|
value="{{ stats.cars_per_train }}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_seats_per_car" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Seats per Car
|
||||||
|
</label>
|
||||||
|
<input type="number"
|
||||||
|
name="stats.seats_per_car"
|
||||||
|
id="id_seats_per_car"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-input dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||||
|
placeholder="Number of seats per car"
|
||||||
|
min="0"
|
||||||
|
value="{{ stats.seats_per_car }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label for="id_track_material" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Track Material
|
||||||
|
</label>
|
||||||
|
<select name="stats.track_material"
|
||||||
|
id="id_track_material"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
||||||
|
<option value="">Select track material...</option>
|
||||||
|
<option value="STEEL" {% if stats.track_material == 'STEEL' %}selected{% endif %}>Steel</option>
|
||||||
|
<option value="WOOD" {% if stats.track_material == 'WOOD' %}selected{% endif %}>Wood</option>
|
||||||
|
<option value="HYBRID" {% if stats.track_material == 'HYBRID' %}selected{% endif %}>Hybrid</option>
|
||||||
|
<option value="OTHER" {% if stats.track_material == 'OTHER' %}selected{% endif %}>Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_roller_coaster_type" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Coaster Type
|
||||||
|
</label>
|
||||||
|
<select name="stats.roller_coaster_type"
|
||||||
|
id="id_roller_coaster_type"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
||||||
|
<option value="">Select coaster type...</option>
|
||||||
|
<option value="SITDOWN" {% if stats.roller_coaster_type == 'SITDOWN' %}selected{% endif %}>Sit-Down</option>
|
||||||
|
<option value="INVERTED" {% if stats.roller_coaster_type == 'INVERTED' %}selected{% endif %}>Inverted</option>
|
||||||
|
<option value="FLYING" {% if stats.roller_coaster_type == 'FLYING' %}selected{% endif %}>Flying</option>
|
||||||
|
<option value="STANDUP" {% if stats.roller_coaster_type == 'STANDUP' %}selected{% endif %}>Stand-Up</option>
|
||||||
|
<option value="WING" {% if stats.roller_coaster_type == 'WING' %}selected{% endif %}>Wing</option>
|
||||||
|
<option value="SUSPENDED" {% if stats.roller_coaster_type == 'SUSPENDED' %}selected{% endif %}>Suspended</option>
|
||||||
|
<option value="BOBSLED" {% if stats.roller_coaster_type == 'BOBSLED' %}selected{% endif %}>Bobsled</option>
|
||||||
|
<option value="PIPELINE" {% if stats.roller_coaster_type == 'PIPELINE' %}selected{% endif %}>Pipeline</option>
|
||||||
|
<option value="MOTORBIKE" {% if stats.roller_coaster_type == 'MOTORBIKE' %}selected{% endif %}>Motorbike</option>
|
||||||
|
<option value="FLOORLESS" {% if stats.roller_coaster_type == 'FLOORLESS' %}selected{% endif %}>Floorless</option>
|
||||||
|
<option value="DIVE" {% if stats.roller_coaster_type == 'DIVE' %}selected{% endif %}>Dive</option>
|
||||||
|
<option value="FAMILY" {% if stats.roller_coaster_type == 'FAMILY' %}selected{% endif %}>Family</option>
|
||||||
|
<option value="WILD_MOUSE" {% if stats.roller_coaster_type == 'WILD_MOUSE' %}selected{% endif %}>Wild Mouse</option>
|
||||||
|
<option value="SPINNING" {% if stats.roller_coaster_type == 'SPINNING' %}selected{% endif %}>Spinning</option>
|
||||||
|
<option value="FOURTH_DIMENSION" {% if stats.roller_coaster_type == 'FOURTH_DIMENSION' %}selected{% endif %}>4th Dimension</option>
|
||||||
|
<option value="OTHER" {% if stats.roller_coaster_type == 'OTHER' %}selected{% endif %}>Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_launch_type" class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Launch Type
|
||||||
|
</label>
|
||||||
|
<select name="stats.launch_type"
|
||||||
|
id="id_launch_type"
|
||||||
|
class="w-full border-gray-300 rounded-lg form-select dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
||||||
|
<option value="">Select launch type...</option>
|
||||||
|
<option value="CHAIN" {% if stats.launch_type == 'CHAIN' %}selected{% endif %}>Chain Lift</option>
|
||||||
|
<option value="CABLE" {% if stats.launch_type == 'CABLE' %}selected{% endif %}>Cable Launch</option>
|
||||||
|
<option value="HYDRAULIC" {% if stats.launch_type == 'HYDRAULIC' %}selected{% endif %}>Hydraulic Launch</option>
|
||||||
|
<option value="LSM" {% if stats.launch_type == 'LSM' %}selected{% endif %}>Linear Synchronous Motor</option>
|
||||||
|
<option value="LIM" {% if stats.launch_type == 'LIM' %}selected{% endif %}>Linear Induction Motor</option>
|
||||||
|
<option value="GRAVITY" {% if stats.launch_type == 'GRAVITY' %}selected{% endif %}>Gravity</option>
|
||||||
|
<option value="OTHER" {% if stats.launch_type == 'OTHER' %}selected{% endif %}>Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div class="flex items-center justify-between p-4 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
<div class="flex items-center justify-between p-4 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<a href="{% url 'moderation:submission_list' %}?status=NEW"
|
<a href="{% url 'moderation:submission_list' %}?status=PENDING"
|
||||||
class="flex items-center px-4 py-2.5 rounded-lg font-medium transition-all duration-200 {% if request.GET.status == 'NEW' or not request.GET.status %}bg-blue-100 text-blue-900 dark:bg-blue-900/40 dark:text-blue-400{% else %}text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-300{% endif %}"
|
class="flex items-center px-4 py-2.5 rounded-lg font-medium transition-all duration-200 {% if request.GET.status == 'PENDING' or not request.GET.status %}bg-blue-100 text-blue-900 dark:bg-blue-900/40 dark:text-blue-400{% else %}text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-300{% endif %}"
|
||||||
hx-get="{% url 'moderation:submission_list' %}?status=NEW"
|
hx-get="{% url 'moderation:submission_list' %}?status=PENDING"
|
||||||
hx-target="#dashboard-content"
|
hx-target="#dashboard-content"
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
hx-indicator="#loading-indicator">
|
hx-indicator="#loading-indicator">
|
||||||
|
|||||||
67
templates/moderation/partials/designer_search_results.html
Normal file
67
templates/moderation/partials/designer_search_results.html
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
||||||
|
{% if designers %}
|
||||||
|
{% for designer in designers %}
|
||||||
|
<button type="button"
|
||||||
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
|
onclick="selectDesignerForSubmission('{{ designer.id }}', '{{ designer.name|escapejs }}', '{{ submission_id }}')">
|
||||||
|
{{ designer.name }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="px-4 py-2 text-gray-700 dark:text-gray-300">
|
||||||
|
{% if search_term %}
|
||||||
|
No designers found
|
||||||
|
{% else %}
|
||||||
|
Start typing to search...
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function selectDesignerForSubmission(id, name, submissionId) {
|
||||||
|
// Debug logging
|
||||||
|
console.log('Selecting designer:', {id, name, submissionId});
|
||||||
|
|
||||||
|
// Find elements
|
||||||
|
const designerInput = document.querySelector(`#designer-input-${submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#designer-search-${submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#designer-search-results-${submissionId}`);
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.log('Found elements:', {
|
||||||
|
designerInput: designerInput?.id,
|
||||||
|
searchInput: searchInput?.id,
|
||||||
|
resultsDiv: resultsDiv?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update hidden input
|
||||||
|
if (designerInput) {
|
||||||
|
designerInput.value = id;
|
||||||
|
console.log('Updated designer input value:', designerInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update search input
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = name;
|
||||||
|
console.log('Updated search input value:', searchInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear results
|
||||||
|
if (resultsDiv) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
console.log('Cleared results div');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close search results when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const searchResults = document.querySelectorAll('[id^="designer-search-results-"]');
|
||||||
|
searchResults.forEach(function(resultsDiv) {
|
||||||
|
const searchInput = document.querySelector(`#designer-search-${resultsDiv.id.split('-').pop()}`);
|
||||||
|
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
132
templates/moderation/partials/edit_submission_form.html
Normal file
132
templates/moderation/partials/edit_submission_form.html
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{% load moderation_tags %}
|
||||||
|
|
||||||
|
<div class="p-6 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50"
|
||||||
|
id="edit-form-{{ submission.id }}">
|
||||||
|
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-300">
|
||||||
|
Edit Submission
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form hx-post="{% url 'moderation:edit_submission' submission.id %}"
|
||||||
|
hx-target="#submissions-content"
|
||||||
|
class="space-y-4">
|
||||||
|
|
||||||
|
{% for field, value in changes.items %}
|
||||||
|
{% if field != 'model_name' %}
|
||||||
|
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">
|
||||||
|
{% if field == 'stats' %}
|
||||||
|
Coaster Stats:
|
||||||
|
{% elif field == 'park_area' %}
|
||||||
|
Park Area:
|
||||||
|
{% elif field == 'ride_model' %}
|
||||||
|
Ride Model:
|
||||||
|
{% elif field == 'min_height_in' %}
|
||||||
|
Minimum Height:
|
||||||
|
{% elif field == 'max_height_in' %}
|
||||||
|
Maximum Height:
|
||||||
|
{% elif field == 'capacity_per_hour' %}
|
||||||
|
Hourly Capacity:
|
||||||
|
{% elif field == 'ride_duration_seconds' %}
|
||||||
|
Ride Duration:
|
||||||
|
{% elif field == 'opening_date' %}
|
||||||
|
Opening Date:
|
||||||
|
{% elif field == 'closing_date' %}
|
||||||
|
Closing Date:
|
||||||
|
{% elif field == 'status_since' %}
|
||||||
|
Status Since:
|
||||||
|
{% elif field == 'operating_season' %}
|
||||||
|
Operating Season:
|
||||||
|
{% elif field == 'size_acres' %}
|
||||||
|
Size (Acres):
|
||||||
|
{% elif field == 'post_closing_status' %}
|
||||||
|
Post-Closing Status:
|
||||||
|
{% else %}
|
||||||
|
{{ field|title }}:
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{% if field == 'stats' %}
|
||||||
|
<div class="space-y-2">
|
||||||
|
{% for stat_name, stat_value in value.items %}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label class="text-sm text-gray-700 dark:text-gray-400">{{ stat_name|title }}:</label>
|
||||||
|
<input type="text"
|
||||||
|
name="stats.{{ stat_name }}"
|
||||||
|
value="{{ stat_value }}"
|
||||||
|
class="flex-1 px-3 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% elif field == 'park' %}
|
||||||
|
<select name="{{ field }}"
|
||||||
|
class="w-full px-3 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
{% for park_id, park_name in parks %}
|
||||||
|
<option value="{{ park_id }}" {% if park_id == value %}selected{% endif %}>{{ park_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% elif field == 'designer' %}
|
||||||
|
<select name="{{ field }}"
|
||||||
|
class="w-full px-3 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
<option value="">None</option>
|
||||||
|
{% for designer_id, designer_name in designers %}
|
||||||
|
<option value="{{ designer_id }}" {% if designer_id == value %}selected{% endif %}>{{ designer_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% elif field == 'manufacturer' %}
|
||||||
|
<select name="{{ field }}"
|
||||||
|
class="w-full px-3 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
<option value="">None</option>
|
||||||
|
{% for manufacturer_id, manufacturer_name in manufacturers %}
|
||||||
|
<option value="{{ manufacturer_id }}" {% if manufacturer_id == value %}selected{% endif %}>{{ manufacturer_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% elif field == 'ride_model' %}
|
||||||
|
<select name="{{ field }}"
|
||||||
|
class="w-full px-3 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
<option value="">None</option>
|
||||||
|
{% for model_id, model_name in ride_models %}
|
||||||
|
<option value="{{ model_id }}" {% if model_id == value %}selected{% endif %}>{{ model_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% elif field == 'park_area' %}
|
||||||
|
<select name="{{ field }}"
|
||||||
|
class="w-full px-3 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
<option value="">None</option>
|
||||||
|
{% for area_id, area_name in park_areas %}
|
||||||
|
<option value="{{ area_id }}" {% if area_id == value %}selected{% endif %}>{{ area_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% else %}
|
||||||
|
<input type="{% if field == 'opening_date' or field == 'closing_date' or field == 'status_since' %}date{% else %}text{% endif %}"
|
||||||
|
name="{{ field }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
class="w-full px-3 py-2 text-gray-900 bg-white border rounded-lg dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="p-4 border rounded-lg bg-blue-50 dark:bg-blue-900/30 border-blue-200/50 dark:border-blue-700/50">
|
||||||
|
<label class="block mb-2 text-sm font-medium text-blue-900 dark:text-blue-300">
|
||||||
|
Notes (required):
|
||||||
|
</label>
|
||||||
|
<textarea name="notes"
|
||||||
|
class="w-full px-3 py-2 text-gray-900 bg-white border rounded-lg resize-none dark:text-gray-300 dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50"
|
||||||
|
rows="3"
|
||||||
|
required
|
||||||
|
placeholder="Explain why you're editing this submission"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-3">
|
||||||
|
<button type="button"
|
||||||
|
class="px-4 py-2 font-medium text-gray-700 transition-all duration-200 bg-gray-100 rounded-lg hover:bg-gray-200 hover:text-gray-900 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||||
|
@click="showEditForm = false">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
class="px-4 py-2 font-medium text-white transition-all duration-200 bg-blue-600 rounded-lg hover:bg-blue-500 dark:bg-blue-700 dark:hover:bg-blue-600">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<select name="status" class="w-full form-select">
|
<select name="status" class="w-full form-select">
|
||||||
<option value="">All Statuses</option>
|
<option value="">All Statuses</option>
|
||||||
<option value="NEW" {% if request.GET.status == 'NEW' %}selected{% endif %}>Pending</option>
|
<option value="PENDING" {% if request.GET.status == 'PENDING' %}selected{% endif %}>Pending</option>
|
||||||
<option value="APPROVED" {% if request.GET.status == 'APPROVED' %}selected{% endif %}>Approved</option>
|
<option value="APPROVED" {% if request.GET.status == 'APPROVED' %}selected{% endif %}>Approved</option>
|
||||||
<option value="REJECTED" {% if request.GET.status == 'REJECTED' %}selected{% endif %}>Rejected</option>
|
<option value="REJECTED" {% if request.GET.status == 'REJECTED' %}selected{% endif %}>Rejected</option>
|
||||||
<option value="ESCALATED" {% if request.GET.status == 'ESCALATED' %}selected{% endif %}>Escalated</option>
|
<option value="ESCALATED" {% if request.GET.status == 'ESCALATED' %}selected{% endif %}>Escalated</option>
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
||||||
|
{% if manufacturers %}
|
||||||
|
{% for manufacturer in manufacturers %}
|
||||||
|
<button type="button"
|
||||||
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
|
onclick="selectManufacturerForSubmission('{{ manufacturer.id }}', '{{ manufacturer.name|escapejs }}', '{{ submission_id }}')">
|
||||||
|
{{ manufacturer.name }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="px-4 py-2 text-gray-700 dark:text-gray-300">
|
||||||
|
{% if search_term %}
|
||||||
|
No manufacturers found
|
||||||
|
{% else %}
|
||||||
|
Start typing to search...
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function selectManufacturerForSubmission(id, name, submissionId) {
|
||||||
|
// Debug logging
|
||||||
|
console.log('Selecting manufacturer:', {id, name, submissionId});
|
||||||
|
|
||||||
|
// Find elements
|
||||||
|
const manufacturerInput = document.querySelector(`#manufacturer-input-${submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#manufacturer-search-${submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#manufacturer-search-results-${submissionId}`);
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.log('Found elements:', {
|
||||||
|
manufacturerInput: manufacturerInput?.id,
|
||||||
|
searchInput: searchInput?.id,
|
||||||
|
resultsDiv: resultsDiv?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update hidden input
|
||||||
|
if (manufacturerInput) {
|
||||||
|
manufacturerInput.value = id;
|
||||||
|
console.log('Updated manufacturer input value:', manufacturerInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update search input
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = name;
|
||||||
|
console.log('Updated search input value:', searchInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear results
|
||||||
|
if (resultsDiv) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
console.log('Cleared results div');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close search results when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const searchResults = document.querySelectorAll('[id^="manufacturer-search-results-"]');
|
||||||
|
searchResults.forEach(function(resultsDiv) {
|
||||||
|
const searchInput = document.querySelector(`#manufacturer-search-${resultsDiv.id.split('-').pop()}`);
|
||||||
|
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
73
templates/moderation/partials/park_search_results.html
Normal file
73
templates/moderation/partials/park_search_results.html
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
||||||
|
{% if parks %}
|
||||||
|
{% for park in parks %}
|
||||||
|
<button type="button"
|
||||||
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
|
onclick="selectParkForSubmission('{{ park.id }}', '{{ park.name|escapejs }}', '{{ submission_id }}')">
|
||||||
|
{{ park.name }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="px-4 py-2 text-gray-700 dark:text-gray-300">
|
||||||
|
{% if search_term %}
|
||||||
|
No parks found
|
||||||
|
{% else %}
|
||||||
|
Start typing to search...
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function selectParkForSubmission(id, name, submissionId) {
|
||||||
|
// Debug logging
|
||||||
|
console.log('Selecting park:', {id, name, submissionId});
|
||||||
|
|
||||||
|
// Find elements
|
||||||
|
const parkInput = document.querySelector(`#park-input-${submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#park-search-${submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#park-search-results-${submissionId}`);
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.log('Found elements:', {
|
||||||
|
parkInput: parkInput?.id,
|
||||||
|
searchInput: searchInput?.id,
|
||||||
|
resultsDiv: resultsDiv?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update hidden input
|
||||||
|
if (parkInput) {
|
||||||
|
parkInput.value = id;
|
||||||
|
console.log('Updated park input value:', parkInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update search input
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = name;
|
||||||
|
console.log('Updated search input value:', searchInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear results
|
||||||
|
if (resultsDiv) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
console.log('Cleared results div');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger park areas update
|
||||||
|
if (parkInput) {
|
||||||
|
htmx.trigger(parkInput, 'change');
|
||||||
|
console.log('Triggered change event');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close search results when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const searchResults = document.querySelectorAll('[id^="park-search-results-"]');
|
||||||
|
searchResults.forEach(function(resultsDiv) {
|
||||||
|
const searchInput = document.querySelector(`#park-search-${resultsDiv.id.split('-').pop()}`);
|
||||||
|
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -3,10 +3,14 @@
|
|||||||
<div>
|
<div>
|
||||||
<h3 class="flex items-center gap-2 text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 class="flex items-center gap-2 text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
<span class="status-badge
|
<span class="status-badge
|
||||||
{% if submission.status == 'NEW' %}status-pending
|
{% if submission.status == 'PENDING' %}status-pending
|
||||||
{% elif submission.status == 'APPROVED' %}status-approved
|
{% elif submission.status == 'APPROVED' %}status-approved
|
||||||
{% elif submission.status == 'REJECTED' %}status-rejected
|
{% elif submission.status == 'REJECTED' %}status-rejected
|
||||||
{% elif submission.status == 'AUTO_APPROVED' %}status-approved{% endif %}">
|
{% elif submission.status == 'ESCALATED' %}status-escalated{% endif %}">
|
||||||
|
<i class="mr-1.5 fas fa-{% if submission.status == 'PENDING' %}clock
|
||||||
|
{% elif submission.status == 'APPROVED' %}check
|
||||||
|
{% elif submission.status == 'REJECTED' %}times
|
||||||
|
{% elif submission.status == 'ESCALATED' %}exclamation{% endif %}"></i>
|
||||||
{{ submission.get_status_display }}
|
{{ submission.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
Photo for {{ submission.content_object }}
|
Photo for {{ submission.content_object }}
|
||||||
@@ -45,7 +49,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if submission.status == 'NEW' %}
|
{% if submission.notes %}
|
||||||
|
<div class="p-4 mt-4 border rounded-lg bg-blue-50 dark:bg-blue-900/30 border-blue-200/50 dark:border-blue-700/50">
|
||||||
|
<div class="text-sm font-medium text-blue-900 dark:text-blue-300">Review Notes:</div>
|
||||||
|
<div class="mt-1.5 text-blue-800 dark:text-blue-200">{{ submission.notes }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if submission.status == 'PENDING' or submission.status == 'ESCALATED' and user.role in 'ADMIN,SUPERUSER' %}
|
||||||
<div class="mt-4 review-notes" x-data="{ showNotes: false }">
|
<div class="mt-4 review-notes" x-data="{ showNotes: false }">
|
||||||
<textarea x-show="showNotes"
|
<textarea x-show="showNotes"
|
||||||
name="notes"
|
name="notes"
|
||||||
@@ -60,6 +71,7 @@
|
|||||||
Add Notes
|
Add Notes
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{% if submission.status != 'ESCALATED' or user.role in 'ADMIN,SUPERUSER' %}
|
||||||
<button class="btn-approve"
|
<button class="btn-approve"
|
||||||
hx-post="{% url 'moderation:approve_photo' submission.id %}"
|
hx-post="{% url 'moderation:approve_photo' submission.id %}"
|
||||||
hx-target="#submission-{{ submission.id }}"
|
hx-target="#submission-{{ submission.id }}"
|
||||||
@@ -79,6 +91,19 @@
|
|||||||
<i class="mr-2 fas fa-times"></i>
|
<i class="mr-2 fas fa-times"></i>
|
||||||
Reject
|
Reject
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.role == 'MODERATOR' and submission.status != 'ESCALATED' %}
|
||||||
|
<button class="btn-escalate"
|
||||||
|
hx-post="{% url 'moderation:escalate_photo' submission.id %}"
|
||||||
|
hx-target="#submission-{{ submission.id }}"
|
||||||
|
hx-include="closest .review-notes"
|
||||||
|
hx-confirm="Are you sure you want to escalate this photo?"
|
||||||
|
hx-indicator="#loading-indicator">
|
||||||
|
<i class="mr-2 fas fa-arrow-up"></i>
|
||||||
|
Escalate
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
67
templates/moderation/partials/ride_model_search_results.html
Normal file
67
templates/moderation/partials/ride_model_search_results.html
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<div class="w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg dark:bg-gray-700 dark:border-gray-600" style="max-height: 240px; overflow-y: auto;">
|
||||||
|
{% if ride_models %}
|
||||||
|
{% for model in ride_models %}
|
||||||
|
<button type="button"
|
||||||
|
class="w-full px-4 py-2 text-left text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600"
|
||||||
|
onclick="selectRideModelForSubmission('{{ model.id }}', '{{ model.name|escapejs }}', '{{ submission_id }}')">
|
||||||
|
{{ model.name }}
|
||||||
|
</button>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="px-4 py-2 text-gray-700 dark:text-gray-300">
|
||||||
|
{% if search_term %}
|
||||||
|
No ride models found
|
||||||
|
{% else %}
|
||||||
|
Start typing to search...
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function selectRideModelForSubmission(id, name, submissionId) {
|
||||||
|
// Debug logging
|
||||||
|
console.log('Selecting ride model:', {id, name, submissionId});
|
||||||
|
|
||||||
|
// Find elements
|
||||||
|
const modelInput = document.querySelector(`#ride-model-input-${submissionId}`);
|
||||||
|
const searchInput = document.querySelector(`#ride-model-search-${submissionId}`);
|
||||||
|
const resultsDiv = document.querySelector(`#ride-model-search-results-${submissionId}`);
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.log('Found elements:', {
|
||||||
|
modelInput: modelInput?.id,
|
||||||
|
searchInput: searchInput?.id,
|
||||||
|
resultsDiv: resultsDiv?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update hidden input
|
||||||
|
if (modelInput) {
|
||||||
|
modelInput.value = id;
|
||||||
|
console.log('Updated ride model input value:', modelInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update search input
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.value = name;
|
||||||
|
console.log('Updated search input value:', searchInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear results
|
||||||
|
if (resultsDiv) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
console.log('Cleared results div');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close search results when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const searchResults = document.querySelectorAll('[id^="ride-model-search-results-"]');
|
||||||
|
searchResults.forEach(function(resultsDiv) {
|
||||||
|
const searchInput = document.querySelector(`#ride-model-search-${resultsDiv.id.split('-').pop()}`);
|
||||||
|
if (!resultsDiv.contains(e.target) && e.target !== searchInput) {
|
||||||
|
resultsDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user