Spaces:
Sleeping
Sleeping
| {% extends "admin/base.html" %} | |
| {% block title %}Submissions - Admin Dashboard{% endblock %} | |
| {% block admin_content %} | |
| <div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3"> | |
| <h2> | |
| All Submissions ({{ submissions|length }}) | |
| {% if flagged_count > 0 %} | |
| <span class="badge bg-danger">{{ flagged_count }} flagged</span> | |
| {% endif %} | |
| </h2> | |
| <div class="d-flex gap-2 align-items-center"> | |
| {% if analyzed %} | |
| <select class="form-select" onchange="filterCategory(this.value)"> | |
| <option value="all" {% if category_filter == 'all' %}selected{% endif %}>All Categories</option> | |
| {% for cat in categories %} | |
| <option value="{{ cat }}" {% if category_filter == cat %}selected{% endif %}>{{ cat }}</option> | |
| {% endfor %} | |
| </select> | |
| {% endif %} | |
| <div class="form-check"> | |
| <input class="form-check-input" type="checkbox" id="flaggedOnly" | |
| {% if flagged_only %}checked{% endif %} onchange="toggleFlagged(this.checked)"> | |
| <label class="form-check-label" for="flaggedOnly"> | |
| <i class="bi bi-flag-fill text-danger"></i> Flagged Only | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| {% if category_filter != 'all' or flagged_only %} | |
| <div class="alert alert-info"> | |
| Showing {{ submissions|length }} submission{{ 's' if submissions|length != 1 else '' }} | |
| {% if category_filter != 'all' %} in category: {{ category_filter }}{% endif %} | |
| {% if flagged_only %} (flagged only){% endif %} | |
| </div> | |
| {% endif %} | |
| <div class="row g-3"> | |
| {% if submissions %} | |
| {% for sub in submissions %} | |
| <div class="col-12"> | |
| <div class="card shadow-sm {% if sub.flagged_as_offensive %}border-danger{% endif %}"> | |
| <div class="card-body"> | |
| <div class="d-flex justify-content-between align-items-start mb-2"> | |
| <div class="d-flex gap-2 flex-wrap align-items-center"> | |
| <span class="badge bg-secondary text-capitalize">{{ sub.contributor_type }}</span> | |
| <select class="form-select form-select-sm" style="width: auto;" | |
| onchange="updateCategory({{ sub.id }}, this.value, this)"> | |
| <option value="">Not categorized</option> | |
| {% for cat in categories %} | |
| <option value="{{ cat }}" {% if sub.category == cat %}selected{% endif %}>{{ cat }}</option> | |
| {% endfor %} | |
| </select> | |
| {% if sub.flagged_as_offensive %} | |
| <span class="badge bg-danger"> | |
| <i class="bi bi-exclamation-triangle-fill"></i> Flagged as Offensive | |
| </span> | |
| {% endif %} | |
| </div> | |
| <small class="text-muted">{{ sub.timestamp.strftime('%Y-%m-%d %H:%M') if sub.timestamp else '' }}</small> | |
| </div> | |
| <p class="mb-3">{{ sub.message }}</p> | |
| {% if sub.sentence_analysis_done and sub.sentences %} | |
| <!-- NEW: Category Distribution --> | |
| <div class="mb-2"> | |
| <strong>Category Distribution:</strong> | |
| {% for category, percentage in sub.get_category_distribution().items() %} | |
| <span class="badge bg-secondary">{{ category }}: {{ percentage }}%</span> | |
| {% endfor %} | |
| </div> | |
| <!-- NEW: Collapsible Sentence Breakdown --> | |
| <button class="btn btn-sm btn-outline-primary mb-2" | |
| type="button" | |
| data-bs-toggle="collapse" | |
| data-bs-target="#sentences-{{ sub.id }}"> | |
| <i class="bi bi-list-nested"></i> View Sentences ({{ sub.sentences|length }}) | |
| </button> | |
| <div class="collapse" id="sentences-{{ sub.id }}"> | |
| <div class="border-start border-primary ps-3 mt-2"> | |
| {% for sentence in sub.sentences %} | |
| <div class="mb-2 p-2 bg-light rounded"> | |
| <div class="d-flex justify-content-between align-items-start"> | |
| <div class="flex-grow-1"> | |
| <small class="text-muted">Sentence {{ sentence.sentence_index + 1 }}:</small> | |
| <p class="mb-1">{{ sentence.text }}</p> | |
| </div> | |
| <div class="ms-2"> | |
| <select class="form-select form-select-sm" | |
| style="width: 150px;" | |
| onchange="updateSentenceCategory({{ sentence.id }}, this.value, this)"> | |
| <option value="">Uncategorized</option> | |
| {% for cat in categories %} | |
| <option value="{{ cat }}" | |
| {% if sentence.category == cat %}selected{% endif %}> | |
| {{ cat }} | |
| </option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| </div> | |
| {% if sentence.confidence %} | |
| <small class="text-muted">Confidence: {{ "%.0f"|format(sentence.confidence * 100) }}%</small> | |
| {% endif %} | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| {% endif %} | |
| {% if sub.latitude and sub.longitude %} | |
| <p class="text-muted small mb-3"> | |
| <i class="bi bi-geo-alt-fill"></i> | |
| {{ sub.latitude|round(6) }}, {{ sub.longitude|round(6) }} | |
| </p> | |
| {% endif %} | |
| <div class="d-flex gap-2 pt-3 border-top"> | |
| <button class="btn btn-sm btn-{{ 'success' if sub.flagged_as_offensive else 'warning' }}" | |
| onclick="toggleFlag({{ sub.id }})"> | |
| <i class="bi bi-flag-fill"></i> | |
| {{ 'Unflag' if sub.flagged_as_offensive else 'Flag as Offensive' }} | |
| </button> | |
| <button class="btn btn-sm btn-danger" onclick="deleteSubmission({{ sub.id }})"> | |
| <i class="bi bi-trash"></i> Delete | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endfor %} | |
| {% else %} | |
| <div class="col-12"> | |
| <div class="text-center py-5 text-muted"> | |
| <i class="bi bi-chat-square-text" style="font-size: 4rem;"></i> | |
| <p class="mt-3"> | |
| {% if flagged_only %} | |
| No flagged submissions | |
| {% elif category_filter == 'all' %} | |
| No submissions yet | |
| {% else %} | |
| No submissions in category: {{ category_filter }} | |
| {% endif %} | |
| </p> | |
| </div> | |
| </div> | |
| {% endif %} | |
| </div> | |
| <script> | |
| function filterCategory(category) { | |
| const url = new URL(window.location); | |
| url.searchParams.set('category', category); | |
| window.location = url; | |
| } | |
| function toggleFlagged(checked) { | |
| const url = new URL(window.location); | |
| url.searchParams.set('flagged', checked ? 'true' : 'false'); | |
| window.location = url; | |
| } | |
| function updateCategory(submissionId, category, selectElement) { | |
| fetch(`{{ url_for("admin.update_category", submission_id=0) }}`.replace('/0', `/${submissionId}`), { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ category: category || null }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| // Show success feedback | |
| const originalBg = selectElement.style.backgroundColor; | |
| selectElement.style.backgroundColor = '#d1fae5'; | |
| setTimeout(() => { | |
| selectElement.style.backgroundColor = originalBg; | |
| }, 500); | |
| } else { | |
| alert('Failed to update category: ' + (data.error || 'Unknown error')); | |
| location.reload(); | |
| } | |
| }) | |
| .catch(err => { | |
| console.error('Category update error:', err); | |
| alert('Error updating category: ' + err.message); | |
| location.reload(); | |
| }); | |
| } | |
| function updateSentenceCategory(sentenceId, category, selectElement) { | |
| fetch(`{{ url_for("admin.update_sentence_category", sentence_id=0) }}`.replace('/0', `/${sentenceId}`), { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ category: category || null }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| // Show success feedback | |
| const originalBg = selectElement.style.backgroundColor; | |
| selectElement.style.backgroundColor = '#d1fae5'; | |
| setTimeout(() => { | |
| selectElement.style.backgroundColor = originalBg; | |
| }, 500); | |
| } else { | |
| alert('Failed to update sentence category: ' + (data.error || 'Unknown error')); | |
| location.reload(); | |
| } | |
| }) | |
| .catch(err => { | |
| console.error('Sentence category update error:', err); | |
| alert('Error updating sentence category: ' + err.message); | |
| location.reload(); | |
| }); | |
| } | |
| function toggleFlag(submissionId) { | |
| fetch(`{{ url_for("admin.toggle_flag", submission_id=0) }}`.replace('/0', `/${submissionId}`), { | |
| method: 'POST' | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| location.reload(); | |
| } | |
| }); | |
| } | |
| function deleteSubmission(submissionId) { | |
| if (confirm('Are you sure you want to permanently delete this submission?')) { | |
| fetch(`{{ url_for("admin.delete_submission", submission_id=0) }}`.replace('/0', `/${submissionId}`), { | |
| method: 'DELETE' | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| location.reload(); | |
| } | |
| }); | |
| } | |
| } | |
| </script> | |
| {% endblock %} | |