minor update

This commit is contained in:
2025-08-26 15:20:44 +05:30
parent 7fbab77cda
commit c1ab295514
10 changed files with 151 additions and 2 deletions
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,91 @@
{% extends "core/base.html" %}
{% block content %}
<div class="container my-5">
<div class="card shadow-lg">
<div class="card-header bg-warning text-dark">
<h3>Analytics & Reports</h3>
</div>
<div class="card-body">
<h5>Total Issues Reported:
<span class="badge bg-dark">{{ total_issues }}</span>
</h5>
<canvas id="statusChart" height="120" class="mt-4"></canvas>
<canvas id="deptChart" height="120" class="mt-4"></canvas>
<canvas id="citizenChart" height="120" class="mt-4"></canvas>
<canvas id="trendChart" height="120" class="mt-4"></canvas>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{# Safely pass JSON to JS #}
{{ status_counts|json_script:"status-data" }}
{{ top_departments|json_script:"dept-data" }}
{{ top_citizens|json_script:"citizen-data" }}
{{ issues_last_30_days|json_script:"trend-data" }}
<script>
const statusCounts = JSON.parse(document.getElementById("status-data").textContent);
const deptCounts = JSON.parse(document.getElementById("dept-data").textContent);
const citizenCounts = JSON.parse(document.getElementById("citizen-data").textContent);
const trendCounts = JSON.parse(document.getElementById("trend-data").textContent);
// Status chart
new Chart(document.getElementById('statusChart'), {
type: 'pie',
data: {
labels: statusCounts.map(item => item.status),
datasets: [{
data: statusCounts.map(item => item.count),
backgroundColor: ['#dc3545', '#0dcaf0', '#ffc107', '#198754']
}]
}
});
// Department chart
new Chart(document.getElementById('deptChart'), {
type: 'bar',
data: {
labels: deptCounts.map(item => item.department__name),
datasets: [{
label: 'Issues by Department',
data: deptCounts.map(item => item.count),
backgroundColor: '#0d6efd'
}]
}
});
// Citizens chart
new Chart(document.getElementById('citizenChart'), {
type: 'bar',
data: {
labels: citizenCounts.map(item => item.reporter__username),
datasets: [{
label: 'Top Citizens',
data: citizenCounts.map(item => item.count),
backgroundColor: '#6610f2'
}]
}
});
// Issues over time
new Chart(document.getElementById('trendChart'), {
type: 'line',
data: {
labels: trendCounts.map(item => item.day),
datasets: [{
label: 'Issues Last 30 Days',
data: trendCounts.map(item => item.count),
borderColor: '#fd7e14',
fill: false,
tension: 0.2
}]
}
});
</script>
{% endblock %}
@@ -24,7 +24,7 @@
</li> </li>
<li class="list-group-item"> <li class="list-group-item">
<i class="fas fa-chart-line me-2 text-warning"></i> <i class="fas fa-chart-line me-2 text-warning"></i>
<a href="#">View Analytics & Reports</a> <a href="{% url 'superadmin_reports' %}">View Analytics & Reports</a>
</li> </li>
</ul> </ul>
@@ -0,0 +1,11 @@
from django import template
register = template.Library()
@register.filter
def pluck(queryset_list, key):
"""
Extracts values from a list of dicts by key.
Example: [{'status': 'open', 'count': 5}]|pluck:"status" -> ['open']
"""
return [d.get(key) for d in queryset_list]
+1
View File
@@ -25,4 +25,5 @@ urlpatterns = [
path('ban-user/<int:user_id>/', views.ban_user, name='ban_user'), path('ban-user/<int:user_id>/', views.ban_user, name='ban_user'),
path('unban-user/<int:user_id>/', views.unban_user, name='unban_user'), path('unban-user/<int:user_id>/', views.unban_user, name='unban_user'),
path('issues/<int:issue_id>/delete_fake/', views.delete_fake_issue, name='delete_fake_issue'), path('issues/<int:issue_id>/delete_fake/', views.delete_fake_issue, name='delete_fake_issue'),
path("reports/", views.superadmin_reports, name="superadmin_reports"),
] ]
+47 -1
View File
@@ -3,10 +3,11 @@ from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import Exists, OuterRef from django.db.models import Exists, OuterRef, Count
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import now, timedelta
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from .models import Issue, User, Vote, Comment, Department from .models import Issue, User, Vote, Comment, Department
from .forms import CitizenRegistrationForm, IssueForm, CommentForm from .forms import CitizenRegistrationForm, IssueForm, CommentForm
@@ -337,6 +338,51 @@ def delete_fake_issue(request, issue_id):
messages.success(request, f"✅ Issue deleted and user {reporter.username} has been banned for 7 days.") messages.success(request, f"✅ Issue deleted and user {reporter.username} has been banned for 7 days.")
return redirect("manage_issues") return redirect("manage_issues")
@login_required
@user_passes_test(superadmin_check)
def superadmin_reports(request):
# 1. Total issues reported
total_issues = Issue.objects.count()
# 2. Issues per status
status_counts = (
Issue.objects.values('status')
.annotate(count=Count('id'))
.order_by()
)
# 3. Top 5 departments with most assigned issues
top_departments = (
Issue.objects.values('department__name')
.annotate(count=Count('id'))
.order_by('-count')[:5]
)
# 4. Top citizens by number of reports
top_citizens = (
Issue.objects.values('reporter__username')
.annotate(count=Count('id'))
.order_by('-count')[:5]
)
# 5. Issues over time (last 30 days)
last_30_days = now() - timedelta(days=30)
issues_last_30_days = (
Issue.objects.filter(created_at__gte=last_30_days)
.extra(select={'day': "date(created_at)"})
.values('day')
.annotate(count=Count('id'))
.order_by('day')
)
context = {
"total_issues": total_issues,
"status_counts": list(status_counts),
"top_departments": list(top_departments),
"top_citizens": list(top_citizens),
"issues_last_30_days": list(issues_last_30_days),
}
return render(request, "core/superadmin_reports.html", context)
def resolver_check(user): def resolver_check(user):
return user.is_resolver return user.is_resolver