diff --git a/civicfix/core/__pycache__/urls.cpython-313.pyc b/civicfix/core/__pycache__/urls.cpython-313.pyc
index b6bde5b..9e17988 100644
Binary files a/civicfix/core/__pycache__/urls.cpython-313.pyc and b/civicfix/core/__pycache__/urls.cpython-313.pyc differ
diff --git a/civicfix/core/__pycache__/views.cpython-313.pyc b/civicfix/core/__pycache__/views.cpython-313.pyc
index 1c0f9a9..bd48bd5 100644
Binary files a/civicfix/core/__pycache__/views.cpython-313.pyc and b/civicfix/core/__pycache__/views.cpython-313.pyc differ
diff --git a/civicfix/core/templates/core/superadmin_reports.html b/civicfix/core/templates/core/superadmin_reports.html
new file mode 100644
index 0000000..688cad5
--- /dev/null
+++ b/civicfix/core/templates/core/superadmin_reports.html
@@ -0,0 +1,91 @@
+{% extends "core/base.html" %}
+
+{% block content %}
+
+
+
+
+
+
Total Issues Reported:
+ {{ total_issues }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+{# 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" }}
+
+
+{% endblock %}
diff --git a/civicfix/core/templates/dashboard/superadmin_dashboard.html b/civicfix/core/templates/dashboard/superadmin_dashboard.html
index f4edbb2..3a18696 100644
--- a/civicfix/core/templates/dashboard/superadmin_dashboard.html
+++ b/civicfix/core/templates/dashboard/superadmin_dashboard.html
@@ -24,7 +24,7 @@
- View Analytics & Reports
+ View Analytics & Reports
diff --git a/civicfix/core/templatetags/__init__.py b/civicfix/core/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/civicfix/core/templatetags/__pycache__/__init__.cpython-313.pyc b/civicfix/core/templatetags/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..942736d
Binary files /dev/null and b/civicfix/core/templatetags/__pycache__/__init__.cpython-313.pyc differ
diff --git a/civicfix/core/templatetags/__pycache__/custom_filters.cpython-313.pyc b/civicfix/core/templatetags/__pycache__/custom_filters.cpython-313.pyc
new file mode 100644
index 0000000..4fab2c7
Binary files /dev/null and b/civicfix/core/templatetags/__pycache__/custom_filters.cpython-313.pyc differ
diff --git a/civicfix/core/templatetags/custom_filters.py b/civicfix/core/templatetags/custom_filters.py
new file mode 100644
index 0000000..b081d06
--- /dev/null
+++ b/civicfix/core/templatetags/custom_filters.py
@@ -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]
diff --git a/civicfix/core/urls.py b/civicfix/core/urls.py
index 5cabe27..088018e 100644
--- a/civicfix/core/urls.py
+++ b/civicfix/core/urls.py
@@ -25,4 +25,5 @@ urlpatterns = [
path('ban-user//', views.ban_user, name='ban_user'),
path('unban-user//', views.unban_user, name='unban_user'),
path('issues//delete_fake/', views.delete_fake_issue, name='delete_fake_issue'),
+ path("reports/", views.superadmin_reports, name="superadmin_reports"),
]
\ No newline at end of file
diff --git a/civicfix/core/views.py b/civicfix/core/views.py
index 224144c..c416ab0 100644
--- a/civicfix/core/views.py
+++ b/civicfix/core/views.py
@@ -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.forms import AuthenticationForm
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.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
+from django.utils.timezone import now, timedelta
from django.views.decorators.http import require_POST
from .models import Issue, User, Vote, Comment, Department
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.")
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):
return user.is_resolver