# Dashboard Statistics Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Expand Dashboard with comprehensive statistics in Bento Grid layout - attendance by group, homework completion rates, wrestler distribution, and trainer activity. **Architecture:** Backend provides single `/api/v1/stats/dashboard/` endpoint aggregating all statistics. Frontend displays in Bento Grid with progress bars and simple bar charts using CSS/divs (no external chart library needed). **Tech Stack:** Django REST Framework (backend), React/Next.js (frontend), CSS progress bars (no chart library) --- ## File Structure ### Backend - **Create:** `backend/stats/__init__.py` - App init - **Create:** `backend/stats/apps.py` - App config - **Create:** `backend/stats/views.py` - DashboardStatsViewSet - **Modify:** `backend/wrestleDesk/urls.py` - Add stats endpoint - **Modify:** `backend/settings.py` - Add 'stats' to INSTALLED_APPS ### Frontend - **Modify:** `frontend/src/lib/api.ts` - Add `IDashboardStats` interface - **Modify:** `frontend/src/app/(dashboard)/dashboard/page.tsx` - New stat cards and visualizations --- ## Tasks ### Task 1: Create Stats Backend App - [ ] **Step 1: Create stats app directory structure** Create `backend/stats/` with: ``` stats/ ├── __init__.py ├── apps.py └── views.py ``` - [ ] **Step 2: Create apps.py** ```python from django.apps import AppConfig class StatsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'stats' ``` - [ ] **Step 3: Add 'stats' to INSTALLED_APPS in backend/settings.py** Add `'stats'` to the INSTALLED_APPS list. - [ ] **Step 4: Create DashboardStatsViewSet in views.py** ```python from rest_framework import viewsets from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from django.db.models import Count, Sum from datetime import datetime, timedelta from wrestlers.models import Wrestler from trainers.models import Trainer from trainings.models import Training, Attendance from homework.models import TrainingHomeworkAssignment @api_view(['GET']) @permission_classes([IsAuthenticated]) def dashboard_stats(request): today = datetime.now().date() week_start = today - timedelta(days=today.weekday()) two_weeks_ago = today - timedelta(days=14) # Wrestlers stats total_wrestlers = Wrestler.objects.count() wrestlers_this_week = Wrestler.objects.filter( created_at__date__gte=week_start ).count() # Trainers stats total_trainers = Trainer.objects.count() active_trainers = Trainer.objects.filter(is_active=True).count() # Trainings stats total_trainings = Training.objects.count() trainings_this_week = Training.objects.filter( date__gte=week_start ).count() # Homework stats open_homework = TrainingHomeworkAssignment.objects.filter(is_completed=False).count() completed_homework = TrainingHomeworkAssignment.objects.filter(is_completed=True).count() # Attendance by group this week trainings_this_week_qs = Training.objects.filter(date__gte=week_start) attendance_data = {} for group, label in [('kids', 'Kinder'), ('youth', 'Jugend'), ('adults', 'Erwachsene')]: group_wrestlers = Wrestler.objects.filter(group=group) attended = Attendance.objects.filter( training__in=trainings_this_week_qs, wrestler__in=group_wrestlers ).values('wrestler').distinct().count() total = group_wrestlers.count() attendance_data[group] = { 'attended': attended, 'total': total, 'percent': int((attended / total * 100) if total > 0 else 0) } # Activity (last 14 days) activity = [] for i in range(14): day = today - timedelta(days=13 - i) count = Attendance.objects.filter(training__date=day).count() activity.append({'date': day.isoformat(), 'count': count}) # Wrestlers by group wrestlers_by_group = { 'kids': Wrestler.objects.filter(group='kids', is_active=True).count(), 'youth': Wrestler.objects.filter(group='youth', is_active=True).count(), 'adults': Wrestler.objects.filter(group='adults', is_active=True).count(), 'inactive': Wrestler.objects.filter(is_active=False).count(), } # Top trainers (by training count) trainer_stats = Trainer.objects.annotate( training_count=Count('trainings') ).order_by('-training_count')[:5] top_trainers = [ {'name': t.first_name + ' ' + t.last_name[0] + '.', 'training_count': t.training_count} for t in trainer_stats ] return Response({ 'wrestlers': {'total': total_wrestlers, 'this_week': wrestlers_this_week}, 'trainers': {'total': total_trainers, 'active': active_trainers}, 'trainings': {'total': total_trainings, 'this_week': trainings_this_week}, 'homework': {'open': open_homework, 'completed': completed_homework}, 'attendance': { 'this_week': attendance_data, 'average': Attendance.objects.filter(training__date__gte=week_start).values('training').distinct().count(), 'expected': total_wrestlers }, 'activity': activity, 'wrestlers_by_group': wrestlers_by_group, 'top_trainers': top_trainers, }) ``` - [ ] **Step 5: Add stats URL to urls.py** Add to `backend/wrestleDesk/urls.py`: ```python from stats.views import dashboard_stats path('api/v1/stats/dashboard/', dashboard_stats, name='dashboard-stats'), ``` --- ### Task 2: Update Frontend API Types - [ ] **Step 1: Add IDashboardStats interface to frontend/src/lib/api.ts** Add after existing interfaces: ```typescript export interface IDashboardStats { wrestlers: { total: number; this_week: number } trainers: { total: number; active: number } trainings: { total: number; this_week: number } homework: { open: number; completed: number } attendance: { this_week: { kids: { attended: number; total: number; percent: number } youth: { attended: number; total: number; percent: number } adults: { attended: number; total: number; percent: number } } average: number expected: number } activity: { date: string; count: number }[] wrestlers_by_group: { kids: number youth: number adults: number inactive: number } top_trainers: { name: string; training_count: number }[] } ``` --- ### Task 3: Update Dashboard Page - [ ] **Step 1: Update imports in frontend/src/app/(dashboard)/dashboard/page.tsx** Add `Progress` component and `IDashboardStats`: ```typescript import { apiFetch, IDashboardStats } from "@/lib/api" import { Progress } from "@/components/ui/progress" ``` - [ ] **Step 2: Replace Stats interface and statCards with new implementation** Replace the existing interface and statCards with: ```typescript const groupColors = { kids: "bg-blue-500", youth: "bg-purple-500", adults: "bg-orange-500", } const groupLabels = { kids: "Kinder", youth: "Jugend", adults: "Erwachsene", inactive: "Inaktiv", } ``` - [ ] **Step 3: Replace useEffect to fetch from stats endpoint** Replace `fetchStats` with: ```typescript useEffect(() => { if (!token) return const fetchStats = async () => { setIsLoading(true) try { const data = await apiFetch('/stats/dashboard/', { token }) setStats(data) } catch (error) { console.error("Failed to fetch stats:", error) } finally { setIsLoading(false) } } fetchStats() }, [token]) ``` - [ ] **Step 4: Replace the dashboard content** Replace the entire return section with the Bento Grid layout including: - 4 stat cards (enhanced) - Attendance by group card with progress bars - Training activity card with bar chart - Homework completion card (full width) - Wrestlers by group card - Top trainers card Each card uses `FadeIn` with appropriate delay props. --- ## Testing ### Backend - Run: `cd backend && python manage.py check` - Test endpoint: `curl -H "Authorization: Bearer " http://localhost:8000/api/v1/stats/dashboard/` ### Frontend - Run: `cd frontend && npm run lint` - Run: `npm run typecheck` - Visit: http://localhost:3000/dashboard --- ## Notes - Progress bars use Tailwind `bg-*` classes with calculated widths - Bar chart uses flexbox with varying heights - All data loaded asynchronously with loading state - Error handling: console.error on failure, UI continues to show zeros