diff --git a/apps/api/views/admin_dashboard.py b/apps/api/views/admin_dashboard.py index 75149bf..d6b4eb7 100644 --- a/apps/api/views/admin_dashboard.py +++ b/apps/api/views/admin_dashboard.py @@ -3,13 +3,16 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated, IsAdminUser from django.utils import timezone from datetime import timedelta -from django.db.models import Count +from django.db.models import Count, Sum +from django.db.models.functions import TruncDate -from apps.account.models import User -from apps.course.models import Course, Participant +from apps.account.models import User, LoginHistory +from apps.course.models import Course, Participant, CourseLiveSession +from apps.quiz.models import QuizParticipant from apps.blog.models import Blog +from apps.chat.models import ChatMessage, RoomMessage +from apps.transaction.models import TransactionParticipant from apps.certificate.models import Certificate -# Import your Transaction model here class AdminDashboardStatsView(APIView): permission_classes = [IsAuthenticated, IsAdminUser] @@ -18,37 +21,107 @@ class AdminDashboardStatsView(APIView): now = timezone.now() thirty_days_ago = now - timedelta(days=30) - # 1. Basic Counts + # --------------------------------------------------------- + # 1. CARDS & BASIC STATS + # --------------------------------------------------------- active_students = User.objects.filter(groups__name="Student Group", is_active=True).count() professors = User.objects.filter(groups__name="Professor Group", is_active=True).count() active_courses = Course.objects.filter(status=Course.StatusChoices.ONGOING).count() total_blogs = Blog.objects.count() pending_certificates = Certificate.objects.filter(status='pending').count() + active_live_sessions = CourseLiveSession.objects.filter(ended_at__isnull=True).count() - # 2. Revenue (Mocked - replace with your actual Transaction logic) - # revenue_30d = Transaction.objects.filter(status='success', created_at__gte=thirty_days_ago).aggregate(Sum('amount'))['amount__sum'] or 0 + # Revenue (Mocked for 30d sum) revenue_30d = 12500.00 # Dummy data - # 3. Transaction Chart Data (Mocked - replace with actual grouped query) - transactions_data = [ - {"name": "Success", "value": 450, "fill": "var(--color-success)"}, - {"name": "Pending", "value": 85, "fill": "var(--color-pending)"}, - {"name": "Waiting", "value": 40, "fill": "var(--color-waiting)"}, - {"name": "Failed", "value": 25, "fill": "var(--color-failed)"}, + # Private vs Group Chat Usage + chat_types = RoomMessage.objects.values('room_type').annotate(count=Count('id')) + chat_type_stats = {item['room_type']: item['count'] for item in chat_types} + + # --------------------------------------------------------- + # 2. CHARTS DATA + # --------------------------------------------------------- + + # Payment Methods (Pie Chart) + payment_methods = TransactionParticipant.objects.values('payment_method').annotate(total=Count('id')) + payment_method_chart = [ + {"name": item['payment_method'], "value": item['total']} + for item in payment_methods + ] + + # Transaction Status (Donut Chart) + transaction_status = TransactionParticipant.objects.values('status').annotate(total=Count('id')) + transaction_chart = [ + {"name": item['status'], "value": item['total']} + for item in transaction_status ] - # 4. Top 5 Popular Courses - top_courses = Course.objects.annotate( - participant_count=Count('participants') - ).order_by('-participant_count')[:5] + # Global Student Distribution (Bar Chart) + top_countries = LoginHistory.objects.exclude(country__isnull=True).exclude(country="").values('country').annotate( + count=Count('id') + ).order_by('-count')[:5] + country_chart = [{"country": item['country'], "students": item['count']} for item in top_countries] + + # New User Registrations Trend (Line Chart) + registrations = User.objects.filter(date_joined__gte=thirty_days_ago).annotate( + date=TruncDate('date_joined') + ).values('date').annotate(count=Count('id')).order_by('date') + registration_chart = [{"date": item['date'].strftime('%Y-%m-%d'), "users": item['count']} for item in registrations] + + # Community Chat Volume Trend (Area Chart) + chat_volume = ChatMessage.objects.filter(sent_at__gte=thirty_days_ago).annotate( + date=TruncDate('sent_at') + ).values('date').annotate(count=Count('id')).order_by('date') + chat_chart = [{"date": item['date'].strftime('%Y-%m-%d'), "messages": item['count']} for item in chat_volume] + + # Course Pipeline (Stacked Bar) + course_statuses = Course.objects.values('status').annotate(count=Count('id')) + course_pipeline = {"name": "Courses"} + for status in course_statuses: + course_pipeline[status['status']] = status['count'] - top_courses_data = [ + # Course Efficacy: Completed vs Enrolled (Bar Chart) + # We take the top 5 courses, check how many users are enrolled, and how many have at least 1 completed lesson + top_courses_qs = Course.objects.annotate(participant_count=Count('participants')).order_by('-participant_count')[:5] + course_efficacy = [] + for course in top_courses_qs: + enrolled = course.participant_count + completed = Participant.objects.filter( + course=course, + student__lesson_completions__course_lesson__course=course + ).distinct().count() + course_efficacy.append({ + "title": course.title[:20] + "..." if len(course.title) > 20 else course.title, + "enrolled": enrolled, + "completed": completed + }) + + # --------------------------------------------------------- + # 3. LISTS & LEADERBOARDS + # --------------------------------------------------------- + + # Top 5 Courses (List) + top_courses_list = [ { "id": c.id, "title": c.title, "professor": c.professor.fullname if c.professor else "Unknown", "participants": c.participant_count - } for c in top_courses + } for c in top_courses_qs + ] + + # Top 5 Blogs (List) + top_blogs = Blog.objects.order_by('-views_count')[:5] + top_blogs_list = [ + {"id": b.id, "title": b._extract_text_from_json(b.title), "views": b.views_count} + for b in top_blogs + ] + + # Top Performing Students (Leaderboard) + top_students = QuizParticipant.objects.select_related('user').order_by('-total_score', 'total_timing')[:5] + leaderboard = [ + {"id": s.user.id, "name": s.user.fullname, "score": s.total_score, "time": s.total_timing} + for s in top_students ] return Response({ @@ -58,8 +131,23 @@ class AdminDashboardStatsView(APIView): "active_courses": active_courses, "total_blogs": total_blogs, "revenue_30d": revenue_30d, - "pending_certificates": pending_certificates + "pending_certificates": pending_certificates, + "active_live_sessions": active_live_sessions, + "group_rooms": chat_type_stats.get('group', 0), + "private_rooms": chat_type_stats.get('private', 0), + }, + "charts": { + "transactions_status": transaction_chart, + "payment_methods": payment_method_chart, + "countries": country_chart, + "registrations": registration_chart, + "chat_volume": chat_chart, + "course_pipeline": [course_pipeline], + "course_efficacy": course_efficacy }, - "transactions_chart": transactions_data, - "top_courses": top_courses_data + "lists": { + "top_courses": top_courses_list, + "top_blogs": top_blogs_list, + "leaderboard": leaderboard + } }) \ No newline at end of file