mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 13:51:09 -05:00
Add version control system functionality with branch management, history tracking, and merge operations
This commit is contained in:
@@ -1,129 +1,69 @@
|
||||
{% comment %}
|
||||
This template contains the Alpine.js store for managing filter state in the moderation dashboard
|
||||
{% endcomment %}
|
||||
{% load static %}
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('filters', {
|
||||
active: [],
|
||||
|
||||
init() {
|
||||
this.updateActiveFilters();
|
||||
|
||||
// Listen for filter changes
|
||||
window.addEventListener('filter-changed', () => {
|
||||
this.updateActiveFilters();
|
||||
});
|
||||
},
|
||||
|
||||
updateActiveFilters() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.active = [];
|
||||
|
||||
// Submission Type
|
||||
if (urlParams.has('submission_type')) {
|
||||
this.active.push({
|
||||
name: 'submission_type',
|
||||
label: 'Submission',
|
||||
value: this.getSubmissionTypeLabel(urlParams.get('submission_type'))
|
||||
});
|
||||
}
|
||||
|
||||
// Type
|
||||
if (urlParams.has('type')) {
|
||||
this.active.push({
|
||||
name: 'type',
|
||||
label: 'Type',
|
||||
value: this.getTypeLabel(urlParams.get('type'))
|
||||
});
|
||||
}
|
||||
|
||||
// Content Type
|
||||
if (urlParams.has('content_type')) {
|
||||
this.active.push({
|
||||
name: 'content_type',
|
||||
label: 'Content',
|
||||
value: this.getContentTypeLabel(urlParams.get('content_type'))
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getSubmissionTypeLabel(value) {
|
||||
const labels = {
|
||||
'text': 'Text',
|
||||
'photo': 'Photo'
|
||||
};
|
||||
return labels[value] || value;
|
||||
},
|
||||
|
||||
getTypeLabel(value) {
|
||||
const labels = {
|
||||
'CREATE': 'New',
|
||||
'EDIT': 'Edit'
|
||||
};
|
||||
return labels[value] || value;
|
||||
},
|
||||
|
||||
getContentTypeLabel(value) {
|
||||
const labels = {
|
||||
'park': 'Parks',
|
||||
'ride': 'Rides',
|
||||
'company': 'Companies'
|
||||
};
|
||||
return labels[value] || value;
|
||||
},
|
||||
|
||||
get hasActiveFilters() {
|
||||
return this.active.length > 0;
|
||||
},
|
||||
|
||||
clear() {
|
||||
const form = document.querySelector('form[hx-get]');
|
||||
if (form) {
|
||||
form.querySelectorAll('select').forEach(select => {
|
||||
select.value = '';
|
||||
});
|
||||
form.dispatchEvent(new Event('change'));
|
||||
}
|
||||
},
|
||||
|
||||
// Accessibility Helpers
|
||||
announceFilterChange() {
|
||||
const message = this.hasActiveFilters
|
||||
? `Applied filters: ${this.active.map(f => f.label + ': ' + f.value).join(', ')}`
|
||||
: 'All filters cleared';
|
||||
|
||||
const announcement = document.createElement('div');
|
||||
announcement.setAttribute('role', 'status');
|
||||
announcement.setAttribute('aria-live', 'polite');
|
||||
announcement.className = 'sr-only';
|
||||
announcement.textContent = message;
|
||||
|
||||
document.body.appendChild(announcement);
|
||||
setTimeout(() => announcement.remove(), 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Watch for filter changes and update URL params
|
||||
document.addEventListener('filter-changed', (e) => {
|
||||
const form = e.target.closest('form');
|
||||
if (!form) return;
|
||||
|
||||
const formData = new FormData(form);
|
||||
const params = new URLSearchParams();
|
||||
|
||||
for (let [key, value] of formData.entries()) {
|
||||
if (value) {
|
||||
params.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Update URL without page reload
|
||||
const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
|
||||
window.history.pushState({}, '', newUrl);
|
||||
|
||||
// Announce changes for screen readers
|
||||
Alpine.store('filters').announceFilterChange();
|
||||
});
|
||||
</script>
|
||||
<div x-data
|
||||
x-init="$store.filters = {
|
||||
active: [],
|
||||
labels: {
|
||||
'submission_type': 'Type',
|
||||
'content_type': 'Content',
|
||||
'type': 'Change Type',
|
||||
'status': 'Status'
|
||||
},
|
||||
values: {
|
||||
'submission_type': {
|
||||
'text': 'Text',
|
||||
'photo': 'Photo'
|
||||
},
|
||||
'content_type': {
|
||||
'park': 'Park',
|
||||
'ride': 'Ride',
|
||||
'company': 'Company'
|
||||
},
|
||||
'type': {
|
||||
'CREATE': 'New',
|
||||
'EDIT': 'Edit'
|
||||
},
|
||||
'status': {
|
||||
'PENDING': 'Pending',
|
||||
'APPROVED': 'Approved',
|
||||
'REJECTED': 'Rejected',
|
||||
'ESCALATED': 'Escalated'
|
||||
}
|
||||
},
|
||||
hasActiveFilters: false,
|
||||
updateActiveFilters() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
this.active = [];
|
||||
|
||||
params.forEach((value, key) => {
|
||||
if (value && this.labels[key]) {
|
||||
this.active.push({
|
||||
name: key,
|
||||
label: this.labels[key],
|
||||
value: this.values[key][value] || value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.hasActiveFilters = this.active.length > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize filters from URL
|
||||
$store.filters.updateActiveFilters();
|
||||
|
||||
// Listen for filter changes
|
||||
window.addEventListener('filter-changed', () => {
|
||||
$store.filters.updateActiveFilters();
|
||||
});
|
||||
|
||||
// Listen for URL changes
|
||||
window.addEventListener('htmx:historyRestore', () => {
|
||||
$store.filters.updateActiveFilters();
|
||||
});
|
||||
|
||||
// Listen for HTMX after swap
|
||||
window.addEventListener('htmx:afterSwap', () => {
|
||||
$store.filters.updateActiveFilters();
|
||||
})">
|
||||
</div>
|
||||
@@ -1,66 +1,69 @@
|
||||
{% load static %}
|
||||
|
||||
<div class="animate-pulse">
|
||||
<!-- Filter Bar Skeleton -->
|
||||
<div class="flex items-center justify-between p-4 mb-6 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="space-y-6 animate-pulse">
|
||||
<!-- Navigation Skeleton -->
|
||||
<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">
|
||||
{% for i in "1234" %}
|
||||
{% for i in '1234'|make_list %}
|
||||
<div class="w-24 h-10 bg-gray-200 rounded-lg dark:bg-gray-700"></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="w-24 h-10 bg-gray-200 rounded-lg dark:bg-gray-700"></div>
|
||||
</div>
|
||||
|
||||
<!-- Filter Form Skeleton -->
|
||||
<div class="p-6 mb-6 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="flex flex-wrap items-end gap-4">
|
||||
{% for i in "123" %}
|
||||
<div class="flex-1 min-w-[200px] space-y-2">
|
||||
<div class="w-24 h-4 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="w-full h-10 bg-gray-200 rounded-lg dark:bg-gray-700"></div>
|
||||
<!-- Filter Section Skeleton -->
|
||||
<div class="p-6 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="mb-6">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
{% for i in '123'|make_list %}
|
||||
<div class="space-y-2">
|
||||
<div class="w-24 h-4 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="w-full h-10 bg-gray-200 rounded-lg dark:bg-gray-700"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submissions Skeleton -->
|
||||
<div class="space-y-4">
|
||||
{% for i in '123'|make_list %}
|
||||
<div class="p-6 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
<!-- Left Column -->
|
||||
<div class="space-y-4">
|
||||
<div class="w-32 h-6 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="space-y-2">
|
||||
{% for j in '1234'|make_list %}
|
||||
<div class="flex items-center">
|
||||
<div class="w-5 h-5 mr-2 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="w-32 h-4 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column -->
|
||||
<div class="md:col-span-2 space-y-4">
|
||||
<!-- Content Details -->
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
{% for j in '1234'|make_list %}
|
||||
<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="w-24 h-4 mb-2 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="w-full h-4 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-end gap-3">
|
||||
{% for j in '1234'|make_list %}
|
||||
<div class="w-24 h-10 bg-gray-200 rounded-lg dark:bg-gray-700"></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submission List Skeleton -->
|
||||
{% for i in "123" %}
|
||||
<div class="p-6 mb-4 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50">
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
<!-- Left Column -->
|
||||
<div class="space-y-4 md:col-span-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-24 h-6 bg-gray-200 rounded-lg dark:bg-gray-700"></div>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
{% for i in "1234" %}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-5 h-5 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="w-32 h-4 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column -->
|
||||
<div class="md:col-span-2">
|
||||
{% for i in "12" %}
|
||||
<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="w-24 h-4 mb-2 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="w-full h-4 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 mt-4 md:grid-cols-2">
|
||||
{% for i in "1234" %}
|
||||
<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="w-24 h-4 mb-2 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
<div class="w-full h-4 bg-gray-200 rounded dark:bg-gray-700"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -6,8 +6,10 @@
|
||||
{% endblock %}
|
||||
|
||||
{% 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"
|
||||
<div class="p-4 sm:p-6 bg-white border rounded-lg dark:bg-gray-800 border-gray-200/50 dark:border-gray-700/50"
|
||||
id="submission-{{ submission.id }}"
|
||||
role="article"
|
||||
aria-labelledby="submission-header-{{ submission.id }}"
|
||||
x-data="{
|
||||
showSuccess: false,
|
||||
isEditing: false,
|
||||
@@ -173,8 +175,13 @@
|
||||
<!-- Edit Mode -->
|
||||
<form x-show="isEditing"
|
||||
x-cloak
|
||||
id="edit-form-{{ submission.id }}"
|
||||
hx-post="{% url 'moderation:edit_submission' submission.id %}"
|
||||
hx-target="#submission-{{ submission.id }}"
|
||||
hx-indicator="#loading-indicator-{{ submission.id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::before-request="document.getElementById('edit-form-{{ submission.id }}').classList.add('submitting')"
|
||||
hx-on::after-request="document.getElementById('edit-form-{{ submission.id }}').classList.remove('submitting')"
|
||||
class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
|
||||
<!-- Location Widget for Parks -->
|
||||
@@ -224,13 +231,23 @@
|
||||
<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 %}>
|
||||
<div class="relative">
|
||||
<input type="date"
|
||||
id="{{ field }}-{{ submission.id }}"
|
||||
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 touch-friendly-select"
|
||||
{% if field == 'closing_date' %}
|
||||
:required="status === 'CLOSING'"
|
||||
data-validate="date"
|
||||
{% endif %}
|
||||
aria-describedby="{{ field }}-error-{{ submission.id }}"
|
||||
min="1800-01-01"
|
||||
max="{{ now|date:'Y-m-d' }}">
|
||||
<div id="{{ field }}-error-{{ submission.id }}"
|
||||
class="hidden absolute -bottom-6 left-0 text-sm text-red-600 dark:text-red-400"
|
||||
role="alert"></div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if field == 'park' %}
|
||||
<div class="relative space-y-2">
|
||||
@@ -339,12 +356,24 @@
|
||||
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">
|
||||
<div class="relative">
|
||||
<input type="number"
|
||||
id="{{ field }}-{{ submission.id }}"
|
||||
name="{{ field }}"
|
||||
value="{{ value }}"
|
||||
min="0"
|
||||
step="0.1"
|
||||
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 touch-friendly-select"
|
||||
placeholder="Height in inches"
|
||||
aria-describedby="{{ field }}-error-{{ submission.id }}"
|
||||
data-validate="numeric">
|
||||
<div id="{{ field }}-error-{{ submission.id }}"
|
||||
class="hidden absolute -bottom-6 left-0 text-sm text-red-600 dark:text-red-400"
|
||||
role="alert"></div>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">in</span>
|
||||
</div>
|
||||
</div>
|
||||
{% elif field == 'capacity_per_hour' %}
|
||||
<input type="number"
|
||||
name="{{ field }}"
|
||||
@@ -378,14 +407,23 @@
|
||||
</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">
|
||||
<label for="notes-{{ submission.id }}"
|
||||
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 class="relative">
|
||||
<textarea id="notes-{{ submission.id }}"
|
||||
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
|
||||
aria-required="true"
|
||||
aria-describedby="notes-error-{{ submission.id }}"
|
||||
placeholder="Explain why you're editing this submission"></textarea>
|
||||
<div id="notes-error-{{ submission.id }}"
|
||||
class="hidden absolute -bottom-6 left-0 text-sm text-red-600 dark:text-red-400"
|
||||
role="alert"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end col-span-2 gap-3">
|
||||
@@ -424,52 +462,93 @@
|
||||
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
|
||||
<div class="flex flex-col sm:flex-row items-stretch sm: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 touch-target"
|
||||
@click="showNotes = !showNotes"
|
||||
aria-expanded="showNotes"
|
||||
aria-controls="notes-section-{{ submission.id }}">
|
||||
<i class="mr-2 fas fa-comment-alt" aria-hidden="true"></i>
|
||||
<span>Add Notes</span>
|
||||
</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 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 touch-target"
|
||||
@click="isEditing = !isEditing"
|
||||
aria-expanded="isEditing"
|
||||
aria-controls="edit-form-{{ submission.id }}">
|
||||
<i class="mr-2 fas fa-edit" aria-hidden="true"></i>
|
||||
<span>Edit</span>
|
||||
</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"
|
||||
<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 touch-target disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
hx-post="{% url 'moderation:approve_submission' submission.id %}"
|
||||
hx-target="#submissions-content"
|
||||
hx-target="#submission-{{ submission.id }}"
|
||||
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
|
||||
hx-indicator="#loading-indicator-{{ submission.id }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::before-request="this.disabled = true"
|
||||
hx-on::after-request="this.disabled = false"
|
||||
aria-label="Approve submission">
|
||||
<i class="mr-2 fas fa-check" aria-hidden="true"></i>
|
||||
<span>Approve</span>
|
||||
<span class="htmx-indicator ml-2">
|
||||
<i class="fas fa-spinner fa-spin" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center px-4 py-2.5 font-medium text-white transition-all duration-200 bg-red-600 rounded-lg hover:bg-red-500 dark:bg-red-700 dark:hover:bg-red-600 shadow-sm hover:shadow-md"
|
||||
<button class="inline-flex items-center px-4 py-2.5 font-medium text-white transition-all duration-200 bg-red-600 rounded-lg hover:bg-red-500 dark:bg-red-700 dark:hover:bg-red-600 shadow-sm hover:shadow-md touch-target disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
hx-post="{% url 'moderation:reject_submission' submission.id %}"
|
||||
hx-target="#submissions-content"
|
||||
hx-target="#submission-{{ submission.id }}"
|
||||
hx-include="closest .review-notes"
|
||||
hx-confirm="Are you sure you want to reject this submission?"
|
||||
hx-indicator="#loading-indicator">
|
||||
<i class="mr-2 fas fa-times"></i>
|
||||
Reject
|
||||
hx-indicator="#loading-indicator-{{ submission.id }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::before-request="this.disabled = true"
|
||||
hx-on::after-request="this.disabled = false"
|
||||
aria-label="Reject submission">
|
||||
<i class="mr-2 fas fa-times" aria-hidden="true"></i>
|
||||
<span>Reject</span>
|
||||
<span class="htmx-indicator ml-2">
|
||||
<i class="fas fa-spinner fa-spin" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if user.role == 'MODERATOR' and submission.status != 'ESCALATED' %}
|
||||
<button class="inline-flex items-center px-4 py-2.5 font-medium text-white transition-all duration-200 bg-yellow-600 rounded-lg hover:bg-yellow-500 dark:bg-yellow-700 dark:hover:bg-yellow-600 shadow-sm hover:shadow-md"
|
||||
<button class="inline-flex items-center px-4 py-2.5 font-medium text-white transition-all duration-200 bg-yellow-600 rounded-lg hover:bg-yellow-500 dark:bg-yellow-700 dark:hover:bg-yellow-600 shadow-sm hover:shadow-md touch-target disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
hx-post="{% url 'moderation:escalate_submission' submission.id %}"
|
||||
hx-target="#submissions-content"
|
||||
hx-target="#submission-{{ submission.id }}"
|
||||
hx-include="closest .review-notes"
|
||||
hx-confirm="Are you sure you want to escalate this submission?"
|
||||
hx-indicator="#loading-indicator">
|
||||
<i class="mr-2 fas fa-arrow-up"></i>
|
||||
Escalate
|
||||
hx-indicator="#loading-indicator-{{ submission.id }}"
|
||||
hx-disabled-elt="this"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::before-request="this.disabled = true"
|
||||
hx-on::after-request="this.disabled = false"
|
||||
aria-label="Escalate submission">
|
||||
<i class="mr-2 fas fa-arrow-up" aria-hidden="true"></i>
|
||||
<span>Escalate</span>
|
||||
<span class="htmx-indicator ml-2">
|
||||
<i class="fas fa-spinner fa-spin" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<!-- Submission-specific loading indicator -->
|
||||
<div id="loading-indicator-{{ submission.id }}"
|
||||
class="htmx-indicator fixed inset-0 bg-black/20 dark:bg-black/40 flex items-center justify-center z-50"
|
||||
role="status"
|
||||
aria-live="polite">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 shadow-xl">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 border-4 border-blue-500 rounded-full animate-spin border-t-transparent"></div>
|
||||
<span class="text-gray-900 dark:text-gray-100">Processing...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user