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 %} +
+
+
+

Analytics & Reports

+
+
+ +
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