mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:31:08 -05:00
the ghost of the sea
This commit is contained in:
@@ -183,6 +183,14 @@ def submission_list(request: HttpRequest) -> HttpResponse:
|
||||
if content_type := request.GET.get('content_type'):
|
||||
queryset = queryset.filter(content_type__model=content_type)
|
||||
|
||||
# Get park areas for each park submission
|
||||
park_areas_by_park = {}
|
||||
for submission in queryset:
|
||||
if submission.content_type.model == 'park' and submission.changes.get('park'):
|
||||
park_id = submission.changes['park']
|
||||
if park_id not in park_areas_by_park:
|
||||
park_areas_by_park[park_id] = [(a.id, str(a)) for a in ParkArea.objects.filter(park_id=park_id)]
|
||||
|
||||
context = {
|
||||
'submissions': queryset,
|
||||
'user': request.user,
|
||||
@@ -190,7 +198,8 @@ def submission_list(request: HttpRequest) -> HttpResponse:
|
||||
'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'])]
|
||||
'owners': [(u.id, str(u)) for u in User.objects.filter(role__in=['OWNER', 'ADMIN', 'SUPERUSER'])],
|
||||
'park_areas_by_park': park_areas_by_park
|
||||
}
|
||||
|
||||
# If it's an HTMX request, return just the content
|
||||
@@ -302,36 +311,42 @@ def reject_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||
"""HTMX endpoint for rejecting a submission"""
|
||||
user = cast(User, request.user)
|
||||
submission = get_object_or_404(EditSubmission, id=submission_id)
|
||||
|
||||
|
||||
if submission.status == 'ESCALATED' and not (user.role in ['ADMIN', 'SUPERUSER'] or user.is_superuser):
|
||||
return HttpResponse("Only admins can handle escalated submissions", status=403)
|
||||
if not (user.role in MODERATOR_ROLES or user.is_superuser):
|
||||
return HttpResponse(status=403)
|
||||
|
||||
|
||||
submission.reject(user)
|
||||
_update_submission_notes(submission, request.POST.get('notes'))
|
||||
|
||||
|
||||
# Get updated queryset with filters
|
||||
status = request.GET.get('status', 'PENDING')
|
||||
submission_type = request.GET.get('submission_type', '')
|
||||
|
||||
|
||||
if submission_type == 'photo':
|
||||
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
||||
else:
|
||||
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
||||
|
||||
|
||||
if type_filter := request.GET.get('type'):
|
||||
queryset = queryset.filter(submission_type=type_filter)
|
||||
|
||||
|
||||
if content_type := request.GET.get('content_type'):
|
||||
queryset = queryset.filter(content_type__model=content_type)
|
||||
|
||||
|
||||
context = {
|
||||
'submissions': queryset,
|
||||
'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=submission.changes.get('park'))] if submission.changes.get('park') else []
|
||||
}
|
||||
|
||||
return render(request, 'moderation/partials/dashboard_content.html', context)
|
||||
return render(request, 'moderation/partials/submission_list.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def escalate_submission(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||
@@ -339,35 +354,36 @@ def escalate_submission(request: HttpRequest, submission_id: int) -> HttpRespons
|
||||
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 submission.status == 'ESCALATED':
|
||||
if submission.status == "ESCALATED":
|
||||
return HttpResponse("Submission is already escalated", status=400)
|
||||
|
||||
|
||||
submission.escalate(user)
|
||||
_update_submission_notes(submission, request.POST.get('notes'))
|
||||
|
||||
_update_submission_notes(submission, request.POST.get("notes"))
|
||||
|
||||
# Get updated queryset with filters
|
||||
status = request.GET.get('status', 'PENDING')
|
||||
submission_type = request.GET.get('submission_type', '')
|
||||
|
||||
if submission_type == 'photo':
|
||||
queryset = PhotoSubmission.objects.filter(status=status).order_by('-created_at')
|
||||
status = request.GET.get("status", "PENDING")
|
||||
submission_type = request.GET.get("submission_type", "")
|
||||
|
||||
if submission_type == "photo":
|
||||
queryset = PhotoSubmission.objects.filter(status=status).order_by("-created_at")
|
||||
else:
|
||||
queryset = EditSubmission.objects.filter(status=status).order_by('-created_at')
|
||||
|
||||
if type_filter := request.GET.get('type'):
|
||||
queryset = EditSubmission.objects.filter(status=status).order_by("-created_at")
|
||||
|
||||
if type_filter := request.GET.get("type"):
|
||||
queryset = queryset.filter(submission_type=type_filter)
|
||||
|
||||
if content_type := request.GET.get('content_type'):
|
||||
|
||||
if content_type := request.GET.get("content_type"):
|
||||
queryset = queryset.filter(content_type__model=content_type)
|
||||
|
||||
|
||||
context = {
|
||||
'submissions': queryset,
|
||||
'user': request.user,
|
||||
"submissions": queryset,
|
||||
"user": request.user,
|
||||
}
|
||||
|
||||
return render(request, 'moderation/partials/dashboard_content.html', context)
|
||||
|
||||
return render(request, "moderation/partials/dashboard_content.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||
@@ -375,26 +391,34 @@ def approve_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||
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(PhotoSubmission, id=submission_id)
|
||||
|
||||
|
||||
try:
|
||||
submission.approve(user, request.POST.get('notes', ''))
|
||||
return render(request, 'moderation/partials/photo_submission.html', {'submission': submission})
|
||||
submission.approve(user, request.POST.get("notes", ""))
|
||||
return render(
|
||||
request,
|
||||
"moderation/partials/photo_submission.html",
|
||||
{"submission": submission},
|
||||
)
|
||||
except Exception as e:
|
||||
return HttpResponse(str(e), status=400)
|
||||
|
||||
|
||||
@login_required
|
||||
def reject_photo(request: HttpRequest, submission_id: int) -> HttpResponse:
|
||||
"""HTMX endpoint for rejecting a photo 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(PhotoSubmission, id=submission_id)
|
||||
submission.reject(user, request.POST.get('notes', ''))
|
||||
|
||||
return render(request, 'moderation/partials/photo_submission.html', {'submission': submission})
|
||||
submission.reject(user, request.POST.get("notes", ""))
|
||||
|
||||
return render(
|
||||
request, "moderation/partials/photo_submission.html", {"submission": submission}
|
||||
)
|
||||
|
||||
|
||||
def _update_submission_notes(submission: EditSubmission, notes: Optional[str]) -> None:
|
||||
"""Update submission notes if provided."""
|
||||
|
||||
@@ -2181,6 +2181,10 @@ select {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
@@ -2304,6 +2308,11 @@ select {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.my-auto {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.-mb-px {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
@@ -2500,10 +2509,6 @@ select {
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.min-h-\[calc\(100vh-16rem\)\] {
|
||||
min-height: calc(100vh - 16rem);
|
||||
}
|
||||
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -3033,6 +3038,10 @@ select {
|
||||
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-red-900\/40 {
|
||||
background-color: rgb(127 29 29 / 0.4);
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
@@ -3065,6 +3074,10 @@ select {
|
||||
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-yellow-900\/40 {
|
||||
background-color: rgb(113 63 18 / 0.4);
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
@@ -3392,6 +3405,11 @@ select {
|
||||
color: rgb(79 70 229 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(248 113 113 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(239 68 68 / var(--tw-text-opacity));
|
||||
@@ -4313,6 +4331,11 @@ select {
|
||||
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.md\:py-12 {
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.md\:text-2xl {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
|
||||
@@ -5,8 +5,445 @@
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
{% endblock %}
|
||||
|
||||
<!-- Keep existing content until the approve button -->
|
||||
{{ existing_content_until_approve_button|safe }}
|
||||
{% for submission in submissions %}
|
||||
<div class="p-6 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50"
|
||||
id="submission-{{ submission.id }}"
|
||||
x-data="{
|
||||
showSuccess: false,
|
||||
isEditing: false,
|
||||
status: '{{ submission.changes.status|default:"" }}',
|
||||
category: '{{ submission.changes.category|default:"" }}',
|
||||
showCoasterFields: {% if submission.changes.category == 'RC' %}true{% else %}false{% endif %},
|
||||
init() {
|
||||
this.$watch('category', value => {
|
||||
this.showCoasterFields = value === 'RC';
|
||||
});
|
||||
}
|
||||
}"
|
||||
@htmx:afterOnLoad="showSuccess = true; setTimeout(() => showSuccess = false, 3000)">
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
<!-- Left Column: Header & Status -->
|
||||
<div class="md:col-span-1">
|
||||
<div class="submission-header">
|
||||
<h3 class="flex items-center gap-3 text-lg font-semibold text-gray-900 dark:text-gray-300">
|
||||
<span class="status-badge
|
||||
{% if submission.status == 'PENDING' %}status-pending
|
||||
{% elif submission.status == 'APPROVED' %}status-approved
|
||||
{% elif submission.status == 'REJECTED' %}status-rejected
|
||||
{% 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 }}
|
||||
</span>
|
||||
</h3>
|
||||
<div class="mt-3 text-gray-600 dark:text-gray-400">
|
||||
<div class="flex items-center mb-2">
|
||||
<i class="w-5 mr-2 fas fa-file-alt"></i>
|
||||
{{ submission.get_content_type_display }} -
|
||||
{% if submission.submission_type == 'CREATE' %}New{% else %}Edit{% endif %}
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<i class="w-5 mr-2 fas fa-user"></i>
|
||||
{{ submission.user.username }}
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<i class="w-5 mr-2 fas fa-clock"></i>
|
||||
{{ submission.created_at|date:"M d, Y H:i" }}
|
||||
</div>
|
||||
{% if submission.handled_by %}
|
||||
<div class="flex items-center">
|
||||
<i class="w-5 mr-2 fas fa-user-shield"></i>
|
||||
{{ submission.handled_by.username }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Middle Column: Content Details -->
|
||||
<div class="md:col-span-2">
|
||||
{% if submission.content_object %}
|
||||
<div class="p-4 mb-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-300">Current Object:</div>
|
||||
<div class="mt-1.5 text-gray-600 dark:text-gray-400">
|
||||
{{ submission.content_object }}
|
||||
{% if submission.content_type.model == 'park' or submission.content_type.model == 'company' or submission.content_type.model == 'designer' %}
|
||||
<div class="mt-1 text-sm">
|
||||
{{ submission.content_object.address }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if submission.reason %}
|
||||
<div class="p-4 mb-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-300">Reason:</div>
|
||||
<div class="mt-1.5 text-gray-600 dark:text-gray-400">{{ submission.reason }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if submission.source %}
|
||||
<div class="p-4 mb-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-300">Source:</div>
|
||||
<div class="mt-1.5">
|
||||
<a href="{{ submission.source }}"
|
||||
target="_blank"
|
||||
class="inline-flex items-center text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
<span>{{ submission.source }}</span>
|
||||
<i class="ml-1.5 text-xs fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- View Mode -->
|
||||
<div x-show="!isEditing">
|
||||
<!-- Location Map (View Mode) -->
|
||||
{% if submission.content_type.model == 'park' and submission.changes.latitude and submission.changes.longitude %}
|
||||
<div class="mb-4">
|
||||
{% include "moderation/partials/location_map.html" with location=submission.changes %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
{% for field, value in submission.changes.items %}
|
||||
{% if field != 'model_name' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' %}
|
||||
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-300">
|
||||
{{ field|title }}:
|
||||
</div>
|
||||
<div class="mt-1.5 text-gray-600 dark:text-gray-400">
|
||||
{% if field == 'opening_date' or field == 'closing_date' or field == 'status_since' %}
|
||||
{{ value|date:"Y-m-d" }}
|
||||
{% elif field == 'size_acres' %}
|
||||
{{ value }} acres
|
||||
{% elif field == 'website' %}
|
||||
<a href="{{ value }}" target="_blank" class="text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
{{ value }}
|
||||
</a>
|
||||
{% elif field == 'park' %}
|
||||
{% with park_name=value|get_object_name:'parks.Park' %}
|
||||
{{ park_name }}
|
||||
{% endwith %}
|
||||
{% elif field == 'designer' %}
|
||||
{% with designer_name=value|get_object_name:'designers.Designer' %}
|
||||
{{ designer_name|default:'None' }}
|
||||
{% endwith %}
|
||||
{% elif field == 'manufacturer' %}
|
||||
{% with manufacturer_name=value|get_object_name:'companies.Manufacturer' %}
|
||||
{{ manufacturer_name|default:'None' }}
|
||||
{% endwith %}
|
||||
{% elif field == 'ride_model' %}
|
||||
{% with model_name=value|get_object_name:'rides.RideModel' %}
|
||||
{{ model_name|default:'None' }}
|
||||
{% endwith %}
|
||||
{% elif field == 'park_area' %}
|
||||
{% with park_id=submission.changes.park %}
|
||||
{{ value|get_park_area_name:park_id|default:'None' }}
|
||||
{% endwith %}
|
||||
{% elif field == 'category' %}
|
||||
{{ value|get_category_display }}
|
||||
{% elif field == 'stats' %}
|
||||
<div class="space-y-2">
|
||||
{% if value.height_ft %}<div>Height: {{ value.height_ft }} ft</div>{% endif %}
|
||||
{% if value.length_ft %}<div>Length: {{ value.length_ft }} ft</div>{% endif %}
|
||||
{% if value.speed_mph %}<div>Speed: {{ value.speed_mph }} mph</div>{% endif %}
|
||||
{% if value.inversions %}<div>Inversions: {{ value.inversions }}</div>{% endif %}
|
||||
{% if value.launch_type %}<div>Launch Type: {{ value.launch_type }}</div>{% endif %}
|
||||
{% if value.track_material %}<div>Track Material: {{ value.track_material }}</div>{% endif %}
|
||||
{% if value.roller_coaster_type %}<div>Type: {{ value.roller_coaster_type }}</div>{% endif %}
|
||||
{% if value.trains_count %}<div>Number of Trains: {{ value.trains_count }}</div>{% endif %}
|
||||
{% if value.cars_per_train %}<div>Cars per Train: {{ value.cars_per_train }}</div>{% endif %}
|
||||
{% if value.seats_per_car %}<div>Seats per Car: {{ value.seats_per_car }}</div>{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Mode -->
|
||||
<form x-show="isEditing"
|
||||
x-cloak
|
||||
hx-post="{% url 'moderation:edit_submission' submission.id %}"
|
||||
hx-target="#submission-{{ submission.id }}"
|
||||
class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
{% for field, value in submission.changes.items %}
|
||||
{% if field != 'model_name' and field != 'stats' and field != 'latitude' and field != 'longitude' and field != 'street_address' and field != 'city' and field != 'state' and field != 'postal_code' and field != 'country' %}
|
||||
<div class="p-4 bg-gray-100 border rounded-lg dark:bg-gray-900 border-gray-200/50 dark:border-gray-700/50"
|
||||
{% if field == 'post_closing_status' %}x-show="status === 'CLOSING'"{% endif %}
|
||||
{% if field == 'closing_date' %}x-show="['CLOSING', 'SBNO', 'CLOSED_PERM', 'DEMOLISHED', 'RELOCATED'].includes(status)"{% endif %}>
|
||||
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">
|
||||
{{ field|title }}:
|
||||
</label>
|
||||
|
||||
{% if field == 'category' %}
|
||||
<select name="{{ field }}"
|
||||
x-model="category"
|
||||
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 focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">Select ride type</option>
|
||||
<option value="RC" {% if value == 'RC' %}selected{% endif %}>Roller Coaster</option>
|
||||
<option value="DR" {% if value == 'DR' %}selected{% endif %}>Dark Ride</option>
|
||||
<option value="FR" {% if value == 'FR' %}selected{% endif %}>Flat Ride</option>
|
||||
<option value="WR" {% if value == 'WR' %}selected{% endif %}>Water Ride</option>
|
||||
<option value="TR" {% if value == 'TR' %}selected{% endif %}>Transport</option>
|
||||
<option value="OT" {% if value == 'OT' %}selected{% endif %}>Other</option>
|
||||
</select>
|
||||
{% elif field == 'status' %}
|
||||
<select name="{{ field }}"
|
||||
x-model="status"
|
||||
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 focus:ring-2 focus:ring-blue-500">
|
||||
<option value="OPERATING" {% if value == 'OPERATING' %}selected{% endif %}>Operating</option>
|
||||
<option value="SBNO" {% if value == 'SBNO' %}selected{% endif %}>Standing But Not Operating</option>
|
||||
<option value="CLOSING" {% if value == 'CLOSING' %}selected{% endif %}>Closing</option>
|
||||
<option value="CLOSED_PERM" {% if value == 'CLOSED_PERM' %}selected{% endif %}>Permanently Closed</option>
|
||||
<option value="UNDER_CONSTRUCTION" {% if value == 'UNDER_CONSTRUCTION' %}selected{% endif %}>Under Construction</option>
|
||||
<option value="DEMOLISHED" {% if value == 'DEMOLISHED' %}selected{% endif %}>Demolished</option>
|
||||
<option value="RELOCATED" {% if value == 'RELOCATED' %}selected{% endif %}>Relocated</option>
|
||||
</select>
|
||||
{% elif field == 'post_closing_status' %}
|
||||
<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 focus:ring-2 focus:ring-blue-500">
|
||||
<option value="SBNO" {% if value == 'SBNO' %}selected{% endif %}>Standing But Not Operating</option>
|
||||
<option value="CLOSED_PERM" {% if value == 'CLOSED_PERM' %}selected{% endif %}>Permanently Closed</option>
|
||||
</select>
|
||||
{% elif field == 'opening_date' or field == 'closing_date' or field == 'status_since' %}
|
||||
<input type="date"
|
||||
name="{{ field }}"
|
||||
value="{{ value|date:'Y-m-d' }}"
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
{% if field == 'closing_date' %}
|
||||
:required="status === 'CLOSING'"
|
||||
{% endif %}>
|
||||
{% else %}
|
||||
{% if field == 'park' %}
|
||||
<div class="relative space-y-2">
|
||||
<input type="text"
|
||||
id="park-search-{{ submission.id }}"
|
||||
placeholder="Search for a park..."
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
hx-get="{% url 'moderation:search_parks' %}"
|
||||
hx-trigger="click, input delay:200ms"
|
||||
hx-target="#park-search-results-{{ submission.id }}"
|
||||
hx-vals='{"submission_id": "{{ submission.id }}"}'
|
||||
name="q"
|
||||
autocomplete="off"
|
||||
{% with park_name=value|get_object_name:'parks.Park' %}
|
||||
value="{{ park_name }}"
|
||||
{% endwith %}>
|
||||
<div id="park-search-results-{{ submission.id }}" class="absolute z-50 w-full"></div>
|
||||
<input type="hidden"
|
||||
id="park-input-{{ submission.id }}"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}"
|
||||
hx-trigger="change"
|
||||
hx-get="/parks/areas/"
|
||||
hx-target="#park-area-select-{{ submission.id }}">
|
||||
</div>
|
||||
{% elif field == 'park_area' %}
|
||||
<select name="{{ field }}"
|
||||
id="park-area-select-{{ submission.id }}"
|
||||
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 focus:ring-2 focus:ring-blue-500">
|
||||
<option value="">Select area</option>
|
||||
{% with park_id=submission.changes.park %}
|
||||
{% with areas=park_areas_by_park|get_item:park_id %}
|
||||
{% for area_id, area_name in areas %}
|
||||
<option value="{{ area_id }}" {% if value == area_id %}selected{% endif %}>{{ area_name }}</option>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
</select>
|
||||
{% elif field == 'manufacturer' %}
|
||||
<div class="relative space-y-2">
|
||||
<input type="text"
|
||||
id="manufacturer-search-{{ submission.id }}"
|
||||
placeholder="Search for a manufacturer..."
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
hx-get="{% url 'moderation:search_manufacturers' %}"
|
||||
hx-trigger="click, input delay:200ms"
|
||||
hx-target="#manufacturer-search-results-{{ submission.id }}"
|
||||
hx-vals='{"submission_id": "{{ submission.id }}"}'
|
||||
name="q"
|
||||
autocomplete="off"
|
||||
{% with manufacturer_name=value|get_object_name:'companies.Manufacturer' %}
|
||||
value="{{ manufacturer_name }}"
|
||||
{% endwith %}>
|
||||
<div id="manufacturer-search-results-{{ submission.id }}" class="absolute z-50 w-full"></div>
|
||||
<input type="hidden"
|
||||
id="manufacturer-input-{{ submission.id }}"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}">
|
||||
</div>
|
||||
{% elif field == 'designer' %}
|
||||
<div class="relative space-y-2">
|
||||
<input type="text"
|
||||
id="designer-search-{{ submission.id }}"
|
||||
placeholder="Search for a designer..."
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
hx-get="{% url 'moderation:search_designers' %}"
|
||||
hx-trigger="click, input delay:200ms"
|
||||
hx-target="#designer-search-results-{{ submission.id }}"
|
||||
hx-vals='{"submission_id": "{{ submission.id }}"}'
|
||||
name="q"
|
||||
autocomplete="off"
|
||||
{% with designer_name=value|get_object_name:'designers.Designer' %}
|
||||
value="{{ designer_name }}"
|
||||
{% endwith %}>
|
||||
<div id="designer-search-results-{{ submission.id }}" class="absolute z-50 w-full"></div>
|
||||
<input type="hidden"
|
||||
id="designer-input-{{ submission.id }}"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}">
|
||||
</div>
|
||||
{% elif field == 'ride_model' %}
|
||||
<div class="relative space-y-2">
|
||||
<input type="text"
|
||||
id="ride-model-search-{{ submission.id }}"
|
||||
placeholder="Search for a ride model..."
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
hx-get="{% url 'moderation:search_ride_models' %}"
|
||||
hx-trigger="click, input delay:200ms"
|
||||
hx-target="#ride-model-search-results-{{ submission.id }}"
|
||||
hx-vals='{"submission_id": "{{ submission.id }}"}'
|
||||
hx-include="[name='manufacturer']"
|
||||
name="q"
|
||||
autocomplete="off"
|
||||
{% with model_name=value|get_object_name:'rides.RideModel' %}
|
||||
value="{{ model_name }}"
|
||||
{% endwith %}>
|
||||
<div id="ride-model-search-results-{{ submission.id }}" class="absolute z-50 w-full"></div>
|
||||
<input type="hidden"
|
||||
id="ride-model-input-{{ submission.id }}"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}">
|
||||
</div>
|
||||
{% elif field == 'description' %}
|
||||
<textarea name="{{ field }}"
|
||||
rows="4"
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="General description and notable features">{{ value }}</textarea>
|
||||
{% elif field == 'min_height_in' or field == 'max_height_in' %}
|
||||
<input type="number"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}"
|
||||
min="0"
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Height in inches">
|
||||
{% elif field == 'capacity_per_hour' %}
|
||||
<input type="number"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}"
|
||||
min="0"
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Theoretical hourly ride capacity">
|
||||
{% elif field == 'ride_duration_seconds' %}
|
||||
<input type="number"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}"
|
||||
min="0"
|
||||
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 focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Total duration of one ride cycle in seconds">
|
||||
{% else %}
|
||||
<input type="text"
|
||||
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 focus:ring-2 focus:ring-blue-500">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<!-- Location Widget for Parks -->
|
||||
{% if submission.content_type.model == 'park' %}
|
||||
<div class="col-span-2">
|
||||
{% include "moderation/partials/location_widget.html" with form=submission.changes %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Coaster Fields -->
|
||||
<div x-show="showCoasterFields"
|
||||
x-cloak
|
||||
class="col-span-2">
|
||||
{% include 'moderation/partials/coaster_fields.html' with stats=submission.changes.stats %}
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 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 focus:ring-2 focus:ring-blue-500"
|
||||
rows="3"
|
||||
required
|
||||
placeholder="Explain why you're editing this submission"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end col-span-2 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="isEditing = 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>
|
||||
|
||||
<!-- Keep existing review notes section -->
|
||||
{% 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-6 review-notes" x-data="{ showNotes: false }">
|
||||
<div x-show="showNotes"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 transform -translate-y-2"
|
||||
x-transition:enter-end="opacity-100 transform translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 transform translate-y-0"
|
||||
x-transition:leave-end="opacity-0 transform -translate-y-2">
|
||||
<textarea name="notes"
|
||||
class="w-full px-4 py-3 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 focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Add review notes (optional)"
|
||||
rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 action-buttons">
|
||||
<button class="inline-flex items-center px-4 py-2.5 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 shadow-sm hover:shadow-md"
|
||||
@click="showNotes = !showNotes">
|
||||
<i class="mr-2 fas fa-comment-alt"></i>
|
||||
Add Notes
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center px-4 py-2.5 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 shadow-sm hover:shadow-md"
|
||||
@click="isEditing = !isEditing">
|
||||
<i class="mr-2 fas fa-edit"></i>
|
||||
Edit
|
||||
</button>
|
||||
|
||||
{% if submission.status != 'ESCALATED' or user.role in 'ADMIN,SUPERUSER' %}
|
||||
<button class="inline-flex items-center px-4 py-2.5 font-medium text-white transition-all duration-200 bg-green-600 rounded-lg hover:bg-green-500 dark:bg-green-700 dark:hover:bg-green-600 shadow-sm hover:shadow-md"
|
||||
hx-post="{% url 'moderation:approve_submission' submission.id %}"
|
||||
hx-target="#submissions-content"
|
||||
hx-include="closest .review-notes"
|
||||
hx-confirm="Are you sure you want to approve this submission?"
|
||||
hx-indicator="#loading-indicator">
|
||||
<i class="mr-2 fas fa-check"></i>
|
||||
Approve
|
||||
</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user