from __future__ import annotations import datetime from typing import Any, Dict, List from django.db.models import Count from django.db.models.functions import TruncDate from django.http import HttpRequest, HttpResponse from django.utils import timezone from django.views import View from inertia import render from labs.models import EventVoucher, LabInstance, User from .base import AdminBaseViewMixin class DashboardView(AdminBaseViewMixin, View): """Admin dashboard view with basic stats and charts.""" def get_chart_data(self, model_class, days: int) -> Dict[str, List[Any]]: end_date = timezone.now().date() start_date = end_date - datetime.timedelta(days=days - 1) # Query and aggregate data = ( model_class.objects.filter(date_joined__date__gte=start_date) if model_class == User else model_class.objects.filter(created_at__date__gte=start_date) ) date_field = "date_joined" if model_class == User else "created_at" qs = ( data.annotate(date=TruncDate(date_field)) .values("date") .annotate(count=Count("id")) .order_by("date") ) # Convert to dict for easy lookup counts_map = {item["date"]: item["count"] for item in qs} # Fill missing dates dates = [] counts = [] current = start_date while current <= end_date: dates.append(current.isoformat()) counts.append(counts_map.get(current, 0)) current += datetime.timedelta(days=1) return {"dates": dates, "counts": counts} def serialize_voucher(self, voucher: EventVoucher) -> Dict[str, Any]: return { "id": str(voucher.id), "unique_code": voucher.unique_code, "event_name": voucher.event_name, "start_begin_date": voucher.start_begin_date.isoformat() if voucher.start_begin_date else None, "start_end_date": voucher.start_end_date.isoformat() if voucher.start_end_date else None, } def get(self, request: HttpRequest) -> HttpResponse: try: days = int(request.GET.get("days", 30)) except ValueError: days = 30 if days not in [30, 90, 365]: days = 30 # Basic stats total_users = User.objects.count() staff_users = User.objects.filter(is_staff=True).count() total_instances = LabInstance.objects.count() active_instances = LabInstance.objects.filter( status__in=[ LabInstance.LabInstanceStatus.RUNNING, LabInstance.LabInstanceStatus.CREATING, ], ).count() # Workshop stats now = timezone.now().date() next_week = now + datetime.timedelta(days=7) happening_vouchers = EventVoucher.objects.filter( start_begin_date__lte=now, start_end_date__gte=now ).order_by("start_end_date")[:5] upcoming_vouchers = EventVoucher.objects.filter( start_begin_date__gt=now, start_begin_date__lte=next_week ).order_by("start_begin_date")[:5] # Chart data user_chart = self.get_chart_data(User, days) instance_chart = self.get_chart_data(LabInstance, days) return render( request, "Dashboard", { "stats": { "total_users": total_users, "staff_users": staff_users, "total_instances": total_instances, "active_instances": active_instances, }, "workshops": { "happening": [ self.serialize_voucher(v) for v in happening_vouchers ], "upcoming": [self.serialize_voucher(v) for v in upcoming_vouchers], }, "charts": { "user_growth": user_chart, "instance_creation": instance_chart, }, "filters": {"days": days}, }, )